dataframe-toolkit 0.14.0__tar.gz → 0.15.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.14.0 → dataframe_toolkit-0.15.1}/PKG-INFO +51 -3
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/README.md +50 -2
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/pyproject.toml +1 -1
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/pyproject.toml.orig +1 -1
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/merge_cmd.py +20 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/print_cmd.py +8 -2
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/query_cmd.py +17 -1
- dataframe_toolkit-0.15.1/src/dftk/common/docformats.py +262 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/common/io.py +46 -5
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/skills/dftk/SKILL.md +6 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/__init__.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/cli.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/__init__.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/annotate_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/base.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/binx_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/chi_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/clean_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/cleannames_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/concat_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/corr_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/dataset_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/describe_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/eval_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/explode_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/fit_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/func_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/help.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/hist_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/implode_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/info_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/interp_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/line_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/melt_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/pivot_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/randvar_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/sample_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/scale_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/scat_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/segid_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/skill_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/split_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/stat_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/test_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/transpose_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/commands/wstat_cmd.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/common/__init__.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/common/copula.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/common/evalfuncs.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/common/plot.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/common/seed.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/common/seq.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/common/stats.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/src/dftk/io.py +0 -0
- {dataframe_toolkit-0.14.0 → dataframe_toolkit-0.15.1}/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.15.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
|
|
@@ -166,7 +166,7 @@ publication-quality output.
|
|
|
166
166
|
| `sample` | Random row sampling (with or without replacement, by count or fraction) |
|
|
167
167
|
| `split` | Split a dataframe into one file per group |
|
|
168
168
|
| `annotate` | Read and write provenance metadata (genome, source, …) in parquet files |
|
|
169
|
-
| `print` | Read any dftk input (TSV, parquet, stdin) and write TSV
|
|
169
|
+
| `print` | Read any dftk input (TSV, parquet, stdin) and write TSV, JSON, Markdown or LaTeX |
|
|
170
170
|
| `clean` | Remove leftover temp parquet pipe files from interrupted pipelines |
|
|
171
171
|
| `help` | List all subcommands or show full help for one: `dftk help stat` |
|
|
172
172
|
|
|
@@ -195,7 +195,7 @@ For your own export, `dftk cleannames data.tsv --backend duckdb -o data.parquet`
|
|
|
195
195
|
|
|
196
196
|
### JSON files
|
|
197
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.
|
|
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. In TSV a nested object or list is shown as one line of JSON (`["x","y"]`), which later commands can't compute on, so flatten it in the same query: `pos.*` makes a column of each field, `len()` counts a list and `unnest()` gives one row per item:
|
|
199
199
|
|
|
200
200
|
```bash
|
|
201
201
|
printf '%s\n' '{"id": 1, "pos": {"chr": "1", "start": 100}, "tags": ["x", "y"]}' \
|
|
@@ -210,6 +210,32 @@ dftk query --sql "SELECT id, unnest(tags) AS tag FROM 'records.jsonl'" -o \
|
|
|
210
210
|
| dftk pivot -i tag -v id -f count
|
|
211
211
|
```
|
|
212
212
|
|
|
213
|
+
A field that is a string in some records and a list in others (a variant catalog's `ReferenceRegion`, say) gets DuckDB type JSON; TSV shows its strings without quotes and its lists as JSON. `json_type()` tells them apart, so a list can become one row per item:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
printf '%s\n' '{"locus": "AFF2", "region": "chrX:1-60"}' \
|
|
217
|
+
'{"locus": "ATXN7", "region": ["chr3:1-30", "chr3:30-42"]}' \
|
|
218
|
+
> catalog.jsonl
|
|
219
|
+
dftk query --sql "
|
|
220
|
+
SELECT locus,
|
|
221
|
+
unnest(CASE WHEN json_type(region) = 'ARRAY' THEN region->>'\$[*]'
|
|
222
|
+
ELSE [region->>'\$'] END) AS region
|
|
223
|
+
FROM 'catalog.jsonl'"
|
|
224
|
+
# locus region
|
|
225
|
+
# AFF2 chrX:1-60
|
|
226
|
+
# ATXN7 chr3:1-30
|
|
227
|
+
# ATXN7 chr3:30-42
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
To get JSON back, write `-o FILE.json` or `.jsonl`, or `--format json` for stdout (see [Output formats](#output-formats)). Nested values and mixed fields are written as JSON again; a key missing from a record is written as `null`:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
dftk query --sql "SELECT * FROM 'catalog.jsonl' WHERE locus LIKE 'ATXN%'" --format json
|
|
234
|
+
# [
|
|
235
|
+
# {"locus": "ATXN7", "region": ["chr3:1-30", "chr3:30-42"]}
|
|
236
|
+
# ]
|
|
237
|
+
```
|
|
238
|
+
|
|
213
239
|
### Large tables: hexbin instead of points
|
|
214
240
|
|
|
215
241
|
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:
|
|
@@ -420,6 +446,28 @@ Older scripts give `...` as the file to read a parquet path from stdin; that
|
|
|
420
446
|
still works. In zsh with Oh My Zsh an unquoted `...` becomes `../..`, so
|
|
421
447
|
leave it out (or use `-`) in scripts you share.
|
|
422
448
|
|
|
449
|
+
### Output formats
|
|
450
|
+
|
|
451
|
+
Without `-o`, output is TSV on stdout. `-o` alone, or `-o FILE.parquet`, writes parquet and prints the path for the next command; `-o FILE` writes TSV, except for these extensions:
|
|
452
|
+
|
|
453
|
+
| `-o` | Writes |
|
|
454
|
+
|------|--------|
|
|
455
|
+
| `FILE.json` | JSON array of records |
|
|
456
|
+
| `FILE.jsonl` | JSON Lines, one record per line |
|
|
457
|
+
| `FILE.md` | Markdown table (numeric columns right-aligned) |
|
|
458
|
+
| `FILE.tex` | bare LaTeX `tabular` with booktabs rules |
|
|
459
|
+
|
|
460
|
+
`--format tsv|json|jsonl|md|tex` writes the same formats to stdout, or overrides the extension. `--digits N` applies to all of them. The `.tex` file is only the tabular, with special characters (`_ & % $ # { } ~ ^ \ | < >`) escaped; it needs `\usepackage{booktabs}`, and goes inside your own `table` environment with `\input{tips_by_day.tex}`. Add `\usepackage[T1]{fontenc}` (pdfLaTeX's default font has no straight `"`, which JSON lists contain):
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
dftk dataset tips -o | dftk pivot -i day -v tip -f mean count --digits 3 --format md
|
|
464
|
+
# | day | mean_tip | count_tip |
|
|
465
|
+
# | --- | ---: | ---: |
|
|
466
|
+
# | Thur | 2.77 | 62 |
|
|
467
|
+
# ...
|
|
468
|
+
dftk dataset tips -o | dftk pivot -i day -v tip -f mean count --digits 3 -o tips_by_day.tex
|
|
469
|
+
```
|
|
470
|
+
|
|
423
471
|
### Comment lines and preambles
|
|
424
472
|
|
|
425
473
|
dftk has no comment or skip-rows option: `#` is ordinary data, so headers
|
|
@@ -136,7 +136,7 @@ publication-quality output.
|
|
|
136
136
|
| `sample` | Random row sampling (with or without replacement, by count or fraction) |
|
|
137
137
|
| `split` | Split a dataframe into one file per group |
|
|
138
138
|
| `annotate` | Read and write provenance metadata (genome, source, …) in parquet files |
|
|
139
|
-
| `print` | Read any dftk input (TSV, parquet, stdin) and write TSV
|
|
139
|
+
| `print` | Read any dftk input (TSV, parquet, stdin) and write TSV, JSON, Markdown or LaTeX |
|
|
140
140
|
| `clean` | Remove leftover temp parquet pipe files from interrupted pipelines |
|
|
141
141
|
| `help` | List all subcommands or show full help for one: `dftk help stat` |
|
|
142
142
|
|
|
@@ -165,7 +165,7 @@ For your own export, `dftk cleannames data.tsv --backend duckdb -o data.parquet`
|
|
|
165
165
|
|
|
166
166
|
### JSON files
|
|
167
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.
|
|
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. In TSV a nested object or list is shown as one line of JSON (`["x","y"]`), which later commands can't compute on, so flatten it in the same query: `pos.*` makes a column of each field, `len()` counts a list and `unnest()` gives one row per item:
|
|
169
169
|
|
|
170
170
|
```bash
|
|
171
171
|
printf '%s\n' '{"id": 1, "pos": {"chr": "1", "start": 100}, "tags": ["x", "y"]}' \
|
|
@@ -180,6 +180,32 @@ dftk query --sql "SELECT id, unnest(tags) AS tag FROM 'records.jsonl'" -o \
|
|
|
180
180
|
| dftk pivot -i tag -v id -f count
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
+
A field that is a string in some records and a list in others (a variant catalog's `ReferenceRegion`, say) gets DuckDB type JSON; TSV shows its strings without quotes and its lists as JSON. `json_type()` tells them apart, so a list can become one row per item:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
printf '%s\n' '{"locus": "AFF2", "region": "chrX:1-60"}' \
|
|
187
|
+
'{"locus": "ATXN7", "region": ["chr3:1-30", "chr3:30-42"]}' \
|
|
188
|
+
> catalog.jsonl
|
|
189
|
+
dftk query --sql "
|
|
190
|
+
SELECT locus,
|
|
191
|
+
unnest(CASE WHEN json_type(region) = 'ARRAY' THEN region->>'\$[*]'
|
|
192
|
+
ELSE [region->>'\$'] END) AS region
|
|
193
|
+
FROM 'catalog.jsonl'"
|
|
194
|
+
# locus region
|
|
195
|
+
# AFF2 chrX:1-60
|
|
196
|
+
# ATXN7 chr3:1-30
|
|
197
|
+
# ATXN7 chr3:30-42
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
To get JSON back, write `-o FILE.json` or `.jsonl`, or `--format json` for stdout (see [Output formats](#output-formats)). Nested values and mixed fields are written as JSON again; a key missing from a record is written as `null`:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
dftk query --sql "SELECT * FROM 'catalog.jsonl' WHERE locus LIKE 'ATXN%'" --format json
|
|
204
|
+
# [
|
|
205
|
+
# {"locus": "ATXN7", "region": ["chr3:1-30", "chr3:30-42"]}
|
|
206
|
+
# ]
|
|
207
|
+
```
|
|
208
|
+
|
|
183
209
|
### Large tables: hexbin instead of points
|
|
184
210
|
|
|
185
211
|
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 +416,28 @@ Older scripts give `...` as the file to read a parquet path from stdin; that
|
|
|
390
416
|
still works. In zsh with Oh My Zsh an unquoted `...` becomes `../..`, so
|
|
391
417
|
leave it out (or use `-`) in scripts you share.
|
|
392
418
|
|
|
419
|
+
### Output formats
|
|
420
|
+
|
|
421
|
+
Without `-o`, output is TSV on stdout. `-o` alone, or `-o FILE.parquet`, writes parquet and prints the path for the next command; `-o FILE` writes TSV, except for these extensions:
|
|
422
|
+
|
|
423
|
+
| `-o` | Writes |
|
|
424
|
+
|------|--------|
|
|
425
|
+
| `FILE.json` | JSON array of records |
|
|
426
|
+
| `FILE.jsonl` | JSON Lines, one record per line |
|
|
427
|
+
| `FILE.md` | Markdown table (numeric columns right-aligned) |
|
|
428
|
+
| `FILE.tex` | bare LaTeX `tabular` with booktabs rules |
|
|
429
|
+
|
|
430
|
+
`--format tsv|json|jsonl|md|tex` writes the same formats to stdout, or overrides the extension. `--digits N` applies to all of them. The `.tex` file is only the tabular, with special characters (`_ & % $ # { } ~ ^ \ | < >`) escaped; it needs `\usepackage{booktabs}`, and goes inside your own `table` environment with `\input{tips_by_day.tex}`. Add `\usepackage[T1]{fontenc}` (pdfLaTeX's default font has no straight `"`, which JSON lists contain):
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
dftk dataset tips -o | dftk pivot -i day -v tip -f mean count --digits 3 --format md
|
|
434
|
+
# | day | mean_tip | count_tip |
|
|
435
|
+
# | --- | ---: | ---: |
|
|
436
|
+
# | Thur | 2.77 | 62 |
|
|
437
|
+
# ...
|
|
438
|
+
dftk dataset tips -o | dftk pivot -i day -v tip -f mean count --digits 3 -o tips_by_day.tex
|
|
439
|
+
```
|
|
440
|
+
|
|
393
441
|
### Comment lines and preambles
|
|
394
442
|
|
|
395
443
|
dftk has no comment or skip-rows option: `#` is ordinary data, so headers
|
|
@@ -29,6 +29,7 @@ import uuid
|
|
|
29
29
|
import pandas as pd
|
|
30
30
|
|
|
31
31
|
from dftk.commands.base import BaseCommand
|
|
32
|
+
from dftk.common import docformats
|
|
32
33
|
from dftk.common.io import check_cols, io
|
|
33
34
|
|
|
34
35
|
logger = logging.getLogger(__name__)
|
|
@@ -113,6 +114,24 @@ def _add_cross_col(
|
|
|
113
114
|
args.drop = drop
|
|
114
115
|
|
|
115
116
|
|
|
117
|
+
def _carry_json_columns(
|
|
118
|
+
merged: pd.DataFrame,
|
|
119
|
+
left: pd.DataFrame,
|
|
120
|
+
right: pd.DataFrame,
|
|
121
|
+
suffixes: tuple[str, str],
|
|
122
|
+
) -> None:
|
|
123
|
+
"""Mark the JSON-text columns of both inputs in *merged* (see docformats)."""
|
|
124
|
+
names = []
|
|
125
|
+
for side, other, suffix in ((left, right, suffixes[0]), (right, left, suffixes[1])):
|
|
126
|
+
for col in docformats.json_columns(side):
|
|
127
|
+
name = col
|
|
128
|
+
if col in other.columns and f"{col}{suffix}" in merged.columns:
|
|
129
|
+
name = f"{col}{suffix}"
|
|
130
|
+
if name in merged.columns and name not in names:
|
|
131
|
+
names.append(name)
|
|
132
|
+
docformats.mark_json_columns(merged, names)
|
|
133
|
+
|
|
134
|
+
|
|
116
135
|
def _gap_sides(args: argparse.Namespace) -> tuple[bool, bool]:
|
|
117
136
|
"""Return (left, right): which side's columns can get missing values.
|
|
118
137
|
|
|
@@ -227,6 +246,7 @@ def _do_merge(
|
|
|
227
246
|
if "_merge" in df_merged.columns:
|
|
228
247
|
df_merged = df_merged.drop(columns=["_merge"])
|
|
229
248
|
|
|
249
|
+
_carry_json_columns(df_merged, df_left, df_right, suffixes)
|
|
230
250
|
return df_merged
|
|
231
251
|
|
|
232
252
|
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
dftk.commands.print_cmd — the ``dftk print`` subcommand.
|
|
3
3
|
|
|
4
4
|
Reads any dftk-compatible input (TSV file, TSV on stdin, or a parquet pipe
|
|
5
|
-
path on stdin) and writes TSV to stdout
|
|
5
|
+
path on stdin) and writes TSV to stdout, or JSON, a Markdown table or a
|
|
6
|
+
LaTeX tabular with --format json|jsonl|md|tex. Acts as the terminal stage of a
|
|
6
7
|
dftk pipeline:
|
|
7
8
|
|
|
8
9
|
dftk stat -g grp -s val -o \\
|
|
@@ -12,6 +13,11 @@ dftk pipeline:
|
|
|
12
13
|
With --noheader and --move it names the columns of a headerless file:
|
|
13
14
|
|
|
14
15
|
dftk print data.txt --noheader --move V1:chr V2:begin V3:end
|
|
16
|
+
|
|
17
|
+
A table for a document:
|
|
18
|
+
|
|
19
|
+
dftk dataset tips -o | dftk pivot -i day -v tip -f mean -o \\
|
|
20
|
+
| dftk print --digits 3 --format md
|
|
15
21
|
"""
|
|
16
22
|
|
|
17
23
|
import argparse
|
|
@@ -24,7 +30,7 @@ class PrintCommand(BaseCommand):
|
|
|
24
30
|
"""Read any dftk input and write TSV to stdout."""
|
|
25
31
|
|
|
26
32
|
name = "print"
|
|
27
|
-
help = "
|
|
33
|
+
help = "Write any input (TSV or parquet pipe) as TSV, JSON, Markdown or LaTeX"
|
|
28
34
|
|
|
29
35
|
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
30
36
|
# The standard output options (--move, --select, --removeheader, …)
|
|
@@ -33,6 +33,7 @@ import os
|
|
|
33
33
|
import pandas as pd
|
|
34
34
|
|
|
35
35
|
from dftk.commands.base import BaseCommand
|
|
36
|
+
from dftk.common import docformats
|
|
36
37
|
from dftk.common.io import io
|
|
37
38
|
|
|
38
39
|
try:
|
|
@@ -100,7 +101,11 @@ def _sql_query(args: argparse.Namespace, sql: str, table: str) -> pd.DataFrame:
|
|
|
100
101
|
df = result.df()
|
|
101
102
|
except Exception as exc:
|
|
102
103
|
raise ValueError(f"DuckDB SQL failed: {exc}") from exc
|
|
103
|
-
|
|
104
|
+
df = _hugeint_to_int64(df, description)
|
|
105
|
+
docformats.mark_json_columns(
|
|
106
|
+
df, [d[0] for d in description or [] if str(d[1]) == "JSON"]
|
|
107
|
+
)
|
|
108
|
+
return df
|
|
104
109
|
|
|
105
110
|
|
|
106
111
|
def _uses_table(sql: str, table: str) -> bool:
|
|
@@ -256,6 +261,17 @@ column of each field, len() counts a list, unnest() gives a row per item:
|
|
|
256
261
|
"SELECT * EXCLUDE (pos, tags), pos.* FROM 'records.jsonl'" -o \\
|
|
257
262
|
| dftk stat -c start
|
|
258
263
|
|
|
264
|
+
In TSV output a list or object that is left nested is shown as one line
|
|
265
|
+
of JSON (["x","y"]). A field that is a string in some records and a list
|
|
266
|
+
in others gets DuckDB type JSON; its plain strings are shown without
|
|
267
|
+
quotes. To get JSON back, give -o FILE.json (or .jsonl) or --format json;
|
|
268
|
+
nested values, and such mixed fields, are written as JSON again, and a key
|
|
269
|
+
missing from a record is written as null:
|
|
270
|
+
|
|
271
|
+
dftk query --sql "SELECT * FROM 'records.jsonl' WHERE pos.chr = '1'" \\
|
|
272
|
+
--format json
|
|
273
|
+
dftk query --sql "SELECT id, tags FROM 'records.jsonl'" -o ids.jsonl
|
|
274
|
+
|
|
259
275
|
OUT-OF-CORE LARGE FILES
|
|
260
276
|
------------------------
|
|
261
277
|
When the input is a named .parquet file, DuckDB queries it natively without
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dftk.common.docformats — JSON, Markdown and LaTeX output for io.printdf.
|
|
3
|
+
|
|
4
|
+
Nested values (lists, structs, maps) come from DuckDB as numpy arrays and
|
|
5
|
+
dicts. TSV, Markdown and LaTeX show them as one line of JSON text; JSON
|
|
6
|
+
output writes them as JSON.
|
|
7
|
+
|
|
8
|
+
A DuckDB column of type JSON (from a JSON file whose field is a string in one
|
|
9
|
+
record and a list in another) reaches pandas as JSON text, e.g.
|
|
10
|
+
'"chrX:1-2"' and '["chr3:1-2","chr3:2-3"]'. query_cmd lists such columns in
|
|
11
|
+
the parquet metadata key JSON_COLUMNS_KEY, so they survive -o pipes; the text
|
|
12
|
+
view drops the quotes around a plain string and JSON output parses them.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import contextlib
|
|
16
|
+
import decimal
|
|
17
|
+
import json
|
|
18
|
+
import math
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
import pandas as pd
|
|
22
|
+
|
|
23
|
+
JSON_COLUMNS_KEY = "dftk_json_columns"
|
|
24
|
+
|
|
25
|
+
# Output format by file extension; --format names the same formats
|
|
26
|
+
EXTENSIONS = {".json": "json", ".jsonl": "jsonl", ".md": "md", ".tex": "tex"}
|
|
27
|
+
FORMATS = ("tsv", "json", "jsonl", "md", "tex")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def json_columns(df: pd.DataFrame) -> list[str]:
|
|
31
|
+
"""Columns of *df* that hold DuckDB JSON text (see module docstring)."""
|
|
32
|
+
names = df.attrs.get("_parquet_meta", {}).get(JSON_COLUMNS_KEY, "")
|
|
33
|
+
return [c for c in names.split(",") if c in df.columns]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def mark_json_columns(df: pd.DataFrame, columns: list[str]) -> None:
|
|
37
|
+
"""Record *columns* as JSON text in df.attrs (carried into parquet)."""
|
|
38
|
+
meta = dict(df.attrs.get("_parquet_meta", {}))
|
|
39
|
+
if columns:
|
|
40
|
+
meta[JSON_COLUMNS_KEY] = ",".join(columns)
|
|
41
|
+
else:
|
|
42
|
+
meta.pop(JSON_COLUMNS_KEY, None)
|
|
43
|
+
df.attrs["_parquet_meta"] = meta
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def rename_json_columns(df: pd.DataFrame, mapping: dict[str, str]) -> None:
|
|
47
|
+
"""Follow a --move rename in the JSON column list."""
|
|
48
|
+
names = df.attrs.get("_parquet_meta", {}).get(JSON_COLUMNS_KEY)
|
|
49
|
+
if names:
|
|
50
|
+
mark_json_columns(df, [mapping.get(c, c) for c in names.split(",")])
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _plain(value):
|
|
54
|
+
"""json.dumps fallback for numpy, Decimal and other non-JSON values."""
|
|
55
|
+
if isinstance(value, np.ndarray): # MaskedArray.tolist() gives None
|
|
56
|
+
return value.tolist()
|
|
57
|
+
if isinstance(value, np.generic):
|
|
58
|
+
return value.item()
|
|
59
|
+
if isinstance(value, decimal.Decimal):
|
|
60
|
+
return float(value)
|
|
61
|
+
return str(value)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _is_nested(value) -> bool:
|
|
65
|
+
return isinstance(value, (np.ndarray, list, tuple, dict))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _dumps(value) -> str:
|
|
69
|
+
text = json.dumps(value, default=_plain, ensure_ascii=False, separators=(",", ":"))
|
|
70
|
+
if "NaN" in text or "Infinity" in text:
|
|
71
|
+
# NaN and Infinity aren't JSON: write them as null
|
|
72
|
+
parsed = json.loads(text, parse_constant=lambda _: None)
|
|
73
|
+
text = json.dumps(parsed, ensure_ascii=False, separators=(",", ":"))
|
|
74
|
+
return text
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def nested_as_text(df: pd.DataFrame) -> pd.DataFrame:
|
|
78
|
+
"""Return *df* with nested values and JSON-column text as one-line text."""
|
|
79
|
+
jcols = set(json_columns(df))
|
|
80
|
+
out = None
|
|
81
|
+
# By position: column names may repeat
|
|
82
|
+
for i, col in enumerate(df.columns):
|
|
83
|
+
series = df.iloc[:, i]
|
|
84
|
+
dtype = series.dtype
|
|
85
|
+
if not (
|
|
86
|
+
pd.api.types.is_object_dtype(dtype) or pd.api.types.is_string_dtype(dtype)
|
|
87
|
+
):
|
|
88
|
+
continue
|
|
89
|
+
if col in jcols:
|
|
90
|
+
new = series.map(_json_text_cell)
|
|
91
|
+
elif series.map(_is_nested).any():
|
|
92
|
+
new = series.map(lambda v: _dumps(v) if _is_nested(v) else v)
|
|
93
|
+
else:
|
|
94
|
+
continue
|
|
95
|
+
if out is None:
|
|
96
|
+
out = df.copy()
|
|
97
|
+
out.isetitem(i, new)
|
|
98
|
+
return df if out is None else out
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _json_text_cell(value):
|
|
102
|
+
"""'"chrX:1-2"' → 'chrX:1-2'; a list or object stays JSON text."""
|
|
103
|
+
if not isinstance(value, str):
|
|
104
|
+
return value
|
|
105
|
+
try:
|
|
106
|
+
parsed = json.loads(value)
|
|
107
|
+
except ValueError:
|
|
108
|
+
return value
|
|
109
|
+
return parsed if isinstance(parsed, str) else value
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _json_value(value, digits: int | None, is_json_text: bool):
|
|
113
|
+
"""A cell as a JSON-ready Python value."""
|
|
114
|
+
if is_json_text and isinstance(value, str):
|
|
115
|
+
try:
|
|
116
|
+
return json.loads(value)
|
|
117
|
+
except ValueError:
|
|
118
|
+
return value
|
|
119
|
+
if _is_nested(value):
|
|
120
|
+
return json.loads(_dumps(value))
|
|
121
|
+
if value is None or value is pd.NA or value is pd.NaT:
|
|
122
|
+
return None
|
|
123
|
+
if isinstance(value, np.generic):
|
|
124
|
+
value = value.item()
|
|
125
|
+
if isinstance(value, float):
|
|
126
|
+
if math.isnan(value) or math.isinf(value):
|
|
127
|
+
return None
|
|
128
|
+
if digits is not None:
|
|
129
|
+
return float(f"{value:.{digits}g}")
|
|
130
|
+
return value
|
|
131
|
+
if isinstance(value, (int, bool, str)):
|
|
132
|
+
return value
|
|
133
|
+
return _plain(value)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def write_json(df: pd.DataFrame, dest, lines: bool, digits: int | None) -> None:
|
|
137
|
+
"""Write *df* as an array of records, or one record per line (*lines*).
|
|
138
|
+
|
|
139
|
+
A missing value is written as null; the key is kept.
|
|
140
|
+
"""
|
|
141
|
+
repeated = df.columns[df.columns.duplicated()].unique().tolist()
|
|
142
|
+
if repeated:
|
|
143
|
+
raise ValueError(
|
|
144
|
+
f"JSON output needs unique column names; repeated: {repeated} "
|
|
145
|
+
"(rename one with --move OLD:NEW)"
|
|
146
|
+
)
|
|
147
|
+
jcols = set(json_columns(df))
|
|
148
|
+
cols = list(df.columns)
|
|
149
|
+
records = (
|
|
150
|
+
{
|
|
151
|
+
col: _json_value(value, digits, col in jcols)
|
|
152
|
+
for col, value in zip(cols, row, strict=True)
|
|
153
|
+
}
|
|
154
|
+
for row in df.itertuples(index=False, name=None)
|
|
155
|
+
)
|
|
156
|
+
with _open(dest) as fh:
|
|
157
|
+
if lines:
|
|
158
|
+
for record in records:
|
|
159
|
+
fh.write(json.dumps(record, ensure_ascii=False) + "\n")
|
|
160
|
+
else:
|
|
161
|
+
items = [json.dumps(r, ensure_ascii=False) for r in records]
|
|
162
|
+
fh.write("[\n" + ",\n".join(" " + s for s in items) + "\n]\n")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _cell_text(value, float_format: str | None) -> str:
|
|
166
|
+
if value is None or value is pd.NA or value is pd.NaT:
|
|
167
|
+
return ""
|
|
168
|
+
if isinstance(value, (float, np.floating)):
|
|
169
|
+
if math.isnan(value):
|
|
170
|
+
return ""
|
|
171
|
+
return float_format % value if float_format else str(float(value))
|
|
172
|
+
return str(value)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _is_numeric(series: pd.Series) -> bool:
|
|
176
|
+
return pd.api.types.is_numeric_dtype(series) and not pd.api.types.is_bool_dtype(
|
|
177
|
+
series
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _table(df: pd.DataFrame, float_format: str | None, escape):
|
|
182
|
+
"""Escaped header and rows of *df* (nested values as JSON text)."""
|
|
183
|
+
df = nested_as_text(df)
|
|
184
|
+
header = [escape(str(c)) for c in df.columns]
|
|
185
|
+
rows = [
|
|
186
|
+
[escape(_cell_text(v, float_format)) for v in row]
|
|
187
|
+
for row in df.itertuples(index=False, name=None)
|
|
188
|
+
]
|
|
189
|
+
numeric = [_is_numeric(df.iloc[:, i]) for i in range(df.shape[1])]
|
|
190
|
+
return header, rows, numeric
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _escape_markdown(text: str) -> str:
|
|
194
|
+
return text.replace("\\", "\\\\").replace("|", "\\|").replace("\n", " ")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def write_markdown(
|
|
198
|
+
df: pd.DataFrame, dest, float_format: str | None, header: bool
|
|
199
|
+
) -> None:
|
|
200
|
+
"""Write *df* as a pipe table; numeric columns are right-aligned.
|
|
201
|
+
|
|
202
|
+
A Markdown table always has a header row; with --removeheader it is empty.
|
|
203
|
+
"""
|
|
204
|
+
names, rows, numeric = _table(df, float_format, _escape_markdown)
|
|
205
|
+
if not header:
|
|
206
|
+
names = [""] * len(names)
|
|
207
|
+
rule = ["---:" if num else "---" for num in numeric]
|
|
208
|
+
with _open(dest) as fh:
|
|
209
|
+
for cells in [names, rule, *rows]:
|
|
210
|
+
fh.write("| " + " | ".join(cells) + " |\n")
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
_LATEX_SPECIAL = {
|
|
214
|
+
"\\": r"\textbackslash{}",
|
|
215
|
+
"&": r"\&",
|
|
216
|
+
"%": r"\%",
|
|
217
|
+
"$": r"\$",
|
|
218
|
+
"#": r"\#",
|
|
219
|
+
"_": r"\_",
|
|
220
|
+
"{": r"\{",
|
|
221
|
+
"}": r"\}",
|
|
222
|
+
# A row starting with [ after \\ would be read as \\[length]
|
|
223
|
+
"[": "{[}",
|
|
224
|
+
"]": "{]}",
|
|
225
|
+
"~": r"\textasciitilde{}",
|
|
226
|
+
"^": r"\textasciicircum{}",
|
|
227
|
+
# The default OT1 font prints these as an em dash and as inverted ! and ?
|
|
228
|
+
"|": r"\textbar{}",
|
|
229
|
+
"<": r"\textless{}",
|
|
230
|
+
">": r"\textgreater{}",
|
|
231
|
+
"\n": " ",
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _escape_latex(text: str) -> str:
|
|
236
|
+
return "".join(_LATEX_SPECIAL.get(ch, ch) for ch in text)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def write_latex(df: pd.DataFrame, dest, float_format: str | None, header: bool) -> None:
|
|
240
|
+
"""Write *df* as a bare booktabs tabular (needs \\usepackage{booktabs}).
|
|
241
|
+
|
|
242
|
+
Numeric columns are right-aligned (r), others left (l). Wrap it in your
|
|
243
|
+
own table environment with \\input{file.tex}. A " (in JSON lists, say)
|
|
244
|
+
and ~ ^ print as intended with \\usepackage[T1]{fontenc}; the default
|
|
245
|
+
OT1 font has no straight double quote.
|
|
246
|
+
"""
|
|
247
|
+
names, rows, numeric = _table(df, float_format, _escape_latex)
|
|
248
|
+
spec = "".join("r" if num else "l" for num in numeric)
|
|
249
|
+
with _open(dest) as fh:
|
|
250
|
+
fh.write(f"\\begin{{tabular}}{{{spec}}}\n\\toprule\n")
|
|
251
|
+
if header:
|
|
252
|
+
fh.write(" & ".join(names) + " \\\\\n\\midrule\n")
|
|
253
|
+
for cells in rows:
|
|
254
|
+
fh.write(" & ".join(cells) + " \\\\\n")
|
|
255
|
+
fh.write("\\bottomrule\n\\end{tabular}\n")
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _open(dest):
|
|
259
|
+
"""Open *dest* for writing if it is a path; use it as is if a stream."""
|
|
260
|
+
if isinstance(dest, str):
|
|
261
|
+
return open(dest, "w", encoding="utf-8", newline="")
|
|
262
|
+
return contextlib.nullcontext(dest)
|
|
@@ -10,6 +10,10 @@ Ported from df/src/df/dfio.py with the following changes:
|
|
|
10
10
|
-o FILE.parquet → named parquet written to FILE, path printed to stdout
|
|
11
11
|
(NOT auto-deleted — reusable later in the same pipeline)
|
|
12
12
|
-o FILE → TSV written to FILE, nothing on stdout
|
|
13
|
+
-o FILE.json / .jsonl / .md / .tex
|
|
14
|
+
→ JSON, JSON Lines, Markdown or LaTeX table written to
|
|
15
|
+
FILE (common/docformats.py); --format sends the same
|
|
16
|
+
formats to stdout
|
|
13
17
|
|
|
14
18
|
• Parquet pipes: when stdin (no DATAFILE, or -) or a pipe given as
|
|
15
19
|
DATAFILE (<(...)) holds just the path of an existing .parquet file, that
|
|
@@ -18,7 +22,7 @@ Ported from df/src/df/dfio.py with the following changes:
|
|
|
18
22
|
they can be referenced again downstream. The older ``...`` DATAFILE
|
|
19
23
|
marker still works and requires a parquet path.
|
|
20
24
|
|
|
21
|
-
• Removed --sqlout
|
|
25
|
+
• Removed --sqlout. --latex became -o FILE.tex / --format tex.
|
|
22
26
|
• Removed --experimental (unused).
|
|
23
27
|
• parser_printdf kept as an alias for parser_output for compatibility.
|
|
24
28
|
"""
|
|
@@ -36,6 +40,8 @@ import tempfile
|
|
|
36
40
|
import numpy as np
|
|
37
41
|
import pandas as pd
|
|
38
42
|
|
|
43
|
+
from dftk.common import docformats
|
|
44
|
+
|
|
39
45
|
try:
|
|
40
46
|
import duckdb
|
|
41
47
|
except ImportError:
|
|
@@ -529,12 +535,27 @@ class io:
|
|
|
529
535
|
"path to stdout (auto-deleted by the next reader). "
|
|
530
536
|
"Use -o FILE.parquet to write a named parquet and print its path to "
|
|
531
537
|
"stdout (not auto-deleted — reusable later in the same pipeline). "
|
|
532
|
-
"Use -o FILE (no .parquet) to write TSV to FILE with no stdout output
|
|
538
|
+
"Use -o FILE (no .parquet) to write TSV to FILE with no stdout output; "
|
|
539
|
+
"FILE.json, .jsonl, .md or .tex writes JSON, JSON Lines, a "
|
|
540
|
+
"Markdown table or a LaTeX tabular instead."
|
|
541
|
+
),
|
|
542
|
+
)
|
|
543
|
+
g.add_argument(
|
|
544
|
+
"--format",
|
|
545
|
+
choices=docformats.FORMATS,
|
|
546
|
+
default=None,
|
|
547
|
+
help=(
|
|
548
|
+
"Output format for stdout or -o FILE, instead of the format "
|
|
549
|
+
"the file extension gives: tsv, json (array of records), "
|
|
550
|
+
"jsonl (one record per line), md (Markdown table), tex "
|
|
551
|
+
"(bare booktabs tabular: \\usepackage{booktabs} and [T1]{fontenc}, "
|
|
552
|
+
"then \\input the file). Not with parquet output."
|
|
533
553
|
),
|
|
534
554
|
)
|
|
535
555
|
g.add_argument(
|
|
536
556
|
"--digits",
|
|
537
|
-
help="Significant floating-point digits in TSV
|
|
557
|
+
help="Significant floating-point digits in TSV, JSON, Markdown and "
|
|
558
|
+
"LaTeX output",
|
|
538
559
|
type=int,
|
|
539
560
|
default=None,
|
|
540
561
|
)
|
|
@@ -688,6 +709,7 @@ class io:
|
|
|
688
709
|
try:
|
|
689
710
|
old, new = item.split(":")
|
|
690
711
|
df = df.rename(columns={old: new})
|
|
712
|
+
docformats.rename_json_columns(df, {old: new})
|
|
691
713
|
except Exception:
|
|
692
714
|
logging.warning(f"--move: failed to rename from {item!r}")
|
|
693
715
|
|
|
@@ -836,12 +858,22 @@ class io:
|
|
|
836
858
|
|
|
837
859
|
# Header: written unless --removeheader (--noheader is about the input)
|
|
838
860
|
header = not getattr(args, "removeheader", False)
|
|
839
|
-
|
|
861
|
+
to_parquet = output == "" or str(output).endswith(".parquet")
|
|
862
|
+
if not header and to_parquet:
|
|
840
863
|
logging.warning(
|
|
841
864
|
"--removeheader has no effect on parquet output (-o); parquet "
|
|
842
865
|
"always keeps column names"
|
|
843
866
|
)
|
|
844
867
|
|
|
868
|
+
fmt: str | None = getattr(args, "format", None)
|
|
869
|
+
if fmt is not None and to_parquet:
|
|
870
|
+
raise ValueError(
|
|
871
|
+
f"--format {fmt} can't be combined with parquet output (-o, "
|
|
872
|
+
"-o FILE.parquet): give -o FILE or leave -o out"
|
|
873
|
+
)
|
|
874
|
+
if fmt is None and output:
|
|
875
|
+
fmt = docformats.EXTENSIONS.get(os.path.splitext(output)[1].lower())
|
|
876
|
+
|
|
845
877
|
if output == "":
|
|
846
878
|
# Pipe mode — write temp parquet, print path
|
|
847
879
|
os.makedirs(DFTK_TMPDIR, exist_ok=True)
|
|
@@ -857,13 +889,22 @@ class io:
|
|
|
857
889
|
_write_parquet(df, output, meta=meta or None)
|
|
858
890
|
print(output)
|
|
859
891
|
|
|
892
|
+
elif fmt in ("json", "jsonl"):
|
|
893
|
+
dest = output if output is not None else sys.stdout
|
|
894
|
+
docformats.write_json(df, dest, lines=fmt == "jsonl", digits=digits)
|
|
895
|
+
|
|
896
|
+
elif fmt in ("md", "tex"):
|
|
897
|
+
dest = output if output is not None else sys.stdout
|
|
898
|
+
write = docformats.write_markdown if fmt == "md" else docformats.write_latex
|
|
899
|
+
write(df, dest, float_format=float_format, header=header)
|
|
900
|
+
|
|
860
901
|
else:
|
|
861
902
|
# TSV — stdout or named file
|
|
862
903
|
if digits is not None:
|
|
863
904
|
_warn_digits_exponent(df, digits)
|
|
864
905
|
dest = output if output is not None else sys.stdout
|
|
865
906
|
_write_tsv(
|
|
866
|
-
df,
|
|
907
|
+
docformats.nested_as_text(df),
|
|
867
908
|
dest,
|
|
868
909
|
sep="\t",
|
|
869
910
|
index=False,
|
|
@@ -87,6 +87,12 @@ columns), `--drop COL…`, `--move OLD:NEW…` (rename), `--postquery EXPR`,
|
|
|
87
87
|
`--dropna`, `--na_rep VAL`, `--removeheader`, and
|
|
88
88
|
`--meta KEY=VALUE` (provenance, see below).
|
|
89
89
|
|
|
90
|
+
Output format: TSV unless `-o FILE` ends in `.json` (array of records),
|
|
91
|
+
`.jsonl`, `.md` (Markdown table) or `.tex` (bare booktabs tabular, for
|
|
92
|
+
`\input` in a LaTeX table); `--format tsv|json|jsonl|md|tex` does the same
|
|
93
|
+
on stdout. Lists and nested values are one line of JSON in TSV and stay
|
|
94
|
+
nested in JSON output.
|
|
95
|
+
|
|
90
96
|
## Choosing a command
|
|
91
97
|
|
|
92
98
|
Look at a table first:
|
|
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.14.0 → dataframe_toolkit-0.15.1}/src/dftk/skills/dftk/references/snakemake.md
RENAMED
|
File without changes
|