microdf-python 0.4.4__py3-none-any.whl → 0.4.6__py3-none-any.whl

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.
@@ -0,0 +1,232 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ import microdf as mdf
5
+ from microdf.generic import MicroDataFrame, MicroSeries
6
+
7
+
8
+ def test_df_init() -> None:
9
+ arr = np.array([0, 1, 1])
10
+ w = np.array([3, 0, 9])
11
+ df = mdf.MicroDataFrame({"a": arr}, weights=w)
12
+ assert df.a.mean() == np.average(arr, weights=w)
13
+
14
+ df = mdf.MicroDataFrame()
15
+ df["a"] = arr
16
+ df.set_weights(w)
17
+ assert df.a.mean() == np.average(arr, weights=w)
18
+
19
+ df = mdf.MicroDataFrame()
20
+ df["a"] = arr
21
+ df["w"] = w
22
+ df.set_weight_col("w")
23
+ assert df.a.mean() == np.average(arr, weights=w)
24
+
25
+
26
+ def test_series_getitem() -> None:
27
+ arr = np.array([0, 1, 1])
28
+ w = np.array([3, 0, 9])
29
+ s = mdf.MicroSeries(arr, weights=w)
30
+ assert s[[1, 2]].sum() == np.sum(arr[[1, 2]] * w[[1, 2]])
31
+
32
+ assert s[1:3].sum() == np.sum(arr[1:3] * w[1:3])
33
+
34
+
35
+ def test_sum() -> None:
36
+ arr = np.array([0, 1, 1])
37
+ w = np.array([3, 0, 9])
38
+ series = mdf.MicroSeries(arr, weights=w)
39
+ assert series.sum() == (arr * w).sum()
40
+
41
+ arr = np.linspace(-20, 100, 100)
42
+ w = np.linspace(1, 3, 100)
43
+ series = mdf.MicroSeries(arr)
44
+ series.set_weights(w)
45
+ assert series.sum() == (arr * w).sum()
46
+
47
+ # Verify that an error is thrown when passing weights of different size
48
+ # from the values.
49
+ w = np.linspace(1, 3, 101)
50
+ series = mdf.MicroSeries(arr)
51
+ try:
52
+ series.set_weights(w)
53
+ assert False
54
+ except Exception:
55
+ pass
56
+
57
+
58
+ def test_mean() -> None:
59
+ arr = np.array([3, 0, 2])
60
+ w = np.array([4, 1, 1])
61
+ series = mdf.MicroSeries(arr, weights=w)
62
+ assert series.mean() == np.average(arr, weights=w)
63
+
64
+ arr = np.linspace(-20, 100, 100)
65
+ w = np.linspace(1, 3, 100)
66
+ series = mdf.MicroSeries(arr)
67
+ series.set_weights(w)
68
+ assert series.mean() == np.average(arr, weights=w)
69
+
70
+ w = np.linspace(1, 3, 101)
71
+ series = mdf.MicroSeries(arr)
72
+ try:
73
+ series.set_weights(w)
74
+ assert False
75
+ except Exception:
76
+ pass
77
+
78
+
79
+ def test_poverty_count() -> None:
80
+ arr = np.array([10000, 20000, 50000])
81
+ w = np.array([1123, 1144, 2211])
82
+ df = MicroDataFrame(weights=w)
83
+ df["income"] = arr
84
+ df["threshold"] = 16000
85
+ assert df.poverty_count("income", "threshold") == w[0]
86
+
87
+
88
+ def test_median() -> None:
89
+ # 1, 2, 3, 4, *4*, 4, 5, 5, 5
90
+ arr = np.array([1, 2, 3, 4, 5])
91
+ w = np.array([1, 1, 1, 3, 3])
92
+ series = mdf.MicroSeries(arr, weights=w)
93
+ assert series.median() == 4
94
+
95
+
96
+ def test_unweighted_groupby() -> None:
97
+ df = mdf.MicroDataFrame({"x": [1, 2], "y": [3, 4], "z": [5, 6]})
98
+ assert (df.groupby("x").z.sum().values == np.array([5.0, 6.0])).all()
99
+
100
+
101
+ def test_multiple_groupby() -> None:
102
+ df = mdf.MicroDataFrame({"x": [1, 2], "y": [3, 4], "z": [5, 6]})
103
+ assert (df.groupby(["x", "y"]).z.sum() == np.array([5, 6])).all()
104
+
105
+
106
+ def test_concat() -> None:
107
+ df1 = mdf.MicroDataFrame({"x": [1, 2]}, weights=[3, 4])
108
+ df2 = mdf.MicroDataFrame({"y": [5, 6]}, weights=[7, 8])
109
+ # Verify that pd.concat returns DataFrame (probably no way to fix this).
110
+ pd_long = pd.concat([df1, df2])
111
+ assert isinstance(pd_long, pd.DataFrame)
112
+ assert not isinstance(pd_long, mdf.MicroDataFrame)
113
+ # Verify that mdf.concat works.
114
+ mdf_long = mdf.concat([df1, df2])
115
+ assert isinstance(mdf_long, mdf.MicroDataFrame)
116
+ # Weights should be preserved.
117
+ assert mdf_long.weights.equals(pd.concat([df1.weights, df2.weights]))
118
+ # Verify it works horizontally too (take the first set of weights).
119
+ mdf_wide = mdf.concat([df1, df2], axis=1)
120
+ assert isinstance(mdf_wide, mdf.MicroDataFrame)
121
+ assert mdf_wide.weights.equals(df1.weights)
122
+
123
+
124
+ def test_set_index() -> None:
125
+ d = mdf.MicroDataFrame(dict(x=[1, 2, 3]), weights=[4, 5, 6])
126
+ assert d.x.__class__ == MicroSeries
127
+ d.index = [1, 2, 3]
128
+ assert d.x.__class__ == MicroSeries
129
+
130
+
131
+ def test_reset_index() -> None:
132
+ d = mdf.MicroDataFrame(dict(x=[1, 2, 3]), weights=[4, 5, 6])
133
+ assert d.reset_index().__class__ == MicroDataFrame
134
+
135
+
136
+ def test_cumsum() -> None:
137
+ s = mdf.MicroSeries([1, 2, 3], weights=[4, 5, 6])
138
+ assert np.array_equal(s.cumsum().values, [4, 14, 32])
139
+
140
+ s = mdf.MicroSeries([2, 1, 3], weights=[5, 4, 6])
141
+ assert np.array_equal(s.cumsum().values, [10, 14, 32])
142
+
143
+ s = mdf.MicroSeries([3, 1, 2], weights=[6, 4, 5])
144
+ assert np.array_equal(s.cumsum().values, [18, 22, 32])
145
+
146
+
147
+ def test_rank() -> None:
148
+ s = mdf.MicroSeries([1, 2, 3], weights=[4, 5, 6])
149
+ assert np.array_equal(s.rank().values, [4, 9, 15])
150
+
151
+ s = mdf.MicroSeries([3, 1, 2], weights=[6, 4, 5])
152
+ assert np.array_equal(s.rank().values, [15, 4, 9])
153
+
154
+ s = mdf.MicroSeries([2, 1, 3], weights=[5, 4, 6])
155
+ assert np.array_equal(s.rank().values, [9, 4, 15])
156
+
157
+
158
+ def test_percentile_rank() -> None:
159
+ s = mdf.MicroSeries([4, 2, 3, 1], weights=[20, 40, 20, 20])
160
+ assert np.array_equal(s.percentile_rank().values, [100, 60, 80, 20])
161
+
162
+
163
+ def test_quartile_rank() -> None:
164
+ s = mdf.MicroSeries([4, 2, 3], weights=[25, 50, 25])
165
+ assert np.array_equal(s.quartile_rank().values, [4, 2, 3])
166
+
167
+
168
+ def test_quintile_rank() -> None:
169
+ s = mdf.MicroSeries([4, 2, 3], weights=[20, 60, 20])
170
+ assert np.array_equal(s.quintile_rank().values, [5, 3, 4])
171
+
172
+
173
+ def test_decile_rank_rank() -> None:
174
+ s = mdf.MicroSeries(
175
+ [5, 4, 3, 2, 1, 6, 7, 8, 9],
176
+ weights=[10, 20, 10, 10, 10, 10, 10, 10, 10, 10],
177
+ )
178
+ assert np.array_equal(s.decile_rank().values, [6, 5, 3, 2, 1, 7, 8, 9, 10])
179
+
180
+
181
+ def test_copy_equals() -> None:
182
+ d = mdf.MicroDataFrame(
183
+ {"x": [1, 2], "y": [3, 4], "z": [5, 6]}, weights=[7, 8]
184
+ )
185
+ d_copy = d.copy()
186
+ d_copy_diff_weights = d_copy.copy()
187
+ d_copy_diff_weights.weights *= 2
188
+ assert d.equals(d_copy)
189
+ assert not d.equals(d_copy_diff_weights)
190
+ # Same for a MicroSeries.
191
+ assert d.x.equals(d_copy.x)
192
+ assert not d.x.equals(d_copy_diff_weights.x)
193
+
194
+
195
+ def test_subset() -> None:
196
+ df = mdf.MicroDataFrame(
197
+ {"x": [1, 2], "y": [3, 4], "z": [5, 6]}, weights=[7, 8]
198
+ )
199
+ df_no_z = mdf.MicroDataFrame({"x": [1, 2], "y": [3, 4]}, weights=[7, 8])
200
+ assert df[["x", "y"]].equals(df_no_z)
201
+ df_no_z_diff_weights = df_no_z.copy()
202
+ df_no_z_diff_weights.weights += 1
203
+ assert not df[["x", "y"]].equals(df_no_z_diff_weights)
204
+
205
+
206
+ def test_value_subset() -> None:
207
+ d = mdf.MicroDataFrame({"x": [1, 2, 3], "y": [1, 2, 2]}, weights=[4, 5, 6])
208
+ d2 = d[d.y > 1]
209
+ assert d2.y.shape == d2.weights.shape
210
+
211
+
212
+ def test_bitwise_ops_return_microseries() -> None:
213
+ s1 = mdf.MicroSeries([True, False, True], weights=[1, 2, 3])
214
+ s2 = mdf.MicroSeries([False, False, True], weights=[1, 2, 3])
215
+ and_result = s1 & s2
216
+ or_result = s1 | s2
217
+ assert isinstance(and_result, mdf.MicroSeries)
218
+ assert isinstance(or_result, mdf.MicroSeries)
219
+ expected_and = mdf.MicroSeries([False, False, True], weights=[1, 2, 3])
220
+ expected_or = mdf.MicroSeries([True, False, True], weights=[1, 2, 3])
221
+ assert and_result.equals(expected_and)
222
+ assert or_result.equals(expected_or)
223
+
224
+
225
+ def test_additional_ops_return_microseries() -> None:
226
+ s = mdf.MicroSeries([1, 2, 3], weights=[4, 5, 6])
227
+ radd = 1 + s
228
+ xor = s ^ mdf.MicroSeries([0, 1, 0], weights=[4, 5, 6])
229
+ inv = ~mdf.MicroSeries([True, False], weights=[1, 1])
230
+ assert isinstance(radd, mdf.MicroSeries)
231
+ assert isinstance(xor, mdf.MicroSeries)
232
+ assert isinstance(inv, mdf.MicroSeries)
@@ -0,0 +1,20 @@
1
+ import pandas as pd
2
+
3
+ import microdf as mdf
4
+
5
+
6
+ def test_top_pct() -> None:
7
+ x = list(range(1, 11)) # 1 to 10. Sum = 10 * 11 / 2 = 55.
8
+ df = pd.DataFrame({"x": x})
9
+ ms = mdf.MicroSeries(x)
10
+ RES = 10 / 55
11
+ assert mdf.top_10_pct_share(df, "x") == RES
12
+ assert ms.top_10_pct_share() == RES
13
+ x = list(range(1, 4))
14
+ df = pd.DataFrame({"x": x, "w": x})
15
+ ms = mdf.MicroSeries(x, weights=x)
16
+ # This is equivalent to [1, 2, 2, 3, 3, 3]
17
+ # Sum = 14, top half is 9.
18
+ RES = 9 / 14
19
+ assert mdf.top_50_pct_share(df, "x", "w") == RES
20
+ assert ms.top_50_pct_share() == RES
@@ -0,0 +1,10 @@
1
+ import microdf as mdf
2
+
3
+
4
+ def test_read_stata_zip() -> None:
5
+ """"""
6
+ SCF2016 = "https://www.federalreserve.gov/econres/files/scfp2016s.zip"
7
+ COLS = ["wgt", "networth"]
8
+ df = mdf.read_stata_zip(SCF2016, columns=COLS)
9
+ assert df.columns.tolist() == COLS
10
+ assert df.shape[0] > 0
@@ -0,0 +1,54 @@
1
+ import sys
2
+ import types
3
+
4
+ import pytest
5
+
6
+ from microdf._optional import VERSIONS, import_optional_dependency
7
+
8
+
9
+ def test_import_optional() -> None:
10
+ """"""
11
+ match = "Missing .*notapackage.* pip .* conda .* notapackage"
12
+ with pytest.raises(ImportError, match=match):
13
+ import_optional_dependency("notapackage")
14
+
15
+ result = import_optional_dependency("notapackage", raise_on_missing=False)
16
+ assert result is None
17
+
18
+
19
+ def test_xlrd_version_fallback() -> None:
20
+ """"""
21
+ pytest.importorskip("xlrd")
22
+ import_optional_dependency("xlrd")
23
+
24
+
25
+ def test_bad_version() -> None:
26
+ """"""
27
+ name = "fakemodule"
28
+ module = types.ModuleType(name)
29
+ module.__version__ = "0.9.0"
30
+ sys.modules[name] = module
31
+ VERSIONS[name] = "1.0.0"
32
+
33
+ match = "microdf requires .*1.0.0.* of .fakemodule.*'0.9.0'"
34
+ with pytest.raises(ImportError, match=match):
35
+ import_optional_dependency("fakemodule")
36
+
37
+ with pytest.warns(UserWarning):
38
+ result = import_optional_dependency("fakemodule", on_version="warn")
39
+ assert result is None
40
+
41
+ module.__version__ = "1.0.0" # exact match is OK
42
+ result = import_optional_dependency("fakemodule")
43
+ assert result is module
44
+
45
+
46
+ def test_no_version_raises() -> None:
47
+ """"""
48
+ name = "fakemodule"
49
+ module = types.ModuleType(name)
50
+ sys.modules[name] = module
51
+ VERSIONS[name] = "1.0.0"
52
+
53
+ with pytest.raises(ImportError, match="Can't determine .* fakemodule"):
54
+ import_optional_dependency(name)
@@ -0,0 +1,73 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ import microdf as mdf
5
+
6
+ df = pd.DataFrame(
7
+ {
8
+ "income": [-10, 0, 10, 20],
9
+ "threshold": [15, 10, 15, 10],
10
+ "weight": [1, 2, 3, 4],
11
+ }
12
+ )
13
+ md = mdf.MicroDataFrame(df[["income", "threshold"]], weights=df.weight)
14
+
15
+
16
+ def test_poverty_rate() -> None:
17
+ # Unweighted
18
+ assert np.allclose(mdf.poverty_rate(df, "income", "threshold"), 3 / 4)
19
+ # Weighted
20
+ assert np.allclose(
21
+ mdf.poverty_rate(df, "income", "threshold", "weight"), 6 / 10
22
+ )
23
+ assert np.allclose(md.poverty_rate("income", "threshold"), 6 / 10)
24
+
25
+
26
+ def test_deep_poverty_rate() -> None:
27
+ # Unweighted
28
+ assert np.allclose(mdf.deep_poverty_rate(df, "income", "threshold"), 2 / 4)
29
+ # Weighted
30
+ assert np.allclose(
31
+ mdf.deep_poverty_rate(df, "income", "threshold", "weight"), 3 / 10
32
+ )
33
+ assert np.allclose(md.deep_poverty_rate("income", "threshold"), 3 / 10)
34
+
35
+
36
+ def test_poverty_gap() -> None:
37
+ # Unweighted
38
+ assert np.allclose(mdf.poverty_gap(df, "income", "threshold"), 25 + 10 + 5)
39
+ # Weighted
40
+ RES = 25 * 1 + 10 * 2 + 5 * 3
41
+ assert np.allclose(
42
+ mdf.poverty_gap(df, "income", "threshold", "weight"), RES
43
+ )
44
+ assert np.allclose(md.poverty_gap("income", "threshold"), RES)
45
+
46
+
47
+ def test_squared_poverty_gap() -> None:
48
+ # Unweighted
49
+ assert np.allclose(
50
+ mdf.squared_poverty_gap(df, "income", "threshold"),
51
+ 25**2 + 10**2 + 5**2,
52
+ )
53
+ # Weighted
54
+ RES = 1 * (25**2) + 2 * (10**2) + 3 * (5**2)
55
+ assert np.allclose(
56
+ mdf.squared_poverty_gap(df, "income", "threshold", "weight"),
57
+ RES,
58
+ )
59
+ assert np.allclose(md.squared_poverty_gap("income", "threshold"), RES)
60
+
61
+
62
+ def test_deep_poverty_gap() -> None:
63
+ # Unweighted
64
+ assert np.allclose(
65
+ mdf.deep_poverty_gap(df, "income", "threshold"), 17.5 + 5 + 0 + 0
66
+ )
67
+ # Weighted
68
+ RES = 17.5 * 1 + 5 * 2 + 0 * 3 + 0 * 4
69
+ assert np.allclose(
70
+ mdf.deep_poverty_gap(df, "income", "threshold", "weight"), RES
71
+ )
72
+ # Same in MicroDataFrame.
73
+ assert np.allclose(md.deep_poverty_gap("income", "threshold"), RES)
@@ -0,0 +1,18 @@
1
+ import pandas as pd
2
+
3
+ import microdf as mdf
4
+
5
+ V1 = [1, 2, 3]
6
+ V2 = [4, 5, 6]
7
+ W1 = [7, 8, 9]
8
+ W2 = [10, 11, 12]
9
+ DF1 = pd.DataFrame({"v": V1, "w": W1})
10
+ DF2 = pd.DataFrame({"v": V2, "w": W2})
11
+
12
+
13
+ def test_quantile_chg() -> None:
14
+ mdf.quantile_chg(DF1, DF2, "v", "w", "v", "w")
15
+
16
+
17
+ def test_quantile_pct_chg_plot() -> None:
18
+ mdf.quantile_pct_chg_plot(DF1, DF2, "v", "w", "v", "w")
@@ -0,0 +1,54 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ import pytest
4
+
5
+ import microdf as mdf
6
+
7
+
8
+ def test_tax() -> None:
9
+ """"""
10
+ # Consider a MTR schedule of 0% up to 10,000, then 10% after that.
11
+ BRACKETS = [0, 10e3]
12
+ RATES = [0, 0.1]
13
+ INCOME = [0, 5e3, 10e3, 10e3 + 1, 20e3]
14
+ EXPECTED = [0, 0, 0, 0.1, 1e3]
15
+ res = mdf.tax_from_mtrs(INCOME, BRACKETS, RATES)
16
+ pd.testing.assert_series_equal(res, pd.Series(EXPECTED))
17
+ # Try with 10% avoidance.
18
+ EXPECTED_10PCT_AVOIDANCE = [0, 0, 0, 0, 800.0]
19
+ res_10pct_avoidance = mdf.tax_from_mtrs(INCOME, BRACKETS, RATES, 0.1)
20
+ pd.testing.assert_series_equal(
21
+ res_10pct_avoidance, pd.Series(EXPECTED_10PCT_AVOIDANCE)
22
+ )
23
+ # Try with avoidance elasticity of 2.
24
+ EXPECTED_E2_AVOIDANCE = [
25
+ 0,
26
+ 0,
27
+ 0,
28
+ 0, # Taxable base becomes (10e3 + 1) * (1 - 2 * 0.1)
29
+ # Taxable base becomes 20e3 * (exp(-2 * 0.1)).
30
+ 0.1 * (20e3 * np.exp(-0.2) - 10e3),
31
+ ]
32
+ res_e2_avoidance = mdf.tax_from_mtrs(
33
+ INCOME, BRACKETS, RATES, avoidance_elasticity=2
34
+ )
35
+ pd.testing.assert_series_equal(
36
+ res_e2_avoidance, pd.Series(EXPECTED_E2_AVOIDANCE)
37
+ )
38
+ # Try with flat avoidance elasticity of 2.
39
+ EXPECTED_E2_AVOIDANCE_FLAT = [
40
+ 0,
41
+ 0,
42
+ 0,
43
+ 0, # Taxable base becomes (10e3 + 1) * (1 - 2 * 0.1)
44
+ 600.0,
45
+ ] # Taxable base becomes 20e3 * (1 - 2 * 0.1) = 16e3.
46
+ res_e2_avoidance_flat = mdf.tax_from_mtrs(
47
+ INCOME, BRACKETS, RATES, avoidance_elasticity_flat=2
48
+ )
49
+ pd.testing.assert_series_equal(
50
+ res_e2_avoidance_flat, pd.Series(EXPECTED_E2_AVOIDANCE_FLAT)
51
+ )
52
+ # Ensure error when passing both rate and elasticity.
53
+ with pytest.raises(Exception):
54
+ mdf.tax_from_mtrs(INCOME, BRACKETS, RATES, 0.1, 2)
@@ -0,0 +1,25 @@
1
+ import pytest
2
+
3
+ import microdf as mdf
4
+
5
+ try:
6
+ import taxcalc as tc
7
+
8
+ _HAVE_TAXCALC = True
9
+ except ImportError:
10
+ _HAVE_TAXCALC = False
11
+
12
+
13
+ def test_calc_df() -> None:
14
+ """"""
15
+ if not _HAVE_TAXCALC:
16
+ pytest.skip("taxcalc is not installed")
17
+ mdf.calc_df()
18
+
19
+
20
+ def test_static_baseline_calc() -> None:
21
+ """"""
22
+ if not _HAVE_TAXCALC:
23
+ pytest.skip("taxcalc is not installed")
24
+ recs = tc.Records.cps_constructor()
25
+ mdf.static_baseline_calc(recs, 2020)
@@ -0,0 +1,39 @@
1
+ import pandas as pd
2
+
3
+ import microdf as mdf
4
+
5
+
6
+ def test_cartesian_product() -> None:
7
+ """"""
8
+ res = mdf.cartesian_product(
9
+ {"a": [1, 2, 3], "b": ["val1", "val2"], "c": [100, 101]}
10
+ )
11
+ EXPECTED = pd.DataFrame(
12
+ {
13
+ "a": [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3],
14
+ "b": [
15
+ "val1",
16
+ "val1",
17
+ "val2",
18
+ "val2",
19
+ "val1",
20
+ "val1",
21
+ "val2",
22
+ "val2",
23
+ "val1",
24
+ "val1",
25
+ "val2",
26
+ "val2",
27
+ ],
28
+ "c": [100, 101, 100, 101, 100, 101, 100, 101, 100, 101, 100, 101],
29
+ }
30
+ )
31
+ pd.testing.assert_frame_equal(res, EXPECTED)
32
+
33
+
34
+ def test_flatten() -> None:
35
+ """"""
36
+ L = [[[1, 2, 3], [4, 5]], 6]
37
+ res = list(mdf.flatten(L))
38
+ EXPECTED = [1, 2, 3, 4, 5, 6]
39
+ assert res == EXPECTED
@@ -0,0 +1,74 @@
1
+ import pandas as pd
2
+ import pytest
3
+
4
+ import microdf as mdf
5
+
6
+ X = [1, 5, 2]
7
+ Y = [0, -6, 3]
8
+ W = [4, 1, 1]
9
+ df = pd.DataFrame({"x": X, "y": Y, "w": W})
10
+ ms = mdf.MicroSeries(X, weights=W)
11
+ md = mdf.MicroDataFrame(df[["x", "y"]], weights=W)
12
+ # Also make a version with groups.
13
+ df2 = df.copy(deep=True)
14
+ df2.x *= 2
15
+ df2.y *= 1.5
16
+ dfg = pd.concat([df, df2])
17
+ dfg["g"] = ["a"] * 3 + ["b"] * 3
18
+ mdg = mdf.MicroDataFrame(dfg[["x", "y", "g"]], weights=W)
19
+
20
+
21
+ def test_weighted_quantile() -> None:
22
+ Q = [0, 0.5, 1]
23
+ mdf.weighted_quantile(df, "x", "w", Q).tolist()
24
+
25
+
26
+ def test_weighted_median() -> None:
27
+ assert mdf.weighted_median(df, "x") == 2
28
+ mdf.weighted_median(df, "x", "w")
29
+ # Test with groups.
30
+ mdf.weighted_median(dfg, "x", "w", "g")
31
+
32
+
33
+ def test_weighted_mean() -> None:
34
+ # Test umweighted.
35
+ assert mdf.weighted_mean(df, "x") == 8 / 3
36
+ # Test weighted.
37
+ assert mdf.weighted_mean(df, "x", "w") == 11 / 6
38
+ # Test weighted with multiple columns.
39
+ assert mdf.weighted_mean(df, ["x", "y"], "w").tolist() == [11 / 6, -3 / 6]
40
+ # Test grouped.
41
+ mdf.weighted_mean(dfg, "x", "w", "g")
42
+ mdf.weighted_mean(dfg, ["x", "y"], "w", "g")
43
+
44
+
45
+ def test_weighted_sum() -> None:
46
+ # Test unweighted.
47
+ assert mdf.weighted_sum(df, "x") == 8
48
+ # Test weighted.
49
+ assert mdf.weighted_sum(df, "x", "w") == 11
50
+ # Test weighted with multiple columns.
51
+ assert mdf.weighted_sum(df, ["x", "y"], "w").tolist() == [11, -3]
52
+ # Test grouped.
53
+ mdf.weighted_sum(dfg, "x", "w", "g")
54
+ mdf.weighted_sum(dfg, ["x", "y"], "w", "g")
55
+
56
+
57
+ def test_gini() -> None:
58
+ # Test nothing breaks.
59
+ ms.gini()
60
+ # Unweighted.
61
+ mdf.gini(df, "x")
62
+ # Weighted
63
+ mdf.gini(df, "x", "w")
64
+ # Unweighted, grouped
65
+ mdf.gini(dfg, "x", groupby="g")
66
+ # Weighted, grouped
67
+ mdf.gini(dfg, "x", "w", groupby="g")
68
+ # Test old and new match.
69
+ assert ms.gini() == mdf.gini(df, "x", "w")
70
+
71
+
72
+ def test_add_weighted_quantiles() -> None:
73
+ with pytest.deprecated_call():
74
+ mdf.add_weighted_quantiles(df, "x", "w")
microdf/ubi.py CHANGED
@@ -1,32 +1,34 @@
1
+ from typing import List, Optional, Union
2
+
1
3
  import numpy as np
