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.
- {microdf_python-1.3.5 → microdf_python-1.3.7}/PKG-INFO +2 -2
- {microdf_python-1.3.5 → microdf_python-1.3.7}/README.md +1 -1
- microdf_python-1.3.7/microdf/__init__.py +22 -0
- {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf/microdataframe.py +60 -22
- {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf/microseries.py +3 -2
- microdf_python-1.3.7/microdf/tests/test_aggregation_errors.py +57 -0
- microdf_python-1.3.7/microdf/tests/test_version_metadata.py +8 -0
- {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf_python.egg-info/PKG-INFO +2 -2
- {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf_python.egg-info/SOURCES.txt +2 -0
- {microdf_python-1.3.5 → microdf_python-1.3.7}/pyproject.toml +1 -1
- microdf_python-1.3.5/microdf/__init__.py +0 -14
- {microdf_python-1.3.5 → microdf_python-1.3.7}/LICENSE +0 -0
- {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf/tests/conftest.py +0 -0
- {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf/tests/test_dataframe_weight_storage.py +0 -0
- {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf/tests/test_microseries_dataframe.py +0 -0
- {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf/tests/test_nullify_weights_index.py +0 -0
- {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf/tests/test_pandas3_compatibility.py +0 -0
- {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf/tests/test_quantile_missing_values.py +0 -0
- {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf_python.egg-info/dependency_links.txt +0 -0
- {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf_python.egg-info/requires.txt +0 -0
- {microdf_python-1.3.5 → microdf_python-1.3.7}/microdf_python.egg-info/top_level.txt +0 -0
- {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.
|
|
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
|
[](https://github.com/PolicyEngine/microdf/actions)
|
|
23
|
-
[](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
|
[](https://github.com/PolicyEngine/microdf/actions)
|
|
2
|
-
[](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
|
|
154
|
-
# Skip columns
|
|
155
|
-
|
|
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
|
|
177
|
-
# Skip columns
|
|
178
|
-
|
|
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
|
|
212
|
-
# Skip columns
|
|
213
|
-
|
|
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
|
|
229
|
-
# Skip columns
|
|
230
|
-
|
|
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
|
|
843
|
-
# Skip columns
|
|
844
|
-
|
|
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
|
|
863
|
-
# Skip columns
|
|
864
|
-
|
|
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
|
|
925
|
-
|
|
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
|
|
944
|
-
|
|
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"
|
|
407
|
-
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"))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: microdf-python
|
|
3
|
-
Version: 1.3.
|
|
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
|
[](https://github.com/PolicyEngine/microdf/actions)
|
|
23
|
-
[](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
|
|
@@ -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
|
{microdf_python-1.3.5 → microdf_python-1.3.7}/microdf/tests/test_dataframe_weight_storage.py
RENAMED
|
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
|