dataframe-toolkit 0.5.1__tar.gz → 0.7.1__tar.gz

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.
Files changed (48) hide show
  1. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/PKG-INFO +1 -1
  2. dataframe_toolkit-0.7.1/pyproject.toml +82 -0
  3. dataframe_toolkit-0.5.1/pyproject.toml → dataframe_toolkit-0.7.1/pyproject.toml.orig +1 -1
  4. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/cli.py +31 -3
  5. dataframe_toolkit-0.7.1/src/dftk/commands/__init__.py +98 -0
  6. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/annotate_cmd.py +4 -0
  7. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/binx_cmd.py +4 -2
  8. dataframe_toolkit-0.7.1/src/dftk/commands/chi_cmd.py +193 -0
  9. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/concat_cmd.py +15 -12
  10. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/corr_cmd.py +92 -9
  11. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/describe_cmd.py +4 -4
  12. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/eval_cmd.py +53 -42
  13. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/fit_cmd.py +6 -5
  14. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/func_cmd.py +6 -5
  15. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/help.py +6 -21
  16. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/hist_cmd.py +13 -11
  17. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/info_cmd.py +5 -1
  18. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/interp_cmd.py +35 -13
  19. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/line_cmd.py +25 -12
  20. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/melt_cmd.py +20 -10
  21. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/merge_cmd.py +34 -13
  22. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/pivot_cmd.py +34 -21
  23. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/print_cmd.py +1 -4
  24. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/query_cmd.py +31 -22
  25. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/randvar_cmd.py +5 -1
  26. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/sample_cmd.py +9 -7
  27. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/scale_cmd.py +24 -12
  28. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/scat_cmd.py +10 -7
  29. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/segid_cmd.py +7 -0
  30. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/split_cmd.py +8 -5
  31. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/stat_cmd.py +8 -2
  32. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/test_cmd.py +11 -2
  33. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/transpose_cmd.py +12 -0
  34. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/wstat_cmd.py +7 -7
  35. dataframe_toolkit-0.7.1/src/dftk/common/copula.py +213 -0
  36. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/common/io.py +11 -24
  37. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/common/plot.py +80 -7
  38. dataframe_toolkit-0.5.1/src/dftk/commands/__init__.py +0 -103
  39. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/README.md +0 -0
  40. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/__init__.py +0 -0
  41. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/base.py +0 -0
  42. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/clean_cmd.py +0 -0
  43. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/commands/dataset_cmd.py +0 -0
  44. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/common/__init__.py +0 -0
  45. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/common/seed.py +0 -0
  46. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/common/seq.py +0 -0
  47. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/common/stats.py +0 -0
  48. {dataframe_toolkit-0.5.1 → dataframe_toolkit-0.7.1}/src/dftk/io.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dataframe-toolkit
3
- Version: 0.5.1
3
+ Version: 0.7.1
4
4
  Summary: A command-line toolkit for DataFrame analysis and manipulation
5
5
  Keywords: dataframe,statistics,data analysis,CLI,TSV,pandas
6
6
  Author: Gunnar Pálsson
