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.
@@ -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)
@@ -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