dataframe-toolkit 0.7.0__tar.gz → 0.7.2__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.7.0 → dataframe_toolkit-0.7.2}/PKG-INFO +16 -1
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/README.md +14 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/pyproject.toml +2 -1
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/pyproject.toml.orig +2 -1
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/cli.py +22 -3
- dataframe_toolkit-0.7.2/src/dftk/commands/__init__.py +98 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/concat_cmd.py +13 -4
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/eval_cmd.py +47 -37
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/fit_cmd.py +19 -5
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/help.py +6 -21
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/stat_cmd.py +3 -0
- dataframe_toolkit-0.7.0/src/dftk/commands/__init__.py +0 -105
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/__init__.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/annotate_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/base.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/binx_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/chi_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/clean_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/corr_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/dataset_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/describe_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/func_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/hist_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/info_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/interp_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/line_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/melt_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/merge_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/pivot_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/print_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/query_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/randvar_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/sample_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/scale_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/scat_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/segid_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/split_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/test_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/transpose_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/commands/wstat_cmd.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/common/__init__.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/common/copula.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/common/io.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/common/plot.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/common/seed.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/common/seq.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/common/stats.py +0 -0
- {dataframe_toolkit-0.7.0 → dataframe_toolkit-0.7.2}/src/dftk/io.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: dataframe-toolkit
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
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
|
|
@@ -17,6 +17,7 @@ Requires-Dist: numpy>=1.24
|
|
|
17
17
|
Requires-Dist: pyarrow>=12.0
|
|
18
18
|
Requires-Dist: scipy>=1.10
|
|
19
19
|
Requires-Dist: statsmodels>=0.14
|
|
20
|
+
Requires-Dist: patsy>=0.5.6
|
|
20
21
|
Requires-Dist: duckdb>=0.8
|
|
21
22
|
Requires-Dist: matplotlib>=3.7
|
|
22
23
|
Requires-Dist: seaborn>=0.12
|
|
@@ -73,6 +74,20 @@ cd dataframe-toolkit
|
|
|
73
74
|
uv tool install .
|
|
74
75
|
```
|
|
75
76
|
|
|
77
|
+
### Skill for coding agents
|
|
78
|
+
|
|
79
|
+
`skills/dftk/` is an agent skill: a short guide that coding agents such as
|
|
80
|
+
Codex and Claude Code load when a task involves dftk. It covers the piping
|
|
81
|
+
conventions, which command to use for what, and patterns for Snakemake and
|
|
82
|
+
Make. Copy it into your agent's skills directory, for example:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
cp -r skills/dftk ~/.codex/skills/ # Codex
|
|
86
|
+
cp -r skills/dftk ~/.claude/skills/ # Claude Code
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Copy it again after upgrading dftk so it matches the installed version.
|
|
90
|
+
|
|
76
91
|
## Quick example
|
|
77
92
|
|
|
78
93
|
```bash
|
|
@@ -45,6 +45,20 @@ cd dataframe-toolkit
|
|
|
45
45
|
uv tool install .
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
### Skill for coding agents
|
|
49
|
+
|
|
50
|
+
`skills/dftk/` is an agent skill: a short guide that coding agents such as
|
|
51
|
+
Codex and Claude Code load when a task involves dftk. It covers the piping
|
|
52
|
+
conventions, which command to use for what, and patterns for Snakemake and
|
|
53
|
+
Make. Copy it into your agent's skills directory, for example:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
cp -r skills/dftk ~/.codex/skills/ # Codex
|
|
57
|
+
cp -r skills/dftk ~/.claude/skills/ # Claude Code
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Copy it again after upgrading dftk so it matches the installed version.
|
|
61
|
+
|
|
48
62
|
## Quick example
|
|
49
63
|
|
|
50
64
|
```bash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dataframe-toolkit"
|
|
3
|
-
version = "0.7.
|
|
3
|
+
version = "0.7.2"
|
|
4
4
|
description = "A command-line toolkit for DataFrame analysis and manipulation"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
keywords = [
|
|
@@ -26,6 +26,7 @@ dependencies = [
|
|
|
26
26
|
"pyarrow>=12.0",
|
|
27
27
|
"scipy>=1.10",
|
|
28
28
|
"statsmodels>=0.14",
|
|
29
|
+
"patsy>=0.5.6",
|
|
29
30
|
"duckdb>=0.8",
|
|
30
31
|
"matplotlib>=3.7",
|
|
31
32
|
"seaborn>=0.12",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dataframe-toolkit"
|
|
3
|
-
version = "0.7.
|
|
3
|
+
version = "0.7.2"
|
|
4
4
|
description = "A command-line toolkit for DataFrame analysis and manipulation"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -23,6 +23,7 @@ dependencies = [
|
|
|
23
23
|
"pyarrow>=12.0",
|
|
24
24
|
"scipy>=1.10",
|
|
25
25
|
"statsmodels>=0.14",
|
|
26
|
+
"patsy>=0.5.6",
|
|
26
27
|
"duckdb>=0.8",
|
|
27
28
|
"matplotlib>=3.7",
|
|
28
29
|
"seaborn>=0.12",
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
"""
|
|
2
2
|
dftk.cli — entry point for the ``dftk`` command.
|
|
3
3
|
|
|
4
|
-
All subcommands are discovered through the
|
|
4
|
+
All subcommands are discovered through the registry in
|
|
5
5
|
dftk/commands/__init__.py. This file contains no command-specific
|
|
6
6
|
logic and never needs to change when a new subcommand is added.
|
|
7
|
+
|
|
8
|
+
Only the command being run is imported, so e.g. ``dftk print`` does not pay
|
|
9
|
+
for importing scipy, statsmodels or matplotlib. The full set is imported only
|
|
10
|
+
when it is needed: ``dftk --help``, no command, or an unknown command name.
|
|
7
11
|
"""
|
|
8
12
|
|
|
9
13
|
import argparse
|
|
@@ -13,13 +17,28 @@ import signal
|
|
|
13
17
|
import sys
|
|
14
18
|
|
|
15
19
|
from dftk import __version__
|
|
16
|
-
from dftk.commands import
|
|
20
|
+
from dftk.commands import COMMAND_NAMES, load_command, load_command_groups
|
|
21
|
+
from dftk.commands.base import BaseCommand
|
|
17
22
|
from dftk.common.io import DFTK_TMPDIR
|
|
18
23
|
|
|
19
24
|
logger = logging.getLogger("dftk")
|
|
20
25
|
logging.basicConfig(format="%(asctime)s %(module)s %(message)s", level=logging.INFO)
|
|
21
26
|
|
|
22
27
|
|
|
28
|
+
def _commands_for(argv: list[str]) -> list[BaseCommand]:
|
|
29
|
+
"""Return the commands whose subparsers *argv* needs.
|
|
30
|
+
|
|
31
|
+
The top-level parser has no options that take a value, so a valid
|
|
32
|
+
invocation always has the command name first.
|
|
33
|
+
"""
|
|
34
|
+
if argv and argv[0] in COMMAND_NAMES:
|
|
35
|
+
return [load_command(argv[0])]
|
|
36
|
+
# `dftk --help`, no command, or an unknown name: load everything so the
|
|
37
|
+
# command listing and argparse's "invalid choice" message are complete.
|
|
38
|
+
grouped = [cmd for _, cmds in load_command_groups() for cmd in cmds]
|
|
39
|
+
return grouped + [load_command("help")]
|
|
40
|
+
|
|
41
|
+
|
|
23
42
|
def main() -> None:
|
|
24
43
|
parser = argparse.ArgumentParser(
|
|
25
44
|
prog="dftk",
|
|
@@ -40,7 +59,7 @@ def main() -> None:
|
|
|
40
59
|
subparsers = parser.add_subparsers(dest="command", metavar="COMMAND")
|
|
41
60
|
subparsers.required = True
|
|
42
61
|
|
|
43
|
-
for cmd in
|
|
62
|
+
for cmd in _commands_for(sys.argv[1:]):
|
|
44
63
|
cmd_parser = subparsers.add_parser(cmd.name, help=cmd.help)
|
|
45
64
|
cmd.add_arguments(cmd_parser)
|
|
46
65
|
cmd_parser.set_defaults(command_instance=cmd)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# dftk.commands — registry of all dftk subcommands.
|
|
2
|
+
#
|
|
3
|
+
# Commands are registered by name and import path instead of being imported
|
|
4
|
+
# here, so that running one command imports only that command's module and its
|
|
5
|
+
# dependencies (scipy, statsmodels, matplotlib, …), not every command's.
|
|
6
|
+
#
|
|
7
|
+
# To add a new subcommand:
|
|
8
|
+
# 1. Create src/dftk/commands/<name>_cmd.py with a class that inherits
|
|
9
|
+
# from BaseCommand (see base.py for the contract and an example skeleton).
|
|
10
|
+
# 2. Add a ("<name>", "<name>_cmd:<ClassName>") entry to COMMAND_GROUPS below.
|
|
11
|
+
# That's it — cli.py discovers commands solely through this registry.
|
|
12
|
+
# tests/commands/test_registry.py checks that every entry imports and that
|
|
13
|
+
# every *_cmd.py module is registered.
|
|
14
|
+
|
|
15
|
+
import importlib
|
|
16
|
+
|
|
17
|
+
from dftk.commands.base import BaseCommand
|
|
18
|
+
|
|
19
|
+
# Ported subcommands — add as each one is implemented:
|
|
20
|
+
# ("color", "color_cmd:ColorCommand")
|
|
21
|
+
# ("wavelet", "wavelet_cmd:WaveletCommand")
|
|
22
|
+
# ("fisher", ...) — superseded by eval's fisher_test/fisher_OR
|
|
23
|
+
|
|
24
|
+
COMMAND_GROUPS: list[tuple[str, list[tuple[str, str]]]] = [
|
|
25
|
+
(
|
|
26
|
+
"Data transformation",
|
|
27
|
+
[
|
|
28
|
+
("eval", "eval_cmd:EvalCommand"),
|
|
29
|
+
("query", "query_cmd:QueryCommand"),
|
|
30
|
+
("merge", "merge_cmd:MergeCommand"),
|
|
31
|
+
("concat", "concat_cmd:ConcatCommand"),
|
|
32
|
+
("melt", "melt_cmd:MeltCommand"),
|
|
33
|
+
("pivot", "pivot_cmd:PivotCommand"),
|
|
34
|
+
("func", "func_cmd:FuncCommand"),
|
|
35
|
+
("scale", "scale_cmd:ScaleCommand"),
|
|
36
|
+
("interp", "interp_cmd:InterpCommand"),
|
|
37
|
+
("binx", "binx_cmd:BinxCommand"),
|
|
38
|
+
("transpose", "transpose_cmd:TransposeCommand"),
|
|
39
|
+
("segid", "segid_cmd:SegidCommand"),
|
|
40
|
+
],
|
|
41
|
+
),
|
|
42
|
+
(
|
|
43
|
+
"Statistics",
|
|
44
|
+
[
|
|
45
|
+
("stat", "stat_cmd:StatCommand"),
|
|
46
|
+
("wstat", "wstat_cmd:WstatCommand"),
|
|
47
|
+
("fit", "fit_cmd:FitCommand"),
|
|
48
|
+
("test", "test_cmd:TestCommand"),
|
|
49
|
+
("corr", "corr_cmd:CorrCommand"),
|
|
50
|
+
("describe", "describe_cmd:DescribeCommand"),
|
|
51
|
+
("info", "info_cmd:InfoCommand"),
|
|
52
|
+
("randvar", "randvar_cmd:RandvarCommand"),
|
|
53
|
+
],
|
|
54
|
+
),
|
|
55
|
+
(
|
|
56
|
+
"Plots",
|
|
57
|
+
[
|
|
58
|
+
("scat", "scat_cmd:ScatCommand"),
|
|
59
|
+
("line", "line_cmd:LineCommand"),
|
|
60
|
+
("hist", "hist_cmd:HistCommand"),
|
|
61
|
+
("chi", "chi_cmd:ChiCommand"),
|
|
62
|
+
],
|
|
63
|
+
),
|
|
64
|
+
(
|
|
65
|
+
"Utilities",
|
|
66
|
+
[
|
|
67
|
+
("dataset", "dataset_cmd:DatasetCommand"),
|
|
68
|
+
("sample", "sample_cmd:SampleCommand"),
|
|
69
|
+
("split", "split_cmd:SplitCommand"),
|
|
70
|
+
("annotate", "annotate_cmd:AnnotateCommand"),
|
|
71
|
+
("print", "print_cmd:PrintCommand"),
|
|
72
|
+
("clean", "clean_cmd:CleanCommand"),
|
|
73
|
+
],
|
|
74
|
+
),
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
# `help` is a command like the others but is not listed under a group heading.
|
|
78
|
+
_REGISTRY: dict[str, str] = dict(
|
|
79
|
+
[entry for _, entries in COMMAND_GROUPS for entry in entries]
|
|
80
|
+
+ [("help", "help:HelpCommand")]
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
COMMAND_NAMES: list[str] = list(_REGISTRY)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def load_command(name: str) -> BaseCommand:
|
|
87
|
+
"""Import and instantiate the command registered as *name*."""
|
|
88
|
+
module_name, class_name = _REGISTRY[name].split(":")
|
|
89
|
+
module = importlib.import_module(f"dftk.commands.{module_name}")
|
|
90
|
+
return getattr(module, class_name)()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def load_command_groups() -> list[tuple[str, list[BaseCommand]]]:
|
|
94
|
+
"""Import and instantiate every grouped command (slow: imports everything)."""
|
|
95
|
+
return [
|
|
96
|
+
(header, [load_command(name) for name, _ in entries])
|
|
97
|
+
for header, entries in COMMAND_GROUPS
|
|
98
|
+
]
|
|
@@ -40,6 +40,8 @@ COLUMN ALIGNMENT
|
|
|
40
40
|
----------------
|
|
41
41
|
Columns present in all files are aligned by name.
|
|
42
42
|
Columns missing from some files are filled with NaN (or --fill value).
|
|
43
|
+
--fill applies only to those missing columns: a cell that is empty within
|
|
44
|
+
a file stays empty.
|
|
43
45
|
Use --select to enforce a fixed output column set.
|
|
44
46
|
|
|
45
47
|
EXAMPLES
|
|
@@ -79,7 +81,8 @@ Mismatched columns are outer-joined and filled (NaN by default, or --fill):
|
|
|
79
81
|
metavar="VALUE",
|
|
80
82
|
help=(
|
|
81
83
|
"Fill value for columns absent in some files "
|
|
82
|
-
"(default: NaN). Use 0 or '' for numeric/string defaults."
|
|
84
|
+
"(default: NaN). Use 0 or '' for numeric/string defaults. "
|
|
85
|
+
"Cells that are empty within a file stay empty."
|
|
83
86
|
),
|
|
84
87
|
)
|
|
85
88
|
|
|
@@ -152,8 +155,6 @@ Mismatched columns are outer-joined and filled (NaN by default, or --fill):
|
|
|
152
155
|
if not dfs:
|
|
153
156
|
raise ValueError("concat: no data read from any input file")
|
|
154
157
|
|
|
155
|
-
result = pd.concat(dfs, ignore_index=True)
|
|
156
|
-
|
|
157
158
|
if args.fill is not None:
|
|
158
159
|
fill: int | float | str
|
|
159
160
|
try:
|
|
@@ -163,6 +164,14 @@ Mismatched columns are outer-joined and filled (NaN by default, or --fill):
|
|
|
163
164
|
fill = float(args.fill)
|
|
164
165
|
except ValueError:
|
|
165
166
|
fill = args.fill
|
|
166
|
-
|
|
167
|
+
# Add each file's missing columns before stacking, so --fill
|
|
168
|
+
# touches only those, not cells that were already empty in a file.
|
|
169
|
+
columns = list(dict.fromkeys(c for df in dfs for c in df.columns))
|
|
170
|
+
dfs = [
|
|
171
|
+
df.assign(**{c: fill for c in columns if c not in df.columns})[columns]
|
|
172
|
+
for df in dfs
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
result = pd.concat(dfs, ignore_index=True)
|
|
167
176
|
|
|
168
177
|
io.printdf(result, args)
|
|
@@ -19,11 +19,9 @@ from pathlib import Path
|
|
|
19
19
|
|
|
20
20
|
import numpy as np
|
|
21
21
|
import pandas as pd
|
|
22
|
-
from scipy import special as _sp
|
|
23
22
|
|
|
24
23
|
from dftk.commands.base import BaseCommand
|
|
25
24
|
from dftk.common import seq as _seq
|
|
26
|
-
from dftk.common import stats as _stats
|
|
27
25
|
from dftk.common.io import io
|
|
28
26
|
|
|
29
27
|
logger = logging.getLogger(__name__)
|
|
@@ -190,18 +188,23 @@ def _parse_formula(formula: str) -> tuple[str, str, list[str], str]:
|
|
|
190
188
|
# Special functions (used as pandas agg fallback inside -f)
|
|
191
189
|
# ---------------------------------------------------------------------------
|
|
192
190
|
|
|
191
|
+
# The function tables below hold names, not function objects: dftk.common.stats
|
|
192
|
+
# and scipy.special are imported only when a formula actually uses one of them,
|
|
193
|
+
# so plain `dftk eval -f` formulas don't pay scipy's import time.
|
|
194
|
+
|
|
195
|
+
# dftk.common.stats functions, same name in the formula as in the module
|
|
193
196
|
_STATS_DISPATCH = {
|
|
194
|
-
"binom_test"
|
|
195
|
-
"fisher_test"
|
|
196
|
-
"fisher_OR"
|
|
197
|
-
"boschloo_test"
|
|
198
|
-
"boschloo_OR"
|
|
199
|
-
"pval2se"
|
|
200
|
-
"t2pval"
|
|
201
|
-
"chi2_to_neglogp"
|
|
202
|
-
"neglogp_to_chi2"
|
|
203
|
-
"pval_to_chi2"
|
|
204
|
-
"generalized_poisson_nll"
|
|
197
|
+
"binom_test",
|
|
198
|
+
"fisher_test",
|
|
199
|
+
"fisher_OR",
|
|
200
|
+
"boschloo_test",
|
|
201
|
+
"boschloo_OR",
|
|
202
|
+
"pval2se",
|
|
203
|
+
"t2pval",
|
|
204
|
+
"chi2_to_neglogp",
|
|
205
|
+
"neglogp_to_chi2",
|
|
206
|
+
"pval_to_chi2",
|
|
207
|
+
"generalized_poisson_nll",
|
|
205
208
|
}
|
|
206
209
|
|
|
207
210
|
_PATH_FUNCS = {"basename", "dirname", "exists", "getsize", "realpath"}
|
|
@@ -209,35 +212,33 @@ _NP_FUNCS = {"sign", "cbrt", "log1p", "expm1"}
|
|
|
209
212
|
_ROW_AGG_FUNCS = {"sum", "mean", "std", "min", "max", "median"}
|
|
210
213
|
_ROW_IDX_FUNCS = {"idxmax", "idxmin"}
|
|
211
214
|
|
|
212
|
-
# scipy.special single-column functions (element-wise, vectorised)
|
|
215
|
+
# scipy.special single-column functions (element-wise, vectorised):
|
|
216
|
+
# formula name -> scipy.special attribute name
|
|
213
217
|
_SCIPY_SINGLE = {
|
|
214
|
-
"gamma":
|
|
215
|
-
"lgamma":
|
|
216
|
-
"digamma":
|
|
217
|
-
"erf":
|
|
218
|
-
"erfc":
|
|
219
|
-
"erfinv":
|
|
220
|
-
"expit":
|
|
221
|
-
"logit":
|
|
218
|
+
"gamma": "gamma", # Γ(x)
|
|
219
|
+
"lgamma": "gammaln", # log Γ(x)
|
|
220
|
+
"digamma": "digamma", # ψ(x) (log-derivative of Γ)
|
|
221
|
+
"erf": "erf", # error function
|
|
222
|
+
"erfc": "erfc", # complementary error function
|
|
223
|
+
"erfinv": "erfinv", # inverse error function
|
|
224
|
+
"expit": "expit", # logistic sigmoid 1/(1+exp(-x))
|
|
225
|
+
"logit": "logit", # log(x/(1-x))
|
|
222
226
|
# Bessel J (first kind), fixed order
|
|
223
|
-
"j0":
|
|
224
|
-
"j1":
|
|
227
|
+
"j0": "j0",
|
|
228
|
+
"j1": "j1",
|
|
225
229
|
# Bessel Y (second kind), fixed order
|
|
226
|
-
"y0":
|
|
227
|
-
"y1":
|
|
230
|
+
"y0": "y0",
|
|
231
|
+
"y1": "y1",
|
|
228
232
|
# Modified Bessel I, fixed order
|
|
229
|
-
"i0":
|
|
230
|
-
"i1":
|
|
233
|
+
"i0": "i0",
|
|
234
|
+
"i1": "i1",
|
|
231
235
|
# Modified Bessel K, fixed order
|
|
232
|
-
"k0":
|
|
233
|
-
"k1":
|
|
236
|
+
"k0": "k0",
|
|
237
|
+
"k1": "k1",
|
|
234
238
|
}
|
|
235
239
|
|
|
236
240
|
# scipy.special two-column functions: func(col_a, col_b)
|
|
237
|
-
_SCIPY_DUAL = {
|
|
238
|
-
"beta": lambda x: _sp.beta(x[0], x[1]), # B(a, b)
|
|
239
|
-
"betaln": lambda x: _sp.betaln(x[0], x[1]), # log B(a, b)
|
|
240
|
-
}
|
|
241
|
+
_SCIPY_DUAL = {"beta", "betaln"} # B(a, b), log B(a, b)
|
|
241
242
|
|
|
242
243
|
# scipy.special parametric Bessel: func_name(order_literal, col)
|
|
243
244
|
# Covers all-order variants: jn(n,x), yn(n,x), iv(v,x), kv(v,x)
|
|
@@ -365,12 +366,21 @@ def _special_function(formula: str, df_columns: list[str]):
|
|
|
365
366
|
elif func_name in _PATH_FUNCS:
|
|
366
367
|
func = np.frompyfunc(getattr(os.path, func_name), 1, 1)
|
|
367
368
|
elif func_name in _STATS_DISPATCH:
|
|
368
|
-
|
|
369
|
+
from dftk.common import stats as _stats
|
|
370
|
+
|
|
371
|
+
func = getattr(_stats, func_name)
|
|
369
372
|
elif func_name in _SCIPY_SINGLE:
|
|
370
|
-
|
|
373
|
+
from scipy import special as _sp
|
|
374
|
+
|
|
375
|
+
func = getattr(_sp, _SCIPY_SINGLE[func_name])
|
|
371
376
|
elif func_name in _SCIPY_DUAL:
|
|
372
|
-
|
|
377
|
+
from scipy import special as _sp
|
|
378
|
+
|
|
379
|
+
dual = getattr(_sp, func_name)
|
|
380
|
+
func = partial(lambda x: dual(x[0], x[1]))
|
|
373
381
|
elif func_name in _SCIPY_BESSEL_ORDER:
|
|
382
|
+
from scipy import special as _sp
|
|
383
|
+
|
|
374
384
|
try:
|
|
375
385
|
order = float(cols[0])
|
|
376
386
|
except (ValueError, IndexError):
|
|
@@ -34,10 +34,12 @@ tidy coefficient tables into one output dataframe.
|
|
|
34
34
|
import argparse
|
|
35
35
|
import logging
|
|
36
36
|
|
|
37
|
+
import numpy as np # noqa: F401 — for formulas such as "np.log(y) ~ x", see below
|
|
37
38
|
import pandas as pd
|
|
38
39
|
import statsmodels.api as sm
|
|
39
40
|
import statsmodels.formula.api as smf
|
|
40
41
|
import statsmodels.stats.anova as sa
|
|
42
|
+
from patsy import PatsyError
|
|
41
43
|
|
|
42
44
|
from dftk.commands.base import BaseCommand
|
|
43
45
|
from dftk.common.io import check_cols, io
|
|
@@ -50,6 +52,11 @@ logger = logging.getLogger(__name__)
|
|
|
50
52
|
# ---------------------------------------------------------------------------
|
|
51
53
|
|
|
52
54
|
|
|
55
|
+
# Formulas are evaluated in the namespace of the function that builds the
|
|
56
|
+
# model (statsmodels' default eval_env), i.e. this module's globals — which
|
|
57
|
+
# is why numpy is imported above as np.
|
|
58
|
+
|
|
59
|
+
|
|
53
60
|
def standard_model(df: pd.DataFrame, args: argparse.Namespace):
|
|
54
61
|
"""Return an OLS or RLM (robust) model, not yet fitted."""
|
|
55
62
|
if getattr(args, "robust", False):
|
|
@@ -67,11 +74,18 @@ def weighted_model(df: pd.DataFrame, args: argparse.Namespace):
|
|
|
67
74
|
|
|
68
75
|
|
|
69
76
|
def regress_it(df: pd.DataFrame, args: argparse.Namespace):
|
|
70
|
-
"""Fit the appropriate model and return the fitted result object.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
"""Fit the appropriate model and return the fitted result object.
|
|
78
|
+
|
|
79
|
+
A formula that cannot be evaluated (unknown column or function) raises
|
|
80
|
+
ValueError, which the CLI reports as an input error.
|
|
81
|
+
"""
|
|
82
|
+
try:
|
|
83
|
+
if getattr(args, "weights", None) is not None:
|
|
84
|
+
mod = weighted_model(df, args)
|
|
85
|
+
else:
|
|
86
|
+
mod = standard_model(df, args)
|
|
87
|
+
except PatsyError as exc:
|
|
88
|
+
raise ValueError(f"cannot evaluate formula {args.formula!r}: {exc}") from None
|
|
75
89
|
return mod.fit()
|
|
76
90
|
|
|
77
91
|
|
|
@@ -7,6 +7,7 @@ dftk.commands.help — the ``dftk help`` subcommand.
|
|
|
7
7
|
|
|
8
8
|
import argparse
|
|
9
9
|
|
|
10
|
+
from dftk.commands import COMMAND_NAMES, load_command, load_command_groups
|
|
10
11
|
from dftk.commands.base import BaseCommand
|
|
11
12
|
|
|
12
13
|
|
|
@@ -16,14 +17,6 @@ class HelpCommand(BaseCommand):
|
|
|
16
17
|
name = "help"
|
|
17
18
|
help = "Show detailed help for a subcommand"
|
|
18
19
|
|
|
19
|
-
def __init__(
|
|
20
|
-
self,
|
|
21
|
-
available_commands: list[BaseCommand] | None = None,
|
|
22
|
-
command_groups: list[tuple[str, list[BaseCommand]]] | None = None,
|
|
23
|
-
) -> None:
|
|
24
|
-
self.available_commands: list[BaseCommand] = available_commands or []
|
|
25
|
-
self.command_groups: list[tuple[str, list[BaseCommand]]] = command_groups or []
|
|
26
|
-
|
|
27
20
|
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
28
21
|
parser.add_argument(
|
|
29
22
|
"command_name",
|
|
@@ -34,24 +27,16 @@ class HelpCommand(BaseCommand):
|
|
|
34
27
|
|
|
35
28
|
def execute(self, args: argparse.Namespace) -> None:
|
|
36
29
|
if args.command_name is None:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
for cmd in cmds:
|
|
41
|
-
print(f" {cmd.name:<16} {cmd.help}")
|
|
42
|
-
else:
|
|
43
|
-
print("Available commands:")
|
|
44
|
-
for cmd in self.available_commands:
|
|
30
|
+
for header, cmds in load_command_groups():
|
|
31
|
+
print(f"\n{header}:")
|
|
32
|
+
for cmd in cmds:
|
|
45
33
|
print(f" {cmd.name:<16} {cmd.help}")
|
|
46
34
|
print("\nUse 'dftk help <command>' for detailed help on a command.")
|
|
47
35
|
return
|
|
48
36
|
|
|
49
|
-
|
|
50
|
-
(c for c in self.available_commands if c.name == args.command_name),
|
|
51
|
-
None,
|
|
52
|
-
)
|
|
53
|
-
if cmd is None:
|
|
37
|
+
if args.command_name not in COMMAND_NAMES:
|
|
54
38
|
raise ValueError(f"Unknown command: {args.command_name!r}")
|
|
39
|
+
cmd = load_command(args.command_name)
|
|
55
40
|
|
|
56
41
|
# Build a temporary parser for this command so argparse formats the
|
|
57
42
|
# help text exactly as it would appear during normal use.
|
|
@@ -132,6 +132,9 @@ def _compute_stats(df: pd.DataFrame, args: argparse.Namespace) -> pd.DataFrame:
|
|
|
132
132
|
stats = (
|
|
133
133
|
df[cols].agg(agg_funcs).T.reset_index().rename(columns={"index": "name"})
|
|
134
134
|
)
|
|
135
|
+
# Before the transpose each column held count and the float stats
|
|
136
|
+
# together, so count came out as float; a count is never missing.
|
|
137
|
+
stats["count"] = stats["count"].astype("int64")
|
|
135
138
|
return stats[["name"] + _STAT_NAMES]
|
|
136
139
|
|
|
137
140
|
else:
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
# dftk.commands — explicit registry of all dftk subcommands.
|
|
2
|
-
#
|
|
3
|
-
# To add a new subcommand:
|
|
4
|
-
# 1. Create src/dftk/commands/<name>.py with a class that inherits
|
|
5
|
-
# from BaseCommand (see base.py for the contract and an example skeleton).
|
|
6
|
-
# 2. Import the class here and add an instance to command_list below.
|
|
7
|
-
# That's it — cli.py discovers commands solely through this COMMANDS list.
|
|
8
|
-
|
|
9
|
-
from dftk.commands.annotate_cmd import AnnotateCommand
|
|
10
|
-
from dftk.commands.binx_cmd import BinxCommand
|
|
11
|
-
from dftk.commands.chi_cmd import ChiCommand
|
|
12
|
-
from dftk.commands.clean_cmd import CleanCommand
|
|
13
|
-
from dftk.commands.concat_cmd import ConcatCommand
|
|
14
|
-
from dftk.commands.corr_cmd import CorrCommand
|
|
15
|
-
from dftk.commands.dataset_cmd import DatasetCommand
|
|
16
|
-
from dftk.commands.describe_cmd import DescribeCommand
|
|
17
|
-
from dftk.commands.eval_cmd import EvalCommand
|
|
18
|
-
from dftk.commands.fit_cmd import FitCommand
|
|
19
|
-
from dftk.commands.func_cmd import FuncCommand
|
|
20
|
-
from dftk.commands.help import HelpCommand
|
|
21
|
-
from dftk.commands.hist_cmd import HistCommand
|
|
22
|
-
from dftk.commands.info_cmd import InfoCommand
|
|
23
|
-
from dftk.commands.interp_cmd import InterpCommand
|
|
24
|
-
from dftk.commands.line_cmd import LineCommand
|
|
25
|
-
from dftk.commands.melt_cmd import MeltCommand
|
|
26
|
-
from dftk.commands.merge_cmd import MergeCommand
|
|
27
|
-
from dftk.commands.pivot_cmd import PivotCommand
|
|
28
|
-
from dftk.commands.print_cmd import PrintCommand
|
|
29
|
-
from dftk.commands.query_cmd import QueryCommand
|
|
30
|
-
from dftk.commands.randvar_cmd import RandvarCommand
|
|
31
|
-
from dftk.commands.sample_cmd import SampleCommand
|
|
32
|
-
from dftk.commands.scale_cmd import ScaleCommand
|
|
33
|
-
from dftk.commands.scat_cmd import ScatCommand
|
|
34
|
-
from dftk.commands.segid_cmd import SegidCommand
|
|
35
|
-
from dftk.commands.split_cmd import SplitCommand
|
|
36
|
-
from dftk.commands.stat_cmd import StatCommand
|
|
37
|
-
from dftk.commands.test_cmd import TestCommand
|
|
38
|
-
from dftk.commands.transpose_cmd import TransposeCommand
|
|
39
|
-
from dftk.commands.wstat_cmd import WstatCommand
|
|
40
|
-
|
|
41
|
-
# Ported subcommands — uncomment as each one is added:
|
|
42
|
-
# from dftk.commands.color import ColorCommand
|
|
43
|
-
# from dftk.commands.wavelet import WaveletCommand
|
|
44
|
-
# from dftk.commands.fisher import FisherCommand # superseded by eval's
|
|
45
|
-
# # fisher_test/fisher_OR
|
|
46
|
-
|
|
47
|
-
COMMAND_GROUPS: list[tuple[str, list]] = [
|
|
48
|
-
(
|
|
49
|
-
"Data transformation",
|
|
50
|
-
[
|
|
51
|
-
EvalCommand(),
|
|
52
|
-
QueryCommand(),
|
|
53
|
-
MergeCommand(),
|
|
54
|
-
ConcatCommand(),
|
|
55
|
-
MeltCommand(),
|
|
56
|
-
PivotCommand(),
|
|
57
|
-
FuncCommand(),
|
|
58
|
-
ScaleCommand(),
|
|
59
|
-
InterpCommand(),
|
|
60
|
-
BinxCommand(),
|
|
61
|
-
TransposeCommand(),
|
|
62
|
-
SegidCommand(),
|
|
63
|
-
],
|
|
64
|
-
),
|
|
65
|
-
(
|
|
66
|
-
"Statistics",
|
|
67
|
-
[
|
|
68
|
-
StatCommand(),
|
|
69
|
-
WstatCommand(),
|
|
70
|
-
FitCommand(),
|
|
71
|
-
TestCommand(),
|
|
72
|
-
CorrCommand(),
|
|
73
|
-
DescribeCommand(),
|
|
74
|
-
InfoCommand(),
|
|
75
|
-
RandvarCommand(),
|
|
76
|
-
],
|
|
77
|
-
),
|
|
78
|
-
(
|
|
79
|
-
"Plots",
|
|
80
|
-
[
|
|
81
|
-
ScatCommand(),
|
|
82
|
-
LineCommand(),
|
|
83
|
-
HistCommand(),
|
|
84
|
-
ChiCommand(),
|
|
85
|
-
],
|
|
86
|
-
),
|
|
87
|
-
(
|
|
88
|
-
"Utilities",
|
|
89
|
-
[
|
|
90
|
-
DatasetCommand(),
|
|
91
|
-
SampleCommand(),
|
|
92
|
-
SplitCommand(),
|
|
93
|
-
AnnotateCommand(),
|
|
94
|
-
PrintCommand(),
|
|
95
|
-
CleanCommand(),
|
|
96
|
-
],
|
|
97
|
-
),
|
|
98
|
-
]
|
|
99
|
-
|
|
100
|
-
command_list = [cmd for _, cmds in COMMAND_GROUPS for cmd in cmds]
|
|
101
|
-
|
|
102
|
-
# HelpCommand receives the live list and groups so it can introspect any peer.
|
|
103
|
-
help_command = HelpCommand(command_list, COMMAND_GROUPS)
|
|
104
|
-
|
|
105
|
-
COMMANDS = command_list + [help_command]
|
|
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
|