4
+ import pandas as pd
2
5
 
3
6
  import microdf as mdf
4
7
 
5
8
 
6
9
  def ubi_or_bens(
7
- df,
8
- ben_cols,
9
- max_ubi="max_ubi",
10
- ubi="ubi",
11
- bens="bens",
12
- update_income_measures=None,
13
- ):
14
- """Calculates whether a tax unit will take UBI or benefits,
15
- and adjusts values accordingly.
10
+ df: pd.DataFrame,
11
+ ben_cols: Union[str, List[str]],
12
+ max_ubi: str = "max_ubi",
13
+ ubi: str = "ubi",
14
+ bens: str = "bens",
15
+ update_income_measures: Optional[List[str]] = None,
16
+ ) -> None:
17
+ """Calculates whether a tax unit will take UBI or benefits, and adjusts
18
+ values accordingly.
16
19
 
17
20
  :param df: DataFrame.
18
21
  :param ben_cols: List of columns for benefits.
19
- :param max_ubi: Column name of the maximum UBI, before accounting
20
- for benefits. Defaults to 'max_ubi'.
22
+ :param max_ubi: Column name of the maximum UBI, before accounting for
23
+ benefits. Defaults to 'max_ubi'.
21
24
  :param ubi: Column name to add representing the UBI. Defaults to 'ubi'.
22
25
  :param bens: Column name to add representing total benefits (after
23
- adjustment). Defaults to 'bens'.
24
- :param update_income_measures: List of income measures to update.
25
- Defaults to ['expanded_income', 'aftertax_income'].
26
+ adjustment). Defaults to 'bens'.
27
+ :param update_income_measures: List of income measures to update. Defaults
28
+ to ['expanded_income', 'aftertax_income'].
26
29
  :returns: Nothing. Benefits in ben_cols are adjusted, ubi and bens columns
27
30
  are added, and expanded_income and aftertax_income are updated
28
31
  according to the net difference.
29
-
30
32
  """
31
33
  if update_income_measures is None:
32
34
  update_income_measures = ["expanded_income", "aftertax_income"]