dataframe-toolkit 0.5.1__tar.gz → 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/PKG-INFO +1 -1
- dataframe_toolkit-0.7.0/pyproject.toml +82 -0
- dataframe_toolkit-0.5.1/pyproject.toml → dataframe_toolkit-0.7.0/pyproject.toml.orig +1 -1
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/cli.py +9 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/__init__.py +2 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/annotate_cmd.py +4 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/binx_cmd.py +4 -2
- dataframe_toolkit-0.7.0/src/dftk/commands/chi_cmd.py +193 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/concat_cmd.py +15 -12
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/corr_cmd.py +92 -9
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/describe_cmd.py +4 -4
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/eval_cmd.py +6 -5
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/fit_cmd.py +6 -5
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/func_cmd.py +6 -5
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/hist_cmd.py +13 -11
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/info_cmd.py +5 -1
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/interp_cmd.py +35 -13
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/line_cmd.py +25 -12
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/melt_cmd.py +20 -10
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/merge_cmd.py +34 -13
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/pivot_cmd.py +34 -21
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/print_cmd.py +1 -4
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/query_cmd.py +31 -22
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/randvar_cmd.py +5 -1
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/sample_cmd.py +9 -7
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/scale_cmd.py +24 -12
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/scat_cmd.py +10 -7
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/segid_cmd.py +7 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/split_cmd.py +8 -5
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/stat_cmd.py +8 -2
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/test_cmd.py +11 -2
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/transpose_cmd.py +12 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/wstat_cmd.py +7 -7
- dataframe_toolkit-0.7.0/src/dftk/common/copula.py +213 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/common/io.py +11 -24
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/common/plot.py +80 -7
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/README.md +0 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/__init__.py +0 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/base.py +0 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/clean_cmd.py +0 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/dataset_cmd.py +0 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/commands/help.py +0 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/common/__init__.py +0 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/common/seed.py +0 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/common/seq.py +0 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/common/stats.py +0 -0
- {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.0}/src/dftk/io.py +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "dataframe-toolkit"
|
|
3
|
+
version = "0.7.0"
|
|
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
|
+
]
|
|
@@ -8,6 +8,8 @@ logic and never needs to change when a new subcommand is added.
|
|
|
8
8
|
|
|
9
9
|
import argparse
|
|
10
10
|
import logging
|
|
11
|
+
import os
|
|
12
|
+
import signal
|
|
11
13
|
import sys
|
|
12
14
|
|
|
13
15
|
from dftk import __version__
|
|
@@ -47,6 +49,13 @@ def main() -> None:
|
|
|
47
49
|
|
|
48
50
|
try:
|
|
49
51
|
args.command_instance.execute(args)
|
|
52
|
+
sys.stdout.flush()
|
|
53
|
+
except BrokenPipeError:
|
|
54
|
+
# The reader closed the pipe early (e.g. `dftk ... | head`). Exit
|
|
55
|
+
# quietly with the SIGPIPE status like cat/grep, and point stdout at
|
|
56
|
+
# devnull so the interpreter's final flush doesn't raise again.
|
|
57
|
+
os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stdout.fileno())
|
|
58
|
+
sys.exit(128 + signal.SIGPIPE)
|
|
50
59
|
except FileNotFoundError as exc:
|
|
51
60
|
print(f"File error: {exc}", file=sys.stderr)
|
|
52
61
|
sys.exit(1)
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
from dftk.commands.annotate_cmd import AnnotateCommand
|
|
10
10
|
from dftk.commands.binx_cmd import BinxCommand
|
|
11
|
+
from dftk.commands.chi_cmd import ChiCommand
|
|
11
12
|
from dftk.commands.clean_cmd import CleanCommand
|
|
12
13
|
from dftk.commands.concat_cmd import ConcatCommand
|
|
13
14
|
from dftk.commands.corr_cmd import CorrCommand
|
|
@@ -80,6 +81,7 @@ COMMAND_GROUPS: list[tuple[str, list]] = [
|
|
|
80
81
|
ScatCommand(),
|
|
81
82
|
LineCommand(),
|
|
82
83
|
HistCommand(),
|
|
84
|
+
ChiCommand(),
|
|
83
85
|
],
|
|
84
86
|
),
|
|
85
87
|
(
|
|
@@ -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)",
|
|
@@ -18,6 +18,11 @@ import pandas as pd
|
|
|
18
18
|
import scipy.stats as ss
|
|
19
19
|
|
|
20
20
|
from dftk.commands.base import BaseCommand
|
|
21
|
+
from dftk.common.copula import (
|
|
22
|
+
MIN_TAIL_N,
|
|
23
|
+
empirical_tail_dependence,
|
|
24
|
+
pseudo_observations,
|
|
25
|
+
)
|
|
21
26
|
from dftk.common.io import check_cols, io
|
|
22
27
|
from dftk.common.seed import normalize_seed
|
|
23
28
|
|
|
@@ -112,6 +117,29 @@ def _bootstrap_stats(
|
|
|
112
117
|
return p_perm, ci_lo, ci_hi
|
|
113
118
|
|
|
114
119
|
|
|
120
|
+
def _tail_stats(a: np.ndarray, b: np.ndarray, q_grid: np.ndarray) -> tuple:
|
|
121
|
+
"""Return a flat tuple of (lambda_lower_qX, lambda_upper_qX, ...) for each q
|
|
122
|
+
in q_grid, in the same order, plus a stderr warning if any q is too small
|
|
123
|
+
for the sample size to resolve.
|
|
124
|
+
"""
|
|
125
|
+
n = len(a)
|
|
126
|
+
u, v = pseudo_observations(a, b)
|
|
127
|
+
lam_L, lam_U = empirical_tail_dependence(u, v, q_grid)
|
|
128
|
+
thin = [q for q in q_grid if n * q < MIN_TAIL_N]
|
|
129
|
+
if thin:
|
|
130
|
+
logger.warning(
|
|
131
|
+
"tail dependence: n=%d gives fewer than %d points in the tail for "
|
|
132
|
+
"q=%s -- these estimates will be noisy. See --tail-q / MIN_TAIL_N.",
|
|
133
|
+
n,
|
|
134
|
+
MIN_TAIL_N,
|
|
135
|
+
", ".join(f"{q:g}" for q in thin),
|
|
136
|
+
)
|
|
137
|
+
out = []
|
|
138
|
+
for lo, hi in zip(lam_L, lam_U, strict=True):
|
|
139
|
+
out.extend([float(lo), float(hi)])
|
|
140
|
+
return tuple(out)
|
|
141
|
+
|
|
142
|
+
|
|
115
143
|
def _corr(
|
|
116
144
|
df: pd.DataFrame, args: argparse.Namespace, rng: np.random.Generator
|
|
117
145
|
) -> pd.DataFrame:
|
|
@@ -122,12 +150,17 @@ def _corr(
|
|
|
122
150
|
raise ValueError(f"-c pairs must be col1:col2, got: {bad}")
|
|
123
151
|
|
|
124
152
|
ci = args.ci and args.method == "pearson"
|
|
153
|
+
tail = getattr(args, "tail", False)
|
|
154
|
+
q_grid = np.asarray(getattr(args, "tail_q", None) or [0.05])
|
|
125
155
|
|
|
126
156
|
header = ["col1", "col2", "nobs", "correlation", "pvalue"]
|
|
127
157
|
if ci:
|
|
128
158
|
header += ["cilo", "cihi"]
|
|
129
159
|
if args.bootstrap is not None:
|
|
130
160
|
header += ["p_perm", "ci_boot_lo", "ci_boot_hi"]
|
|
161
|
+
if tail:
|
|
162
|
+
for q in q_grid:
|
|
163
|
+
header += [f"lambda_lower_q{q:g}", f"lambda_upper_q{q:g}"]
|
|
131
164
|
|
|
132
165
|
rows = []
|
|
133
166
|
|
|
@@ -136,13 +169,14 @@ def _corr(
|
|
|
136
169
|
pair_df = sub[[col1, col2]].dropna()
|
|
137
170
|
a, b = pair_df[col1].values, pair_df[col2].values
|
|
138
171
|
cr = _compute_correlation(a, b, method_fn, ci)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
boot = _bootstrap_stats(
|
|
172
|
+
extra = ()
|
|
173
|
+
if args.bootstrap is not None:
|
|
174
|
+
extra += _bootstrap_stats(
|
|
143
175
|
a, b, method_fn, args.bootstrap, rng, args.confidence / 100.0
|
|
144
176
|
)
|
|
145
|
-
|
|
177
|
+
if tail:
|
|
178
|
+
extra += _tail_stats(a, b, q_grid)
|
|
179
|
+
rows.append((*prefix, col1, col2, *cr, *extra))
|
|
146
180
|
|
|
147
181
|
if args.groups:
|
|
148
182
|
header = [*args.groups, *header]
|
|
@@ -161,7 +195,7 @@ COLUMN PAIRS (-c)
|
|
|
161
195
|
Pairs are specified as col1:col2. Multiple pairs may be given:
|
|
162
196
|
|
|
163
197
|
dftk corr data.tsv -c x:y
|
|
164
|
-
dftk corr
|
|
198
|
+
dftk dataset tips -o | dftk corr ... -c total_bill:tip tip:size
|
|
165
199
|
|
|
166
200
|
METHODS (--method)
|
|
167
201
|
------------------
|
|
@@ -195,12 +229,45 @@ BOOTSTRAP (--bootstrap N)
|
|
|
195
229
|
|
|
196
230
|
Use --randomseed for reproducibility.
|
|
197
231
|
|
|
232
|
+
TAIL DEPENDENCE (--tail / --tail-q)
|
|
233
|
+
------------------------------------
|
|
234
|
+
Adds lambda_lower_qN / lambda_upper_qN columns: nonparametric estimates of
|
|
235
|
+
how likely one variable is to be extreme given the other is, separately
|
|
236
|
+
for the lower and upper tail, at each threshold N given to --tail-q.
|
|
237
|
+
|
|
238
|
+
A correlation coefficient describes overall association; it says nothing
|
|
239
|
+
about whether the *extremes* specifically move together more (or less)
|
|
240
|
+
than the bulk of the data. Two pairs can share the same Spearman rho and
|
|
241
|
+
have very different tail behavior -- this is what tells them apart. Two
|
|
242
|
+
markers can look equally correlated genome-wide while only one shares
|
|
243
|
+
its rare, extreme values; two assets can look equally correlated day to
|
|
244
|
+
day while only one actually crashes together.
|
|
245
|
+
|
|
246
|
+
Reading it: pass 2-3 values to --tail-q spanning an order of magnitude
|
|
247
|
+
(e.g. --tail-q 0.02 0.05 0.1). If both lambda columns shrink as q shrinks,
|
|
248
|
+
there is no real tail dependence (whatever correlation exists is spread
|
|
249
|
+
evenly, not concentrated in the extremes). If a lambda column stays
|
|
250
|
+
roughly flat across q instead of shrinking, that side has genuine tail
|
|
251
|
+
dependence. A single q in isolation is easy to misread, since even a pair
|
|
252
|
+
with NO tail dependence gives an elevated, nonzero reading at any finite
|
|
253
|
+
q if the pair is strongly correlated overall -- it is the trend across q
|
|
254
|
+
that carries the signal, not any one number.
|
|
255
|
+
|
|
256
|
+
This needs enough data in the tail region to be reliable: a warning is
|
|
257
|
+
printed to stderr when nobs * q for the smallest requested q falls below
|
|
258
|
+
20, since anything thinner is dominated by sampling noise. Pair with
|
|
259
|
+
`dftk chi` for a visual, corner-resolved view of the same question.
|
|
260
|
+
|
|
198
261
|
EXAMPLES
|
|
199
262
|
--------
|
|
200
263
|
dftk corr data.tsv -c x:y
|
|
201
|
-
dftk
|
|
202
|
-
dftk corr
|
|
203
|
-
dftk
|
|
264
|
+
dftk dataset tips -o | dftk corr ... -c total_bill:tip
|
|
265
|
+
dftk dataset tips -o | dftk corr ... -c total_bill:tip -g day --method spearman
|
|
266
|
+
dftk dataset tips -o | dftk corr ... -c total_bill:tip --ci
|
|
267
|
+
dftk dataset tips -o \\
|
|
268
|
+
| dftk corr ... -c total_bill:tip --method spearman \\
|
|
269
|
+
--bootstrap 2000 --randomseed 42
|
|
270
|
+
dftk dataset diamonds -o | dftk corr ... -c carat:price --tail --tail-q 0.02 0.05 0.1
|
|
204
271
|
"""
|
|
205
272
|
|
|
206
273
|
|
|
@@ -267,6 +334,22 @@ class CorrCommand(BaseCommand):
|
|
|
267
334
|
metavar="SEED",
|
|
268
335
|
help="Random seed for bootstrap/permutation (integer or string).",
|
|
269
336
|
)
|
|
337
|
+
g.add_argument(
|
|
338
|
+
"--tail",
|
|
339
|
+
action="store_true",
|
|
340
|
+
help="Add nonparametric lower/upper tail-dependence coefficient "
|
|
341
|
+
"columns (see --tail-q).",
|
|
342
|
+
)
|
|
343
|
+
g.add_argument(
|
|
344
|
+
"--tail-q",
|
|
345
|
+
type=float,
|
|
346
|
+
nargs="+",
|
|
347
|
+
default=None,
|
|
348
|
+
metavar="Q",
|
|
349
|
+
help="Tail probability threshold(s) for --tail, e.g. 0.02 0.05 0.1 "
|
|
350
|
+
"(default: 0.05). Smaller q looks deeper into the tail but needs "
|
|
351
|
+
"more data to stay reliable -- see epilog.",
|
|
352
|
+
)
|
|
270
353
|
|
|
271
354
|
def execute(self, args: argparse.Namespace) -> None:
|
|
272
355
|
seed = normalize_seed(args.randomseed) if args.randomseed is not None else None
|
|
@@ -37,7 +37,7 @@ Optional flags
|
|
|
37
37
|
Example
|
|
38
38
|
-------
|
|
39
39
|
dftk describe data.tsv
|
|
40
|
-
dftk describe
|
|
40
|
+
dftk dataset iris -o | dftk describe ... --summary --correlations
|
|
41
41
|
"""
|
|
42
42
|
|
|
43
43
|
import argparse
|
|
@@ -314,9 +314,9 @@ NOTES FLAGS
|
|
|
314
314
|
EXAMPLES
|
|
315
315
|
--------
|
|
316
316
|
dftk describe data.tsv
|
|
317
|
-
dftk
|
|
318
|
-
dftk describe
|
|
319
|
-
dftk dataset iris | dftk describe
|
|
317
|
+
dftk dataset iris -o | dftk describe ...
|
|
318
|
+
dftk dataset iris -o | dftk describe ... --summary --correlations
|
|
319
|
+
dftk dataset iris -o | dftk describe ... --summary --correlations --corr-threshold 0.5
|
|
320
320
|
"""
|
|
321
321
|
io.parser_read(parser)
|
|
322
322
|
|
|
@@ -43,7 +43,8 @@ Each -f argument is first passed to pandas df.eval(). This handles
|
|
|
43
43
|
arithmetic and boolean expressions directly on column names:
|
|
44
44
|
|
|
45
45
|
dftk eval data.tsv -f "z = x + y * 2"
|
|
46
|
-
dftk eval
|
|
46
|
+
dftk dataset tips -o | dftk eval ... -f "bill_and_tip = total_bill + tip * 2"
|
|
47
|
+
dftk dataset tips -o | dftk eval ... -f "good_tip = (tip > 3) & (total_bill < 30)"
|
|
47
48
|
|
|
48
49
|
If df.eval() fails (e.g. for function calls), the expression is retried
|
|
49
50
|
as a special function call using the syntax:
|
|
@@ -61,8 +62,8 @@ Available special functions:
|
|
|
61
62
|
true_val and false_val can each be: a column name, a quoted
|
|
62
63
|
string literal ("foo"), or a numeric literal.
|
|
63
64
|
Example:
|
|
64
|
-
dftk eval
|
|
65
|
-
-f "big =
|
|
65
|
+
dftk dataset tips -o | dftk eval ... \
|
|
66
|
+
-f "big = total_bill > 30" \
|
|
66
67
|
-f "label = where(big, \"large\", \"small\")"
|
|
67
68
|
|
|
68
69
|
Bitwise:
|
|
@@ -119,8 +120,8 @@ CONSTANT COLUMNS (-c)
|
|
|
119
120
|
----------------------
|
|
120
121
|
Add a column with a fixed value:
|
|
121
122
|
|
|
122
|
-
dftk eval
|
|
123
|
-
dftk eval
|
|
123
|
+
dftk dataset tips -o | dftk eval ... -c "batch = 3"
|
|
124
|
+
dftk dataset tips -o | dftk eval ... -c "region = northeast"
|
|
124
125
|
|
|
125
126
|
The value is coerced to numeric when possible.
|
|
126
127
|
|
|
@@ -155,26 +155,27 @@ EXAMPLES
|
|
|
155
155
|
Simple OLS:
|
|
156
156
|
|
|
157
157
|
dftk fit data.tsv -f "y ~ x"
|
|
158
|
+
dftk dataset mpg -o | dftk fit ... -f "mpg ~ horsepower"
|
|
158
159
|
|
|
159
160
|
Grouped OLS (one regression per group):
|
|
160
161
|
|
|
161
|
-
dftk fit
|
|
162
|
+
dftk dataset mpg -o | dftk fit ... -f "mpg ~ horsepower" -g origin
|
|
162
163
|
|
|
163
164
|
Weighted least squares:
|
|
164
165
|
|
|
165
|
-
dftk fit
|
|
166
|
+
dftk dataset mpg -o | dftk fit ... -f "mpg ~ horsepower" -w weight
|
|
166
167
|
|
|
167
168
|
Robust regression (HuberT):
|
|
168
169
|
|
|
169
|
-
dftk fit
|
|
170
|
+
dftk dataset mpg -o | dftk fit ... -f "mpg ~ horsepower" --robust
|
|
170
171
|
|
|
171
172
|
Print detailed summary (no grouping):
|
|
172
173
|
|
|
173
|
-
dftk fit
|
|
174
|
+
dftk dataset mpg -o | dftk fit ... -f "mpg ~ horsepower" --summary
|
|
174
175
|
|
|
175
176
|
ANOVA table (OLS, no grouping):
|
|
176
177
|
|
|
177
|
-
dftk fit
|
|
178
|
+
dftk dataset mpg -o | dftk fit ... -f "mpg ~ horsepower + weight" --anova
|
|
178
179
|
"""
|
|
179
180
|
|
|
180
181
|
|