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,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)