@@ -0,0 +1,82 @@
1
+ [project]
2
+ name = "dataframe-toolkit"
3
+ version = "0.7.1"
4
+ description = "A command-line toolkit for DataFrame analysis and manipulation"
5
+ readme = "README.md"
6
+ keywords = [
7
+ "dataframe",
8
+ "statistics",
9
+ "data analysis",
10
+ "CLI",
11
+ "TSV",
12
+ "pandas",
13
+ ]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Science/Research",
17
+ "Intended Audience :: Developers",
18
+ "Topic :: Scientific/Engineering :: Information Analysis",
19
+ "Topic :: Utilities",
20
+ "Programming Language :: Python :: 3",
21
+ ]
22
+ requires-python = ">=3.12"
23
+ dependencies = [
24
+ "pandas>=2.1",
25
+ "numpy>=1.24",
26
+ "pyarrow>=12.0",
27
+ "scipy>=1.10",
28
+ "statsmodels>=0.14",
29
+ "duckdb>=0.8",
30
+ "matplotlib>=3.7",
31
+ "seaborn>=0.12",
32
+ "tqdm>=4.65",
33
+ ]
34
+
35
+ [[project.authors]]
36
+ name = "Gunnar Pálsson"
37
+ email = "hemingur@gmail.com"
38
+
39
+ [project.license]
40
+ text = "MIT"
41
+
42
+ [project.urls]
43
+ Homepage = "https://github.com/hemingur/dataframe-toolkit"
44
+ Repository = "https://github.com/hemingur/dataframe-toolkit"
45
+
46
+ [project.scripts]
47
+ dftk = "dftk.cli:main"
48
+
49
+ [build-system]
50
+ requires = ["uv_build>=0.10.4,<0.11.0"]
51
+ build-backend = "uv_build"
52
+
53
+ [tool.uv.build-backend]
54
+ module-name = "dftk"
55
+
56
+ [tool.pytest.ini_options]
57
+ testpaths = ["tests"]
58
+
59
+ [tool.ruff]
60
+ line-length = 88
61
+ target-version = "py312"
62
+
63
+ [tool.ruff.lint]
64
+ select = [
65
+ "E",
66
+ "W",
67
+ "F",
68
+ "I",
69
+ "UP",
70
+ "B",
71
+ ]
72
+
73
+ [tool.ruff.lint.isort]
74
+ known-first-party = ["dftk"]
75
+
76
+ [dependency-groups]
77
+ dev = [
78
+ "pre-commit>=4.6.0",
79
+ "pytest>=8.0",
80
+ "pytest-cov>=7.1.0",
81
+ "ruff>=0.15.12",
82
+ ]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dataframe-toolkit"
3
- version = "0.5.1"
3
+ version = "0.7.1"
4
4
  description = "A command-line toolkit for DataFrame analysis and manipulation"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -1,23 +1,44 @@
1
1
  """
2
2
  dftk.cli — entry point for the ``dftk`` command.
3
3
 
4
- All subcommands are discovered through the COMMANDS list in
4
+ All subcommands are discovered through the registry in
5
5
  dftk/commands/__init__.py. This file contains no command-specific
6
6
  logic and never needs to change when a new subcommand is added.
7
+
8
+ Only the command being run is imported, so e.g. ``dftk print`` does not pay
9
+ for importing scipy, statsmodels or matplotlib. The full set is imported only
10
+ when it is needed: ``dftk --help``, no command, or an unknown command name.
7
11
  """
8
12
 
9
13
  import argparse
10
14
  import logging
15
+ import os
16
+ import signal
11
17
  import sys
12
18
 
13
19
  from dftk import __version__
14
- from dftk.commands import COMMANDS
20
+ from dftk.commands import COMMAND_NAMES, load_command, load_command_groups
21
+ from dftk.commands.base import BaseCommand
15
22
  from dftk.common.io import DFTK_TMPDIR
16
23
 
17
24
  logger = logging.getLogger("dftk")
18
25
  logging.basicConfig(format="%(asctime)s %(module)s %(message)s", level=logging.INFO)
19
26
 
20
27
 
28
+ def _commands_for(argv: list[str]) -> list[BaseCommand]:
29
+ """Return the commands whose subparsers *argv* needs.
30
+
31
+ The top-level parser has no options that take a value, so a valid
32
+ invocation always has the command name first.
33
+ """
34
+ if argv and argv[0] in COMMAND_NAMES:
35
+ return [load_command(argv[0])]
36
+ # `dftk --help`, no command, or an unknown name: load everything so the
37
+ # command listing and argparse's "invalid choice" message are complete.
38
+ grouped = [cmd for _, cmds in load_command_groups() for cmd in cmds]
39
+ return grouped + [load_command("help")]
40
+
41
+
21
42
  def main() -> None:
22
43
  parser = argparse.ArgumentParser(
23
44
  prog="dftk",
@@ -38,7 +59,7 @@ def main() -> None:
38
59
  subparsers = parser.add_subparsers(dest="command", metavar="COMMAND")
39
60
  subparsers.required = True
40
61
 
41
- for cmd in COMMANDS:
62
+ for cmd in _commands_for(sys.argv[1:]):
42
63
  cmd_parser = subparsers.add_parser(cmd.name, help=cmd.help)
43
64
  cmd.add_arguments(cmd_parser)
44
65
  cmd_parser.set_defaults(command_instance=cmd)
@@ -47,6 +68,13 @@ def main() -> None:
47
68
 
48
69
  try:
49
70
  args.command_instance.execute(args)
71
+ sys.stdout.flush()
72
+ except BrokenPipeError:
73
+ # The reader closed the pipe early (e.g. `dftk ... | head`). Exit
74
+ # quietly with the SIGPIPE status like cat/grep, and point stdout at
75
+ # devnull so the interpreter's final flush doesn't raise again.
76
+ os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stdout.fileno())
77
+ sys.exit(128 + signal.SIGPIPE)
50
78
  except FileNotFoundError as exc:
