dataframe-toolkit 0.5.1__tar.gz → 0.7.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.5.1 → dataframe_toolkit-0.7.1}/PKG-INFO +1 -1
- dataframe_toolkit-0.7.1/pyproject.toml +82 -0
- dataframe_toolkit-0.5.1/pyproject.toml → dataframe_toolkit-0.7.1/pyproject.toml.orig +1 -1
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/cli.py +31 -3
- dataframe_toolkit-0.7.1/src/dftk/commands/__init__.py +98 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/annotate_cmd.py +4 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/binx_cmd.py +4 -2
- dataframe_toolkit-0.7.1/src/dftk/commands/chi_cmd.py +193 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/concat_cmd.py +15 -12
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/corr_cmd.py +92 -9
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/describe_cmd.py +4 -4
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/eval_cmd.py +53 -42
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/fit_cmd.py +6 -5
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/func_cmd.py +6 -5
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/help.py +6 -21
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/hist_cmd.py +13 -11
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/info_cmd.py +5 -1
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/interp_cmd.py +35 -13
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/line_cmd.py +25 -12
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/melt_cmd.py +20 -10
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/merge_cmd.py +34 -13
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/pivot_cmd.py +34 -21
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/print_cmd.py +1 -4
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/query_cmd.py +31 -22
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/randvar_cmd.py +5 -1
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/sample_cmd.py +9 -7
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/scale_cmd.py +24 -12
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/scat_cmd.py +10 -7
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/segid_cmd.py +7 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/split_cmd.py +8 -5
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/stat_cmd.py +8 -2
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/test_cmd.py +11 -2
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/transpose_cmd.py +12 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/wstat_cmd.py +7 -7
- dataframe_toolkit-0.7.1/src/dftk/common/copula.py +213 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/common/io.py +11 -24
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/common/plot.py +80 -7
- dataframe_toolkit-0.5.1/src/dftk/commands/__init__.py +0 -103
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/README.md +0 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/__init__.py +0 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/base.py +0 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/clean_cmd.py +0 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/dataset_cmd.py +0 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/common/__init__.py +0 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/common/seed.py +0 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/common/seq.py +0 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/common/stats.py +0 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/io.py +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "dataframe-toolkit"
|
|
3
|
+
version = "0.7.1"
|
|
4
|
+
description = "A command-line toolkit for DataFrame analysis and manipulation"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
keywords = [
|
|
7
|
+
"dataframe",
|
|
8
|
+
"statistics",
|
|
9
|
+
"data analysis",
|
|
10
|
+
"CLI",
|
|
11
|
+
"TSV",
|
|
12
|
+
"pandas",
|
|
13
|
+
]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Science/Research",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
19
|
+
"Topic :: Utilities",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
]
|
|
22
|
+
requires-python = ">=3.12"
|
|
23
|
+
dependencies = [
|
|
24
|
+
"pandas>=2.1",
|
|
25
|
+
"numpy>=1.24",
|
|
26
|
+
"pyarrow>=12.0",
|
|
27
|
+
"scipy>=1.10",
|
|
28
|
+
"statsmodels>=0.14",
|
|
29
|
+
"duckdb>=0.8",
|
|
30
|
+
"matplotlib>=3.7",
|
|
31
|
+
"seaborn>=0.12",
|
|
32
|
+
"tqdm>=4.65",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[[project.authors]]
|
|
36
|
+
name = "Gunnar Pálsson"
|
|
37
|
+
email = "hemingur@gmail.com"
|
|
38
|
+
|
|
39
|
+
[project.license]
|
|
40
|
+
text = "MIT"
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
Homepage = "https://github.com/hemingur/dataframe-toolkit"
|
|
44
|
+
Repository = "https://github.com/hemingur/dataframe-toolkit"
|
|
45
|
+
|
|
46
|
+
[project.scripts]
|
|
47
|
+
dftk = "dftk.cli:main"
|
|
48
|
+
|
|
49
|
+
[build-system]
|
|
50
|
+
requires = ["uv_build>=0.10.4,<0.11.0"]
|
|
51
|
+
build-backend = "uv_build"
|
|
52
|
+
|
|
53
|
+
[tool.uv.build-backend]
|
|
54
|
+
module-name = "dftk"
|
|
55
|
+
|
|
56
|
+
[tool.pytest.ini_options]
|
|
57
|
+
testpaths = ["tests"]
|
|
58
|
+
|
|
59
|
+
[tool.ruff]
|
|
60
|
+
line-length = 88
|
|
61
|
+
target-version = "py312"
|
|
62
|
+
|
|
63
|
+
[tool.ruff.lint]
|
|
64
|
+
select = [
|
|
65
|
+
"E",
|
|
66
|
+
"W",
|
|
67
|
+
"F",
|
|
68
|
+
"I",
|
|
69
|
+
"UP",
|
|
70
|
+
"B",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
[tool.ruff.lint.isort]
|
|
74
|
+
known-first-party = ["dftk"]
|
|
75
|
+
|
|
76
|
+
[dependency-groups]
|
|
77
|
+
dev = [
|
|
78
|
+
"pre-commit>=4.6.0",
|
|
79
|
+
"pytest>=8.0",
|
|
80
|
+
"pytest-cov>=7.1.0",
|
|
81
|
+
"ruff>=0.15.12",
|
|
82
|
+
]
|
|
@@ -1,23 +1,44 @@
|
|
|
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
|
|
10
14
|
import logging
|
|
15
|
+
import os
|
|
16
|
+
import signal
|
|
11
17
|
import sys
|
|
12
18
|
|
|
13
19
|
from dftk import __version__
|
|
14
|
-
from dftk.commands import
|
|
20
|
+
from dftk.commands import COMMAND_NAMES, load_command, load_command_groups
|
|
21
|
+
from dftk.commands.base import BaseCommand
|
|
15
22
|
from dftk.common.io import DFTK_TMPDIR
|
|
16
23
|
|
|
17
24
|
logger = logging.getLogger("dftk")
|
|
18
25
|
logging.basicConfig(format="%(asctime)s %(module)s %(message)s", level=logging.INFO)
|
|
19
26
|
|
|
20
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
|
+
|
|
21
42
|
def main() -> None:
|
|
22
43
|
parser = argparse.ArgumentParser(
|
|
23
44
|
prog="dftk",
|
|
@@ -38,7 +59,7 @@ def main() -> None:
|
|
|
38
59
|
subparsers = parser.add_subparsers(dest="command", metavar="COMMAND")
|
|
39
60
|
subparsers.required = True
|
|
40
61
|
|
|
41
|
-
for cmd in
|
|
62
|
+
for cmd in _commands_for(sys.argv[1:]):
|
|
42
63
|
cmd_parser = subparsers.add_parser(cmd.name, help=cmd.help)
|
|
43
64
|
cmd.add_arguments(cmd_parser)
|
|
44
65
|
cmd_parser.set_defaults(command_instance=cmd)
|
|
@@ -47,6 +68,13 @@ def main() -> None:
|
|
|
47
68
|
|
|
48
69
|
try:
|
|
49
70
|
args.command_instance.execute(args)
|
|
71
|
+
sys.stdout.flush()
|
|
72
|
+
except BrokenPipeError:
|
|
73
|
+
# The reader closed the pipe early (e.g. `dftk ... | head`). Exit
|
|
74
|
+
# quietly with the SIGPIPE status like cat/grep, and point stdout at
|
|
75
|
+
# devnull so the interpreter's final flush doesn't raise again.
|
|
76
|
+
os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stdout.fileno())
|
|
77
|
+
sys.exit(128 + signal.SIGPIPE)
|
|
50
78
|
except FileNotFoundError as exc:
|
|
51
79
|
print(f"File error: {exc}", file=sys.stderr)
|
|
52
80
|
sys.exit(1)
|
|
@@ -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
|
+
]
|
|
@@ -25,6 +25,10 @@ Usage examples:
|
|
|
25
25
|
dftk eval data.tsv -f "z = x + y" -o result.parquet \\
|
|
26
26
|
--meta genome=hg38 --meta source=my_pipeline
|
|
27
27
|
|
|
28
|
+
# Same, built from a bundled dataset:
|
|
29
|
+
dftk dataset iris -o | dftk eval ... -f "z = sepal_length + petal_length" \\
|
|
30
|
+
-o result.parquet --meta genome=hg38 --meta source=my_pipeline
|
|
31
|
+
|
|
28
32
|
Annotations propagate through the parquet pipe: a file annotated at
|
|
29
33
|
creation is re-annotated on every subsequent -o write unless explicitly
|
|
30
34
|
overridden.
|
|
@@ -74,8 +74,10 @@ USEVALUE (--usevalue)
|
|
|
74
74
|
EXAMPLES
|
|
75
75
|
--------
|
|
76
76
|
dftk binx data.tsv -c age -b 0:100:10
|
|
77
|
-
dftk binx
|
|
78
|
-
dftk binx
|
|
77
|
+
dftk dataset tips -o | dftk binx ... -c total_bill -b 0:60:10
|
|
78
|
+
dftk dataset tips -o | dftk binx ... -c tip -b 0,2,4,6,10 -d tip_bracket --usevalue m
|
|
79
|
+
dftk dataset tips -o | dftk binx ... -c total_bill -b 0:60:10 -d bill_bin -o \\
|
|
80
|
+
| dftk stat ... -c tip -g bill_bin
|
|
79
81
|
"""
|
|
80
82
|
|
|
81
83
|
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dftk.commands.chi_cmd — dftk chi subcommand.
|
|
3
|
+
|
|
4
|
+
Corner-resolved chi-plot: a visual diagnostic for which direction (if any)
|
|
5
|
+
two variables have tail dependence in, complementing the numeric
|
|
6
|
+
lambda_lower/lambda_upper columns from `dftk corr --tail`.
|
|
7
|
+
|
|
8
|
+
Follows the structure of scat_cmd.py -- same --groupcol / --subgraphcol /
|
|
9
|
+
--size / --fontsize figure conventions as the rest of the Plots group.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
from dftk.commands.base import BaseCommand
|
|
17
|
+
from dftk.common.copula import corner_chi, pseudo_observations
|
|
18
|
+
from dftk.common.io import check_cols, io
|
|
19
|
+
from dftk.common.plot import (
|
|
20
|
+
add_figure_arguments,
|
|
21
|
+
add_group_arguments,
|
|
22
|
+
add_legend_arguments,
|
|
23
|
+
add_xy_arguments,
|
|
24
|
+
apply_labels,
|
|
25
|
+
apply_style,
|
|
26
|
+
make_figure,
|
|
27
|
+
make_grouplabel,
|
|
28
|
+
save_or_show,
|
|
29
|
+
subgraph_groups,
|
|
30
|
+
subgraph_layout,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# Single-axes chi-plot (reused per subplot)
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _chi_ax(ax, df, args, title_suffix: str = ""):
|
|
39
|
+
xcol, ycol = args.xcol, args.ycol
|
|
40
|
+
legendloc = tuple(args.legendloc) if getattr(args, "legendloc", None) else "best"
|
|
41
|
+
|
|
42
|
+
def _draw(sub_df, label=None, **scatter_kw):
|
|
43
|
+
a, b = sub_df[[xcol, ycol]].dropna().values.T
|
|
44
|
+
if len(a) < 3:
|
|
45
|
+
return
|
|
46
|
+
u, v = pseudo_observations(a, b)
|
|
47
|
+
x, chi = corner_chi(u, v)
|
|
48
|
+
mask = np.abs(x) < 0.8 # trim the noisiest extreme points, standard practice
|
|
49
|
+
ax.scatter(x[mask], chi[mask], s=8, alpha=0.4, label=label, **scatter_kw)
|
|
50
|
+
ci = 1.78 / np.sqrt(len(u))
|
|
51
|
+
return ci
|
|
52
|
+
|
|
53
|
+
if args.groupcol is not None:
|
|
54
|
+
# Each group gets its OWN chi statistic computed within its own subset
|
|
55
|
+
# (mixing groups into one leave-one-out CDF would misrepresent both) --
|
|
56
|
+
# overlaid here the same way scat/hist overlay per-group series.
|
|
57
|
+
cis = []
|
|
58
|
+
for groupname, gdf in subgraph_groups(
|
|
59
|
+
df, args.groupcol, getattr(args, "groupcolorder", None)
|
|
60
|
+
):
|
|
61
|
+
label = make_grouplabel(
|
|
62
|
+
groupname, args.groupcol, getattr(args, "groupcolformat", None)
|
|
63
|
+
)
|
|
64
|
+
ci = _draw(gdf, label=label)
|
|
65
|
+
if ci is not None:
|
|
66
|
+
cis.append(ci)
|
|
67
|
+
ax.legend(loc=legendloc, title=getattr(args, "legendtitle", None))
|
|
68
|
+
ci = max(cis) if cis else 0.0
|
|
69
|
+
else:
|
|
70
|
+
ci = _draw(df) or 0.0
|
|
71
|
+
|
|
72
|
+
ax.axhline(0, color="black", lw=0.8)
|
|
73
|
+
ax.axvline(0, color="gray", lw=0.6, ls=":")
|
|
74
|
+
if ci:
|
|
75
|
+
ax.axhline(ci, color="crimson", lw=1, ls="--", alpha=0.7)
|
|
76
|
+
ax.axhline(-ci, color="crimson", lw=1, ls="--", alpha=0.7)
|
|
77
|
+
ax.set_xlim(-1, 1)
|
|
78
|
+
ax.set_ylim(-0.2, 1)
|
|
79
|
+
|
|
80
|
+
apply_labels(
|
|
81
|
+
ax,
|
|
82
|
+
args,
|
|
83
|
+
default_xlabel="<- lower corner | upper corner ->",
|
|
84
|
+
default_ylabel=r"$\chi$ (local dependence)",
|
|
85
|
+
)
|
|
86
|
+
if title_suffix:
|
|
87
|
+
existing = ax.get_title()
|
|
88
|
+
ax.set_title(f"{existing} [{title_suffix}]" if existing else title_suffix)
|
|
89
|
+
|
|
90
|
+
ax.grid(True, linewidth=0.4, alpha=0.5)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
# Command
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
_EPILOG = """\
|
|
98
|
+
Reads as: chi elevated (and rising) on the LEFT (negative x) only => lower-
|
|
99
|
+
tail dependence -- the two variables' worst values tend to co-occur, but
|
|
100
|
+
their best values don't (e.g. joint crashes but not joint rallies). Elevated
|
|
101
|
+
on the RIGHT only => upper-tail dependence (joint highs, not joint lows).
|
|
102
|
+
Elevated similarly on BOTH sides, decaying back toward 0 well before the
|
|
103
|
+
edges => generic correlation with no real tail dependence. Flat at 0
|
|
104
|
+
everywhere => independence. Dashed red lines mark an approximate 95%
|
|
105
|
+
control limit for "indistinguishable from independence".
|
|
106
|
+
|
|
107
|
+
This is a visual complement to `dftk corr --tail`'s numeric
|
|
108
|
+
lambda_lower/lambda_upper columns -- same underlying question, this command
|
|
109
|
+
answers it by eye and per-corner in one plot instead of at a chosen set of
|
|
110
|
+
thresholds. Needs a reasonable amount of data to be legible: a few hundred
|
|
111
|
+
points is usually enough to see the shape, but a strong one-sided asymmetry
|
|
112
|
+
is easiest to trust with several thousand.
|
|
113
|
+
|
|
114
|
+
EXAMPLES
|
|
115
|
+
--------
|
|
116
|
+
Basic chi-plot:
|
|
117
|
+
|
|
118
|
+
dftk chi data.tsv -x recomb_rate -y gc_content
|
|
119
|
+
dftk dataset tips -o | dftk chi ... -x total_bill -y tip
|
|
120
|
+
|
|
121
|
+
Split into a subplot grid by category, to check whether tail dependence is
|
|
122
|
+
consistent across the whole dataset or driven by a few subgroups (e.g.
|
|
123
|
+
chromosome, sector, region — here, diamond cut quality):
|
|
124
|
+
|
|
125
|
+
dftk dataset diamonds -o | dftk chi ... -x carat -y price --subgraphcol cut
|
|
126
|
+
|
|
127
|
+
Colour-code by group instead of splitting into subplots:
|
|
128
|
+
|
|
129
|
+
dftk dataset diamonds -o | dftk chi ... -x carat -y price -g cut
|
|
130
|
+
|
|
131
|
+
Publication figure (Nature single column, PDF):
|
|
132
|
+
|
|
133
|
+
dftk dataset tips -o | dftk chi ... -x total_bill -y tip \\
|
|
134
|
+
--size single --fontsize publication -f fig.pdf
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class ChiCommand(BaseCommand):
|
|
139
|
+
name = "chi"
|
|
140
|
+
help = "Corner-resolved chi-plot: visualize lower vs upper tail dependence."
|
|
141
|
+
|
|
142
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
143
|
+
parser.formatter_class = argparse.RawDescriptionHelpFormatter
|
|
144
|
+
parser.epilog = _EPILOG
|
|
145
|
+
|
|
146
|
+
self.add_read_arguments(parser)
|
|
147
|
+
add_xy_arguments(parser)
|
|
148
|
+
add_figure_arguments(parser)
|
|
149
|
+
add_group_arguments(parser)
|
|
150
|
+
add_legend_arguments(parser)
|
|
151
|
+
|
|
152
|
+
def execute(self, args: argparse.Namespace) -> None:
|
|
153
|
+
if not args.xcol or not args.ycol:
|
|
154
|
+
raise ValueError("--xcol (-x) and --ycol (-y) are required.")
|
|
155
|
+
|
|
156
|
+
df = io.read(args)
|
|
157
|
+
check_cols(df, [args.xcol, args.ycol], "-x/-y")
|
|
158
|
+
check_cols(df, args.groupcol, "-g/--groupcol")
|
|
159
|
+
check_cols(df, args.subgraphcol, "--subgraphcol")
|
|
160
|
+
|
|
161
|
+
if args.file:
|
|
162
|
+
import matplotlib
|
|
163
|
+
|
|
164
|
+
matplotlib.use("Agg")
|
|
165
|
+
|
|
166
|
+
import matplotlib.pyplot as plt
|
|
167
|
+
|
|
168
|
+
if getattr(args, "usetex", False):
|
|
169
|
+
plt.rc("text", usetex=True)
|
|
170
|
+
|
|
171
|
+
apply_style(args)
|
|
172
|
+
|
|
173
|
+
if args.subgraphcol is not None:
|
|
174
|
+
groups = subgraph_groups(
|
|
175
|
+
df, args.subgraphcol, getattr(args, "subgraphorder", None)
|
|
176
|
+
)
|
|
177
|
+
nrows, ncols = subgraph_layout(len(groups), args.ncols)
|
|
178
|
+
fig, axes = make_figure(args, nrows=nrows, ncols=ncols)
|
|
179
|
+
for idx, (groupname, gdf) in enumerate(groups):
|
|
180
|
+
ax = axes[idx // ncols][idx % ncols]
|
|
181
|
+
suffix = make_grouplabel(
|
|
182
|
+
groupname, args.subgraphcol, getattr(args, "subgraphformat", None)
|
|
183
|
+
)
|
|
184
|
+
_chi_ax(ax, gdf, args, title_suffix=suffix)
|
|
185
|
+
for idx in range(len(groups), nrows * ncols):
|
|
186
|
+
axes[idx // ncols][idx % ncols].set_visible(False)
|
|
187
|
+
fig.tight_layout()
|
|
188
|
+
else:
|
|
189
|
+
fig, axes = make_figure(args)
|
|
190
|
+
_chi_ax(axes[0][0], df, args)
|
|
191
|
+
fig.tight_layout()
|
|
192
|
+
|
|
193
|
+
save_or_show(fig, args)
|
|
@@ -8,9 +8,11 @@ present in some but not all files are filled with NaN (or --fill value).
|
|
|
8
8
|
Examples
|
|
9
9
|
--------
|
|
10
10
|
dftk concat a.tsv b.tsv c.tsv
|
|
11
|
-
dftk
|
|
12
|
-
dftk concat
|
|
13
|
-
dftk
|
|
11
|
+
dftk dataset penguins -o penguins.tsv
|
|
12
|
+
dftk concat penguins.tsv penguins.tsv --sourcecol source
|
|
13
|
+
dftk dataset penguins -o penguins.parquet
|
|
14
|
+
dftk concat penguins.parquet penguins.parquet -o
|
|
15
|
+
dftk dataset penguins -o | dftk eval ... -f "x=1" -o | dftk concat ... penguins.tsv
|
|
14
16
|
"""
|
|
15
17
|
|
|
16
18
|
import argparse
|
|
@@ -43,9 +45,16 @@ COLUMN ALIGNMENT
|
|
|
43
45
|
EXAMPLES
|
|
44
46
|
--------
|
|
45
47
|
dftk concat a.tsv b.tsv
|
|
46
|
-
dftk
|
|
47
|
-
dftk concat
|
|
48
|
-
dftk
|
|
48
|
+
dftk dataset penguins -o penguins.tsv
|
|
49
|
+
dftk concat penguins.tsv penguins.tsv --sourcecol source
|
|
50
|
+
dftk dataset penguins -o penguins.parquet
|
|
51
|
+
dftk concat penguins.parquet penguins.parquet -o result.parquet
|
|
52
|
+
dftk dataset penguins -o | dftk eval ... -f "x=1" -o | dftk concat ... penguins.tsv
|
|
53
|
+
|
|
54
|
+
Mismatched columns are outer-joined and filled (NaN by default, or --fill):
|
|
55
|
+
|
|
56
|
+
dftk dataset iris -o iris.tsv
|
|
57
|
+
dftk concat iris.tsv penguins.tsv --fill 0
|
|
49
58
|
"""
|
|
50
59
|
g = parser.add_argument_group("concat options")
|
|
51
60
|
g.add_argument(
|
|
@@ -86,12 +95,6 @@ EXAMPLES
|
|
|
86
95
|
help="Input files have no header row; columns are named V1, V2, …",
|
|
87
96
|
action="store_true",
|
|
88
97
|
)
|
|
89
|
-
r.add_argument(
|
|
90
|
-
"--nrows",
|
|
91
|
-
help="Maximum number of rows to read per file",
|
|
92
|
-
type=int,
|
|
93
|
-
default=None,
|
|
94
|
-
)
|
|
95
98
|
r.add_argument(
|
|
96
99
|
"--delimiter",
|
|
97
100
|
help="Column delimiter for TSV inputs (default: tab)",
|