dataframe-toolkit 0.9.0__tar.gz → 0.9.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/PKG-INFO +16 -1
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/README.md +15 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/pyproject.toml +1 -1
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/pyproject.toml.orig +1 -1
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/merge_cmd.py +48 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/common/io.py +27 -13
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/__init__.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/cli.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/__init__.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/annotate_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/base.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/binx_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/chi_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/clean_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/cleannames_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/concat_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/corr_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/dataset_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/describe_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/eval_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/fit_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/func_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/help.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/hist_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/info_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/interp_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/line_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/melt_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/pivot_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/print_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/query_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/randvar_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/sample_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/scale_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/scat_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/segid_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/split_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/stat_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/test_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/transpose_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/commands/wstat_cmd.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/common/__init__.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/common/copula.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/common/plot.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/common/seed.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/common/seq.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/common/stats.py +0 -0
- {dataframe_toolkit-0.9.0 → dataframe_toolkit-0.9.1}/src/dftk/io.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: dataframe-toolkit
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.1
|
|
4
4
|
Summary: A command-line toolkit for DataFrame analysis and manipulation
|
|
5
5
|
Keywords: dataframe,statistics,data analysis,CLI,TSV,pandas
|
|
6
6
|
Author: Gunnar Pálsson
|
|
@@ -251,6 +251,21 @@ All commands accept:
|
|
|
251
251
|
> parquet and prints *its path* to stdout — `-` would try to parse that path
|
|
252
252
|
> as TSV and fail.
|
|
253
253
|
|
|
254
|
+
### Comment lines and preambles
|
|
255
|
+
|
|
256
|
+
dftk has no comment or skip-rows option: `#` is ordinary data, so headers
|
|
257
|
+
like `Count #` and values like `A #1` are read as they are. Drop comment
|
|
258
|
+
lines or a preamble in the shell instead:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
grep -v '^#' data.tsv | dftk print # drop every line starting with #
|
|
262
|
+
tail -n +3 data.tsv | dftk print # skip a 2-line preamble
|
|
263
|
+
dftk print <(grep -v '^#' data.tsv) # the same, as a filename
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
With `--backend duckdb`, a whole-line `#` comment below the header is an
|
|
267
|
+
error, and `#` lines above the header are skipped.
|
|
268
|
+
|
|
254
269
|
Standard output options (available on all tabular commands):
|
|
255
270
|
|
|
256
271
|
- `-o` / `--output` — controls where output goes; takes an *optional* value:
|
|
@@ -222,6 +222,21 @@ All commands accept:
|
|
|
222
222
|
> parquet and prints *its path* to stdout — `-` would try to parse that path
|
|
223
223
|
> as TSV and fail.
|
|
224
224
|
|
|
225
|
+
### Comment lines and preambles
|
|
226
|
+
|
|
227
|
+
dftk has no comment or skip-rows option: `#` is ordinary data, so headers
|
|
228
|
+
like `Count #` and values like `A #1` are read as they are. Drop comment
|
|
229
|
+
lines or a preamble in the shell instead:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
grep -v '^#' data.tsv | dftk print # drop every line starting with #
|
|
233
|
+
tail -n +3 data.tsv | dftk print # skip a 2-line preamble
|
|
234
|
+
dftk print <(grep -v '^#' data.tsv) # the same, as a filename
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
With `--backend duckdb`, a whole-line `#` comment below the header is an
|
|
238
|
+
error, and `#` lines above the header are skipped.
|
|
239
|
+
|
|
225
240
|
Standard output options (available on all tabular commands):
|
|
226
241
|
|
|
227
242
|
- `-o` / `--output` — controls where output goes; takes an *optional* value:
|
|
@@ -17,6 +17,8 @@ Key features
|
|
|
17
17
|
* --select [:right:] expands to all right-file columns, suppressing ``_l``.
|
|
18
18
|
* Both input files can be TSV or Parquet (.parquet extension auto-detected).
|
|
19
19
|
The left file defaults to stdin, making it parquet-pipe friendly.
|
|
20
|
+
* Integer columns that a left, right or outer join leaves gaps in become
|
|
21
|
+
nullable (Int64) instead of float64.
|
|
20
22
|
"""
|
|
21
23
|
|
|
22
24
|
import argparse
|
|
@@ -107,6 +109,35 @@ def _add_cross_col(
|
|
|
107
109
|
args.drop = drop
|
|
108
110
|
|
|
109
111
|
|
|
112
|
+
def _gap_sides(args: argparse.Namespace) -> tuple[bool, bool]:
|
|
113
|
+
"""Return (left, right): which side's columns can get missing values.
|
|
114
|
+
|
|
115
|
+
--only runs an outer join before filtering, so both sides count.
|
|
116
|
+
"""
|
|
117
|
+
return args.type in ("right", "outer"), args.type in ("left", "outer")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _nullable_ints(df: pd.DataFrame, keep: list[str]) -> pd.DataFrame:
|
|
121
|
+
"""Return *df* with numpy integer columns (except *keep*) as Int64 etc.
|
|
122
|
+
|
|
123
|
+
A plain int64 column cannot hold a missing value, so pandas turns it into
|
|
124
|
+
float64 when a join leaves rows unmatched; the nullable type keeps the
|
|
125
|
+
integers.
|
|
126
|
+
"""
|
|
127
|
+
cols = [
|
|
128
|
+
c
|
|
129
|
+
for c in df.columns
|
|
130
|
+
if c not in keep
|
|
131
|
+
and pd.api.types.is_integer_dtype(df[c])
|
|
132
|
+
and not isinstance(df[c].dtype, pd.api.extensions.ExtensionDtype)
|
|
133
|
+
]
|
|
134
|
+
if not cols:
|
|
135
|
+
return df
|
|
136
|
+
# int32 -> Int32, uint8 -> UInt8, ...
|
|
137
|
+
dtypes = {c: df[c].dtype.name.capitalize().replace("Uint", "UInt") for c in cols}
|
|
138
|
+
return df.astype(dtypes)
|
|
139
|
+
|
|
140
|
+
|
|
110
141
|
def _filter_only(
|
|
111
142
|
df: pd.DataFrame,
|
|
112
143
|
df_left: pd.DataFrame,
|
|
@@ -152,6 +183,15 @@ def _do_merge(
|
|
|
152
183
|
|
|
153
184
|
suffixes = _expand_select(list(df_left.columns), list(df_right.columns), args)
|
|
154
185
|
|
|
186
|
+
# Keep integer columns integers when the join leaves gaps in them. The
|
|
187
|
+
# -k keys are filled from both sides, so they never get gaps.
|
|
188
|
+
keys = args.keys or []
|
|
189
|
+
left_gaps, right_gaps = _gap_sides(args)
|
|
190
|
+
if left_gaps:
|
|
191
|
+
df_left = _nullable_ints(df_left, keys)
|
|
192
|
+
if right_gaps:
|
|
193
|
+
df_right = _nullable_ints(df_right, keys)
|
|
194
|
+
|
|
155
195
|
if args.keys is not None:
|
|
156
196
|
df_merged = pd.merge(
|
|
157
197
|
df_left,
|
|
@@ -278,6 +318,14 @@ would be read as if it were the data.
|
|
|
278
318
|
Named-file self-merge (no stdin required):
|
|
279
319
|
|
|
280
320
|
dftk merge penguins.tsv -r penguins_adelie.tsv -k species --only left
|
|
321
|
+
|
|
322
|
+
INTEGER COLUMNS
|
|
323
|
+
---------------
|
|
324
|
+
A left, right or outer join leaves the other side's columns empty in
|
|
325
|
+
unmatched rows. Integer columns there are made nullable (Int64), so they
|
|
326
|
+
stay integers (100, not 100.0); the -k key columns are not changed. Read
|
|
327
|
+
TSV input with --backend duckdb to keep integer columns that already have
|
|
328
|
+
empty cells as integers too.
|
|
281
329
|
"""
|
|
282
330
|
|
|
283
331
|
|
|
@@ -27,6 +27,7 @@ import fnmatch
|
|
|
27
27
|
import io as _io
|
|
28
28
|
import logging
|
|
29
29
|
import os
|
|
30
|
+
import shutil
|
|
30
31
|
import sys
|
|
31
32
|
import tempfile
|
|
32
33
|
|
|
@@ -365,14 +366,30 @@ class io:
|
|
|
365
366
|
return _name_columns(df)
|
|
366
367
|
|
|
367
368
|
def _csv_with_duckdb(path: str) -> pd.DataFrame:
|
|
368
|
-
# DuckDB keeps integer columns with blanks as Int64 (pandas: float64)
|
|
369
|
+
# DuckDB keeps integer columns with blanks as Int64 (pandas: float64).
|
|
370
|
+
# comment="" stops it guessing "#" as a comment character: after one
|
|
371
|
+
# whole-line "#" comment it cuts every line at "#", headers included
|
|
369
372
|
try:
|
|
370
|
-
df = duckdb.read_csv(
|
|
373
|
+
df = duckdb.read_csv(
|
|
374
|
+
path, sep=sep, header=not noheader, comment=""
|
|
375
|
+
).df()
|
|
371
376
|
except Exception as exc:
|
|
372
377
|
logging.error(f"DuckDB failed to read {path!r}: {exc}")
|
|
373
378
|
raise
|
|
374
379
|
return _name_columns(df)
|
|
375
380
|
|
|
381
|
+
def _spooled_csv_with_duckdb(stream) -> pd.DataFrame:
|
|
382
|
+
# DuckDB reads its input twice (type sniffing, then data), so a
|
|
383
|
+
# stream must be copied to a temp file first
|
|
384
|
+
os.makedirs(DFTK_TMPDIR, exist_ok=True)
|
|
385
|
+
fd, path = tempfile.mkstemp(suffix=".tsv", dir=DFTK_TMPDIR)
|
|
386
|
+
try:
|
|
387
|
+
with os.fdopen(fd, "wb") as fh:
|
|
388
|
+
shutil.copyfileobj(stream, fh)
|
|
389
|
+
return _csv_with_duckdb(path)
|
|
390
|
+
finally:
|
|
391
|
+
os.unlink(path)
|
|
392
|
+
|
|
376
393
|
def _csv_from_file(path: str) -> pd.DataFrame:
|
|
377
394
|
df = _pandas_csv(path)
|
|
378
395
|
df = _with_blank_lines(
|
|
@@ -403,21 +420,18 @@ class io:
|
|
|
403
420
|
df = pd.read_parquet(filename)
|
|
404
421
|
df.attrs["_parquet_meta"] = _read_parquet_meta(filename)
|
|
405
422
|
elif backend == "duckdb" and duckdb is not None:
|
|
406
|
-
|
|
423
|
+
if os.path.exists(filename) and not os.path.isfile(filename):
|
|
424
|
+
# A pipe, e.g. <(grep -v '^#' file): one read would use it up
|
|
425
|
+
with open(filename, "rb") as fh:
|
|
426
|
+
df = _spooled_csv_with_duckdb(fh)
|
|
427
|
+
else:
|
|
428
|
+
df = _csv_with_duckdb(filename)
|
|
407
429
|
else:
|
|
408
430
|
df = _csv_from_file(filename)
|
|
409
431
|
|
|
410
432
|
elif backend == "duckdb" and duckdb is not None:
|
|
411
|
-
# stdin with DuckDB: it reads files, not buffers (that needs fsspec)
|
|
412
|
-
|
|
413
|
-
os.makedirs(DFTK_TMPDIR, exist_ok=True)
|
|
414
|
-
fd, path = tempfile.mkstemp(suffix=".tsv", dir=DFTK_TMPDIR)
|
|
415
|
-
try:
|
|
416
|
-
with os.fdopen(fd, "wb") as fh:
|
|
417
|
-
fh.write(sys.stdin.buffer.read())
|
|
418
|
-
df = _csv_with_duckdb(path)
|
|
419
|
-
finally:
|
|
420
|
-
os.unlink(path)
|
|
433
|
+
# stdin with DuckDB: it reads files, not buffers (that needs fsspec)
|
|
434
|
+
df = _spooled_csv_with_duckdb(sys.stdin.buffer)
|
|
421
435
|
|
|
422
436
|
else:
|
|
423
437
|
# stdin — read TSV directly
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|