51
79
  print(f"File error: {exc}", file=sys.stderr)
52
80
  sys.exit(1)
@@ -0,0 +1,98 @@
1
+ # dftk.commands — registry of all dftk subcommands.
2
+ #
3
+ # Commands are registered by name and import path instead of being imported
4
+ # here, so that running one command imports only that command's module and its
5
+ # dependencies (scipy, statsmodels, matplotlib, …), not every command's.
6
+ #
7
+ # To add a new subcommand:
8
+ # 1. Create src/dftk/commands/<name>_cmd.py with a class that inherits
9
+ # from BaseCommand (see base.py for the contract and an example skeleton).
10
+ # 2. Add a ("<name>", "<name>_cmd:<ClassName>") entry to COMMAND_GROUPS below.
11
+ # That's it — cli.py discovers commands solely through this registry.
12
+ # tests/commands/test_registry.py checks that every entry imports and that
13
+ # every *_cmd.py module is registered.
14
+
15
+ import importlib
16
+
17
+ from dftk.commands.base import BaseCommand
18
+
19
+ # Ported subcommands — add as each one is implemented:
20
+ # ("color", "color_cmd:ColorCommand")
21
+ # ("wavelet", "wavelet_cmd:WaveletCommand")
22
+ # ("fisher", ...) — superseded by eval's fisher_test/fisher_OR
23
+
24
+ COMMAND_GROUPS: list[tuple[str, list[tuple[str, str]]]] = [
25
+ (
26
+ "Data transformation",
27
+ [
28
+ ("eval", "eval_cmd:EvalCommand"),
29
+ ("query", "query_cmd:QueryCommand"),
30
+ ("merge", "merge_cmd:MergeCommand"),
31
+ ("concat", "concat_cmd:ConcatCommand"),
32
+ ("melt", "melt_cmd:MeltCommand"),
33
+ ("pivot", "pivot_cmd:PivotCommand"),
34
+ ("func", "func_cmd:FuncCommand"),
35
+ ("scale", "scale_cmd:ScaleCommand"),
36
+ ("interp", "interp_cmd:InterpCommand"),
37
+ ("binx", "binx_cmd:BinxCommand"),
38
+ ("transpose", "transpose_cmd:TransposeCommand"),
39
+ ("segid", "segid_cmd:SegidCommand"),
40
+ ],
41
+ ),
42
+ (
43
+ "Statistics",
44
+ [
45
+ ("stat", "stat_cmd:StatCommand"),
46
+ ("wstat", "wstat_cmd:WstatCommand"),
47
+ ("fit", "fit_cmd:FitCommand"),
48
+ ("test", "test_cmd:TestCommand"),
49
+ ("corr", "corr_cmd:CorrCommand"),
50
+ ("describe", "describe_cmd:DescribeCommand"),
51
+ ("info", "info_cmd:InfoCommand"),
52
+ ("randvar", "randvar_cmd:RandvarCommand"),
53
+ ],
54
+ ),
55
+ (
56
+ "Plots",
57
+ [
58
+ ("scat", "scat_cmd:ScatCommand"),
59
+ ("line", "line_cmd:LineCommand"),
60
+ ("hist", "hist_cmd:HistCommand"),
61
+ ("chi", "chi_cmd:ChiCommand"),
62
+ ],
63
+ ),
64
+ (
65
+ "Utilities",
66
+ [
67
+ ("dataset", "dataset_cmd:DatasetCommand"),
68
+ ("sample", "sample_cmd:SampleCommand"),
69
+ ("split", "split_cmd:SplitCommand"),
70
+ ("annotate", "annotate_cmd:AnnotateCommand"),
71
+ ("print", "print_cmd:PrintCommand"),
72
+ ("clean", "clean_cmd:CleanCommand"),
73
+ ],
74
+ ),
75
+ ]
76
+
77
+ # `help` is a command like the others but is not listed under a group heading.
78
+ _REGISTRY: dict[str, str] = dict(
79
+ [entry for _, entries in COMMAND_GROUPS for entry in entries]
80
+ + [("help", "help:HelpCommand")]
81
+ )
82
+
83
+ COMMAND_NAMES: list[str] = list(_REGISTRY)
84
+
85
+
86
+ def load_command(name: str) -> BaseCommand:
87
+ """Import and instantiate the command registered as *name*."""
88
+ module_name, class_name = _REGISTRY[name].split(":")
89
+ module = importlib.import_module(f"dftk.commands.{module_name}")
90
+ return getattr(module, class_name)()
91
+
92
+
93
+ def load_command_groups() -> list[tuple[str, list[BaseCommand]]]:
94
+ """Import and instantiate every grouped command (slow: imports everything)."""
95
+ return [
96
+ (header, [load_command(name) for name, _ in entries])
97
+ for header, entries in COMMAND_GROUPS
98
+ ]
@@ -25,6 +25,10 @@ Usage examples:
25
25
  dftk eval data.tsv -f "z = x + y" -o result.parquet \\
