joinem provides a CLI for fast, flexbile concatenation of tabular data using polars
- Free software: MIT license
- Repository: https://github.com/mmore500/joinem
- Documentation: https://github.com/mmore500/joinem/blob/master/README.md
Install
python3 -m pip install joinem
Features
- Lazily streams I/O to expeditiously handle numerous large files.
- Supports CSV, JSON, parquet, and feather input files.
- JSON input must be newline-delimited (
.json,.jsonl, or.ndjson). - Compressed input is supported:
.csv.gz,.csv.zst,.json.gz,.json.zst,.jsonl.gz,.jsonl.zst,.ndjson.gz, and.ndjson.zst. - Feather files are read eagerly rather than streamed.
- Input formats may be mixed.
- JSON input must be newline-delimited (
- Supports CSV, JSON, parquet, and feather output files.
- JSON output is newline-delimited (
.json,.jsonl, or.ndjson). - Compressed output is supported:
.csv.gz,.csv.zst,.json.gz,.json.zst,.jsonl.gz,.jsonl.zst,.ndjson.gz, and.ndjson.zst(requires a recent polars).
- JSON output is newline-delimited (
- Supports pretty-printed text output (
.show), via polarsDataFrame.show. - Allows mismatched columns and/or empty data files with
--how diagonaland--how diagonal_relaxed. - Logs bytes written and write throughput with
--progress. - Add programatically-generated columns to output.
Example Usage
Pass input filenames via stdin, one filename per line.
find path/to/*.parquet path/to/*.csv | python3 -m joinem out.parquet
Output file type is inferred from the extension of the output file name. Supported output types are feather, JSON, parquet, and csv.
find -name '*.parquet' | python3 -m joinem out.json
If file columns may mismatch, use --how diagonal.
find path/to/ -name '*.csv' | python3 -m joinem out.csv --how diagonal
If some files may be empty, use --how diagonal_relaxed.
To run via Singularity/Apptainer,
ls -1 *.csv | singularity run docker://ghcr.io/mmore500/joinem:v0.17.0 out.feather
Advanced Usage
Add literal value column to output.
ls -1 *.csv | python3 -m joinem out.csv --with-column 'pl.lit(2).alias("two")'
Cast a column to categorical in the output, shrink dtypes, and tune compression.
ls -1 *.csv | python3 -m joinem out.pqt \
--with-column 'pl.col("uuid").cast(pl.Categorical)' --string-cache \
--shrink-dtypes \
--write-kwarg 'compression_level=10' --write-kwarg 'compression="zstd"'
Alias an existing column in the output.
ls -1 *.csv | python3 -m joinem out.csv --with-column 'pl.col("a").alias("a2")'
Apply regex on source datafile paths to create new column in output.
ls -1 path/to/*.csv | python3 -m joinem out.csv \
--with-column 'pl.lit(filepath).str.replace(r".*?([^/]*)\.csv", r"${1}").alias("filename stem")'
Write gzip-compressed CSV output; compression is inferred from the output file extension.
ls -1 *.csv.gz | python3 -m joinem out.csv.gz
Read data from stdin and write data to stdout.
cat foo.csv | python3 -m joinem "/dev/stdout" --stdin \
--output-filetype csv --input-filetype csv
Write to parquet via stdout using pv to display progress, cast "myValue" column to categorical, and use lz4 for parquet compression.
ls -1 input/*.pqt | python3 -m joinem "/dev/stdout" --output-filetype pqt \
--with-column 'pl.col("myValue").cast(pl.Categorical)' \
--write-kwarg 'compression="lz4"' \
| pv > concat.pqt
Write pretty print to stdout.
Use --write-kwarg to pass DataFrame.show settings, e.g., to print all rows and columns.
ls -1 *.csv | python3 -m joinem "/dev/stdout" --output-filetype show \
--write-kwarg 'limit=None' --write-kwarg 'tbl_cols=-1'
Use --init-eval to configure Polars before processing begins, e.g., to set pl.Config.set_engine_affinity to prefer the streaming engine.
ls -1 *.csv | python3 -m joinem out.pqt \
--init-eval 'pl.Config.set_engine_affinity(engine="streaming")'
Use --ipipe and --opipe to transform each input frame (before concatenation) or the output frame via df.pipe(...).
Expressions should evaluate to a callable and have access to polars as pl.
ls -1 *.csv | python3 -m joinem out.pqt \
--ipipe 'lambda df: df.with_columns(pl.len().alias("n"))' \
--opipe 'pl.LazyFrame.unique'
Combine --opipe with show to pretty print summary statistics to stdout.
ls -1 *.csv | python3 -m joinem "/dev/stdout" --output-filetype show \
--opipe 'pl.LazyFrame.describe' \
--write-kwarg 'limit=None' --write-kwarg 'tbl_cols=-1'
Similarly, pretty print a column-wise preview from glimpse.
ls -1 *.csv | python3 -m joinem "/dev/stdout" --output-filetype show \
--opipe 'lambda df: df.collect().glimpse(return_type="frame")'
API
usage: __main__.py [-h] [--version] [--progress] [--stdin] [--drop DROP]
[--select SELECT] [--eager-read] [--eager-write]
[--filter FILTERS] [--head HEAD] [--tail TAIL]
[--gather-every GATHER_EVERY] [--sample SAMPLE]
[--sample-stratify SAMPLE_STRATIFY] [--shuffle]
[--seed SEED] [--init-eval INIT_EVALS] [--ipipe IPIPE]
[--opipe OPIPE] [--with-column WITH_COLUMNS]
[--shrink-dtypes] [--string-cache]
[--how {vertical,vertical_relaxed,diagonal,diagonal_relaxed,horizontal,align,align_full,align_inner,align_left,align_right}]
[--input-filetype INPUT_FILETYPE]
[--output-filetype OUTPUT_FILETYPE]
[--read-kwarg READ_KWARGS] [--write-kwarg WRITE_KWARGS]
output_file
CLI for fast, flexbile concatenation of tabular data using Polars.
positional arguments:
output_file Output file name
options:
-h, --help show this help message and exit
--version show program's version number and exit
--progress Log bytes written and write throughput.
--stdin Read data from stdin.
--drop DROP Column names to drop. Flag may be repeated to
provide multiple column names.
--select SELECT Column names to select; otherwise, all columns are
selected. Flag may be repeated to provide multiple
column names.
--eager-read Use read_* instead of scan_*. Can improve
performance in some cases.
--eager-write Use write_* instead of sink_*. Can improve
performance in some cases.
--filter FILTERS Expression to be evaluated and passed to polars
DataFrame.filter. Example: 'pl.col("thing") == 0'
--head HEAD Number of rows to include in output, counting from
front.
--tail TAIL Number of rows to include in output, counting from
back.
--gather-every GATHER_EVERY
Take every nth row.
--sample SAMPLE Number of rows to include in output, sampled
uniformly. Accepts an integer, a float (fraction of
rows), or polars expression, with access to polars
as `pl`. Sampling more rows than available raises an
error; to sample up to a maximum, use an expression
such as 'pl.len().clip(upper_bound=100)'. Pass
--seed for deterministic behavior.
--sample-stratify SAMPLE_STRATIFY
Column name to stratify --sample by, so that the
sample size applies to each distinct value (or
combination of values, if repeated). To stratify by
a derived value, add it as a column with --with-
column and pass its alias. To sample up to a maximum
per group, combine with a --sample expression such
as 'pl.len().clip(upper_bound=100)'.
--shuffle Should output be shuffled? Pass --seed for
deterministic behavior.
--seed SEED Integer seed for deterministic behavior.
--init-eval INIT_EVALS
Expression to be evaluated once for initialization
side effects before any data is processed, has
access to polars as `pl`. Flag may be repeated to
provide multiple expressions. Example:
'pl.Config.set_engine_affinity(engine="streaming")'
--ipipe IPIPE Expression evaluating to a callable applied to each
input frame, before concatenation, via `df.pipe(...)`.
Has access to polars as `pl` and the default input
dataframe op as `op`, which is a no-op for joinem
itself but may do work when joinem is the backing CLI
engine for another tool. Default: 'op'. Example:
'lambda df:
op(df).with_columns(pl.lit(1).alias("one"))'
--opipe OPIPE Expression evaluating to a callable applied to the
concatenated output frame via `df.pipe(...)`. Has
access to polars as `pl` and the default output
dataframe op as `op`, which is a no-op for joinem
itself but may do work when joinem is the backing CLI
engine for another tool. Default: 'op'. Example:
'lambda df: op(df).unique()'
--with-column WITH_COLUMNS
Expression to be evaluated to add a column, has
access to each datafile's filepath as `filepath` and
polars as `pl`. Flag may be repeated to provide
multiple expressions. Example:
'pl.lit(filepath).str.replace(r".*?([^/]*)\.csv",
r"${1}").alias("filename stem")'
--shrink-dtypes Shrink numeric columns to the minimal required
datatype.
--string-cache Enable Polars global string cache.
--how {vertical,vertical_relaxed,diagonal,diagonal_relaxed,horizontal,align,align_full,align_inner,align_left,align_right}
How to concatenate frames. See
<https://docs.pola.rs/py-
polars/html/reference/api/polars.concat.html> for
more information.
--input-filetype INPUT_FILETYPE
Filetype of input. Otherwise, inferred. Example:
csv, parquet, json, feather
--output-filetype OUTPUT_FILETYPE
Filetype of output. Otherwise, inferred. Example:
csv, parquet. Use show to pretty print via
DataFrame.show.
--read-kwarg READ_KWARGS
Additional keyword arguments to pass to pl.read_* or
pl.scan_* call(s). Provide as 'key=value'. Specify
multiple kwargs by using this flag multiple times.
Arguments will be evaluated as Python expressions.
Example: 'infer_schema_length=None'
--write-kwarg WRITE_KWARGS
Additional keyword arguments to pass to pl.write_*
or pl.sink_* call. Provide as 'key=value'. Specify
multiple kwargs by using this flag multiple times.
Arguments will be evaluated as Python expressions.
Example: 'compression="lz4"'
Provide input filepaths via stdin. Example: find path/to/ -name '*.csv' |
python3 -m joinem out.csv
Citing
If joinem contributes to a scholarly work, please cite it as
Matthew Andres Moreno. (2024). mmore500/joinem. Zenodo. https://doi.org/10.5281/zenodo.10701182
@software{moreno2024joinem,
author = {Matthew Andres Moreno},
title = {mmore500/joinem},
month = feb,
year = 2024,
publisher = {Zenodo},
doi = {10.5281/zenodo.10701182},
url = {https://doi.org/10.5281/zenodo.10701182}
}
And don't forget to leave a star on GitHub!
Metadata
Release files for joinem 0.17.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| joinem-0.17.0.tar.gz | 14.7 kB | Without provenance | Details for joinem-0.17.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| joinem-0.17.0-py2.py3-none-any.whl | Python 3, Python 2 | none | any | Details for joinem-0.17.0-py2.py3-none-any.whl |
Total release size: 27.1 kB
Release files / joinem-0.17.0.tar.gz
| Download URL | joinem-0.17.0.tar.gz |
|---|---|
| Size | 14.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4da70fbc677ea8c2f8c9f55863d4dbb60d939a400dd8c25b617d37e9a37de844
|
|
BLAKE2b-256 checksum How to use checksums |
3169d385f65e2b2c48315e52b76df77ad8c60aa8e56b8eeb5ea5df0a96a1a17b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Release files / joinem-0.17.0-py2.py3-none-any.whl
| Download URL | joinem-0.17.0-py2.py3-none-any.whl |
|---|---|
| Size | 12.4 kB |
| Tags | Python 2 Python 3 |
|
SHA-256 checksum How to use checksums |
20172cd90cc58621b5ad042ce30be0f692f9cc4568eb9605998a44e12258700b
|
|
BLAKE2b-256 checksum How to use checksums |
f0ef53ede58c80e0bb8f6c6cad6ccad425fdc0b0d85a4649cbfba62cfca04fe3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Security for joinem 0.17.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.