dataframe-toolkit 0.11.0__tar.gz → 0.11.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.
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/PKG-INFO +1 -1
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/pyproject.toml +1 -1
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/pyproject.toml.orig +1 -1
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/eval_cmd.py +51 -13
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/implode_cmd.py +12 -7
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/README.md +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/__init__.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/cli.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/__init__.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/annotate_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/base.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/binx_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/chi_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/clean_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/cleannames_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/concat_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/corr_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/dataset_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/describe_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/explode_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/fit_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/func_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/help.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/hist_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/info_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/interp_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/line_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/melt_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/merge_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/pivot_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/print_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/query_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/randvar_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/sample_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/scale_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/scat_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/segid_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/split_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/stat_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/test_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/transpose_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/wstat_cmd.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/common/__init__.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/common/copula.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/common/io.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/common/plot.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/common/seed.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/common/seq.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/common/stats.py +0 -0
- {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/io.py +0 -0
|
@@ -35,6 +35,12 @@ try:
|
|
|
35
35
|
except ImportError:
|
|
36
36
|
_HAS_LEVENSHTEIN = False
|
|
37
37
|
|
|
38
|
+
|
|
39
|
+
class _FormulaMistake(ValueError):
|
|
40
|
+
"""A mistake in a formula whatever the column types (an unknown name,
|
|
41
|
+
bad syntax, no assignment): reported even on an empty table."""
|
|
42
|
+
|
|
43
|
+
|
|
38
44
|
_EPILOG = """\
|
|
39
45
|
FORMULA EXPRESSIONS (-f)
|
|
40
46
|
-------------------------
|
|
@@ -260,7 +266,7 @@ def _parse_formula(formula: str) -> tuple[str, str, list[str], str]:
|
|
|
260
266
|
func, right = (x.strip() for x in right.split("(", 1))
|
|
261
267
|
body, _ = (x.strip() for x in right.rsplit(")", 1))
|
|
262
268
|
except Exception:
|
|
263
|
-
raise
|
|
269
|
+
raise _FormulaMistake(
|
|
264
270
|
f"Bad formula syntax: {formula!r} Expected: dest = func(col1, col2, ...)"
|
|
265
271
|
) from None
|
|
266
272
|
cols = [x.strip() for x in body.split(",")]
|
|
@@ -417,7 +423,7 @@ def _special_function(formula: str, df_columns: list[str]):
|
|
|
417
423
|
dest, func_name, cols, body = _parse_formula(formula)
|
|
418
424
|
|
|
419
425
|
if func_name not in _VALID_SPECIAL:
|
|
420
|
-
raise
|
|
426
|
+
raise _FormulaMistake(
|
|
421
427
|
f"Function {func_name!r} is not available as a "
|
|
422
428
|
"special function. See dftk eval --help."
|
|
423
429
|
)
|
|
@@ -599,13 +605,20 @@ def _string_function(formula: str, joinsep: str):
|
|
|
599
605
|
# ---------------------------------------------------------------------------
|
|
600
606
|
|
|
601
607
|
|
|
608
|
+
# A message pandas gives for a method that no column has, whatever its dtype
|
|
609
|
+
_NO_METHOD = re.compile(r"'Series' object has no attribute '(\w+)'")
|
|
610
|
+
|
|
611
|
+
# dest = col.method(...): never a special function
|
|
612
|
+
_METHOD_CALL = re.compile(r"\s*\w+\s*=\s*[A-Za-z_]\w*\.[A-Za-z_]")
|
|
613
|
+
|
|
614
|
+
|
|
602
615
|
def _formula_error(formula: str, exc: Exception) -> str:
|
|
603
616
|
"""Return a short message for a mistake pandas found in an -f formula."""
|
|
604
617
|
msg = str(exc)
|
|
605
|
-
m =
|
|
618
|
+
m = _NO_METHOD.search(msg)
|
|
606
619
|
if m:
|
|
607
620
|
name = m.group(1)
|
|
608
|
-
col = re.search(rf"(\w
|
|
621
|
+
col = re.search(rf"([A-Za-z_]\w*)\.{name}\b", formula)
|
|
609
622
|
col = col.group(1) if col else "COL"
|
|
610
623
|
if hasattr(pd.Series.str, name):
|
|
611
624
|
hint = f"Text methods are under .str: {col}.str.{name}()."
|
|
@@ -620,6 +633,15 @@ def _formula_error(formula: str, exc: Exception) -> str:
|
|
|
620
633
|
def _apply_special_function(df: pd.DataFrame, formula: str) -> None:
|
|
621
634
|
"""Evaluate a special-function formula and store its column in *df*."""
|
|
622
635
|
dest, func, cols = _special_function(formula, list(df.columns))
|
|
636
|
+
missing = [c for c in cols if c not in df.columns]
|
|
637
|
+
if missing:
|
|
638
|
+
hint = ""
|
|
639
|
+
if any(re.fullmatch(r"[-+]?[\d.]+(e[-+]?\d+)?", c) for c in missing):
|
|
640
|
+
hint = (
|
|
641
|
+
" For a fixed value, add it as a column first: "
|
|
642
|
+
"-c 'p = 0.5' -f 'z = func(x, p)'."
|
|
643
|
+
)
|
|
644
|
+
raise _FormulaMistake(f"-f {formula!r}: column(s) not found: {missing}.{hint}")
|
|
623
645
|
if len(df) == 0:
|
|
624
646
|
df[dest] = True
|
|
625
647
|
elif isinstance(func, _WhereFunc):
|
|
@@ -642,23 +664,36 @@ def _eval_formula(df: pd.DataFrame, formula: str) -> pd.DataFrame:
|
|
|
642
664
|
# Not inplace: that writes into the existing column, which must keep
|
|
643
665
|
# its dtype, so x = x.fillna(y) or x = x.isna() failed whenever the
|
|
644
666
|
# result had another dtype
|
|
645
|
-
|
|
646
|
-
except
|
|
647
|
-
#
|
|
648
|
-
raise
|
|
667
|
+
result = df.eval(formula)
|
|
668
|
+
except NameError as exc:
|
|
669
|
+
# An unknown column (pandas' UndefinedVariableError)
|
|
670
|
+
raise _FormulaMistake(_formula_error(formula, exc)) from exc
|
|
671
|
+
except (AttributeError, TypeError, KeyError) as exc:
|
|
672
|
+
# Raised while pandas evaluated the user's expression, so most likely
|
|
673
|
+
# a mistake in it (wrong method, arguments, or .str on numbers)
|
|
674
|
+
cls = _FormulaMistake if _NO_METHOD.search(str(exc)) else ValueError
|
|
675
|
+
raise cls(_formula_error(formula, exc)) from exc
|
|
649
676
|
except (ValueError, SyntaxError) as eval_exc:
|
|
650
|
-
if
|
|
651
|
-
# dest = col.method(...) is never a special function
|
|
677
|
+
if _METHOD_CALL.match(formula):
|
|
652
678
|
raise ValueError(_formula_error(formula, eval_exc)) from eval_exc
|
|
653
679
|
logger.debug(f"df.eval failed for {formula!r}, trying special_function")
|
|
654
680
|
try:
|
|
655
681
|
_apply_special_function(df, formula)
|
|
656
682
|
except ValueError as exc:
|
|
657
|
-
|
|
683
|
+
name = re.search(r"=\s*(\w+)\s*\(", formula)
|
|
684
|
+
if name and name.group(1) in _VALID_SPECIAL:
|
|
685
|
+
raise # a known special function: pandas' error is beside the point
|
|
686
|
+
raise type(exc)(f"{exc} (pandas df.eval: {eval_exc})") from exc
|
|
658
687
|
except Exception:
|
|
659
688
|
logger.error(f"Could not evaluate formula: {formula!r}")
|
|
660
689
|
raise
|
|
661
690
|
return df
|
|
691
|
+
if not isinstance(result, pd.DataFrame):
|
|
692
|
+
# No "dest =": df.eval returned the values instead of a table
|
|
693
|
+
raise _FormulaMistake(
|
|
694
|
+
f"-f {formula!r} has no assignment; write it as 'name = {formula}'"
|
|
695
|
+
)
|
|
696
|
+
return result
|
|
662
697
|
|
|
663
698
|
|
|
664
699
|
def _eval(df: pd.DataFrame, args: argparse.Namespace) -> pd.DataFrame:
|
|
@@ -682,8 +717,11 @@ def _eval(df: pd.DataFrame, args: argparse.Namespace) -> pd.DataFrame:
|
|
|
682
717
|
for formula in getattr(args, "formula", []):
|
|
683
718
|
try:
|
|
684
719
|
df = _eval_formula(df, formula)
|
|
685
|
-
except Exception:
|
|
686
|
-
|
|
720
|
+
except Exception as exc:
|
|
721
|
+
# An empty table has no column types (pandas reads them as
|
|
722
|
+
# float64), so on one only mistakes that don't depend on types
|
|
723
|
+
# are reported; for the rest the column is just added
|
|
724
|
+
if len(df) != 0 or isinstance(exc, _FormulaMistake):
|
|
687
725
|
raise
|
|
688
726
|
df[formula.split("=", 1)[0].strip()] = True
|
|
689
727
|
|
|
@@ -9,6 +9,7 @@ original rows.
|
|
|
9
9
|
|
|
10
10
|
import argparse
|
|
11
11
|
|
|
12
|
+
import numpy as np
|
|
12
13
|
import pandas as pd
|
|
13
14
|
|
|
14
15
|
from dftk.commands.base import BaseCommand
|
|
@@ -68,6 +69,7 @@ def _as_text(s: pd.Series) -> pd.Series:
|
|
|
68
69
|
if (
|
|
69
70
|
pd.api.types.is_float_dtype(s)
|
|
70
71
|
and len(values)
|
|
72
|
+
and np.isfinite(values).all()
|
|
71
73
|
and (values == values.round()).all()
|
|
72
74
|
):
|
|
73
75
|
# Integers with gaps are read as floats: write 10, not 10.0
|
|
@@ -97,23 +99,26 @@ def _implode(df: pd.DataFrame, args: argparse.Namespace) -> pd.DataFrame:
|
|
|
97
99
|
|
|
98
100
|
order = [c for c in df.columns if c in keys or c in cols]
|
|
99
101
|
work = df[keys + cols + ([poscol] if poscol else [])].reset_index(drop=True)
|
|
102
|
+
gid = "_gid" # helper column for the group number; never one of the user's
|
|
103
|
+
while gid in work.columns:
|
|
104
|
+
gid = "_" + gid
|
|
100
105
|
if keys:
|
|
101
|
-
work[
|
|
106
|
+
work[gid] = work.groupby(keys, sort=False, dropna=False).ngroup()
|
|
102
107
|
else:
|
|
103
|
-
work[
|
|
108
|
+
work[gid] = 0
|
|
104
109
|
|
|
105
110
|
if args.sortvalues:
|
|
106
|
-
work = work.sort_values([
|
|
111
|
+
work = work.sort_values([gid, *cols], kind="stable", na_position="last")
|
|
107
112
|
elif poscol:
|
|
108
|
-
work = work.sort_values([
|
|
113
|
+
work = work.sort_values([gid, poscol], kind="stable")
|
|
109
114
|
else:
|
|
110
|
-
work = work.sort_values(
|
|
115
|
+
work = work.sort_values(gid, kind="stable")
|
|
111
116
|
if args.unique:
|
|
112
|
-
work = work.drop_duplicates(subset=[
|
|
117
|
+
work = work.drop_duplicates(subset=[gid, *cols])
|
|
113
118
|
|
|
114
119
|
for col in cols:
|
|
115
120
|
work[col] = _as_text(work[col])
|
|
116
|
-
grouped = work.groupby(
|
|
121
|
+
grouped = work.groupby(gid, sort=True)
|
|
117
122
|
joined = grouped[cols].agg(lambda s: args.sep.join(s.dropna()))
|
|
118
123
|
joined = joined.where(joined != "")
|
|
119
124
|
result = grouped[keys].first().join(joined) if keys else joined
|
|
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
|
|
File without changes
|
|
File without changes
|