26
26
  --meta genome=hg38 --meta source=my_pipeline
27
27
 
28
+ # Same, built from a bundled dataset:
29
+ dftk dataset iris -o | dftk eval ... -f "z = sepal_length + petal_length" \\
30
+ -o result.parquet --meta genome=hg38 --meta source=my_pipeline
31
+
28
32
  Annotations propagate through the parquet pipe: a file annotated at
29
33
  creation is re-annotated on every subsequent -o write unless explicitly
30
34
  overridden.
@@ -74,8 +74,10 @@ USEVALUE (--usevalue)
74
74
  EXAMPLES
75
75
  --------
76
76
  dftk binx data.tsv -c age -b 0:100:10
77
- dftk binx data.tsv -c score -b 0,25,50,75,100 -d quartile --usevalue m
78
- dftk binx data.tsv -c x -b -3:4:1 -d x_bin -o | dftk stat -g x_bin -s y
77
+ dftk dataset tips -o | dftk binx ... -c total_bill -b 0:60:10
78
+ dftk dataset tips -o | dftk binx ... -c tip -b 0,2,4,6,10 -d tip_bracket --usevalue m
79
+ dftk dataset tips -o | dftk binx ... -c total_bill -b 0:60:10 -d bill_bin -o \\
80
+ | dftk stat ... -c tip -g bill_bin
79
81
  """
80
82
 
81
83
 
@@ -0,0 +1,193 @@
1
+ """
2
+ dftk.commands.chi_cmd — dftk chi subcommand.
3
+
4
+ Corner-resolved chi-plot: a visual diagnostic for which direction (if any)
5
+ two variables have tail dependence in, complementing the numeric
6
+ lambda_lower/lambda_upper columns from `dftk corr --tail`.
7
+
8
+ Follows the structure of scat_cmd.py -- same --groupcol / --subgraphcol /
9
+ --size / --fontsize figure conventions as the rest of the Plots group.
10
+ """
11
+
12
+ import argparse
13
+
14
+ import numpy as np
15
+
16
+ from dftk.commands.base import BaseCommand
17
+ from dftk.common.copula import corner_chi, pseudo_observations
18
+ from dftk.common.io import check_cols, io
19
+ from dftk.common.plot import (
20
+ add_figure_arguments,
21
+ add_group_arguments,
22
+ add_legend_arguments,
23
+ add_xy_arguments,
24
+ apply_labels,
25
+ apply_style,
26
+ make_figure,
27
+ make_grouplabel,
28
+ save_or_show,
29
+ subgraph_groups,
30
+ subgraph_layout,
31
+ )
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Single-axes chi-plot (reused per subplot)
35
+ # ---------------------------------------------------------------------------
36
+
37
+
38
+ def _chi_ax(ax, df, args, title_suffix: str = ""):
39
+ xcol, ycol = args.xcol, args.ycol
40
+ legendloc = tuple(args.legendloc) if getattr(args, "legendloc", None) else "best"
41
+
42
+ def _draw(sub_df, label=None, **scatter_kw):
43
+ a, b = sub_df[[xcol, ycol]].dropna().values.T
44
+ if len(a) < 3:
45
+ return
46
+ u, v = pseudo_observations(a, b)
47
+ x, chi = corner_chi(u, v)
48
+ mask = np.abs(x) < 0.8 # trim the noisiest extreme points, standard practice
49
+ ax.scatter(x[mask], chi[mask], s=8, alpha=0.4, label=label, **scatter_kw)
50
+ ci = 1.78 / np.sqrt(len(u))
51
+ return ci
52
+
53
+ if args.groupcol is not None:
54
+ # Each group gets its OWN chi statistic computed within its own subset
55
+ # (mixing groups into one leave-one-out CDF would misrepresent both) --
56
+ # overlaid here the same way scat/hist overlay per-group series.
57
+ cis = []
58
+ for groupname, gdf in subgraph_groups(
59
+ df, args.groupcol, getattr(args, "groupcolorder", None)
60
+ ):
61
+ label = make_grouplabel(
62
+ groupname, args.groupcol, getattr(args, "groupcolformat", None)
63
+ )
64
+ ci = _draw(gdf, label=label)
65
+ if ci is not None:
66
+ cis.append(ci)
67
+ ax.legend(loc=legendloc, title=getattr(args, "legendtitle", None))
68
+ ci = max(cis) if cis else 0.0
69
+ else:
70
+ ci = _draw(df) or 0.0
71
+
72
+ ax.axhline(0, color="black", lw=0.8)
73
+ ax.axvline(0, color="gray", lw=0.6, ls=":")
74
+ if ci:
75
+ ax.axhline(ci, color="crimson", lw=1, ls="--", alpha=0.7)
76
+ ax.axhline(-ci, color="crimson", lw=1, ls="--", alpha=0.7)
77
+ ax.set_xlim(-1, 1)
78
+ ax.set_ylim(-0.2, 1)
79
+
80
+ apply_labels(
81
+ ax,
82
+ args,
83
+ default_xlabel="<- lower corner | upper corner ->",
84
+ default_ylabel=r"$\chi$ (local dependence)",
85
+ )
86
+ if title_suffix:
87
+ existing = ax.get_title()
88
+ ax.set_title(f"{existing} [{title_suffix}]" if existing else title_suffix)
89
+
90
+ ax.grid(True, linewidth=0.4, alpha=0.5)
91
+
92
+
93
+ # ---------------------------------------------------------------------------
94
+ # Command
95
+ # ---------------------------------------------------------------------------
96
+
97
+ _EPILOG = """\
98
+ Reads as: chi elevated (and rising) on the LEFT (negative x) only => lower-
99
+ tail dependence -- the two variables' worst values tend to co-occur, but
100
+ their best values don't (e.g. joint crashes but not joint rallies). Elevated
101
+ on the RIGHT only => upper-tail dependence (joint highs, not joint lows).
102
+ Elevated similarly on BOTH sides, decaying back toward 0 well before the
103
+ edges => generic correlation with no real tail dependence. Flat at 0
104
+ everywhere => independence. Dashed red lines mark an approximate 95%
105
+ control limit for "indistinguishable from independence".
106
+
107
+ This is a visual complement to `dftk corr --tail`'s numeric
108
+ lambda_lower/lambda_upper columns -- same underlying question, this command
109
+ answers it by eye and per-corner in one plot instead of at a chosen set of
110
+ thresholds. Needs a reasonable amount of data to be legible: a few hundred
111
+ points is usually enough to see the shape, but a strong one-sided asymmetry
112
+ is easiest to trust with several thousand.
113
+
114
+ EXAMPLES
115
+ --------
116
+ Basic chi-plot:
117
+
118
+ dftk chi data.tsv -x recomb_rate -y gc_content
119
+ dftk dataset tips -o | dftk chi ... -x total_bill -y tip
120
+
121
+ Split into a subplot grid by category, to check whether tail dependence is
122
+ consistent across the whole dataset or driven by a few subgroups (e.g.
123
+ chromosome, sector, region — here, diamond cut quality):
124
+
125
+ dftk dataset diamonds -o | dftk chi ... -x carat -y price --subgraphcol cut
126
+
127
+ Colour-code by group instead of splitting into subplots:
128
+
129
+ dftk dataset diamonds -o | dftk chi ... -x carat -y price -g cut
130
+
131
+ Publication figure (Nature single column, PDF):
132
+
133
+ dftk dataset tips -o | dftk chi ... -x total_bill -y tip \\
134
+ --size single --fontsize publication -f fig.pdf
135
+ """
136
+
137
+
138
+ class ChiCommand(BaseCommand):
139
+ name = "chi"
140
+ help = "Corner-resolved chi-plot: visualize lower vs upper tail dependence."
141
+
142
+ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
143
+ parser.formatter_class = argparse.RawDescriptionHelpFormatter
144
+ parser.epilog = _EPILOG
145
+
146
+ self.add_read_arguments(parser)
147
+ add_xy_arguments(parser)
148
+ add_figure_arguments(parser)
149
+ add_group_arguments(parser)
150
+ add_legend_arguments(parser)
151
+
152
+ def execute(self, args: argparse.Namespace) -> None:
153
+ if not args.xcol or not args.ycol:
154
+ raise ValueError("--xcol (-x) and --ycol (-y) are required.")
155
+
156
+ df = io.read(args)
157
+ check_cols(df, [args.xcol, args.ycol], "-x/-y")
158
+ check_cols(df, args.groupcol, "-g/--groupcol")
159
+ check_cols(df, args.subgraphcol, "--subgraphcol")
160
+
161
+ if args.file:
162
+ import matplotlib
163
+
164
+ matplotlib.use("Agg")
165
+
166
+ import matplotlib.pyplot as plt
167
+
168
+ if getattr(args, "usetex", False):
169
+ plt.rc("text", usetex=True)
170
+
171
+ apply_style(args)
172
+
173
+ if args.subgraphcol is not None:
174
+ groups = subgraph_groups(
175
+ df, args.subgraphcol, getattr(args, "subgraphorder", None)
176
+ )
177
+ nrows, ncols = subgraph_layout(len(groups), args.ncols)
178
+ fig, axes = make_figure(args, nrows=nrows, ncols=ncols)
179
+ for idx, (groupname, gdf) in enumerate(groups):
180
+ ax = axes[idx // ncols][idx % ncols]
181
+ suffix = make_grouplabel(
182
+ groupname, args.subgraphcol, getattr(args, "subgraphformat", None)
183
+ )
184
+ _chi_ax(ax, gdf, args, title_suffix=suffix)
185
+ for idx in range(len(groups), nrows * ncols):
186
+ axes[idx // ncols][idx % ncols].set_visible(False)
187
+ fig.tight_layout()
188
+ else:
189
+ fig, axes = make_figure(args)
190
+ _chi_ax(axes[0][0], df, args)
191
+ fig.tight_layout()
192
+
193
+ save_or_show(fig, args)
@@ -8,9 +8,11 @@ present in some but not all files are filled with NaN (or --fill value).
8
8
  Examples
9
9
  --------
10
10
  dftk concat a.tsv b.tsv c.tsv
11
- dftk concat a.tsv b.tsv --sourcecol source
12
- dftk concat a.parquet b.parquet -o
13
- dftk eval data.tsv -f "x=1" -o | dftk concat ... extra.tsv
11
+ dftk dataset penguins -o penguins.tsv
12
+ dftk concat penguins.tsv penguins.tsv --sourcecol source
13
+ dftk dataset penguins -o penguins.parquet
14
+ dftk concat penguins.parquet penguins.parquet -o
15
+ dftk dataset penguins -o | dftk eval ... -f "x=1" -o | dftk concat ... penguins.tsv
14
16
  """
