microdf-python 1.3.5__tar.gz → 1.3.7__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 (22) hide show
  1. {microdf_python-1.3.5 → microdf_python-1.3.7}/PKG-INFO +2 -2
  2. {microdf_python-1.3.5 → microdf_python-1.3.7}/README.md +1 -1
  3. microdf_python-1.3.7/microdf/__init__.py +22 -0
  4. {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf/microdataframe.py +60 -22
  5. {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf/microseries.py +3 -2
  6. microdf_python-1.3.7/microdf/tests/test_aggregation_errors.py +57 -0
  7. microdf_python-1.3.7/microdf/tests/test_version_metadata.py +8 -0
  8. {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf_python.egg-info/PKG-INFO +2 -2
  9. {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf_python.egg-info/SOURCES.txt +2 -0
  10. {microdf_python-1.3.5 → microdf_python-1.3.7}/pyproject.toml +1 -1
  11. microdf_python-1.3.5/microdf/__init__.py +0 -14
  12. {microdf_python-1.3.5 → microdf_python-1.3.7}/LICENSE +0 -0
  13. {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf/tests/conftest.py +0 -0
  14. {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf/tests/test_dataframe_weight_storage.py +0 -0
  15. {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf/tests/test_microseries_dataframe.py +0 -0
  16. {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf/tests/test_nullify_weights_index.py +0 -0
  17. {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf/tests/test_pandas3_compatibility.py +0 -0
  18. {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf/tests/test_quantile_missing_values.py +0 -0
  19. {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf_python.egg-info/dependency_links.txt +0 -0
  20. {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf_python.egg-info/requires.txt +0 -0
  21. {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf_python.egg-info/top_level.txt +0 -0
  22. {microdf_python-1.3.5 → microdf_python-1.3.7}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: microdf-python
3
- Version: 1.3.5
3
+ Version: 1.3.7
4
4
  Summary: Weighted pandas DataFrames and Series for survey microdata
5
5
  Author-email: Max Ghenis <max@policyengine.org>
6
6
  License: MIT
@@ -20,7 +20,7 @@ Requires-Dist: towncrier>=24.8.0; extra == "dev"
20
20
  Dynamic: license-file
21
21
 
22
22
  [![Build](https://github.com/PolicyEngine/microdf/workflows/Pull%20request/badge.svg)](https://github.com/PolicyEngine/microdf/actions)
23
- [![Codecov](https://codecov.io/gh/PolicyEngine/microdf/branch/master/graph/badge.svg)](https://codecov.io/gh/PolicyEngine/microdf)
23
+ [![Codecov](https://codecov.io/gh/PolicyEngine/microdf/branch/main/graph/badge.svg)](https://codecov.io/gh/PolicyEngine/microdf)
24
24
 
25
25
  # microdf
26
26
  Weighted pandas DataFrames and Series for survey microdata analysis.
@@ -1,5 +1,5 @@
1
1
  [![Build](https://github.com/PolicyEngine/microdf/workflows/Pull%20request/badge.svg)](https://github.com/PolicyEngine/microdf/actions)
2
- [![Codecov](https://codecov.io/gh/PolicyEngine/microdf/branch/master/graph/badge.svg)](https://codecov.io/gh/PolicyEngine/microdf)
2
+ [![Codecov](https://codecov.io/gh/PolicyEngine/microdf/branch/main/graph/badge.svg)](https://codecov.io/gh/PolicyEngine/microdf)
3
3
 
4
4
  # microdf
5
5
  Weighted pandas DataFrames and Series for survey microdata analysis.
@@ -0,0 +1,22 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ from .microdataframe import MicroDataFrame, MicroDataFrameGroupBy
4
+ from .microseries import MicroSeries, MicroSeriesGroupBy
5
+
6
+ name = "microdf"
7
+
8
+ # Read the version from package metadata so it can't drift from
9
+ # pyproject.toml (the automated bump only touches pyproject).
10
+ try:
11
+ __version__ = version("microdf-python")
12
+ except PackageNotFoundError: # pragma: no cover - running from a source tree
13
+ __version__ = "unknown"
14
+
15
+ __all__ = [
16
+ # microseries.py
17
+ "MicroSeries",
18
+ "MicroSeriesGroupBy",
19
+ # microdataframe.py
20
+ "MicroDataFrame",
21
+ "MicroDataFrameGroupBy",
22
+ ]
@@ -150,9 +150,13 @@ class MicroDataFrame(pd.DataFrame):
150
150
  if pd.api.types.is_numeric_dtype(self[col]):
151
151
  try:
152
152
  results[col] = getattr(self[col], name)(*args, **kwargs)
153
- except Exception:
154
- # Skip columns that can't be aggregated
155
- pass
153
+ except TypeError as exc:
154
+ # Skip columns whose dtype can't take this aggregation.
155
+ # Deliberately narrow: catching every Exception here also
156
+ # swallowed real errors (e.g. the ValueError from
157
+ # gini(negatives=...)) and returned a silently truncated
158
+ # result instead of raising.
159
+ logger.debug("skipping column %s in %s: %s", col, name, exc)
156
160
  return pd.Series(results)
157
161
 
158
162
  return fn
@@ -173,9 +177,13 @@ class MicroDataFrame(pd.DataFrame):
173
177
  result = getattr(self[col], name)(*args, **kwargs)
174
178
  results.append(result)
175
179
  columns.append(col)
176
- except Exception:
177
- # Skip columns that can't be aggregated
178
- pass
180
+ except TypeError as exc:
181
+ # Skip columns whose dtype can't take this aggregation.
182
+ # Deliberately narrow: catching every Exception here also
183
+ # swallowed real errors (e.g. the ValueError from
184
+ # gini(negatives=...)) and returned a silently truncated
185
+ # result instead of raising.
186
+ logger.debug("skipping column %s in %s: %s", col, name, exc)
179
187
 
180
188
  if results:
181
189
  df = pd.DataFrame(results)
@@ -208,9 +216,13 @@ class MicroDataFrame(pd.DataFrame):
208
216
  result = getattr(self[col], name)(*args, **kwargs)
209
217
  results.append(result)
210
218
  columns.append(col)
211
- except Exception:
212
- # Skip columns that can't be aggregated
213
- pass
219
+ except TypeError as exc:
220
+ # Skip columns whose dtype can't take this aggregation.
221
+ # Deliberately narrow: catching every Exception here also
222
+ # swallowed real errors (e.g. the ValueError from
223
+ # gini(negatives=...)) and returned a silently truncated
224
+ # result instead of raising.
225
+ logger.debug("skipping column %s in %s: %s", col, name, exc)
214
226
 
215
227
  if results:
216
228
  df = pd.DataFrame(results)
@@ -225,9 +237,13 @@ class MicroDataFrame(pd.DataFrame):
225
237
  if pd.api.types.is_numeric_dtype(self[col]):
226
238
  try:
227
239
  results[col] = getattr(self[col], name)(*args, **kwargs)
228
- except Exception:
229
- # Skip columns that can't be aggregated
230
- pass
240
+ except TypeError as exc:
241
+ # Skip columns whose dtype can't take this aggregation.
242
+ # Deliberately narrow: catching every Exception here also
243
+ # swallowed real errors (e.g. the ValueError from
244
+ # gini(negatives=...)) and returned a silently truncated
245
+ # result instead of raising.
246
+ logger.debug("skipping column %s in %s: %s", col, name, exc)
231
247
  return pd.Series(results)
232
248
 
233
249
  return fn
@@ -839,9 +855,13 @@ class MicroDataFrameGroupBy(pd.core.groupby.generic.DataFrameGroupBy):
839
855
  results[col] = getattr(getattr(self, col), name)(
840
856
  *args, **kwargs
841
857
  )
842
- except Exception:
843
- # Skip columns that can't be aggregated
844
- pass
858
+ except TypeError as exc:
859
+ # Skip columns whose dtype can't take this aggregation.
860
+ # Deliberately narrow: catching every Exception here also
861
+ # swallowed real errors (e.g. the ValueError from
862
+ # gini(negatives=...)) and returned a silently truncated
863
+ # result instead of raising.
864
+ logger.debug("skipping column %s in %s: %s", col, name, exc)
845
865
  # Return plain DataFrame - aggregated results don't have
846
866
  # per-row weights (weights were already applied)
847
867
  return pd.DataFrame(results) if results else pd.DataFrame()
@@ -859,9 +879,13 @@ class MicroDataFrameGroupBy(pd.core.groupby.generic.DataFrameGroupBy):
859
879
  results[col] = getattr(getattr(self, col), name)(
860
880
  *args, **kwargs
861
881
  )
862
- except Exception:
863
- # Skip columns that can't be aggregated
864
- pass
882
+ except TypeError as exc:
883
+ # Skip columns whose dtype can't take this aggregation.
884
+ # Deliberately narrow: catching every Exception here also
885
+ # swallowed real errors (e.g. the ValueError from
886
+ # gini(negatives=...)) and returned a silently truncated
887
+ # result instead of raising.
888
+ logger.debug("skipping column %s in %s: %s", col, name, exc)
865
889
  # Return plain DataFrame - aggregated results don't have
866
890
  # per-row weights (weights were already applied)
867
891
  return pd.DataFrame(results) if results else pd.DataFrame()
@@ -921,8 +945,15 @@ class MicroDataFrameGroupBy(pd.core.groupby.generic.DataFrameGroupBy):
921
945
  results[col] = getattr(getattr(res, col), name)(
922
946
  *args, **kwargs
923
947
  )
924
- except Exception:
925
- pass
948
+ except TypeError as exc:
949
+ # Skip columns whose dtype can't take this aggregation.
950
+ # Deliberately narrow: catching every Exception here also
951
+ # swallowed real errors (e.g. the ValueError from
952
+ # gini(negatives=...)) and returned a silently truncated
953
+ # result instead of raising.
954
+ logger.debug(
955
+ "skipping column %s in %s: %s", col, name, exc
956
+ )
926
957
  # Return plain DataFrame - aggregated results don't
927
958
  # have per-row weights (weights were already applied)
928
959
  return pd.DataFrame(results) if results else pd.DataFrame()
@@ -940,8 +971,15 @@ class MicroDataFrameGroupBy(pd.core.groupby.generic.DataFrameGroupBy):
940
971
  results[col] = getattr(getattr(res, col), name)(
941
972
  *args, **kwargs
942
973
  )
943
- except Exception:
944
- pass
974
+ except TypeError as exc:
975
+ # Skip columns whose dtype can't take this aggregation.
976
+ # Deliberately narrow: catching every Exception here also
977
+ # swallowed real errors (e.g. the ValueError from
978
+ # gini(negatives=...)) and returned a silently truncated
979
+ # result instead of raising.
980
+ logger.debug(
981
+ "skipping column %s in %s: %s", col, name, exc
982
+ )
945
983
  # Return plain DataFrame - aggregated results don't
946
984
  # have per-row weights (weights were already applied)
947
985
  return pd.DataFrame(results) if results else pd.DataFrame()
@@ -403,8 +403,9 @@ class MicroSeries(pd.Series):
403
403
  w = np.asarray(self.weights.values, dtype=float)
404
404
  if negatives == "zero":
405
405
  x = np.where(x < 0, 0.0, x)
406
- elif negatives == "shift" and len(x) > 0 and np.amin(x) < 0:
407
- x = x - np.amin(x)
406
+ elif negatives == "shift":
407
+ if len(x) > 0 and np.amin(x) < 0:
408
+ x = x - np.amin(x)
408
409
  elif negatives is not None:
409
410
  raise ValueError(
410
411
  f"Unknown negatives option {negatives!r}; expected "
@@ -0,0 +1,57 @@
1
+ import microdf as mdf
2
+ import numpy as np
3
+ import pandas as pd
4
+ import pytest
5
+
6
+
7
+ def test_aggregation_surfaces_real_errors():
8
+ """A genuine argument error must raise, not be swallowed per column."""
9
+ df = mdf.MicroDataFrame(pd.DataFrame({"x": [1, 2, 3]}), weights=[1, 1, 1])
10
+ with pytest.raises(ValueError, match="Unknown negatives option"):
11
+ df.gini(negatives="bogus")
12
+
13
+
14
+ def test_aggregation_still_skips_non_numeric_columns():
15
+ """Narrowing the guard must not change which columns aggregate."""
16
+ df = mdf.MicroDataFrame(
17
+ pd.DataFrame(
18
+ {
19
+ "x": [1, 2, 3],
20
+ "s": ["a", "b", "c"],
21
+ "dt": pd.to_datetime(["2020-01-01"] * 3),
22
+ }
23
+ ),
24
+ weights=[1, 2, 3],
25
+ )
26
+ assert list(df.sum().index) == ["x"]
27
+ assert df.sum()["x"] == 14
28
+ assert list(df.mean().index) == ["x"]
29
+
30
+
31
+ def test_gini_shift_accepts_nonnegative_and_negative_columns():
32
+ frame = mdf.MicroDataFrame(
33
+ {"positive": [1, 2, 3], "negative": [-1, 1, 3]}, weights=[1, 1, 1]
34
+ )
35
+ # Shift leaves [1, 2, 3] unchanged and changes [-1, 1, 3] to [0, 2, 4].
36
+ # The pairwise-difference Ginis are 2/9 and 4/9, respectively.
37
+ expected = pd.Series({"positive": 2 / 9, "negative": 4 / 9})
38
+ pd.testing.assert_series_equal(frame.gini(negatives="shift"), expected)
39
+
40
+
41
+ @pytest.mark.parametrize("selected", [False, True])
42
+ def test_grouped_gini_shift_accepts_positive_groups(selected):
43
+ frame = mdf.MicroDataFrame(
44
+ {"group": ["a", "a", "a", "b", "b", "b"], "value": [1, 2, 3, -1, 1, 3]},
45
+ weights=[1, 1, 1, 1, 1, 1],
46
+ )
47
+ grouped = frame.groupby("group")
48
+ if selected:
49
+ grouped = grouped[["value"]]
50
+ expected = pd.DataFrame(
51
+ {"value": [2 / 9, 4 / 9]}, index=pd.Index(["a", "b"], name="group")
52
+ )
53
+ pd.testing.assert_frame_equal(grouped.gini(negatives="shift"), expected)
54
+
55
+
56
+ def test_gini_shift_accepts_empty_series():
57
+ assert np.isnan(mdf.MicroSeries([], weights=[]).gini(negatives="shift"))
@@ -0,0 +1,8 @@
1
+ import microdf as mdf
2
+
3
+
4
+ def test_version_matches_package_metadata():
5
+ """__version__ must not drift from pyproject.toml."""
6
+ from importlib.metadata import version
7
+
8
+ assert mdf.__version__ == version("microdf-python")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: microdf-python
3
- Version: 1.3.5
3
+ Version: 1.3.7
4
4
  Summary: Weighted pandas DataFrames and Series for survey microdata
5
5
  Author-email: Max Ghenis <max@policyengine.org>
6
6
  License: MIT
@@ -20,7 +20,7 @@ Requires-Dist: towncrier>=24.8.0; extra == "dev"
20
20
  Dynamic: license-file
21
21
 
22
22
  [![Build](https://github.com/PolicyEngine/microdf/workflows/Pull%20request/badge.svg)](https://github.com/PolicyEngine/microdf/actions)
23
- [![Codecov](https://codecov.io/gh/PolicyEngine/microdf/branch/master/graph/badge.svg)](https://codecov.io/gh/PolicyEngine/microdf)
23
+ [![Codecov](https://codecov.io/gh/PolicyEngine/microdf/branch/main/graph/badge.svg)](https://codecov.io/gh/PolicyEngine/microdf)
24
24
 
25
25
  # microdf
26
26
  Weighted pandas DataFrames and Series for survey microdata analysis.
@@ -5,11 +5,13 @@ microdf/__init__.py
5
5
  microdf/microdataframe.py
6
6
  microdf/microseries.py
7
7
  microdf/tests/conftest.py
8
+ microdf/tests/test_aggregation_errors.py
8
9
  microdf/tests/test_dataframe_weight_storage.py
9
10
  microdf/tests/test_microseries_dataframe.py
10
11
  microdf/tests/test_nullify_weights_index.py
11
12
  microdf/tests/test_pandas3_compatibility.py
12
13
  microdf/tests/test_quantile_missing_values.py
14
+ microdf/tests/test_version_metadata.py
13
15
  microdf_python.egg-info/PKG-INFO
14
16
  microdf_python.egg-info/SOURCES.txt
15
17
  microdf_python.egg-info/dependency_links.txt
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "microdf-python"
7
- version = "1.3.5"
7
+ version = "1.3.7"
8
8
  description = "Weighted pandas DataFrames and Series for survey microdata"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -1,14 +0,0 @@
1
- from .microdataframe import MicroDataFrame, MicroDataFrameGroupBy
2
- from .microseries import MicroSeries, MicroSeriesGroupBy
3
-
4
- name = "microdf"
5
- __version__ = "0.1.0"
6
-
7
- __all__ = [
8
- # microseries.py
9
- "MicroSeries",
10
- "MicroSeriesGroupBy",
11
- # microdataframe.py
12
- "MicroDataFrame",
13
- "MicroDataFrameGroupBy",
14
- ]
File without changes
File without changes