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,281 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dftk.commands.corr_cmd — dftk corr subcommand.
|
|
3
|
+
|
|
4
|
+
Port of dfcorr.py: pairwise column correlations with optional grouping,
|
|
5
|
+
analytical confidence intervals (Pearson), and BCa bootstrap inference.
|
|
6
|
+
|
|
7
|
+
Bootstrap mode (--bootstrap N) produces a single summary row per pair with:
|
|
8
|
+
p_perm — permutation p-value (fraction of |r_null| >= |r_obs|)
|
|
9
|
+
ci_boot_lo — BCa 95% CI lower bound (all methods)
|
|
10
|
+
ci_boot_hi — BCa 95% CI upper bound (all methods)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import logging
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
import pandas as pd
|
|
18
|
+
import scipy.stats as ss
|
|
19
|
+
|
|
20
|
+
from dftk.commands.base import BaseCommand
|
|
21
|
+
from dftk.common.io import check_cols, io
|
|
22
|
+
from dftk.common.seed import normalize_seed
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
METHODS = ["pearson", "spearman", "kendall"]
|
|
27
|
+
_METHOD_FUNC = {
|
|
28
|
+
"pearson": ss.pearsonr,
|
|
29
|
+
"spearman": ss.spearmanr,
|
|
30
|
+
"kendall": ss.kendalltau,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _compute_correlation(a: np.ndarray, b: np.ndarray, method_fn, ci: bool) -> tuple:
|
|
35
|
+
result = method_fn(a, b)
|
|
36
|
+
row = (len(a), result.statistic, result.pvalue)
|
|
37
|
+
if ci:
|
|
38
|
+
interval = result.confidence_interval()
|
|
39
|
+
row = (*row, interval.low, interval.high)
|
|
40
|
+
return row
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# BCa jackknife acceleration is estimated from leave-one-out resamples.
|
|
44
|
+
# scipy.stats.bootstrap materialises an (n × n-1) array for this step,
|
|
45
|
+
# which OOMs at large n. We stream one leave-one-out at a time and cap
|
|
46
|
+
# at _MAX_JACK observations so peak memory is O(min(n, _MAX_JACK)).
|
|
47
|
+
_MAX_JACK = 500
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _bootstrap_stats(
|
|
51
|
+
a: np.ndarray,
|
|
52
|
+
b: np.ndarray,
|
|
53
|
+
method_fn,
|
|
54
|
+
n_boot: int,
|
|
55
|
+
rng: np.random.Generator,
|
|
56
|
+
confidence: float = 0.95,
|
|
57
|
+
) -> tuple[float, float, float]:
|
|
58
|
+
"""Return (p_perm, ci_boot_lo, ci_boot_hi) via permutation test and BCa CI."""
|
|
59
|
+
n = len(a)
|
|
60
|
+
r_obs = method_fn(a, b).statistic
|
|
61
|
+
|
|
62
|
+
r_obs_abs = abs(r_obs)
|
|
63
|
+
n_extreme = 0
|
|
64
|
+
for _ in range(n_boot):
|
|
65
|
+
if abs(method_fn(a, rng.permutation(b)).statistic) >= r_obs_abs:
|
|
66
|
+
n_extreme += 1
|
|
67
|
+
p_perm = n_extreme / n_boot
|
|
68
|
+
|
|
69
|
+
theta_boot = np.empty(n_boot)
|
|
70
|
+
for i in range(n_boot):
|
|
71
|
+
idx = rng.integers(0, n, size=n)
|
|
72
|
+
theta_boot[i] = method_fn(a[idx], b[idx]).statistic
|
|
73
|
+
|
|
74
|
+
if n <= _MAX_JACK:
|
|
75
|
+
jack_indices = np.arange(n)
|
|
76
|
+
else:
|
|
77
|
+
jack_indices = rng.choice(n, size=_MAX_JACK, replace=False)
|
|
78
|
+
|
|
79
|
+
theta_jack = np.empty(len(jack_indices))
|
|
80
|
+
mask = np.ones(n, dtype=bool)
|
|
81
|
+
for j, i in enumerate(jack_indices):
|
|
82
|
+
mask[i] = False
|
|
83
|
+
theta_jack[j] = method_fn(a[mask], b[mask]).statistic
|
|
84
|
+
mask[i] = True
|
|
85
|
+
|
|
86
|
+
jmean = theta_jack.mean()
|
|
87
|
+
diff = jmean - theta_jack
|
|
88
|
+
denom = np.sum(diff**2)
|
|
89
|
+
a_hat = np.sum(diff**3) / (6.0 * denom**1.5) if denom > 1e-20 else 0.0
|
|
90
|
+
|
|
91
|
+
prop = np.clip((theta_boot < r_obs).mean(), 1e-10, 1 - 1e-10)
|
|
92
|
+
z0 = ss.norm.ppf(prop)
|
|
93
|
+
|
|
94
|
+
alpha = (1.0 - confidence) / 2.0
|
|
95
|
+
z_lo, z_hi = ss.norm.ppf(alpha), ss.norm.ppf(1.0 - alpha)
|
|
96
|
+
|
|
97
|
+
def _adj(z: float) -> float:
|
|
98
|
+
denom = 1.0 - a_hat * (z0 + z)
|
|
99
|
+
if abs(denom) < 1e-10:
|
|
100
|
+
return float("nan")
|
|
101
|
+
return ss.norm.cdf(z0 + (z0 + z) / denom)
|
|
102
|
+
|
|
103
|
+
a1, a2 = _adj(z_lo), _adj(z_hi)
|
|
104
|
+
|
|
105
|
+
if np.isnan(a1) or np.isnan(a2):
|
|
106
|
+
ci_lo = float(np.percentile(theta_boot, 100.0 * alpha))
|
|
107
|
+
ci_hi = float(np.percentile(theta_boot, 100.0 * (1.0 - alpha)))
|
|
108
|
+
else:
|
|
109
|
+
ci_lo = float(np.percentile(theta_boot, 100.0 * a1))
|
|
110
|
+
ci_hi = float(np.percentile(theta_boot, 100.0 * a2))
|
|
111
|
+
|
|
112
|
+
return p_perm, ci_lo, ci_hi
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _corr(
|
|
116
|
+
df: pd.DataFrame, args: argparse.Namespace, rng: np.random.Generator
|
|
117
|
+
) -> pd.DataFrame:
|
|
118
|
+
method_fn = _METHOD_FUNC[args.method]
|
|
119
|
+
pairs = [c.split(":", maxsplit=1) for c in args.cols]
|
|
120
|
+
bad = [c for c, p in zip(args.cols, pairs, strict=False) if len(p) != 2]
|
|
121
|
+
if bad:
|
|
122
|
+
raise ValueError(f"-c pairs must be col1:col2, got: {bad}")
|
|
123
|
+
|
|
124
|
+
ci = args.ci and args.method == "pearson"
|
|
125
|
+
|
|
126
|
+
header = ["col1", "col2", "nobs", "correlation", "pvalue"]
|
|
127
|
+
if ci:
|
|
128
|
+
header += ["cilo", "cihi"]
|
|
129
|
+
if args.bootstrap is not None:
|
|
130
|
+
header += ["p_perm", "ci_boot_lo", "ci_boot_hi"]
|
|
131
|
+
|
|
132
|
+
rows = []
|
|
133
|
+
|
|
134
|
+
def _add_pairs(sub: pd.DataFrame, prefix: tuple = ()):
|
|
135
|
+
for col1, col2 in pairs:
|
|
136
|
+
pair_df = sub[[col1, col2]].dropna()
|
|
137
|
+
a, b = pair_df[col1].values, pair_df[col2].values
|
|
138
|
+
cr = _compute_correlation(a, b, method_fn, ci)
|
|
139
|
+
if args.bootstrap is None:
|
|
140
|
+
rows.append((*prefix, col1, col2, *cr))
|
|
141
|
+
else:
|
|
142
|
+
boot = _bootstrap_stats(
|
|
143
|
+
a, b, method_fn, args.bootstrap, rng, args.confidence / 100.0
|
|
144
|
+
)
|
|
145
|
+
rows.append((*prefix, col1, col2, *cr, *boot))
|
|
146
|
+
|
|
147
|
+
if args.groups:
|
|
148
|
+
header = [*args.groups, *header]
|
|
149
|
+
for group, gdf in df.groupby(args.groups):
|
|
150
|
+
prefix = group if isinstance(group, tuple) else (group,)
|
|
151
|
+
_add_pairs(gdf, prefix)
|
|
152
|
+
else:
|
|
153
|
+
_add_pairs(df)
|
|
154
|
+
|
|
155
|
+
return pd.DataFrame(rows, columns=header)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
_EPILOG = """\
|
|
159
|
+
COLUMN PAIRS (-c)
|
|
160
|
+
-----------------
|
|
161
|
+
Pairs are specified as col1:col2. Multiple pairs may be given:
|
|
162
|
+
|
|
163
|
+
dftk corr data.tsv -c x:y
|
|
164
|
+
dftk corr data.tsv -c x:y a:b
|
|
165
|
+
|
|
166
|
+
METHODS (--method)
|
|
167
|
+
------------------
|
|
168
|
+
pearson Pearson r (default)
|
|
169
|
+
spearman Spearman rank correlation
|
|
170
|
+
kendall Kendall tau
|
|
171
|
+
|
|
172
|
+
CONFIDENCE INTERVAL (--ci)
|
|
173
|
+
--------------------------
|
|
174
|
+
Adds cilo and cihi columns using the analytical Fisher z-transform formula.
|
|
175
|
+
Only available for Pearson; silently ignored for Spearman/Kendall.
|
|
176
|
+
Use --bootstrap to get non-parametric CIs for any method.
|
|
177
|
+
|
|
178
|
+
BOOTSTRAP (--bootstrap N)
|
|
179
|
+
--------------------------
|
|
180
|
+
Runs N bootstrap resamples and N permutation draws, adding three columns:
|
|
181
|
+
|
|
182
|
+
p_perm Permutation p-value: fraction of |r_null| >= |r_obs| when b
|
|
183
|
+
is repeatedly shuffled to break the pairing with a. This is
|
|
184
|
+
a non-parametric test of the null hypothesis r = 0.
|
|
185
|
+
|
|
186
|
+
ci_boot_lo BCa (bias-corrected and accelerated) CI lower bound.
|
|
187
|
+
ci_boot_hi BCa CI upper bound.
|
|
188
|
+
|
|
189
|
+
The CI level defaults to 95% and is controlled by --confidence.
|
|
190
|
+
BCa corrects for both bias (z0) and skewness (acceleration factor a_hat)
|
|
191
|
+
in the bootstrap distribution. The acceleration factor is estimated via a
|
|
192
|
+
streaming jackknife: leave-one-out statistics are computed one at a time
|
|
193
|
+
(capped at 500 observations when n > 500) so peak memory is O(n) rather
|
|
194
|
+
than O(n^2). Falls back to percentile CI if BCa is degenerate.
|
|
195
|
+
|
|
196
|
+
Use --randomseed for reproducibility.
|
|
197
|
+
|
|
198
|
+
EXAMPLES
|
|
199
|
+
--------
|
|
200
|
+
dftk corr data.tsv -c x:y
|
|
201
|
+
dftk corr data.tsv -c x:y -g group --method spearman
|
|
202
|
+
dftk corr data.tsv -c x:y --ci
|
|
203
|
+
dftk corr data.tsv -c x:y --method spearman --bootstrap 2000 --randomseed 42
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class CorrCommand(BaseCommand):
|
|
208
|
+
@property
|
|
209
|
+
def name(self) -> str:
|
|
210
|
+
return "corr"
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def help(self) -> str:
|
|
214
|
+
return "Pairwise column correlations (Pearson, Spearman, Kendall) with optional bootstrap." # noqa: E501
|
|
215
|
+
|
|
216
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
217
|
+
parser.formatter_class = argparse.RawDescriptionHelpFormatter
|
|
218
|
+
parser.epilog = _EPILOG
|
|
219
|
+
|
|
220
|
+
self.add_io_arguments(parser)
|
|
221
|
+
|
|
222
|
+
g = parser.add_argument_group("corr options")
|
|
223
|
+
g.add_argument(
|
|
224
|
+
"-c",
|
|
225
|
+
"--cols",
|
|
226
|
+
nargs="+",
|
|
227
|
+
required=True,
|
|
228
|
+
metavar="COL1:COL2",
|
|
229
|
+
help="Column pair(s) to correlate, e.g. x:y.",
|
|
230
|
+
)
|
|
231
|
+
g.add_argument(
|
|
232
|
+
"-g",
|
|
233
|
+
"--groups",
|
|
234
|
+
nargs="+",
|
|
235
|
+
default=[],
|
|
236
|
+
metavar="COL",
|
|
237
|
+
help="Group column(s); correlation is computed within each group.",
|
|
238
|
+
)
|
|
239
|
+
g.add_argument(
|
|
240
|
+
"--method",
|
|
241
|
+
choices=METHODS,
|
|
242
|
+
default="pearson",
|
|
243
|
+
help="Correlation method (default: pearson).",
|
|
244
|
+
)
|
|
245
|
+
g.add_argument(
|
|
246
|
+
"--ci",
|
|
247
|
+
action="store_true",
|
|
248
|
+
help="Include analytical 95%% CI columns cilo/cihi (Pearson only).",
|
|
249
|
+
)
|
|
250
|
+
g.add_argument(
|
|
251
|
+
"--bootstrap",
|
|
252
|
+
type=int,
|
|
253
|
+
default=None,
|
|
254
|
+
metavar="N",
|
|
255
|
+
help="Bootstrap N resamples; adds p_perm and BCa CI columns.",
|
|
256
|
+
)
|
|
257
|
+
g.add_argument(
|
|
258
|
+
"--confidence",
|
|
259
|
+
type=float,
|
|
260
|
+
default=95.0,
|
|
261
|
+
metavar="PCT",
|
|
262
|
+
help="CI level in percent for BCa bootstrap bounds (default: 95.0).",
|
|
263
|
+
)
|
|
264
|
+
g.add_argument(
|
|
265
|
+
"--randomseed",
|
|
266
|
+
default=None,
|
|
267
|
+
metavar="SEED",
|
|
268
|
+
help="Random seed for bootstrap/permutation (integer or string).",
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
def execute(self, args: argparse.Namespace) -> None:
|
|
272
|
+
seed = normalize_seed(args.randomseed) if args.randomseed is not None else None
|
|
273
|
+
rng = np.random.default_rng(seed)
|
|
274
|
+
|
|
275
|
+
all_cols = [c for pair in args.cols for c in pair.split(":", maxsplit=1)]
|
|
276
|
+
df = io.read(args)
|
|
277
|
+
check_cols(df, all_cols, "-c/--cols")
|
|
278
|
+
check_cols(df, args.groups, "-g/--groups")
|
|
279
|
+
|
|
280
|
+
result = _corr(df, args, rng)
|
|
281
|
+
io.printdf(result, args)
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dftk.commands.dataset_cmd — the ``dftk dataset`` subcommand.
|
|
3
|
+
|
|
4
|
+
Provides access to curated example datasets from seaborn, statsmodels,
|
|
5
|
+
and (optionally) pydataset. Datasets are written to stdout as TSV or
|
|
6
|
+
into the dftk parquet pipe with -o.
|
|
7
|
+
|
|
8
|
+
Examples
|
|
9
|
+
--------
|
|
10
|
+
dftk dataset iris
|
|
11
|
+
dftk dataset iris --source seaborn
|
|
12
|
+
dftk dataset longley --source statsmodels
|
|
13
|
+
dftk dataset --list
|
|
14
|
+
dftk dataset --list --source seaborn
|
|
15
|
+
dftk dataset iris -o | dftk stat -c sepal_length -g species
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
import pandas as pd
|
|
22
|
+
|
|
23
|
+
from dftk.commands.base import BaseCommand
|
|
24
|
+
from dftk.common.io import io
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# Per-source load/list helpers
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _load_seaborn(name: str) -> pd.DataFrame:
|
|
32
|
+
import seaborn as sns
|
|
33
|
+
|
|
34
|
+
return sns.load_dataset(name)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _list_seaborn() -> list[tuple[str, str, str]]:
|
|
38
|
+
import seaborn as sns
|
|
39
|
+
|
|
40
|
+
return [("seaborn", n, "") for n in sorted(sns.get_dataset_names())]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _load_statsmodels(name: str) -> pd.DataFrame:
|
|
44
|
+
import statsmodels.api as sm
|
|
45
|
+
|
|
46
|
+
mod = getattr(sm.datasets, name, None)
|
|
47
|
+
if mod is not None and hasattr(mod, "load_pandas"):
|
|
48
|
+
return mod.load_pandas().data
|
|
49
|
+
raise ValueError(f"statsmodels dataset {name!r} not found")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _list_statsmodels() -> list[tuple[str, str, str]]:
|
|
53
|
+
import statsmodels.api as sm
|
|
54
|
+
|
|
55
|
+
rows = []
|
|
56
|
+
for name in sorted(dir(sm.datasets)):
|
|
57
|
+
if name.startswith("_"):
|
|
58
|
+
continue
|
|
59
|
+
mod = getattr(sm.datasets, name, None)
|
|
60
|
+
if mod is None or not hasattr(mod, "load_pandas"):
|
|
61
|
+
continue
|
|
62
|
+
title = (getattr(mod, "TITLE", "") or "").strip()
|
|
63
|
+
desc = title or (getattr(mod, "DESCRSHORT", "") or "").strip()
|
|
64
|
+
rows.append(("statsmodels", name, desc))
|
|
65
|
+
return rows
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
import pydataset as _pydataset # noqa: F401
|
|
70
|
+
|
|
71
|
+
_HAS_PYDATASET = True
|
|
72
|
+
except ImportError:
|
|
73
|
+
_HAS_PYDATASET = False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _load_pydataset(name: str) -> pd.DataFrame:
|
|
77
|
+
import pydataset
|
|
78
|
+
|
|
79
|
+
return pydataset.data(name)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _list_pydataset() -> list[tuple[str, str, str]]:
|
|
83
|
+
import pydataset
|
|
84
|
+
|
|
85
|
+
index = pydataset.data()
|
|
86
|
+
return [
|
|
87
|
+
("pydataset", str(row["item"]), str(row.get("title", "")))
|
|
88
|
+
for _, row in index.iterrows()
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# Registry — built at import time so choices are accurate
|
|
93
|
+
_SOURCES: dict[str, tuple] = {
|
|
94
|
+
"seaborn": (_load_seaborn, _list_seaborn),
|
|
95
|
+
"statsmodels": (_load_statsmodels, _list_statsmodels),
|
|
96
|
+
}
|
|
97
|
+
if _HAS_PYDATASET:
|
|
98
|
+
_SOURCES["pydataset"] = (_load_pydataset, _list_pydataset)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# ---------------------------------------------------------------------------
|
|
102
|
+
# Command
|
|
103
|
+
# ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class DatasetCommand(BaseCommand):
|
|
107
|
+
name = "dataset"
|
|
108
|
+
help = "Load a curated example dataset (seaborn, statsmodels, pydataset)"
|
|
109
|
+
|
|
110
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
111
|
+
io.parser_output(parser)
|
|
112
|
+
|
|
113
|
+
g = parser.add_argument_group("dataset options")
|
|
114
|
+
g.add_argument(
|
|
115
|
+
"NAME",
|
|
116
|
+
nargs="?",
|
|
117
|
+
default=None,
|
|
118
|
+
help=(
|
|
119
|
+
"Dataset name (e.g. 'iris', 'longley'). "
|
|
120
|
+
"Omit with --list to show all available datasets."
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
g.add_argument(
|
|
124
|
+
"--source",
|
|
125
|
+
nargs="+",
|
|
126
|
+
default=None,
|
|
127
|
+
choices=list(_SOURCES),
|
|
128
|
+
metavar="SOURCE",
|
|
129
|
+
help=(
|
|
130
|
+
f"Source(s) to search: {', '.join(_SOURCES)}. "
|
|
131
|
+
"Default: try all sources in order."
|
|
132
|
+
),
|
|
133
|
+
)
|
|
134
|
+
g.add_argument(
|
|
135
|
+
"--list",
|
|
136
|
+
action="store_true",
|
|
137
|
+
dest="list_datasets",
|
|
138
|
+
help="List available datasets as TSV (source, name, description).",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def execute(self, args: argparse.Namespace) -> None:
|
|
142
|
+
sources = args.source or list(_SOURCES)
|
|
143
|
+
|
|
144
|
+
if args.list_datasets:
|
|
145
|
+
self._print_list(sources)
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
if args.NAME is None:
|
|
149
|
+
raise ValueError(
|
|
150
|
+
"Provide a dataset NAME, or use --list to browse available datasets."
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
df = self._load(args.NAME, sources)
|
|
154
|
+
io.printdf(df, args)
|
|
155
|
+
|
|
156
|
+
# ------------------------------------------------------------------ #
|
|
157
|
+
# Helpers #
|
|
158
|
+
# ------------------------------------------------------------------ #
|
|
159
|
+
|
|
160
|
+
def _load(self, name: str, sources: list[str]) -> pd.DataFrame:
|
|
161
|
+
errors: list[str] = []
|
|
162
|
+
for src in sources:
|
|
163
|
+
load_fn, _ = _SOURCES[src]
|
|
164
|
+
try:
|
|
165
|
+
return load_fn(name)
|
|
166
|
+
except Exception as exc:
|
|
167
|
+
errors.append(f" {src}: {exc}")
|
|
168
|
+
raise ValueError(
|
|
169
|
+
f"Dataset {name!r} not found in: {', '.join(sources)}.\n"
|
|
170
|
+
+ "\n".join(errors)
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def _print_list(self, sources: list[str]) -> None:
|
|
174
|
+
print("source\tname\tdescription")
|
|
175
|
+
for src in sources:
|
|
176
|
+
_, list_fn = _SOURCES[src]
|
|
177
|
+
try:
|
|
178
|
+
for source, name, desc in list_fn():
|
|
179
|
+
print(f"{source}\t{name}\t{desc}")
|
|
180
|
+
except Exception as exc:
|
|
181
|
+
print(f"Warning: could not list {src} datasets: {exc}", file=sys.stderr)
|