dataframe-toolkit 0.12.0__tar.gz → 0.14.0__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.12.0 → dataframe_toolkit-0.14.0}/PKG-INFO +48 -1
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/README.md +47 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/pyproject.toml +1 -1
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/pyproject.toml.orig +1 -1
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/eval_cmd.py +6 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/print_cmd.py +8 -6
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/query_cmd.py +61 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/common/io.py +24 -6
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/skills/dftk/SKILL.md +4 -2
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/__init__.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/cli.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/__init__.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/annotate_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/base.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/binx_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/chi_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/clean_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/cleannames_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/concat_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/corr_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/dataset_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/describe_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/explode_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/fit_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/func_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/help.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/hist_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/implode_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/info_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/interp_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/line_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/melt_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/merge_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/pivot_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/randvar_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/sample_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/scale_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/scat_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/segid_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/skill_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/split_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/stat_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/test_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/transpose_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/commands/wstat_cmd.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/common/__init__.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/common/copula.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/common/evalfuncs.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/common/plot.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/common/seed.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/common/seq.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/common/stats.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/io.py +0 -0
- {dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/skills/dftk/references/snakemake.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: dataframe-toolkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.14.0
|
|
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
|
|
@@ -193,6 +193,23 @@ dftk dataset fertility -o \
|
|
|
193
193
|
|
|
194
194
|
For your own export, `dftk cleannames data.tsv --backend duckdb -o data.parquet` also keeps integer columns with empty cells as integers.
|
|
195
195
|
|
|
196
|
+
### JSON files
|
|
197
|
+
|
|
198
|
+
DuckDB reads JSON itself, so `query --sql` takes a JSON file in `FROM` (a file of records, `[{...}, {...}]`, or JSON Lines, one object per line; `'logs/*.jsonl'` reads several). When the SQL doesn't use the `data` table, dftk reads no input. Nested objects and lists would end up as one text cell each, so flatten them in the same query: `pos.*` makes a column of each field, `len()` counts a list and `unnest()` gives one row per item:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
printf '%s\n' '{"id": 1, "pos": {"chr": "1", "start": 100}, "tags": ["x", "y"]}' \
|
|
202
|
+
'{"id": 2, "pos": {"chr": "2", "start": 200}, "tags": []}' \
|
|
203
|
+
> records.jsonl
|
|
204
|
+
dftk query --sql \
|
|
205
|
+
"SELECT * EXCLUDE (pos, tags), pos.*, len(tags) AS ntags FROM 'records.jsonl'"
|
|
206
|
+
# id chr start ntags
|
|
207
|
+
# 1 1 100 2
|
|
208
|
+
# 2 2 200 0
|
|
209
|
+
dftk query --sql "SELECT id, unnest(tags) AS tag FROM 'records.jsonl'" -o \
|
|
210
|
+
| dftk pivot -i tag -v id -f count
|
|
211
|
+
```
|
|
212
|
+
|
|
196
213
|
### Large tables: hexbin instead of points
|
|
197
214
|
|
|
198
215
|
With tens of thousands of points a scatter plot is a solid blob. `--hexbin` counts points per hexagon instead (`--hexlog` for a log colour scale), and `--fit lowess` adds a smooth curve:
|
|
@@ -390,6 +407,8 @@ All commands accept:
|
|
|
390
407
|
a previous command's bare `-o` (that temp file is then read and deleted)
|
|
391
408
|
- A pipe as the filename, e.g. `<(grep -v '^#' data.tsv)` or `<(dftk … -o)`,
|
|
392
409
|
read the same way as stdin
|
|
410
|
+
- JSON through `query --sql "SELECT … FROM 'f.jsonl'"`, which needs no
|
|
411
|
+
other input (see [JSON files](#json-files))
|
|
393
412
|
|
|
394
413
|
```bash
|
|
395
414
|
dftk dataset tips -o | dftk stat -c tip -g day # parquet path on stdin
|
|
@@ -416,6 +435,32 @@ dftk print <(grep -v '^#' data.tsv) # the same, as a filename
|
|
|
416
435
|
With `--backend duckdb`, a whole-line `#` comment below the header is an
|
|
417
436
|
error, and `#` lines above the header are skipped.
|
|
418
437
|
|
|
438
|
+
### Files without a header
|
|
439
|
+
|
|
440
|
+
`--noheader` reads the first line as data and names the columns `V1`,
|
|
441
|
+
`V2`, …. It only describes the input: the output gets a header (`V1 V2 …`)
|
|
442
|
+
unless you add `--removeheader`. Name the columns with `--move`:
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
dftk print data.txt --noheader --move V1:chr V2:begin V3:end -o named.tsv
|
|
446
|
+
dftk eval data.txt --noheader -f "len = V3 - V2" --removeheader # no header out
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
In `merge`, `--noheader` applies to both files, so both sides are named
|
|
450
|
+
`V1 V2 …`. The key appears once and the right side's other columns get the
|
|
451
|
+
`_r` suffix. `--move` runs after the merge, so it renames the merged names:
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
dftk merge -l left.txt -r right.txt -k V1 --noheader
|
|
455
|
+
# V1 V2 V3 V2_r V3_r
|
|
456
|
+
dftk merge -l left.txt -r right.txt -k V1 --noheader \
|
|
457
|
+
--move V1:id V2:n V3:s V2_r:n2 V3_r:s2
|
|
458
|
+
# id n s n2 s2
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
Parquet always has column names, so `--removeheader` with `-o` and
|
|
462
|
+
`--noheader` on parquet input do nothing (dftk warns).
|
|
463
|
+
|
|
419
464
|
Standard output options (available on all tabular commands):
|
|
420
465
|
|
|
421
466
|
- `-o` / `--output` — controls where output goes; takes an *optional* value:
|
|
@@ -427,6 +472,8 @@ Standard output options (available on all tabular commands):
|
|
|
427
472
|
- `--drop col1 col2 …` — remove these columns
|
|
428
473
|
- `--round N` — round numeric output
|
|
429
474
|
- `--postquery EXPR` — filter output rows after processing
|
|
475
|
+
- `--move OLD:NEW …` — rename columns
|
|
476
|
+
- `--removeheader` — leave out the header row in TSV output
|
|
430
477
|
- `--meta KEY=VALUE` — embed provenance metadata in parquet output (repeatable)
|
|
431
478
|
|
|
432
479
|
### Provenance annotations
|
|
@@ -163,6 +163,23 @@ dftk dataset fertility -o \
|
|
|
163
163
|
|
|
164
164
|
For your own export, `dftk cleannames data.tsv --backend duckdb -o data.parquet` also keeps integer columns with empty cells as integers.
|
|
165
165
|
|
|
166
|
+
### JSON files
|
|
167
|
+
|
|
168
|
+
DuckDB reads JSON itself, so `query --sql` takes a JSON file in `FROM` (a file of records, `[{...}, {...}]`, or JSON Lines, one object per line; `'logs/*.jsonl'` reads several). When the SQL doesn't use the `data` table, dftk reads no input. Nested objects and lists would end up as one text cell each, so flatten them in the same query: `pos.*` makes a column of each field, `len()` counts a list and `unnest()` gives one row per item:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
printf '%s\n' '{"id": 1, "pos": {"chr": "1", "start": 100}, "tags": ["x", "y"]}' \
|
|
172
|
+
'{"id": 2, "pos": {"chr": "2", "start": 200}, "tags": []}' \
|
|
173
|
+
> records.jsonl
|
|
174
|
+
dftk query --sql \
|
|
175
|
+
"SELECT * EXCLUDE (pos, tags), pos.*, len(tags) AS ntags FROM 'records.jsonl'"
|
|
176
|
+
# id chr start ntags
|
|
177
|
+
# 1 1 100 2
|
|
178
|
+
# 2 2 200 0
|
|
179
|
+
dftk query --sql "SELECT id, unnest(tags) AS tag FROM 'records.jsonl'" -o \
|
|
180
|
+
| dftk pivot -i tag -v id -f count
|
|
181
|
+
```
|
|
182
|
+
|
|
166
183
|
### Large tables: hexbin instead of points
|
|
167
184
|
|
|
168
185
|
With tens of thousands of points a scatter plot is a solid blob. `--hexbin` counts points per hexagon instead (`--hexlog` for a log colour scale), and `--fit lowess` adds a smooth curve:
|
|
@@ -360,6 +377,8 @@ All commands accept:
|
|
|
360
377
|
a previous command's bare `-o` (that temp file is then read and deleted)
|
|
361
378
|
- A pipe as the filename, e.g. `<(grep -v '^#' data.tsv)` or `<(dftk … -o)`,
|
|
362
379
|
read the same way as stdin
|
|
380
|
+
- JSON through `query --sql "SELECT … FROM 'f.jsonl'"`, which needs no
|
|
381
|
+
other input (see [JSON files](#json-files))
|
|
363
382
|
|
|
364
383
|
```bash
|
|
365
384
|
dftk dataset tips -o | dftk stat -c tip -g day # parquet path on stdin
|
|
@@ -386,6 +405,32 @@ dftk print <(grep -v '^#' data.tsv) # the same, as a filename
|
|
|
386
405
|
With `--backend duckdb`, a whole-line `#` comment below the header is an
|
|
387
406
|
error, and `#` lines above the header are skipped.
|
|
388
407
|
|
|
408
|
+
### Files without a header
|
|
409
|
+
|
|
410
|
+
`--noheader` reads the first line as data and names the columns `V1`,
|
|
411
|
+
`V2`, …. It only describes the input: the output gets a header (`V1 V2 …`)
|
|
412
|
+
unless you add `--removeheader`. Name the columns with `--move`:
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
dftk print data.txt --noheader --move V1:chr V2:begin V3:end -o named.tsv
|
|
416
|
+
dftk eval data.txt --noheader -f "len = V3 - V2" --removeheader # no header out
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
In `merge`, `--noheader` applies to both files, so both sides are named
|
|
420
|
+
`V1 V2 …`. The key appears once and the right side's other columns get the
|
|
421
|
+
`_r` suffix. `--move` runs after the merge, so it renames the merged names:
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
dftk merge -l left.txt -r right.txt -k V1 --noheader
|
|
425
|
+
# V1 V2 V3 V2_r V3_r
|
|
426
|
+
dftk merge -l left.txt -r right.txt -k V1 --noheader \
|
|
427
|
+
--move V1:id V2:n V3:s V2_r:n2 V3_r:s2
|
|
428
|
+
# id n s n2 s2
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
Parquet always has column names, so `--removeheader` with `-o` and
|
|
432
|
+
`--noheader` on parquet input do nothing (dftk warns).
|
|
433
|
+
|
|
389
434
|
Standard output options (available on all tabular commands):
|
|
390
435
|
|
|
391
436
|
- `-o` / `--output` — controls where output goes; takes an *optional* value:
|
|
@@ -397,6 +442,8 @@ Standard output options (available on all tabular commands):
|
|
|
397
442
|
- `--drop col1 col2 …` — remove these columns
|
|
398
443
|
- `--round N` — round numeric output
|
|
399
444
|
- `--postquery EXPR` — filter output rows after processing
|
|
445
|
+
- `--move OLD:NEW …` — rename columns
|
|
446
|
+
- `--removeheader` — leave out the header row in TSV output
|
|
400
447
|
- `--meta KEY=VALUE` — embed provenance metadata in parquet output (repeatable)
|
|
401
448
|
|
|
402
449
|
### Provenance annotations
|
|
@@ -44,6 +44,12 @@ methods are under .str (name.str.lower(), not name.lower() or tolower()):
|
|
|
44
44
|
|
|
45
45
|
x = x.abs() x = x.round(1) x = x.clip(lower=0) y = name.str.upper()
|
|
46
46
|
|
|
47
|
+
A regular expression test gives True/False; fullmatch checks the whole value
|
|
48
|
+
(match only its start). Here: exactly 7 ASCII letters, empty cells False:
|
|
49
|
+
|
|
50
|
+
dftk dataset diamonds -o \\
|
|
51
|
+
| dftk eval -f "is7 = cut.str.fullmatch('[A-Za-z]{7}')"
|
|
52
|
+
|
|
47
53
|
|
|
48
54
|
FILLING MISSING VALUES (-f)
|
|
49
55
|
---------------------------
|
|
@@ -8,10 +8,13 @@ dftk pipeline:
|
|
|
8
8
|
dftk stat -g grp -s val -o \\
|
|
9
9
|
| dftk pivot -i grp -v val_mean -o \\
|
|
10
10
|
| dftk print
|
|
11
|
+
|
|
12
|
+
With --noheader and --move it names the columns of a headerless file:
|
|
13
|
+
|
|
14
|
+
dftk print data.txt --noheader --move V1:chr V2:begin V3:end
|
|
11
15
|
"""
|
|
12
16
|
|
|
13
17
|
import argparse
|
|
14
|
-
import sys
|
|
15
18
|
|
|
16
19
|
from dftk.commands.base import BaseCommand
|
|
17
20
|
from dftk.common.io import io
|
|
@@ -24,10 +27,9 @@ class PrintCommand(BaseCommand):
|
|
|
24
27
|
help = "Convert any input (TSV or parquet pipe) to TSV on stdout"
|
|
25
28
|
|
|
26
29
|
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
|
|
30
|
+
# The standard output options (--move, --select, --removeheader, …)
|
|
31
|
+
# work too; with -o FILE print writes there instead of stdout
|
|
32
|
+
self.add_io_arguments(parser)
|
|
30
33
|
|
|
31
34
|
def execute(self, args: argparse.Namespace) -> None:
|
|
32
|
-
|
|
33
|
-
df.to_csv(sys.stdout, sep="\t", index=False)
|
|
35
|
+
io.printdf(io.read(args), args)
|
|
@@ -26,6 +26,7 @@ sql mode (--sql)
|
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
28
|
import argparse
|
|
29
|
+
import json
|
|
29
30
|
import logging
|
|
30
31
|
import os
|
|
31
32
|
|
|
@@ -85,6 +86,9 @@ def _sql_query(args: argparse.Namespace, sql: str, table: str) -> pd.DataFrame:
|
|
|
85
86
|
if direct_parquet:
|
|
86
87
|
rel = conn.read_parquet(filename)
|
|
87
88
|
conn.register(table, rel)
|
|
89
|
+
elif filename is None and not _uses_table(sql, table):
|
|
90
|
+
# SQL reads its own files (FROM 'f.jsonl'): don't wait for stdin.
|
|
91
|
+
pass
|
|
88
92
|
else:
|
|
89
93
|
# stdin, TSV files, or parquet-pipe: read into pandas first.
|
|
90
94
|
df = io.read(args)
|
|
@@ -99,6 +103,37 @@ def _sql_query(args: argparse.Namespace, sql: str, table: str) -> pd.DataFrame:
|
|
|
99
103
|
return _hugeint_to_int64(df, description)
|
|
100
104
|
|
|
101
105
|
|
|
106
|
+
def _uses_table(sql: str, table: str) -> bool:
|
|
107
|
+
"""Return True unless DuckDB's parser says *sql* never names *table*.
|
|
108
|
+
|
|
109
|
+
Uses json_serialize_sql, which only parses (duckdb.get_table_names also
|
|
110
|
+
binds, and fails on valid SQL such as SELECT pos.*). On a parse error
|
|
111
|
+
the input is read as before and the query itself reports the error.
|
|
112
|
+
"""
|
|
113
|
+
try:
|
|
114
|
+
tree = json.loads(
|
|
115
|
+
duckdb.execute("SELECT json_serialize_sql(?)", [sql]).fetchone()[0]
|
|
116
|
+
)
|
|
117
|
+
except Exception: # an old duckdb without json_serialize_sql
|
|
118
|
+
return True
|
|
119
|
+
if tree.get("error"):
|
|
120
|
+
return True
|
|
121
|
+
names: set[str] = set()
|
|
122
|
+
|
|
123
|
+
def walk(node) -> None:
|
|
124
|
+
if isinstance(node, dict):
|
|
125
|
+
if node.get("type") == "BASE_TABLE":
|
|
126
|
+
names.add(node.get("table_name", "").lower())
|
|
127
|
+
for value in node.values():
|
|
128
|
+
walk(value)
|
|
129
|
+
elif isinstance(node, list):
|
|
130
|
+
for value in node:
|
|
131
|
+
walk(value)
|
|
132
|
+
|
|
133
|
+
walk(tree)
|
|
134
|
+
return table.lower() in names
|
|
135
|
+
|
|
136
|
+
|
|
102
137
|
def _hugeint_to_int64(df: pd.DataFrame, description) -> pd.DataFrame:
|
|
103
138
|
"""Return 128-bit integer result columns as Int64 instead of float64.
|
|
104
139
|
|
|
@@ -197,6 +232,30 @@ CTEs:
|
|
|
197
232
|
)
|
|
198
233
|
SELECT * FROM ranked WHERE rank <= 3"
|
|
199
234
|
|
|
235
|
+
JSON FILES
|
|
236
|
+
----------
|
|
237
|
+
DuckDB reads JSON itself: name the file in FROM, in single quotes. A file
|
|
238
|
+
of records ([{...}, {...}]) and JSON Lines (one object per line) both
|
|
239
|
+
work, and 'logs/*.jsonl' reads several files as one table. When the SQL
|
|
240
|
+
doesn't use the data table, dftk reads no input, so nothing is piped in:
|
|
241
|
+
|
|
242
|
+
printf '%s\\n' '{"id": 1, "pos": {"chr": "1", "start": 100}, "tags": ["x", "y"]}' \\
|
|
243
|
+
'{"id": 2, "pos": {"chr": "2", "start": 200}, "tags": []}' \\
|
|
244
|
+
> records.jsonl
|
|
245
|
+
dftk query --sql "SELECT * FROM 'records.jsonl'"
|
|
246
|
+
|
|
247
|
+
Nested objects and lists come out as one text cell each, which later
|
|
248
|
+
commands can't compute on. Flatten them in the same query: pos.* makes a
|
|
249
|
+
column of each field, len() counts a list, unnest() gives a row per item:
|
|
250
|
+
|
|
251
|
+
dftk query --sql \\
|
|
252
|
+
"SELECT * EXCLUDE (pos, tags), pos.*, len(tags) AS ntags
|
|
253
|
+
FROM 'records.jsonl'"
|
|
254
|
+
dftk query --sql "SELECT id, unnest(tags) AS tag FROM 'records.jsonl'"
|
|
255
|
+
dftk query --sql \\
|
|
256
|
+
"SELECT * EXCLUDE (pos, tags), pos.* FROM 'records.jsonl'" -o \\
|
|
257
|
+
| dftk stat -c start
|
|
258
|
+
|
|
200
259
|
OUT-OF-CORE LARGE FILES
|
|
201
260
|
------------------------
|
|
202
261
|
When the input is a named .parquet file, DuckDB queries it natively without
|
|
@@ -254,6 +313,8 @@ class QueryCommand(BaseCommand):
|
|
|
254
313
|
"Full DuckDB SQL query. The input is registered as a virtual "
|
|
255
314
|
"table named 'data' (see --table). Named .parquet inputs are "
|
|
256
315
|
"queried natively without full load into memory. "
|
|
316
|
+
"SQL that only reads its own files (FROM 'f.jsonl') "
|
|
317
|
+
"needs no input. "
|
|
257
318
|
"Cannot be combined with -q. Requires duckdb."
|
|
258
319
|
),
|
|
259
320
|
)
|
|
@@ -91,6 +91,13 @@ def _is_temp_pipe_path(path: str) -> bool:
|
|
|
91
91
|
_MAX_PATH_LINE = 4096
|
|
92
92
|
|
|
93
93
|
|
|
94
|
+
def _warn_noheader_parquet(args: argparse.Namespace) -> None:
|
|
95
|
+
if getattr(args, "noheader", False):
|
|
96
|
+
logging.warning(
|
|
97
|
+
"--noheader has no effect on parquet input; parquet always has column names"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
94
101
|
def _read_pipe_parquet(path: str) -> pd.DataFrame:
|
|
95
102
|
"""Read the parquet file *path* named on stdin; delete it if it's dftk's temp."""
|
|
96
103
|
df = pd.read_parquet(path)
|
|
@@ -305,7 +312,11 @@ class io:
|
|
|
305
312
|
)
|
|
306
313
|
g.add_argument(
|
|
307
314
|
"--noheader",
|
|
308
|
-
help=
|
|
315
|
+
help=(
|
|
316
|
+
"Input has no header row; columns are named V1, V2, … "
|
|
317
|
+
"(rename with --move V1:NAME). Output still gets a header "
|
|
318
|
+
"unless --removeheader."
|
|
319
|
+
),
|
|
309
320
|
action="store_true",
|
|
310
321
|
)
|
|
311
322
|
g.add_argument(
|
|
@@ -418,6 +429,7 @@ class io:
|
|
|
418
429
|
if _is_pipe_path(path):
|
|
419
430
|
rest = stream.read()
|
|
420
431
|
if not rest.strip():
|
|
432
|
+
_warn_noheader_parquet(args)
|
|
421
433
|
return _read_pipe_parquet(path)
|
|
422
434
|
stream = _io.BytesIO(rest)
|
|
423
435
|
if backend == "duckdb" and duckdb is not None:
|
|
@@ -441,6 +453,7 @@ class io:
|
|
|
441
453
|
raise ValueError(
|
|
442
454
|
f"'...' expects a .parquet path on stdin, got {pipe_path!r}"
|
|
443
455
|
)
|
|
456
|
+
_warn_noheader_parquet(args)
|
|
444
457
|
df = _read_pipe_parquet(pipe_path)
|
|
445
458
|
|
|
446
459
|
elif filename is not None and filename != "-":
|
|
@@ -453,6 +466,7 @@ class io:
|
|
|
453
466
|
"stdin."
|
|
454
467
|
)
|
|
455
468
|
if filename.endswith(".parquet"):
|
|
469
|
+
_warn_noheader_parquet(args)
|
|
456
470
|
df = pd.read_parquet(filename)
|
|
457
471
|
df.attrs["_parquet_meta"] = _read_parquet_meta(filename)
|
|
458
472
|
elif os.path.exists(filename) and not os.path.isfile(filename):
|
|
@@ -573,7 +587,8 @@ class io:
|
|
|
573
587
|
)
|
|
574
588
|
g.add_argument(
|
|
575
589
|
"--removeheader",
|
|
576
|
-
help="
|
|
590
|
+
help="Leave out the header row in TSV output (parquet always "
|
|
591
|
+
"keeps column names).",
|
|
577
592
|
action="store_true",
|
|
578
593
|
)
|
|
579
594
|
g.add_argument(
|
|
@@ -819,10 +834,13 @@ class io:
|
|
|
819
834
|
except ValueError:
|
|
820
835
|
logging.warning(f"--meta: expected KEY=VALUE, got {item!r}")
|
|
821
836
|
|
|
822
|
-
# Header:
|
|
823
|
-
header = not getattr(args, "
|
|
824
|
-
if header and
|
|
825
|
-
|
|
837
|
+
# Header: written unless --removeheader (--noheader is about the input)
|
|
838
|
+
header = not getattr(args, "removeheader", False)
|
|
839
|
+
if not header and (output == "" or str(output).endswith(".parquet")):
|
|
840
|
+
logging.warning(
|
|
841
|
+
"--removeheader has no effect on parquet output (-o); parquet "
|
|
842
|
+
"always keeps column names"
|
|
843
|
+
)
|
|
826
844
|
|
|
827
845
|
if output == "":
|
|
828
846
|
# Pipe mode — write temp parquet, print path
|
|
@@ -75,8 +75,9 @@ left by an interrupted pipe are removed with `dftk clean`.
|
|
|
75
75
|
## Options shared by every data command
|
|
76
76
|
|
|
77
77
|
Input: `--prequery EXPR` (filter rows right after reading), `--noheader`
|
|
78
|
-
(columns become `V1, V2,
|
|
79
|
-
|
|
78
|
+
(columns become `V1, V2, …`; the output still gets a header unless
|
|
79
|
+
`--removeheader`, and `--move V1:chr …` names them), `--delimiter`,
|
|
80
|
+
`--readasobject COL…` (keep values like IDs as strings).
|
|
80
81
|
|
|
81
82
|
Output, applied after the command's own work: `--select COL… [+]` (choose
|
|
82
83
|
and order columns; globs allowed; a trailing `+` keeps the remaining
|
|
@@ -103,6 +104,7 @@ Transform:
|
|
|
103
104
|
| New column from a formula: `z = x / y`, `where(...)`, string, DNA and statistical functions | `eval -f`, `eval -c` (constant), `eval -s` (string) |
|
|
104
105
|
| Fill empty cells: from another column, a group mean, the row above (`ffill`, per group with `groupby`) or by interpolation; sort first, fills follow row order | `eval -f "x = x.fillna(y)"`, `"x = x.groupby(g).ffill()"` (see `eval -h`) |
|
|
105
106
|
| Filter rows (pandas expression) or run any DuckDB SQL (`FROM data`) | `query -q` / `query --sql` |
|
|
107
|
+
| Read JSON or JSON Lines (no input needed); flatten nested fields with `pos.*`, lists with `unnest()` | `query --sql "SELECT * EXCLUDE (pos), pos.* FROM 'f.jsonl'"` |
|
|
106
108
|
| Join two tables, including anti-joins | `merge DATAFILE -r OTHER -k KEY [-t left] [--only left]` |
|
|
107
109
|
| Stack tables row-wise | `concat A B C [--sourcecol src]` |
|
|
108
110
|
| Wide to long / long to wide plus aggregation | `melt` / `pivot -i ROWS -g COLS -v VALS -f FUNCS` |
|
|
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
|
|
File without changes
|
|
File without changes
|
{dataframe_toolkit-0.12.0 → dataframe_toolkit-0.14.0}/src/dftk/skills/dftk/references/snakemake.md
RENAMED
|
File without changes
|