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.
Files changed (50) hide show
  1. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/PKG-INFO +1 -1
  2. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/pyproject.toml +1 -1
  3. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/pyproject.toml.orig +1 -1
  4. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/eval_cmd.py +51 -13
  5. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/implode_cmd.py +12 -7
  6. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/README.md +0 -0
  7. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/__init__.py +0 -0
  8. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/cli.py +0 -0
  9. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/__init__.py +0 -0
  10. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/annotate_cmd.py +0 -0
  11. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/base.py +0 -0
  12. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/binx_cmd.py +0 -0
  13. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/chi_cmd.py +0 -0
  14. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/clean_cmd.py +0 -0
  15. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/cleannames_cmd.py +0 -0
  16. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/concat_cmd.py +0 -0
  17. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/corr_cmd.py +0 -0
  18. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/dataset_cmd.py +0 -0
  19. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/describe_cmd.py +0 -0
  20. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/explode_cmd.py +0 -0
  21. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/fit_cmd.py +0 -0
  22. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/func_cmd.py +0 -0
  23. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/help.py +0 -0
  24. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/hist_cmd.py +0 -0
  25. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/info_cmd.py +0 -0
  26. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/interp_cmd.py +0 -0
  27. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/line_cmd.py +0 -0
  28. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/melt_cmd.py +0 -0
  29. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/merge_cmd.py +0 -0
  30. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/pivot_cmd.py +0 -0
  31. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/print_cmd.py +0 -0
  32. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/query_cmd.py +0 -0
  33. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/randvar_cmd.py +0 -0
  34. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/sample_cmd.py +0 -0
  35. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/scale_cmd.py +0 -0
  36. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/scat_cmd.py +0 -0
  37. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/segid_cmd.py +0 -0
  38. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/split_cmd.py +0 -0
  39. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/stat_cmd.py +0 -0
  40. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/test_cmd.py +0 -0
  41. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/transpose_cmd.py +0 -0
  42. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/commands/wstat_cmd.py +0 -0
  43. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/common/__init__.py +0 -0
  44. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/common/copula.py +0 -0
  45. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/common/io.py +0 -0
  46. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/common/plot.py +0 -0
  47. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/common/seed.py +0 -0
  48. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/common/seq.py +0 -0
  49. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.1}/src/dftk/common/stats.py +0 -0
  50. {dataframe_toolkit-0.11.0 → dataframe_toolkit-0.11.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.11.0
3
+ Version: 0.11.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dataframe-toolkit"
3
- version = "0.11.0"
3
+ version = "0.11.1"
4
4
  description = "A command-line toolkit for DataFrame analysis and manipulation"
5
5
  readme = "README.md"
6
6
  keywords = [
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dataframe-toolkit"
3
- version = "0.11.0"
3
+ version = "0.11.1"
4
4
  description = "A command-line toolkit for DataFrame analysis and manipulation"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -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 ValueError(
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 ValueError(
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 = re.search(r"'Series' object has no attribute '(\w+)'", msg)
618
+ m = _NO_METHOD.search(msg)
606
619
  if m:
607
620
  name = m.group(1)
608
- col = re.search(rf"(\w+)\.{name}\b", formula)
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
- return df.eval(formula)
646
- except (AttributeError, TypeError, NameError, KeyError) as exc:
647
- # pandas ran the expression: a mistake in it, not a dftk bug
648
- raise ValueError(_formula_error(formula, exc)) from exc
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 re.match(r"\s*\w+\s*=\s*\w+\.", formula):
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
- raise ValueError(f"{exc} (pandas df.eval: {eval_exc})") from exc
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
- if len(df) != 0:
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["_gid"] = work.groupby(keys, sort=False, dropna=False).ngroup()
106
+ work[gid] = work.groupby(keys, sort=False, dropna=False).ngroup()
102
107
  else:
103
- work["_gid"] = 0
108
+ work[gid] = 0
104
109
 
105
110
  if args.sortvalues:
106
- work = work.sort_values(["_gid", *cols], kind="stable", na_position="last")
111
+ work = work.sort_values([gid, *cols], kind="stable", na_position="last")
107
112
  elif poscol:
108
- work = work.sort_values(["_gid", poscol], kind="stable")
113
+ work = work.sort_values([gid, poscol], kind="stable")
109
114
  else:
110
- work = work.sort_values("_gid", kind="stable")
115
+ work = work.sort_values(gid, kind="stable")
111
116
  if args.unique:
112
- work = work.drop_duplicates(subset=["_gid", *cols])
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("_gid", sort=True)
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