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,320 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dftk.commands.line_cmd — dftk line subcommand.
|
|
3
|
+
|
|
4
|
+
Line plot with optional error bars, confidence intervals, and regression
|
|
5
|
+
line overlay.
|
|
6
|
+
|
|
7
|
+
Port of dfline.py with improvements:
|
|
8
|
+
- --groupcol draws one line per group value (colours from Wong palette)
|
|
9
|
+
- --subgraphcol splits into a subplot grid
|
|
10
|
+
- --size / --fontsize publication-quality figure presets
|
|
11
|
+
- --yerr / --yci symmetric or asymmetric error bars
|
|
12
|
+
- --fit overlays an OLS regression line (uses fit_cmd.regress_it)
|
|
13
|
+
- --drawstyle step / default for staircase plots
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
import matplotlib.lines as mlines
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
from dftk.commands.base import BaseCommand
|
|
23
|
+
from dftk.common.io import check_cols, io
|
|
24
|
+
from dftk.common.plot import (
|
|
25
|
+
add_figure_arguments,
|
|
26
|
+
add_group_arguments,
|
|
27
|
+
add_legend_arguments,
|
|
28
|
+
add_xy_arguments,
|
|
29
|
+
apply_labels,
|
|
30
|
+
apply_limits,
|
|
31
|
+
apply_style,
|
|
32
|
+
apply_ticks,
|
|
33
|
+
make_figure,
|
|
34
|
+
make_grouplabel,
|
|
35
|
+
save_or_show,
|
|
36
|
+
subgraph_groups,
|
|
37
|
+
subgraph_layout,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
# Error bar helper
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _resolve_yerr(df, args):
|
|
46
|
+
"""Return yerr array for df, or None. Handles --yerr and --yci."""
|
|
47
|
+
if args.yerr is not None:
|
|
48
|
+
err = df[args.yerr].values
|
|
49
|
+
return [err, err]
|
|
50
|
+
if args.yci is not None:
|
|
51
|
+
lo_col, hi_col = args.yci.split(",")
|
|
52
|
+
lo = (df[args.ycol] - df[lo_col]).values
|
|
53
|
+
hi = (df[hi_col] - df[args.ycol]).values
|
|
54
|
+
return [lo, hi]
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
# Fit overlay (shared logic with scat)
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _fit_overlay(ax, x_series, y_series, args):
|
|
64
|
+
import pandas as pd
|
|
65
|
+
|
|
66
|
+
from dftk.commands.fit_cmd import regress_it
|
|
67
|
+
|
|
68
|
+
df_fit = pd.DataFrame({"_x": x_series.values, "_y": y_series.values}).dropna()
|
|
69
|
+
fit_args = argparse.Namespace(
|
|
70
|
+
formula="_y ~ _x" if not args.nointercept else "_y ~ _x - 1",
|
|
71
|
+
weights=getattr(args, "weights", None),
|
|
72
|
+
robust=getattr(args, "robust", False),
|
|
73
|
+
)
|
|
74
|
+
res = regress_it(df_fit, fit_args)
|
|
75
|
+
print(res.summary(), file=sys.stderr)
|
|
76
|
+
|
|
77
|
+
xmin, xmax = ax.get_xlim()
|
|
78
|
+
X_plot = np.linspace(xmin, xmax, 200)
|
|
79
|
+
|
|
80
|
+
if args.nointercept:
|
|
81
|
+
slope = res.params["_x"]
|
|
82
|
+
Y_plot = slope * X_plot
|
|
83
|
+
label_text = f"{slope:+.3g} × {args.xcol}"
|
|
84
|
+
else:
|
|
85
|
+
intercept = res.params["Intercept"]
|
|
86
|
+
slope = res.params["_x"]
|
|
87
|
+
Y_plot = intercept + slope * X_plot
|
|
88
|
+
label_text = f"{intercept:.3g}{slope:+.3g} × {args.xcol}"
|
|
89
|
+
|
|
90
|
+
if getattr(args, "pvalue", False):
|
|
91
|
+
pval = res.pvalues.get("_x", res.pvalues.iloc[-1])
|
|
92
|
+
label_text += f" -log10(p)={-np.log10(pval):.2g}"
|
|
93
|
+
|
|
94
|
+
ax.plot(X_plot, Y_plot, "r--", linewidth=1)
|
|
95
|
+
ax.legend(
|
|
96
|
+
handles=[mlines.Line2D([], [], color="red", linestyle="--", label=label_text)],
|
|
97
|
+
loc=0,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# ---------------------------------------------------------------------------
|
|
102
|
+
# Single-axes line plot
|
|
103
|
+
# ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _line_ax(ax, df, args, title_suffix: str = ""):
|
|
107
|
+
xcol = args.xcol
|
|
108
|
+
ycol = args.ycol
|
|
109
|
+
legendloc = tuple(args.legendloc) if args.legendloc else "best"
|
|
110
|
+
marker = getattr(args, "marker", "")
|
|
111
|
+
drawstyle = getattr(args, "drawstyle", "default")
|
|
112
|
+
|
|
113
|
+
if args.groupcol is not None:
|
|
114
|
+
for groupname, gdf in subgraph_groups(
|
|
115
|
+
df, args.groupcol, getattr(args, "groupcolorder", None)
|
|
116
|
+
):
|
|
117
|
+
label = make_grouplabel(
|
|
118
|
+
groupname, args.groupcol, getattr(args, "groupcolformat", None)
|
|
119
|
+
)
|
|
120
|
+
yerr = _resolve_yerr(gdf, args)
|
|
121
|
+
ax.errorbar(
|
|
122
|
+
gdf[xcol],
|
|
123
|
+
gdf[ycol],
|
|
124
|
+
yerr=yerr,
|
|
125
|
+
marker=marker,
|
|
126
|
+
drawstyle=drawstyle,
|
|
127
|
+
label=label,
|
|
128
|
+
capsize=3,
|
|
129
|
+
)
|
|
130
|
+
ax.legend(loc=legendloc, title=args.legendtitle)
|
|
131
|
+
else:
|
|
132
|
+
yerr = _resolve_yerr(df, args)
|
|
133
|
+
label = args.legend or ""
|
|
134
|
+
ax.errorbar(
|
|
135
|
+
df[xcol],
|
|
136
|
+
df[ycol],
|
|
137
|
+
yerr=yerr,
|
|
138
|
+
marker=marker,
|
|
139
|
+
drawstyle=drawstyle,
|
|
140
|
+
label=label if label else None,
|
|
141
|
+
capsize=3,
|
|
142
|
+
)
|
|
143
|
+
if label:
|
|
144
|
+
ax.legend(loc=legendloc, title=args.legendtitle)
|
|
145
|
+
|
|
146
|
+
if getattr(args, "fit", False) and args.groupcol is None:
|
|
147
|
+
_fit_overlay(ax, df[xcol], df[ycol], args)
|
|
148
|
+
|
|
149
|
+
apply_limits(ax, args)
|
|
150
|
+
apply_ticks(ax, args)
|
|
151
|
+
apply_labels(ax, args, default_xlabel=xcol or "", default_ylabel=ycol or "")
|
|
152
|
+
if title_suffix:
|
|
153
|
+
existing = ax.get_title()
|
|
154
|
+
ax.set_title(f"{existing} [{title_suffix}]" if existing else title_suffix)
|
|
155
|
+
|
|
156
|
+
ax.grid(True, linewidth=0.4, alpha=0.5)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# ---------------------------------------------------------------------------
|
|
160
|
+
# Command
|
|
161
|
+
# ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
_EPILOG = """\
|
|
164
|
+
EXAMPLES
|
|
165
|
+
--------
|
|
166
|
+
Basic line plot:
|
|
167
|
+
|
|
168
|
+
dftk line data.tsv -x time -y value
|
|
169
|
+
|
|
170
|
+
Multiple lines by group:
|
|
171
|
+
|
|
172
|
+
dftk line data.tsv -x time -y value -g group
|
|
173
|
+
|
|
174
|
+
Symmetric error bars:
|
|
175
|
+
|
|
176
|
+
dftk line data.tsv -x time -y mean --yerr stderr
|
|
177
|
+
|
|
178
|
+
Asymmetric confidence intervals:
|
|
179
|
+
|
|
180
|
+
dftk line data.tsv -x time -y mean --yci cilo,cihi
|
|
181
|
+
|
|
182
|
+
Subplot grid by condition:
|
|
183
|
+
|
|
184
|
+
dftk line data.tsv -x time -y value --subgraphcol condition -g group
|
|
185
|
+
|
|
186
|
+
Staircase / step plot:
|
|
187
|
+
|
|
188
|
+
dftk line data.tsv -x time -y value --drawstyle steps-mid
|
|
189
|
+
|
|
190
|
+
Fit and overlay regression line:
|
|
191
|
+
|
|
192
|
+
dftk line data.tsv -x time -y value --fit
|
|
193
|
+
|
|
194
|
+
Publication figure (Nature double column, PDF):
|
|
195
|
+
|
|
196
|
+
dftk line data.tsv -x time -y value --size double --fontsize publication -f fig.pdf
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class LineCommand(BaseCommand):
|
|
201
|
+
name = "line"
|
|
202
|
+
help = "Line plot with optional error bars and regression overlay."
|
|
203
|
+
|
|
204
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
205
|
+
parser.formatter_class = argparse.RawDescriptionHelpFormatter
|
|
206
|
+
parser.epilog = _EPILOG
|
|
207
|
+
|
|
208
|
+
self.add_read_arguments(parser)
|
|
209
|
+
add_xy_arguments(parser)
|
|
210
|
+
add_figure_arguments(parser)
|
|
211
|
+
add_group_arguments(parser)
|
|
212
|
+
add_legend_arguments(parser)
|
|
213
|
+
|
|
214
|
+
s = parser.add_argument_group("line options")
|
|
215
|
+
s.add_argument(
|
|
216
|
+
"-m",
|
|
217
|
+
"--marker",
|
|
218
|
+
default="",
|
|
219
|
+
metavar="MARKER",
|
|
220
|
+
help="Matplotlib marker style (default: none).",
|
|
221
|
+
)
|
|
222
|
+
s.add_argument(
|
|
223
|
+
"--drawstyle",
|
|
224
|
+
default="default",
|
|
225
|
+
metavar="STYLE",
|
|
226
|
+
help="Matplotlib drawstyle, e.g. steps-mid (default: default).",
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
e = parser.add_argument_group("error bars (mutually exclusive)")
|
|
230
|
+
mx = e.add_mutually_exclusive_group()
|
|
231
|
+
mx.add_argument(
|
|
232
|
+
"--yerr",
|
|
233
|
+
default=None,
|
|
234
|
+
metavar="COL",
|
|
235
|
+
help="Column of symmetric ±error values.",
|
|
236
|
+
)
|
|
237
|
+
mx.add_argument(
|
|
238
|
+
"--yci",
|
|
239
|
+
default=None,
|
|
240
|
+
metavar="CILO,CIHI",
|
|
241
|
+
help="Two column names for asymmetric confidence interval "
|
|
242
|
+
"(comma-separated, e.g. cilo,cihi).",
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
f = parser.add_argument_group("fit options")
|
|
246
|
+
f.add_argument(
|
|
247
|
+
"--fit",
|
|
248
|
+
action="store_true",
|
|
249
|
+
help="Overlay an OLS regression line (no grouping). "
|
|
250
|
+
"Prints summary to stderr.",
|
|
251
|
+
)
|
|
252
|
+
f.add_argument(
|
|
253
|
+
"-r",
|
|
254
|
+
"--robust",
|
|
255
|
+
action="store_true",
|
|
256
|
+
help="Use robust regression (RLM) for --fit.",
|
|
257
|
+
)
|
|
258
|
+
f.add_argument(
|
|
259
|
+
"-w",
|
|
260
|
+
"--weights",
|
|
261
|
+
default=None,
|
|
262
|
+
metavar="COL",
|
|
263
|
+
help="Weight column for WLS fit.",
|
|
264
|
+
)
|
|
265
|
+
f.add_argument(
|
|
266
|
+
"--nointercept", action="store_true", help="Suppress intercept in --fit."
|
|
267
|
+
)
|
|
268
|
+
f.add_argument(
|
|
269
|
+
"--pvalue",
|
|
270
|
+
action="store_true",
|
|
271
|
+
help="Add −log₁₀(p) to the fit legend label.",
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
def execute(self, args: argparse.Namespace) -> None:
|
|
275
|
+
if not args.xcol or not args.ycol:
|
|
276
|
+
raise ValueError("--xcol (-x) and --ycol (-y) are required.")
|
|
277
|
+
|
|
278
|
+
df = io.read(args)
|
|
279
|
+
check_cols(df, [args.xcol, args.ycol], "-x/-y")
|
|
280
|
+
check_cols(df, args.groupcol, "-g/--groupcol")
|
|
281
|
+
check_cols(df, args.subgraphcol, "--subgraphcol")
|
|
282
|
+
if args.yerr:
|
|
283
|
+
check_cols(df, [args.yerr], "--yerr")
|
|
284
|
+
if args.yci:
|
|
285
|
+
lo, hi = args.yci.split(",")
|
|
286
|
+
check_cols(df, [lo.strip(), hi.strip()], "--yci")
|
|
287
|
+
|
|
288
|
+
if args.file:
|
|
289
|
+
import matplotlib
|
|
290
|
+
|
|
291
|
+
matplotlib.use("Agg")
|
|
292
|
+
|
|
293
|
+
import matplotlib.pyplot as plt
|
|
294
|
+
|
|
295
|
+
if args.usetex:
|
|
296
|
+
plt.rc("text", usetex=True)
|
|
297
|
+
|
|
298
|
+
apply_style(args)
|
|
299
|
+
|
|
300
|
+
if args.subgraphcol is not None:
|
|
301
|
+
groups = subgraph_groups(
|
|
302
|
+
df, args.subgraphcol, getattr(args, "subgraphorder", None)
|
|
303
|
+
)
|
|
304
|
+
nrows, ncols = subgraph_layout(len(groups), args.ncols)
|
|
305
|
+
fig, axes = make_figure(args, nrows=nrows, ncols=ncols)
|
|
306
|
+
for idx, (groupname, gdf) in enumerate(groups):
|
|
307
|
+
ax = axes[idx // ncols][idx % ncols]
|
|
308
|
+
suffix = make_grouplabel(
|
|
309
|
+
groupname, args.subgraphcol, getattr(args, "subgraphformat", None)
|
|
310
|
+
)
|
|
311
|
+
_line_ax(ax, gdf, args, title_suffix=suffix)
|
|
312
|
+
for idx in range(len(groups), nrows * ncols):
|
|
313
|
+
axes[idx // ncols][idx % ncols].set_visible(False)
|
|
314
|
+
fig.tight_layout()
|
|
315
|
+
else:
|
|
316
|
+
fig, axes = make_figure(args)
|
|
317
|
+
_line_ax(axes[0][0], df, args)
|
|
318
|
+
fig.tight_layout()
|
|
319
|
+
|
|
320
|
+
save_or_show(fig, args)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dftk.commands.melt_cmd — dftk melt subcommand.
|
|
3
|
+
|
|
4
|
+
Port of dfmelt.py: wide-to-long reshape via pandas.melt.
|
|
5
|
+
|
|
6
|
+
Unpivots all columns that are not listed as index columns (-i) into two
|
|
7
|
+
new columns: a variable column (column names) and a value column (cell
|
|
8
|
+
values).
|
|
9
|
+
|
|
10
|
+
EXAMPLE
|
|
11
|
+
-------
|
|
12
|
+
Wide format:
|
|
13
|
+
|
|
14
|
+
sample gene_A gene_B gene_C
|
|
15
|
+
s1 1.2 3.4 5.6
|
|
16
|
+
s2 7.8 9.0 1.1
|
|
17
|
+
|
|
18
|
+
dftk melt data.tsv -i sample
|
|
19
|
+
|
|
20
|
+
Long format output:
|
|
21
|
+
|
|
22
|
+
sample variable value
|
|
23
|
+
s1 gene_A 1.2
|
|
24
|
+
s1 gene_B 3.4
|
|
25
|
+
s1 gene_C 5.6
|
|
26
|
+
s2 gene_A 7.8
|
|
27
|
+
…
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import argparse
|
|
31
|
+
|
|
32
|
+
from dftk.commands.base import BaseCommand
|
|
33
|
+
from dftk.common.io import check_cols, io
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class MeltCommand(BaseCommand):
|
|
37
|
+
name = "melt"
|
|
38
|
+
help = "Reshape wide-to-long (unpivot) via pandas.melt."
|
|
39
|
+
|
|
40
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
41
|
+
self.add_io_arguments(parser)
|
|
42
|
+
|
|
43
|
+
g = parser.add_argument_group("melt options")
|
|
44
|
+
g.add_argument(
|
|
45
|
+
"-i",
|
|
46
|
+
"--indexcols",
|
|
47
|
+
nargs="+",
|
|
48
|
+
default=None,
|
|
49
|
+
metavar="COL",
|
|
50
|
+
help="Column(s) to keep as row identifiers (id_vars). "
|
|
51
|
+
"All other columns are melted.",
|
|
52
|
+
)
|
|
53
|
+
g.add_argument(
|
|
54
|
+
"-d",
|
|
55
|
+
"--destcol",
|
|
56
|
+
default="variable",
|
|
57
|
+
metavar="NAME",
|
|
58
|
+
help="Name of the new column that holds the original column names "
|
|
59
|
+
"(default: variable).",
|
|
60
|
+
)
|
|
61
|
+
g.add_argument(
|
|
62
|
+
"-v",
|
|
63
|
+
"--valuecol",
|
|
64
|
+
default="value",
|
|
65
|
+
metavar="NAME",
|
|
66
|
+
help="Name of the new column that holds the cell values (default: value).",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def execute(self, args: argparse.Namespace) -> None:
|
|
70
|
+
df = io.read(args)
|
|
71
|
+
check_cols(df, args.indexcols, "-i/--indexcols")
|
|
72
|
+
|
|
73
|
+
result = df.melt(
|
|
74
|
+
id_vars=args.indexcols,
|
|
75
|
+
var_name=args.destcol,
|
|
76
|
+
value_name=args.valuecol,
|
|
77
|
+
)
|
|
78
|
+
io.printdf(result, args)
|