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
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dftk.commands.test_cmd — the ``dftk test`` subcommand.
|
|
3
|
+
|
|
4
|
+
Computes p-values for statistical tests between pairs of columns,
|
|
5
|
+
optionally within groups. Each column pair ``col1:col2`` produces one
|
|
6
|
+
output column named by the corresponding ``--dest`` entry.
|
|
7
|
+
|
|
8
|
+
Tests available
|
|
9
|
+
---------------
|
|
10
|
+
bootstrap Sign-based p-value for paired bootstrap differences
|
|
11
|
+
student_t Welch's independent-samples t-test
|
|
12
|
+
paired_student_t Paired t-test
|
|
13
|
+
anova One-way analysis of variance (F-test)
|
|
14
|
+
pearson Pearson product-moment correlation
|
|
15
|
+
kendall Kendall rank correlation τ
|
|
16
|
+
spearman Spearman rank-order correlation
|
|
17
|
+
mannwhitneyu Mann-Whitney U rank test
|
|
18
|
+
wilcoxon Wilcoxon signed-rank test
|
|
19
|
+
kruskal Kruskal-Wallis H-test
|
|
20
|
+
kolmogorov_smirnov Two-sample Kolmogorov-Smirnov test
|
|
21
|
+
|
|
22
|
+
The bootstrap test counts sign agreements between paired differences.
|
|
23
|
+
``--randomize`` shuffles the second column before pairing, which provides
|
|
24
|
+
a permutation-based null distribution. Use ``--randomseed`` for
|
|
25
|
+
reproducibility.
|
|
26
|
+
|
|
27
|
+
Example
|
|
28
|
+
-------
|
|
29
|
+
dftk test data.tsv -c col1:col2 -d pvalue --test student_t
|
|
30
|
+
dftk test data.tsv -c a:b c:d -d p_ab p_cd -g group --test mannwhitneyu
|
|
31
|
+
dftk test boot.tsv -c s1:s2 -d p --test bootstrap --randomseed 42
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
import argparse
|
|
35
|
+
import logging
|
|
36
|
+
|
|
37
|
+
import numpy as np
|
|
38
|
+
import pandas as pd
|
|
39
|
+
import scipy.stats as ss
|
|
40
|
+
|
|
41
|
+
from dftk.commands.base import BaseCommand
|
|
42
|
+
from dftk.common.io import check_cols, io
|
|
43
|
+
from dftk.common.seed import normalize_seed
|
|
44
|
+
|
|
45
|
+
logger = logging.getLogger("dftk")
|
|
46
|
+
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
# Test registry
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
TESTS = [
|
|
52
|
+
"bootstrap",
|
|
53
|
+
"student_t",
|
|
54
|
+
"paired_student_t",
|
|
55
|
+
"anova",
|
|
56
|
+
"pearson",
|
|
57
|
+
"kendall",
|
|
58
|
+
"spearman",
|
|
59
|
+
"mannwhitneyu",
|
|
60
|
+
"wilcoxon",
|
|
61
|
+
"kruskal",
|
|
62
|
+
"kolmogorov_smirnov",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _bootstrap(v1: np.ndarray, v2: np.ndarray, randomize: bool = False) -> float:
|
|
67
|
+
"""Sign-based paired bootstrap p-value."""
|
|
68
|
+
if randomize:
|
|
69
|
+
v2 = np.random.permutation(v2)
|
|
70
|
+
diffs = v1 - v2
|
|
71
|
+
signs = np.sign(diffs)
|
|
72
|
+
nonzero = np.nonzero(signs)[0]
|
|
73
|
+
if nonzero.size == 0:
|
|
74
|
+
return 1.0
|
|
75
|
+
positive = int(np.sum(signs > 0))
|
|
76
|
+
negative = int(np.sum(signs < 0))
|
|
77
|
+
excess = min(positive, negative)
|
|
78
|
+
return 2 * excess / len(diffs)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _pvalue(v1: np.ndarray, v2: np.ndarray, test: str, randomize: bool) -> float:
|
|
82
|
+
"""Return the p-value for *test* applied to arrays *v1* and *v2*."""
|
|
83
|
+
if test == "bootstrap":
|
|
84
|
+
return _bootstrap(v1, v2, randomize)
|
|
85
|
+
fn = {
|
|
86
|
+
"student_t": ss.ttest_ind,
|
|
87
|
+
"paired_student_t": ss.ttest_rel,
|
|
88
|
+
"anova": ss.f_oneway,
|
|
89
|
+
"pearson": ss.pearsonr,
|
|
90
|
+
"kendall": ss.kendalltau,
|
|
91
|
+
"spearman": ss.spearmanr,
|
|
92
|
+
"mannwhitneyu": ss.mannwhitneyu,
|
|
93
|
+
"wilcoxon": ss.wilcoxon,
|
|
94
|
+
"kruskal": ss.kruskal,
|
|
95
|
+
"kolmogorov_smirnov": ss.ks_2samp,
|
|
96
|
+
}[test]
|
|
97
|
+
return float(fn(v1, v2).pvalue)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# ---------------------------------------------------------------------------
|
|
101
|
+
# Core computation
|
|
102
|
+
# ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _run_test(df: pd.DataFrame, args: argparse.Namespace) -> pd.DataFrame:
|
|
106
|
+
"""Return a DataFrame of p-values for one or more column pairs.
|
|
107
|
+
|
|
108
|
+
*args* must have:
|
|
109
|
+
- ``cols`` list of ``"col1:col2"`` strings
|
|
110
|
+
- ``dest`` list of output column names, same length as cols
|
|
111
|
+
- ``groups`` list of group-by column names, or empty list / None
|
|
112
|
+
- ``test`` test name (one of TESTS)
|
|
113
|
+
- ``randomize`` bool — shuffle second column before bootstrap pairing
|
|
114
|
+
"""
|
|
115
|
+
col_pairs = [x.split(":", 1) for x in args.cols]
|
|
116
|
+
dest = args.dest
|
|
117
|
+
groups = args.groups or []
|
|
118
|
+
test = args.test
|
|
119
|
+
randomize = getattr(args, "randomize", False)
|
|
120
|
+
|
|
121
|
+
def _row_values(sub: pd.DataFrame) -> list:
|
|
122
|
+
row = []
|
|
123
|
+
for col1, col2 in col_pairs:
|
|
124
|
+
try:
|
|
125
|
+
row.append(_pvalue(sub[col1].values, sub[col2].values, test, randomize))
|
|
126
|
+
except Exception as exc:
|
|
127
|
+
logger.warning("test failed for %s:%s — %s", col1, col2, exc)
|
|
128
|
+
row.append(float("nan"))
|
|
129
|
+
return row
|
|
130
|
+
|
|
131
|
+
if not groups:
|
|
132
|
+
return pd.DataFrame([_row_values(df)], columns=dest)
|
|
133
|
+
|
|
134
|
+
results = []
|
|
135
|
+
for group_key, group_df in df.groupby(groups):
|
|
136
|
+
if not isinstance(group_key, tuple):
|
|
137
|
+
group_key = (group_key,)
|
|
138
|
+
results.append(list(group_key) + _row_values(group_df))
|
|
139
|
+
|
|
140
|
+
return pd.DataFrame(results, columns=groups + dest)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ---------------------------------------------------------------------------
|
|
144
|
+
# Command
|
|
145
|
+
# ---------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class TestCommand(BaseCommand):
|
|
149
|
+
"""Run statistical tests between column pairs, optionally within groups."""
|
|
150
|
+
|
|
151
|
+
name = "test"
|
|
152
|
+
help = (
|
|
153
|
+
"Compute p-values between column pairs "
|
|
154
|
+
"(t-test, Mann-Whitney, bootstrap, …); supports grouping"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
158
|
+
self.add_io_arguments(parser)
|
|
159
|
+
|
|
160
|
+
g = parser.add_argument_group("test")
|
|
161
|
+
g.add_argument(
|
|
162
|
+
"-c",
|
|
163
|
+
"--cols",
|
|
164
|
+
help="Column pair(s) to test, in the form col1:col2",
|
|
165
|
+
nargs="+",
|
|
166
|
+
required=True,
|
|
167
|
+
metavar="COL1:COL2",
|
|
168
|
+
)
|
|
169
|
+
g.add_argument(
|
|
170
|
+
"-d",
|
|
171
|
+
"--dest",
|
|
172
|
+
help="Output column name(s) for p-values (one per --cols pair)",
|
|
173
|
+
nargs="+",
|
|
174
|
+
required=True,
|
|
175
|
+
metavar="NAME",
|
|
176
|
+
)
|
|
177
|
+
g.add_argument(
|
|
178
|
+
"-g",
|
|
179
|
+
"--groups",
|
|
180
|
+
help="Group-by column(s); test is run within each group",
|
|
181
|
+
nargs="+",
|
|
182
|
+
default=[],
|
|
183
|
+
metavar="COL",
|
|
184
|
+
)
|
|
185
|
+
g.add_argument(
|
|
186
|
+
"--test",
|
|
187
|
+
help="Statistical test to use (default: bootstrap)",
|
|
188
|
+
choices=TESTS,
|
|
189
|
+
default="bootstrap",
|
|
190
|
+
)
|
|
191
|
+
g.add_argument(
|
|
192
|
+
"--tags",
|
|
193
|
+
help="Extra tag columns appended to output, in tag:value format",
|
|
194
|
+
nargs="+",
|
|
195
|
+
default=None,
|
|
196
|
+
metavar="TAG:VALUE",
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
bs = parser.add_argument_group("bootstrap options")
|
|
200
|
+
bs.add_argument(
|
|
201
|
+
"--randomize",
|
|
202
|
+
help="Shuffle second column before pairing (bootstrap only)",
|
|
203
|
+
action="store_true",
|
|
204
|
+
)
|
|
205
|
+
bs.add_argument(
|
|
206
|
+
"--randomseed",
|
|
207
|
+
help="Random seed — integer or string (MD5-hashed to integer)",
|
|
208
|
+
default=None,
|
|
209
|
+
metavar="SEED",
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def execute(self, args: argparse.Namespace) -> None:
|
|
213
|
+
if len(args.cols) != len(args.dest):
|
|
214
|
+
raise ValueError(
|
|
215
|
+
f"--cols has {len(args.cols)} pair(s) but --dest has "
|
|
216
|
+
f"{len(args.dest)} name(s); counts must match"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
args.randomseed = normalize_seed(args.randomseed)
|
|
220
|
+
np.random.seed(args.randomseed)
|
|
221
|
+
|
|
222
|
+
df = io.read(args)
|
|
223
|
+
|
|
224
|
+
all_cols = [c for pair in args.cols for c in pair.split(":", 1)]
|
|
225
|
+
check_cols(df, all_cols, "-c/--cols")
|
|
226
|
+
check_cols(df, args.groups, "-g/--groups")
|
|
227
|
+
|
|
228
|
+
result = _run_test(df, args)
|
|
229
|
+
|
|
230
|
+
if args.tags:
|
|
231
|
+
for item in args.tags:
|
|
232
|
+
tag, val = item.split(":", 1)
|
|
233
|
+
result[tag] = val
|
|
234
|
+
|
|
235
|
+
io.printdf(result, args)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dftk.commands.wstat_cmd — dftk wstat subcommand.
|
|
3
|
+
|
|
4
|
+
Port of dfwstat.py: weighted descriptive statistics using
|
|
5
|
+
statsmodels.stats.weightstats.DescrStatsW.
|
|
6
|
+
|
|
7
|
+
Output columns
|
|
8
|
+
--------------
|
|
9
|
+
[group(s),] name, weight, totalweight, wsum, wmean, wstd, wmcilo, wmcihi
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
|
|
14
|
+
import pandas as pd
|
|
15
|
+
import statsmodels.stats.weightstats as ssw
|
|
16
|
+
|
|
17
|
+
from dftk.commands.base import BaseCommand
|
|
18
|
+
from dftk.common.io import check_cols, io
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _wstat(df: pd.DataFrame, args: argparse.Namespace) -> pd.DataFrame:
|
|
22
|
+
weights = list(args.weights)
|
|
23
|
+
if len(weights) == 1:
|
|
24
|
+
weights = weights * len(args.cols)
|
|
25
|
+
|
|
26
|
+
if len(args.cols) != len(weights):
|
|
27
|
+
raise ValueError(
|
|
28
|
+
f"Number of weight columns ({len(weights)}) must match "
|
|
29
|
+
f"stat columns ({len(args.cols)}) or be 1."
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
alpha = 1.0 - 0.01 * args.confidencelevel
|
|
33
|
+
probs = [alpha / 2, 1.0 - alpha / 2]
|
|
34
|
+
|
|
35
|
+
header = [
|
|
36
|
+
"name",
|
|
37
|
+
"weight",
|
|
38
|
+
"totalweight",
|
|
39
|
+
"wsum",
|
|
40
|
+
"wmean",
|
|
41
|
+
"wstd",
|
|
42
|
+
"wmcilo",
|
|
43
|
+
"wmcihi",
|
|
44
|
+
]
|
|
45
|
+
rows = []
|
|
46
|
+
|
|
47
|
+
def _add_cols(sub: pd.DataFrame, prefix: tuple = ()) -> None:
|
|
48
|
+
for c, w in zip(args.cols, weights, strict=False):
|
|
49
|
+
pair = sub[[c, w]].dropna()
|
|
50
|
+
ws = ssw.DescrStatsW(pair[c], pair[w])
|
|
51
|
+
q = ws.quantile(probs)
|
|
52
|
+
rows.append(
|
|
53
|
+
(
|
|
54
|
+
*prefix,
|
|
55
|
+
c,
|
|
56
|
+
w,
|
|
57
|
+
float(ws.sum_weights),
|
|
58
|
+
ws.sum,
|
|
59
|
+
ws.mean,
|
|
60
|
+
ws.std,
|
|
61
|
+
float(q.iloc[0]),
|
|
62
|
+
float(q.iloc[1]),
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if args.groups:
|
|
67
|
+
header = [*args.groups, *header]
|
|
68
|
+
for group, gdf in df.groupby(args.groups):
|
|
69
|
+
prefix = group if isinstance(group, tuple) else (group,)
|
|
70
|
+
_add_cols(gdf, prefix)
|
|
71
|
+
else:
|
|
72
|
+
_add_cols(df)
|
|
73
|
+
|
|
74
|
+
return pd.DataFrame(rows, columns=header)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
_EPILOG = """\
|
|
78
|
+
COLUMN PAIRS (-c / -w)
|
|
79
|
+
----------------------
|
|
80
|
+
Each stat column (-c) is paired with a weight column (-w).
|
|
81
|
+
If a single weight column is given it is broadcast to all stat columns:
|
|
82
|
+
|
|
83
|
+
dftk wstat data.tsv -c value -w count
|
|
84
|
+
dftk wstat data.tsv -c x y -w wx wy
|
|
85
|
+
dftk wstat data.tsv -c x y -w w # w used for both x and y
|
|
86
|
+
|
|
87
|
+
OUTPUT COLUMNS
|
|
88
|
+
--------------
|
|
89
|
+
name stat column name
|
|
90
|
+
weight weight column name
|
|
91
|
+
totalweight sum of weights
|
|
92
|
+
wsum weighted sum
|
|
93
|
+
wmean weighted mean
|
|
94
|
+
wstd weighted standard deviation
|
|
95
|
+
wmcilo lower weighted quantile (confidence level / 2)
|
|
96
|
+
wmcihi upper weighted quantile (1 - confidence level / 2)
|
|
97
|
+
|
|
98
|
+
EXAMPLES
|
|
99
|
+
--------
|
|
100
|
+
dftk wstat data.tsv -c value -w count
|
|
101
|
+
dftk wstat data.tsv -c value -w count -g group
|
|
102
|
+
dftk wstat data.tsv -c x y -w w --confidencelevel 99
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class WstatCommand(BaseCommand):
|
|
107
|
+
@property
|
|
108
|
+
def name(self) -> str:
|
|
109
|
+
return "wstat"
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def help(self) -> str:
|
|
113
|
+
return "Weighted descriptive statistics (wmean, wstd, weighted quantile CI)."
|
|
114
|
+
|
|
115
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
116
|
+
parser.formatter_class = argparse.RawDescriptionHelpFormatter
|
|
117
|
+
parser.epilog = _EPILOG
|
|
118
|
+
|
|
119
|
+
self.add_io_arguments(parser)
|
|
120
|
+
|
|
121
|
+
g = parser.add_argument_group("wstat options")
|
|
122
|
+
g.add_argument(
|
|
123
|
+
"-c",
|
|
124
|
+
"--cols",
|
|
125
|
+
nargs="+",
|
|
126
|
+
required=True,
|
|
127
|
+
metavar="COL",
|
|
128
|
+
help="Column(s) to compute weighted statistics on.",
|
|
129
|
+
)
|
|
130
|
+
g.add_argument(
|
|
131
|
+
"-w",
|
|
132
|
+
"--weights",
|
|
133
|
+
nargs="+",
|
|
134
|
+
required=True,
|
|
135
|
+
metavar="COL",
|
|
136
|
+
help="Weight column(s); one per stat column, or one broadcast to all.",
|
|
137
|
+
)
|
|
138
|
+
g.add_argument(
|
|
139
|
+
"-g",
|
|
140
|
+
"--groups",
|
|
141
|
+
nargs="+",
|
|
142
|
+
default=[],
|
|
143
|
+
metavar="COL",
|
|
144
|
+
help="Group column(s); statistics are computed within each group.",
|
|
145
|
+
)
|
|
146
|
+
g.add_argument(
|
|
147
|
+
"--confidencelevel",
|
|
148
|
+
type=float,
|
|
149
|
+
default=95.0,
|
|
150
|
+
metavar="PCT",
|
|
151
|
+
help="Confidence level in percent for wmcilo/wmcihi (default: 95.0).",
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
def execute(self, args: argparse.Namespace) -> None:
|
|
155
|
+
all_cols = list(args.cols) + list(args.weights) + list(args.groups)
|
|
156
|
+
df = io.read(args)
|
|
157
|
+
check_cols(df, all_cols, "-c/-w/-g")
|
|
158
|
+
|
|
159
|
+
if df.empty:
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
result = _wstat(df, args)
|
|
163
|
+
io.printdf(result, args)
|
dftk/common/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# dftk.common — shared utilities for the dftk toolkit.
|
|
2
|
+
#
|
|
3
|
+
# Modules:
|
|
4
|
+
# io.py — dataframe I/O, parquet-pipe transport, output transformations
|
|
5
|
+
#
|
|
6
|
+
# Future candidates for this package:
|
|
7
|
+
# seed.py — random seed normalisation (int or MD5-hashed string → int)
|
|
8
|
+
# ci.py — confidence interval helpers shared across stat/pivot/wstat
|