dataframe-toolkit 0.4.1__py3-none-any.whl
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.4.1.dist-info/METADATA +230 -0
- dataframe_toolkit-0.4.1.dist-info/RECORD +42 -0
- dataframe_toolkit-0.4.1.dist-info/WHEEL +4 -0
- dataframe_toolkit-0.4.1.dist-info/entry_points.txt +3 -0
- dftk/__init__.py +4 -0
- dftk/cli.py +72 -0
- dftk/commands/__init__.py +100 -0
- dftk/commands/annotate_cmd.py +165 -0
- dftk/commands/base.py +85 -0
- dftk/commands/binx_cmd.py +130 -0
- dftk/commands/clean_cmd.py +51 -0
- dftk/commands/concat_cmd.py +165 -0
- dftk/commands/corr_cmd.py +281 -0
- dftk/commands/dataset_cmd.py +181 -0
- dftk/commands/describe_cmd.py +360 -0
- dftk/commands/eval_cmd.py +657 -0
- dftk/commands/fit_cmd.py +265 -0
- dftk/commands/func_cmd.py +186 -0
- dftk/commands/help.py +63 -0
- dftk/commands/hist_cmd.py +321 -0
- dftk/commands/interp_cmd.py +271 -0
- dftk/commands/line_cmd.py +320 -0
- dftk/commands/melt_cmd.py +78 -0
- dftk/commands/merge_cmd.py +373 -0
- dftk/commands/pivot_cmd.py +438 -0
- dftk/commands/print_cmd.py +36 -0
- dftk/commands/query_cmd.py +230 -0
- dftk/commands/randvar_cmd.py +199 -0
- dftk/commands/sample_cmd.py +126 -0
- dftk/commands/scale_cmd.py +299 -0
- dftk/commands/scat_cmd.py +278 -0
- dftk/commands/split_cmd.py +130 -0
- dftk/commands/stat_cmd.py +289 -0
- dftk/commands/test_cmd.py +235 -0
- dftk/commands/wstat_cmd.py +163 -0
- dftk/common/__init__.py +8 -0
- dftk/common/io.py +690 -0
- dftk/common/plot.py +566 -0
- dftk/common/seed.py +25 -0
- dftk/common/seq.py +150 -0
- dftk/common/stats.py +214 -0
- dftk/io.py +10 -0
dftk/commands/base.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dftk.commands.base — abstract base class for all dftk subcommands.
|
|
3
|
+
|
|
4
|
+
Every subcommand must:
|
|
5
|
+
|
|
6
|
+
1. Inherit from BaseCommand.
|
|
7
|
+
2. Define the ``name`` and ``help`` class attributes (or properties).
|
|
8
|
+
3. Implement ``add_arguments(parser)`` to declare its CLI arguments.
|
|
9
|
+
4. Implement ``execute(args)`` to run the command.
|
|
10
|
+
|
|
11
|
+
Most commands also call ``self.add_io_arguments(parser)`` inside
|
|
12
|
+
``add_arguments`` to attach the standard dftk read + output argument groups.
|
|
13
|
+
|
|
14
|
+
Example skeleton::
|
|
15
|
+
|
|
16
|
+
from dftk.commands.base import BaseCommand
|
|
17
|
+
from dftk.common.io import io
|
|
18
|
+
|
|
19
|
+
class MyCommand(BaseCommand):
|
|
20
|
+
name = "mycommand"
|
|
21
|
+
help = "Does something useful"
|
|
22
|
+
|
|
23
|
+
def add_arguments(self, parser):
|
|
24
|
+
self.add_io_arguments(parser) # adds DATAFILE, -o, etc.
|
|
25
|
+
g = parser.add_argument_group("mycommand options")
|
|
26
|
+
g.add_argument("-x", "--xcol", ...)
|
|
27
|
+
|
|
28
|
+
def execute(self, args):
|
|
29
|
+
df = io.read(args)
|
|
30
|
+
# ... transform df ...
|
|
31
|
+
io.printdf(df, args)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
import argparse
|
|
35
|
+
from abc import ABC, abstractmethod
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class BaseCommand(ABC):
|
|
39
|
+
"""Abstract base class for all dftk subcommands."""
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def name(self) -> str:
|
|
44
|
+
"""Command name as it appears on the CLI (e.g. ``"stat"``)."""
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def help(self) -> str:
|
|
49
|
+
"""One-line description shown in ``dftk --help``."""
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
53
|
+
"""Declare all CLI arguments for this command on *parser*."""
|
|
54
|
+
|
|
55
|
+
@abstractmethod
|
|
56
|
+
def execute(self, args: argparse.Namespace) -> None:
|
|
57
|
+
"""Run the command using the parsed *args*."""
|
|
58
|
+
|
|
59
|
+
# ------------------------------------------------------------------ #
|
|
60
|
+
# Shared helpers #
|
|
61
|
+
# ------------------------------------------------------------------ #
|
|
62
|
+
|
|
63
|
+
def add_io_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
64
|
+
"""Attach the standard dftk read + output argument groups to *parser*.
|
|
65
|
+
|
|
66
|
+
Adds:
|
|
67
|
+
* All ``io.parser_read`` arguments (DATAFILE, --backend, --noheader, …)
|
|
68
|
+
* All ``io.parser_output`` arguments (-o/--output, --select, --drop, …)
|
|
69
|
+
|
|
70
|
+
Call this at the top of ``add_arguments`` for any command that reads
|
|
71
|
+
tabular data and produces tabular output.
|
|
72
|
+
"""
|
|
73
|
+
from dftk.common.io import io
|
|
74
|
+
io.parser_read(parser)
|
|
75
|
+
io.parser_output(parser)
|
|
76
|
+
|
|
77
|
+
def add_read_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
78
|
+
"""Attach only the read argument group to *parser* (no output args).
|
|
79
|
+
|
|
80
|
+
Use this for commands that read tabular data but produce non-tabular
|
|
81
|
+
output (e.g. plots), so that --select, --drop, --round, etc. are not
|
|
82
|
+
exposed.
|
|
83
|
+
"""
|
|
84
|
+
from dftk.common.io import io
|
|
85
|
+
io.parser_read(parser)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dftk.commands.binx_cmd — dftk binx subcommand.
|
|
3
|
+
|
|
4
|
+
Port of dfbinx.py: assign each row a bin index based on a column value and
|
|
5
|
+
a set of bin edges, with an optional replacement by the lower, middle, or
|
|
6
|
+
upper edge value of that bin.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
import pandas as pd
|
|
13
|
+
|
|
14
|
+
from dftk.commands.base import BaseCommand
|
|
15
|
+
from dftk.common.io import check_cols, io
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _parse_binspec(binspec: str) -> list[float]:
|
|
19
|
+
"""Parse 'min:max:step' or 'e1,e2,...,eN' into a list of bin edges."""
|
|
20
|
+
if "," in binspec:
|
|
21
|
+
return [float(x) for x in binspec.split(",")]
|
|
22
|
+
parts = binspec.split(":")
|
|
23
|
+
if len(parts) != 3:
|
|
24
|
+
raise ValueError(
|
|
25
|
+
f"binspec must be 'min:max:step' or comma-separated edges, got: {binspec!r}"
|
|
26
|
+
)
|
|
27
|
+
bmin, bmax, bstep = float(parts[0]), float(parts[1]), float(parts[2])
|
|
28
|
+
return list(np.arange(bmin, bmax, bstep))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _binx(df: pd.DataFrame, args: argparse.Namespace) -> pd.DataFrame:
|
|
32
|
+
bins = _parse_binspec(args.binspec)
|
|
33
|
+
if len(bins) < 2:
|
|
34
|
+
raise ValueError(
|
|
35
|
+
f"binspec must produce at least 2 edges; got {len(bins)}: {bins}"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
dest = args.destcol or f"{args.col}_bin"
|
|
39
|
+
df[dest] = pd.cut(df[args.col], bins, labels=False, include_lowest=True)
|
|
40
|
+
|
|
41
|
+
if args.usevalue is not None:
|
|
42
|
+
frac = {"l": 0.0, "m": 0.5, "u": 1.0}[args.usevalue]
|
|
43
|
+
edges = bins
|
|
44
|
+
|
|
45
|
+
def _to_edge(idx: float) -> float:
|
|
46
|
+
if pd.isna(idx):
|
|
47
|
+
return float("nan")
|
|
48
|
+
i = int(idx)
|
|
49
|
+
return (1.0 - frac) * edges[i] + frac * edges[i + 1]
|
|
50
|
+
|
|
51
|
+
df[dest] = df[dest].apply(_to_edge)
|
|
52
|
+
|
|
53
|
+
return df
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
_EPILOG = """\
|
|
57
|
+
BIN SPECIFICATION (--binspec)
|
|
58
|
+
------------------------------
|
|
59
|
+
min:max:step Generate edges with np.arange(min, max, step).
|
|
60
|
+
Note: max is exclusive, same as Python range().
|
|
61
|
+
e1,e2,...,eN Explicit comma-separated edge values.
|
|
62
|
+
|
|
63
|
+
Examples:
|
|
64
|
+
0:100:10 edges [0, 10, 20, ..., 90] → 9 bins
|
|
65
|
+
0,5,10,50 3 bins: [0,5), [5,10), [10,50]
|
|
66
|
+
|
|
67
|
+
USEVALUE (--usevalue)
|
|
68
|
+
---------------------
|
|
69
|
+
Replaces the integer bin index with the actual edge value:
|
|
70
|
+
l lower edge of the bin
|
|
71
|
+
m midpoint between lower and upper edges
|
|
72
|
+
u upper edge of the bin
|
|
73
|
+
|
|
74
|
+
EXAMPLES
|
|
75
|
+
--------
|
|
76
|
+
dftk binx data.tsv -c age -b 0:100:10
|
|
77
|
+
dftk binx data.tsv -c score -b 0,25,50,75,100 -d quartile --usevalue m
|
|
78
|
+
dftk binx data.tsv -c x -b -3:4:1 -d x_bin -o | dftk stat -g x_bin -s y
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class BinxCommand(BaseCommand):
|
|
83
|
+
@property
|
|
84
|
+
def name(self) -> str:
|
|
85
|
+
return "binx"
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def help(self) -> str:
|
|
89
|
+
return "Assign bin indices to a column based on explicit or generated edges."
|
|
90
|
+
|
|
91
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
92
|
+
parser.formatter_class = argparse.RawDescriptionHelpFormatter
|
|
93
|
+
parser.epilog = _EPILOG
|
|
94
|
+
|
|
95
|
+
self.add_io_arguments(parser)
|
|
96
|
+
|
|
97
|
+
g = parser.add_argument_group("binx options")
|
|
98
|
+
g.add_argument(
|
|
99
|
+
"-c",
|
|
100
|
+
"--col",
|
|
101
|
+
required=True,
|
|
102
|
+
metavar="COL",
|
|
103
|
+
help="Column to bin.",
|
|
104
|
+
)
|
|
105
|
+
g.add_argument(
|
|
106
|
+
"-d",
|
|
107
|
+
"--destcol",
|
|
108
|
+
default=None,
|
|
109
|
+
metavar="DESTCOL",
|
|
110
|
+
help="Name of the output bin column (default: <col>_bin).",
|
|
111
|
+
)
|
|
112
|
+
g.add_argument(
|
|
113
|
+
"-b",
|
|
114
|
+
"--binspec",
|
|
115
|
+
required=True,
|
|
116
|
+
metavar="SPEC",
|
|
117
|
+
help="Bin edges: 'min:max:step' or 'e1,e2,...,eN'.",
|
|
118
|
+
)
|
|
119
|
+
g.add_argument(
|
|
120
|
+
"--usevalue",
|
|
121
|
+
choices=["l", "m", "u"],
|
|
122
|
+
default=None,
|
|
123
|
+
help="Replace bin index with bin edge value: l=lower, m=mid, u=upper.",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def execute(self, args: argparse.Namespace) -> None:
|
|
127
|
+
df = io.read(args)
|
|
128
|
+
check_cols(df, [args.col], "-c/--col")
|
|
129
|
+
result = _binx(df, args)
|
|
130
|
+
io.printdf(result, args)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dftk.commands.clean_cmd — the ``dftk clean`` subcommand.
|
|
3
|
+
|
|
4
|
+
Removes all temp parquet pipe files left in DFTK_TMPDIR. Useful after a
|
|
5
|
+
pipeline is interrupted before the final ``dftk print`` stage has had a
|
|
6
|
+
chance to consume (and therefore delete) them.
|
|
7
|
+
|
|
8
|
+
dftk clean
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
from dftk.commands.base import BaseCommand
|
|
16
|
+
from dftk.common.io import DFTK_TMPDIR
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CleanCommand(BaseCommand):
|
|
20
|
+
"""Remove leftover temp parquet pipe files."""
|
|
21
|
+
|
|
22
|
+
name = "clean"
|
|
23
|
+
help = "Remove temp parquet pipe files from the dftk temp directory"
|
|
24
|
+
|
|
25
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
26
|
+
# No arguments needed — always cleans DFTK_TMPDIR.
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
def execute(self, args: argparse.Namespace) -> None:
|
|
30
|
+
if not os.path.isdir(DFTK_TMPDIR):
|
|
31
|
+
print(
|
|
32
|
+
f"Nothing to clean (directory does not exist: {DFTK_TMPDIR})",
|
|
33
|
+
file=sys.stderr,
|
|
34
|
+
)
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
files = [f for f in os.listdir(DFTK_TMPDIR) if f.endswith(".parquet")]
|
|
38
|
+
errors = 0
|
|
39
|
+
for f in files:
|
|
40
|
+
try:
|
|
41
|
+
os.unlink(os.path.join(DFTK_TMPDIR, f))
|
|
42
|
+
except OSError as exc:
|
|
43
|
+
print(f"Warning: could not remove {f}: {exc}", file=sys.stderr)
|
|
44
|
+
errors += 1
|
|
45
|
+
|
|
46
|
+
removed = len(files) - errors
|
|
47
|
+
print(
|
|
48
|
+
f"Removed {removed} file(s) from {DFTK_TMPDIR}"
|
|
49
|
+
+ (f" ({errors} error(s))" if errors else ""),
|
|
50
|
+
file=sys.stderr,
|
|
51
|
+
)
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dftk.commands.concat_cmd — dftk concat subcommand.
|
|
3
|
+
|
|
4
|
+
Concatenate two or more input files (TSV or parquet) row-wise into a single
|
|
5
|
+
DataFrame. Column alignment follows pandas outer-join semantics: columns
|
|
6
|
+
present in some but not all files are filled with NaN (or --fill value).
|
|
7
|
+
|
|
8
|
+
Examples
|
|
9
|
+
--------
|
|
10
|
+
dftk concat a.tsv b.tsv c.tsv
|
|
11
|
+
dftk concat a.tsv b.tsv --sourcecol source
|
|
12
|
+
dftk concat a.parquet b.parquet -o
|
|
13
|
+
dftk eval data.tsv -f "x=1" -o | dftk concat ... extra.tsv
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import copy
|
|
18
|
+
|
|
19
|
+
import pandas as pd
|
|
20
|
+
|
|
21
|
+
from dftk.commands.base import BaseCommand
|
|
22
|
+
from dftk.common.io import io
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ConcatCommand(BaseCommand):
|
|
26
|
+
@property
|
|
27
|
+
def name(self) -> str:
|
|
28
|
+
return "concat"
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def help(self) -> str:
|
|
32
|
+
return "Concatenate multiple input files row-wise"
|
|
33
|
+
|
|
34
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
35
|
+
parser.formatter_class = argparse.RawDescriptionHelpFormatter
|
|
36
|
+
parser.epilog = """\
|
|
37
|
+
COLUMN ALIGNMENT
|
|
38
|
+
----------------
|
|
39
|
+
Columns present in all files are aligned by name.
|
|
40
|
+
Columns missing from some files are filled with NaN (or --fill value).
|
|
41
|
+
Use --select to enforce a fixed output column set.
|
|
42
|
+
|
|
43
|
+
EXAMPLES
|
|
44
|
+
--------
|
|
45
|
+
dftk concat a.tsv b.tsv
|
|
46
|
+
dftk concat a.tsv b.tsv c.tsv --sourcecol source
|
|
47
|
+
dftk concat a.parquet b.parquet -o result.parquet
|
|
48
|
+
dftk eval data.tsv -f "x=1" -o | dftk concat ... extra.tsv
|
|
49
|
+
"""
|
|
50
|
+
g = parser.add_argument_group("concat options")
|
|
51
|
+
g.add_argument(
|
|
52
|
+
"DATAFILES",
|
|
53
|
+
nargs="+",
|
|
54
|
+
metavar="FILE",
|
|
55
|
+
help=(
|
|
56
|
+
"Input files to concatenate (TSV or .parquet). "
|
|
57
|
+
"Use - for TSV stdin or ... for a parquet pipe path on stdin. "
|
|
58
|
+
"At most one stdin source is allowed."
|
|
59
|
+
),
|
|
60
|
+
)
|
|
61
|
+
g.add_argument(
|
|
62
|
+
"--sourcecol",
|
|
63
|
+
default=None,
|
|
64
|
+
metavar="COL",
|
|
65
|
+
help="Add a column with this name containing the source filename.",
|
|
66
|
+
)
|
|
67
|
+
g.add_argument(
|
|
68
|
+
"--fill",
|
|
69
|
+
default=None,
|
|
70
|
+
metavar="VALUE",
|
|
71
|
+
help=(
|
|
72
|
+
"Fill value for columns absent in some files "
|
|
73
|
+
"(default: NaN). Use 0 or '' for numeric/string defaults."
|
|
74
|
+
),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
r = parser.add_argument_group("data input")
|
|
78
|
+
r.add_argument(
|
|
79
|
+
"--backend",
|
|
80
|
+
help="Backend for reading data (default: pandas)",
|
|
81
|
+
choices=["pandas", "duckdb"],
|
|
82
|
+
default="pandas",
|
|
83
|
+
)
|
|
84
|
+
r.add_argument(
|
|
85
|
+
"--noheader",
|
|
86
|
+
help="Input files have no header row; columns are named V1, V2, …",
|
|
87
|
+
action="store_true",
|
|
88
|
+
)
|
|
89
|
+
r.add_argument(
|
|
90
|
+
"--nrows",
|
|
91
|
+
help="Maximum number of rows to read per file",
|
|
92
|
+
type=int,
|
|
93
|
+
default=None,
|
|
94
|
+
)
|
|
95
|
+
r.add_argument(
|
|
96
|
+
"--delimiter",
|
|
97
|
+
help="Column delimiter for TSV inputs (default: tab)",
|
|
98
|
+
default=None,
|
|
99
|
+
)
|
|
100
|
+
r.add_argument(
|
|
101
|
+
"--readasobject",
|
|
102
|
+
help=(
|
|
103
|
+
"Read named columns as object dtype. "
|
|
104
|
+
"Omit column names to apply to all columns."
|
|
105
|
+
),
|
|
106
|
+
nargs="*",
|
|
107
|
+
default=None,
|
|
108
|
+
metavar="COL",
|
|
109
|
+
)
|
|
110
|
+
r.add_argument(
|
|
111
|
+
"--prequery",
|
|
112
|
+
help="Pandas query expression after reading each file. Repeatable.",
|
|
113
|
+
default=[],
|
|
114
|
+
action="append",
|
|
115
|
+
metavar="EXPR",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
io.parser_output(parser)
|
|
119
|
+
|
|
120
|
+
def execute(self, args: argparse.Namespace) -> None:
|
|
121
|
+
stdin_used = False
|
|
122
|
+
dfs: list[pd.DataFrame] = []
|
|
123
|
+
|
|
124
|
+
for path in args.DATAFILES:
|
|
125
|
+
if path in ("-", "..."):
|
|
126
|
+
if stdin_used:
|
|
127
|
+
raise ValueError(
|
|
128
|
+
"stdin (-/...) can only appear once in a concat file list"
|
|
129
|
+
)
|
|
130
|
+
stdin_used = True
|
|
131
|
+
|
|
132
|
+
# Build a per-file args with DATAFILE set to this path.
|
|
133
|
+
file_args = copy.copy(args)
|
|
134
|
+
file_args.DATAFILE = path
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
df = io.read(file_args)
|
|
138
|
+
except pd.errors.EmptyDataError:
|
|
139
|
+
import logging
|
|
140
|
+
|
|
141
|
+
logging.warning(f"concat: skipping empty file {path!r}")
|
|
142
|
+
continue
|
|
143
|
+
|
|
144
|
+
if args.sourcecol:
|
|
145
|
+
df[args.sourcecol] = path
|
|
146
|
+
|
|
147
|
+
dfs.append(df)
|
|
148
|
+
|
|
149
|
+
if not dfs:
|
|
150
|
+
raise ValueError("concat: no data read from any input file")
|
|
151
|
+
|
|
152
|
+
result = pd.concat(dfs, ignore_index=True)
|
|
153
|
+
|
|
154
|
+
if args.fill is not None:
|
|
155
|
+
fill: int | float | str
|
|
156
|
+
try:
|
|
157
|
+
fill = int(args.fill)
|
|
158
|
+
except ValueError:
|
|
159
|
+
try:
|
|
160
|
+
fill = float(args.fill)
|
|
161
|
+
except ValueError:
|
|
162
|
+
fill = args.fill
|
|
163
|
+
result = result.fillna(fill)
|
|
164
|
+
|
|
165
|
+
io.printdf(result, args)
|