15
17
 
16
18
  import argparse
@@ -43,9 +45,16 @@ COLUMN ALIGNMENT
43
45
  EXAMPLES
44
46
  --------
45
47
  dftk concat a.tsv b.tsv
46
- dftk concat a.tsv b.tsv c.tsv --sourcecol source
47
- dftk concat a.parquet b.parquet -o result.parquet
48
- dftk eval data.tsv -f "x=1" -o | dftk concat ... extra.tsv
48
+ dftk dataset penguins -o penguins.tsv
49
+ dftk concat penguins.tsv penguins.tsv --sourcecol source
50
+ dftk dataset penguins -o penguins.parquet
51
+ dftk concat penguins.parquet penguins.parquet -o result.parquet
52
+ dftk dataset penguins -o | dftk eval ... -f "x=1" -o | dftk concat ... penguins.tsv
53
+
54
+ Mismatched columns are outer-joined and filled (NaN by default, or --fill):
55
+
56
+ dftk dataset iris -o iris.tsv
57
+ dftk concat iris.tsv penguins.tsv --fill 0
49
58
  """
50
59
  g = parser.add_argument_group("concat options")
51
60
  g.add_argument(
@@ -86,12 +95,6 @@ EXAMPLES
86
95
  help="Input files have no header row; columns are named V1, V2, …",
87
96
  action="store_true",
88
97
  )
89
- r.add_argument(
90
- "--nrows",
91
- help="Maximum number of rows to read per file",
92
- type=int,
93
- default=None,
94
- )
95
98
  r.add_argument(
96
99
  "--delimiter",
97
100
  help="Column delimiter for TSV inputs (default: tab)",