dataframe-toolkit 0.9.1__tar.gz → 0.10.0__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.
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/PKG-INFO +2 -1
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/README.md +1 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/pyproject.toml +1 -1
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/pyproject.toml.orig +1 -1
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/__init__.py +1 -0
- dataframe_toolkit-0.10.0/src/dftk/commands/explode_cmd.py +180 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/__init__.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/cli.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/annotate_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/base.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/binx_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/chi_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/clean_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/cleannames_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/concat_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/corr_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/dataset_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/describe_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/eval_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/fit_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/func_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/help.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/hist_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/info_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/interp_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/line_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/melt_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/merge_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/pivot_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/print_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/query_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/randvar_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/sample_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/scale_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/scat_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/segid_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/split_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/stat_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/test_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/transpose_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/commands/wstat_cmd.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/common/__init__.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/common/copula.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/common/io.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/common/plot.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/common/seed.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/common/seq.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/common/stats.py +0 -0
- {dataframe_toolkit-0.9.1 → dataframe_toolkit-0.10.0}/src/dftk/io.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: dataframe-toolkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
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
|
|
@@ -116,6 +116,7 @@ dftk dataset tips -o \
|
|
|
116
116
|
| `merge` | Join two tables on key columns (inner/left/right/outer) |
|
|
117
117
|
| `concat` | Concatenate two or more tables row-wise |
|
|
118
118
|
| `melt` | Reshape wide-to-long (`pd.melt`) |
|
|
119
|
+
| `explode` | Split separated values in a cell (`a,b`) into one row per value |
|
|
119
120
|
| `pivot` | Reshape long-to-wide with per-cell aggregation |
|
|
120
121
|
| `func` | Column transforms: cumsum, group mean/sum/min/max/count/median/std, rank, qcut:N |
|
|
121
122
|
| `scale` | Normalise: z-score, min-shift, sum/max/mean scaling, Blom rank, regression residuals |
|
|
@@ -87,6 +87,7 @@ dftk dataset tips -o \
|
|
|
87
87
|
| `merge` | Join two tables on key columns (inner/left/right/outer) |
|
|
88
88
|
| `concat` | Concatenate two or more tables row-wise |
|
|
89
89
|
| `melt` | Reshape wide-to-long (`pd.melt`) |
|
|
90
|
+
| `explode` | Split separated values in a cell (`a,b`) into one row per value |
|
|
90
91
|
| `pivot` | Reshape long-to-wide with per-cell aggregation |
|
|
91
92
|
| `func` | Column transforms: cumsum, group mean/sum/min/max/count/median/std, rank, qcut:N |
|
|
92
93
|
| `scale` | Normalise: z-score, min-shift, sum/max/mean scaling, Blom rank, regression residuals |
|
|
@@ -30,6 +30,7 @@ COMMAND_GROUPS: list[tuple[str, list[tuple[str, str]]]] = [
|
|
|
30
30
|
("merge", "merge_cmd:MergeCommand"),
|
|
31
31
|
("concat", "concat_cmd:ConcatCommand"),
|
|
32
32
|
("melt", "melt_cmd:MeltCommand"),
|
|
33
|
+
("explode", "explode_cmd:ExplodeCommand"),
|
|
33
34
|
("pivot", "pivot_cmd:PivotCommand"),
|
|
34
35
|
("func", "func_cmd:FuncCommand"),
|
|
35
36
|
("scale", "scale_cmd:ScaleCommand"),
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dftk.commands.explode_cmd — dftk explode subcommand.
|
|
3
|
+
|
|
4
|
+
Split a column of separated values (e.g. "GO:1, GO:2") into one row per
|
|
5
|
+
value, repeating the other columns on each new row (pandas str.split +
|
|
6
|
+
explode). Several columns are split in parallel: their n-th items end up
|
|
7
|
+
on the same row, so every row must have the same number of items in them.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
|
|
12
|
+
import pandas as pd
|
|
13
|
+
|
|
14
|
+
from dftk.commands.base import BaseCommand
|
|
15
|
+
from dftk.common.io import check_cols, io
|
|
16
|
+
|
|
17
|
+
_EPILOG = """\
|
|
18
|
+
EXAMPLE
|
|
19
|
+
-------
|
|
20
|
+
gene go tissue
|
|
21
|
+
BRCA1 GO:1, GO:2 breast
|
|
22
|
+
TP53 GO:3 colon
|
|
23
|
+
|
|
24
|
+
dftk explode data.tsv -c go
|
|
25
|
+
|
|
26
|
+
gene go tissue
|
|
27
|
+
BRCA1 GO:1 breast
|
|
28
|
+
BRCA1 GO:2 breast
|
|
29
|
+
TP53 GO:3 colon
|
|
30
|
+
|
|
31
|
+
Spaces around each item are removed. An empty cell stays as one row with
|
|
32
|
+
an empty value; --dropempty removes such rows. If all the new values in a
|
|
33
|
+
split column are numbers, the column becomes numeric (integers stay
|
|
34
|
+
integers); columns named in --readasobject stay text.
|
|
35
|
+
|
|
36
|
+
Several columns are split in parallel, item by item. For every
|
|
37
|
+
combination instead, run explode once per column:
|
|
38
|
+
|
|
39
|
+
id a b
|
|
40
|
+
1 x,y 10,20
|
|
41
|
+
|
|
42
|
+
dftk explode data.tsv -c a b -> (1, x, 10), (1, y, 20)
|
|
43
|
+
|
|
44
|
+
Runnable version: collect each species' islands into one comma-separated
|
|
45
|
+
cell with SQL, then split them back into rows, numbering each item:
|
|
46
|
+
|
|
47
|
+
dftk dataset penguins -o \\
|
|
48
|
+
| dftk query ... -o --sql "SELECT species,
|
|
49
|
+
string_agg(DISTINCT island, ',' ORDER BY island) AS islands
|
|
50
|
+
FROM data GROUP BY species ORDER BY species" \\
|
|
51
|
+
| dftk explode ... -c islands --poscol n
|
|
52
|
+
|
|
53
|
+
Another separator, keeping only some columns:
|
|
54
|
+
|
|
55
|
+
dftk dataset tips -o \\
|
|
56
|
+
| dftk query ... -o --sql "SELECT day,
|
|
57
|
+
string_agg(DISTINCT size::VARCHAR, '|' ORDER BY size::VARCHAR) AS sizes
|
|
58
|
+
FROM data GROUP BY day ORDER BY day" \\
|
|
59
|
+
| dftk explode ... -c sizes --sep '|' --select sizes day
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _split_cell(value, sep: str):
|
|
64
|
+
if pd.isna(value):
|
|
65
|
+
return value
|
|
66
|
+
return [item.strip() for item in str(value).split(sep)]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _to_numeric(s: pd.Series) -> pd.Series:
|
|
70
|
+
"""Return *s* as integers or floats if every non-empty value is one."""
|
|
71
|
+
values = s.dropna()
|
|
72
|
+
if values.empty:
|
|
73
|
+
return s
|
|
74
|
+
text = values.astype(str)
|
|
75
|
+
if text.str.fullmatch(r"[+-]?\d+").all():
|
|
76
|
+
return s.astype("Int64") if s.isna().any() else s.astype("int64")
|
|
77
|
+
try:
|
|
78
|
+
return pd.to_numeric(s)
|
|
79
|
+
except (ValueError, TypeError):
|
|
80
|
+
return s
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _explode(df: pd.DataFrame, args: argparse.Namespace) -> pd.DataFrame:
|
|
84
|
+
cols = args.cols
|
|
85
|
+
if not args.sep:
|
|
86
|
+
raise ValueError("--sep must not be empty")
|
|
87
|
+
if len(set(cols)) != len(cols):
|
|
88
|
+
raise ValueError(f"-c/--cols lists a column more than once: {cols}")
|
|
89
|
+
if args.poscol is not None and args.poscol in df.columns:
|
|
90
|
+
raise ValueError(f"--poscol {args.poscol!r} is already a column")
|
|
91
|
+
|
|
92
|
+
df = df.reset_index(drop=True)
|
|
93
|
+
for col in cols:
|
|
94
|
+
df[col] = df[col].map(lambda v: _split_cell(v, args.sep)).astype(object)
|
|
95
|
+
|
|
96
|
+
if len(cols) > 1:
|
|
97
|
+
counts = pd.DataFrame(
|
|
98
|
+
{c: df[c].map(lambda v: len(v) if isinstance(v, list) else 1) for c in cols}
|
|
99
|
+
)
|
|
100
|
+
bad = counts.index[counts.nunique(axis=1) > 1]
|
|
101
|
+
if len(bad):
|
|
102
|
+
shown = "; ".join(
|
|
103
|
+
f"row {i + 1} ("
|
|
104
|
+
+ ", ".join(f"{c}: {counts.at[i, c]}" for c in cols)
|
|
105
|
+
+ ")"
|
|
106
|
+
for i in bad[:5]
|
|
107
|
+
)
|
|
108
|
+
more = f" and {len(bad) - 5} more" if len(bad) > 5 else ""
|
|
109
|
+
raise ValueError(
|
|
110
|
+
f"Rows with different numbers of items in -c/--cols: {shown}{more}. "
|
|
111
|
+
"Run explode once per column for every combination."
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
result = df.explode(cols)
|
|
115
|
+
if args.poscol is not None:
|
|
116
|
+
result[args.poscol] = result.groupby(level=0).cumcount() + 1
|
|
117
|
+
result = result.reset_index(drop=True)
|
|
118
|
+
|
|
119
|
+
for col in cols:
|
|
120
|
+
result[col] = result[col].replace("", pd.NA)
|
|
121
|
+
if args.dropempty:
|
|
122
|
+
result = result[result[cols].notna().any(axis=1)].reset_index(drop=True)
|
|
123
|
+
|
|
124
|
+
readasobject = getattr(args, "readasobject", None)
|
|
125
|
+
for col in cols:
|
|
126
|
+
if readasobject is not None and (not readasobject or col in readasobject):
|
|
127
|
+
continue
|
|
128
|
+
result[col] = _to_numeric(result[col])
|
|
129
|
+
return result
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class ExplodeCommand(BaseCommand):
|
|
133
|
+
@property
|
|
134
|
+
def name(self) -> str:
|
|
135
|
+
return "explode"
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def help(self) -> str:
|
|
139
|
+
return "Split separated values in a column into one row per value"
|
|
140
|
+
|
|
141
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
142
|
+
parser.formatter_class = argparse.RawDescriptionHelpFormatter
|
|
143
|
+
parser.epilog = _EPILOG
|
|
144
|
+
|
|
145
|
+
self.add_io_arguments(parser)
|
|
146
|
+
|
|
147
|
+
g = parser.add_argument_group("explode options")
|
|
148
|
+
g.add_argument(
|
|
149
|
+
"-c",
|
|
150
|
+
"--cols",
|
|
151
|
+
nargs="+",
|
|
152
|
+
required=True,
|
|
153
|
+
metavar="COL",
|
|
154
|
+
help="Column(s) to split. Several columns are split in parallel "
|
|
155
|
+
"and must have the same number of items on each row.",
|
|
156
|
+
)
|
|
157
|
+
g.add_argument(
|
|
158
|
+
"--sep",
|
|
159
|
+
default=",",
|
|
160
|
+
metavar="SEP",
|
|
161
|
+
help="Separator between items, a plain string, not a regex (default: ,).",
|
|
162
|
+
)
|
|
163
|
+
g.add_argument(
|
|
164
|
+
"--poscol",
|
|
165
|
+
default=None,
|
|
166
|
+
metavar="NAME",
|
|
167
|
+
help="Add a column NAME with each item's position in its cell (1, 2, ...).",
|
|
168
|
+
)
|
|
169
|
+
g.add_argument(
|
|
170
|
+
"--dropempty",
|
|
171
|
+
action="store_true",
|
|
172
|
+
help="Drop rows whose split columns are all empty "
|
|
173
|
+
"(default: keep them, so no input row is lost).",
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
def execute(self, args: argparse.Namespace) -> None:
|
|
177
|
+
df = io.read(args)
|
|
178
|
+
check_cols(df, args.cols, "-c/--cols")
|
|
179
|
+
result = _explode(df, args)
|
|
180
|
+
io.printdf(result, args)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|