microdf-python 0.4.3__py3-none-any.whl → 0.4.5__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.
- microdf/__init__.py +10 -20
- microdf/_optional.py +8 -9
- microdf/agg.py +12 -15
- microdf/concat.py +8 -5
- microdf/custom_taxes.py +51 -57
- microdf/income_measures.py +10 -11
- microdf/inequality.py +71 -22
- microdf/io.py +8 -10
- microdf/microdataframe.py +501 -0
- microdf/microseries.py +625 -0
- microdf/poverty.py +11 -12
- microdf/tax.py +19 -21
- microdf/taxcalc.py +33 -23
- microdf/tests/conftest.py +9 -0
- microdf/tests/test_compare.py +48 -0
- microdf/tests/test_decile_rank.py +91 -0
- microdf/tests/test_inequality.py +20 -0
- microdf/tests/test_io.py +10 -0
- microdf/tests/test_microseries_dataframe.py +290 -0
- microdf/tests/test_optional_dependency.py +54 -0
- microdf/tests/test_poverty.py +73 -0
- microdf/tests/test_quantile_chg.py +14 -0
- microdf/tests/test_tax.py +54 -0
- microdf/tests/test_taxcalc.py +25 -0
- microdf/tests/test_utils.py +39 -0
- microdf/tests/test_weighted.py +74 -0
- microdf/ubi.py +17 -15
- microdf/utils.py +14 -15
- microdf/weighted.py +61 -31
- microdf_python-0.4.5.dist-info/METADATA +47 -0
- microdf_python-0.4.5.dist-info/RECORD +35 -0
- {microdf_python-0.4.3.dist-info → microdf_python-0.4.5.dist-info}/WHEEL +1 -1
- microdf/chart_utils.py +0 -32
- microdf/charts.py +0 -56
- microdf/generic.py +0 -791
- microdf/style.py +0 -54
- microdf_python-0.4.3.dist-info/METADATA +0 -18
- microdf_python-0.4.3.dist-info/RECORD +0 -24
- {microdf_python-0.4.3.dist-info → microdf_python-0.4.5.dist-info/licenses}/LICENSE +0 -0
- {microdf_python-0.4.3.dist-info → microdf_python-0.4.5.dist-info}/top_level.txt +0 -0
microdf/__init__.py
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
from .agg import agg, combine_base_reform, pctchg_base_reform
|
|
2
|
-
from .chart_utils import dollar_format, currency_format
|
|
3
|
-
from .charts import quantile_pct_chg_plot
|
|
4
2
|
from .concat import concat
|
|
5
3
|
from .constants import (
|
|
6
4
|
BENS,
|
|
@@ -32,21 +30,22 @@ from .inequality import (
|
|
|
32
30
|
gini,
|
|
33
31
|
t10_b50,
|
|
34
32
|
top_0_1_pct_share,
|
|
35
|
-
top_10_pct_share,
|
|
36
33
|
top_1_pct_share,
|
|
34
|
+
top_10_pct_share,
|
|
37
35
|
top_50_pct_share,
|
|
38
36
|
top_x_pct_share,
|
|
39
37
|
)
|
|
40
38
|
from .io import read_stata_zip
|
|
39
|
+
from .microdataframe import MicroDataFrame, MicroDataFrameGroupBy
|
|
40
|
+
from .microseries import MicroSeries, MicroSeriesGroupBy
|
|
41
41
|
from .poverty import (
|
|
42
|
-
|
|
43
|
-
poverty_rate,
|
|
42
|
+
deep_poverty_gap,
|
|
44
43
|
deep_poverty_rate,
|
|
44
|
+
fpl,
|
|
45
45
|
poverty_gap,
|
|
46
|
+
poverty_rate,
|
|
46
47
|
squared_poverty_gap,
|
|
47
|
-
deep_poverty_gap,
|
|
48
48
|
)
|
|
49
|
-
from .style import AXIS_COLOR, DPI, GRID_COLOR, TITLE_COLOR, set_plot_style
|
|
50
49
|
from .tax import mtr, tax_from_mtrs
|
|
51
50
|
from .taxcalc import (
|
|
52
51
|
add_weighted_metrics,
|
|
@@ -72,7 +71,6 @@ from .weighted import (
|
|
|
72
71
|
weighted_quantile,
|
|
73
72
|
weighted_sum,
|
|
74
73
|
)
|
|
75
|
-
from .generic import MicroDataFrame, MicroSeries
|
|
76
74
|
|
|
77
75
|
name = "microdf"
|
|
78
76
|
__version__ = "0.1.0"
|
|
@@ -82,11 +80,6 @@ __all__ = [
|
|
|
82
80
|
"combine_base_reform",
|
|
83
81
|
"pctchg_base_reform",
|
|
84
82
|
"agg",
|
|
85
|
-
# chart_utils.py
|
|
86
|
-
"dollar_format",
|
|
87
|
-
"currency_format",
|
|
88
|
-
# charts.py
|
|
89
|
-
"quantile_pct_chg_plot",
|
|
90
83
|
# concat.py
|
|
91
84
|
"concat",
|
|
92
85
|
# constants.py
|
|
@@ -133,12 +126,6 @@ __all__ = [
|
|
|
133
126
|
"poverty_gap",
|
|
134
127
|
"squared_poverty_gap",
|
|
135
128
|
"deep_poverty_gap",
|
|
136
|
-
# style.py
|
|
137
|
-
"AXIS_COLOR",
|
|
138
|
-
"DPI",
|
|
139
|
-
"GRID_COLOR",
|
|
140
|
-
"TITLE_COLOR",
|
|
141
|
-
"set_plot_style",
|
|
142
129
|
# tax.py
|
|
143
130
|
"mtr",
|
|
144
131
|
"tax_from_mtrs",
|
|
@@ -164,7 +151,10 @@ __all__ = [
|
|
|
164
151
|
"weighted_median",
|
|
165
152
|
"add_weighted_quantiles",
|
|
166
153
|
"quantile_chg",
|
|
167
|
-
#
|
|
154
|
+
# microseries.py
|
|
168
155
|
"MicroSeries",
|
|
156
|
+
"MicroSeriesGroupBy",
|
|
157
|
+
# microdataframe.py
|
|
169
158
|
"MicroDataFrame",
|
|
159
|
+
"MicroDataFrameGroupBy",
|
|
170
160
|
]
|
microdf/_optional.py
CHANGED
|
@@ -2,7 +2,7 @@ import distutils.version
|
|
|
2
2
|
import importlib
|
|
3
3
|
import types
|
|
4
4
|
import warnings
|
|
5
|
-
|
|
5
|
+
from typing import Optional, Union
|
|
6
6
|
|
|
7
7
|
# Adapted from:
|
|
8
8
|
# https://github.com/pandas-dev/pandas/blob/master/pandas/compat/_optional.py
|
|
@@ -31,14 +31,13 @@ def _get_version(module: types.ModuleType) -> str:
|
|
|
31
31
|
|
|
32
32
|
def import_optional_dependency(
|
|
33
33
|
name: str,
|
|
34
|
-
extra: str = "",
|
|
35
|
-
raise_on_missing: bool = True,
|
|
36
|
-
on_version: str = "raise",
|
|
37
|
-
):
|
|
38
|
-
"""Import an optional dependency.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
we raise.
|
|
34
|
+
extra: Optional[str] = "",
|
|
35
|
+
raise_on_missing: Optional[bool] = True,
|
|
36
|
+
on_version: Optional[str] = "raise",
|
|
37
|
+
) -> Union[types.ModuleType, None]:
|
|
38
|
+
"""Import an optional dependency. By default, if a dependency is missing an
|
|
39
|
+
ImportError with a nice message will be raised. If a dependency is present,
|
|
40
|
+
but too old, we raise.
|
|
42
41
|
|
|
43
42
|
:param name: The module name. This should be top-level only, so that the
|
|
44
43
|
version may be checked.
|
microdf/agg.py
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import pandas as pd
|
|
2
1
|
from typing import Optional
|
|
3
2
|
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
4
5
|
import microdf as mdf
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
def combine_base_reform(
|
|
8
9
|
base: pd.DataFrame,
|
|
9
10
|
reform: pd.DataFrame,
|
|
10
|
-
base_cols: Optional[list],
|
|
11
|
-
cols: Optional[list],
|
|
12
|
-
reform_cols: Optional[list],
|
|
11
|
+
base_cols: Optional[list] = None,
|
|
12
|
+
cols: Optional[list] = None,
|
|
13
|
+
reform_cols: Optional[list] = None,
|
|
13
14
|
) -> pd.DataFrame:
|
|
14
15
|
"""Combine base and reform with certain columns.
|
|
15
16
|
|
|
@@ -25,7 +26,6 @@ def combine_base_reform(
|
|
|
25
26
|
:type reform_cols: list, optional
|
|
26
27
|
:returns: DataFrame with columns for base ("_base") and reform ("_reform").
|
|
27
28
|
:rtype: pd.DataFrame
|
|
28
|
-
|
|
29
29
|
"""
|
|
30
30
|
all_base_cols = mdf.listify([base_cols] + [cols])
|
|
31
31
|
all_reform_cols = mdf.listify([reform_cols] + [cols])
|
|
@@ -35,17 +35,15 @@ def combine_base_reform(
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def pctchg_base_reform(combined: pd.DataFrame, metric: str) -> pd.Series:
|
|
38
|
-
"""Calculates the percentage change in a metric for a combined
|
|
39
|
-
dataset.
|
|
38
|
+
"""Calculates the percentage change in a metric for a combined dataset.
|
|
40
39
|
|
|
41
40
|
:param combined: Combined DataFrame with _base and _reform columns.
|
|
42
41
|
:type combined: pd.DataFrame
|
|
43
|
-
:param metric: String of the column to calculate the difference.
|
|
44
|
-
|
|
42
|
+
:param metric: String of the column to calculate the difference. Must exist
|
|
43
|
+
as metric_m_base and metric_m_reform in combined.
|
|
45
44
|
:type metric: str
|
|
46
45
|
:returns: Series with percentage change.
|
|
47
46
|
:rtype: pd.Series
|
|
48
|
-
|
|
49
47
|
"""
|
|
50
48
|
return combined[metric + "_m_reform"] / combined[metric + "_m_base"] - 1
|
|
51
49
|
|
|
@@ -55,8 +53,8 @@ def agg(
|
|
|
55
53
|
reform: pd.DataFrame,
|
|
56
54
|
groupby: str,
|
|
57
55
|
metrics: list,
|
|
58
|
-
base_metrics: Optional[list],
|
|
59
|
-
reform_metrics: Optional[list],
|
|
56
|
+
base_metrics: Optional[list] = None,
|
|
57
|
+
reform_metrics: Optional[list] = None,
|
|
60
58
|
) -> pd.DataFrame:
|
|
61
59
|
"""Aggregates differences between base and reform.
|
|
62
60
|
|
|
@@ -67,8 +65,8 @@ def agg(
|
|
|
67
65
|
:param groupby: Variable in base to group on.
|
|
68
66
|
:type groupby: str
|
|
69
67
|
:param metrics: List of variables to agg and calculate the % change of.
|
|
70
|
-
These should have associated weighted columns ending in _m in base
|
|
71
|
-
|
|
68
|
+
These should have associated weighted columns ending in _m in base and
|
|
69
|
+
reform.
|
|
72
70
|
:type metrics: list
|
|
73
71
|
:param base_metrics: List of variables from base to sum.
|
|
74
72
|
:type base_metrics: Optional[list]
|
|
@@ -76,7 +74,6 @@ def agg(
|
|
|
76
74
|
:type reform_metrics: Optional[list]
|
|
77
75
|
:returns: DataFrame with groupby and metrics, and _pctchg metrics.
|
|
78
76
|
:rtype: pd.DataFrame
|
|
79
|
-
|
|
80
77
|
"""
|
|
81
78
|
metrics = mdf.listify(metrics)
|
|
82
79
|
metrics_m = [i + "_m" for i in metrics]
|
microdf/concat.py
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import pandas as pd
|
|
2
1
|
import inspect
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
3
5
|
import microdf as mdf
|
|
6
|
+
from microdf.microdataframe import MicroDataFrame
|
|
4
7
|
|
|
5
8
|
|
|
6
|
-
def concat(*args, **kwargs):
|
|
7
|
-
"""Concatenates MicroDataFrame objects, preserving weights.
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
def concat(*args, **kwargs) -> "MicroDataFrame":
|
|
10
|
+
"""Concatenates MicroDataFrame objects, preserving weights. If
|
|
11
|
+
concatenating horizontally, the first set of weights are used. All args and
|
|
12
|
+
kwargs are passed to pd.concat.
|
|
10
13
|
|
|
11
14
|
:return: MicroDataFrame with concatenated weights.
|
|
12
15
|
:rtype: mdf.MicroDataFrame
|
microdf/custom_taxes.py
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
1
|
+
"""Functions and data for estimating taxes outside the income tax system.
|
|
2
|
+
|
|
3
3
|
Examples include value added tax, financial transaction tax, and carbon tax.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
from typing import Optional
|
|
7
7
|
|
|
8
8
|
import numpy as np
|
|
9
9
|
import pandas as pd
|
|
10
10
|
|
|
11
|
+
import microdf as mdf
|
|
11
12
|
|
|
12
13
|
# Source:
|
|
13
14
|
# https://www.taxpolicycenter.org/briefing-book/who-would-bear-burden-vat
|
|
@@ -35,24 +36,24 @@ FTT_INCIDENCE /= 100
|
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
def add_custom_tax(
|
|
38
|
-
df,
|
|
39
|
-
segment_income,
|
|
40
|
-
w,
|
|
41
|
-
base_income,
|
|
42
|
-
incidence,
|
|
43
|
-
name,
|
|
44
|
-
total=None,
|
|
45
|
-
ratio=None,
|
|
46
|
-
verbose=True,
|
|
47
|
-
):
|
|
39
|
+
df: pd.DataFrame,
|
|
40
|
+
segment_income: str,
|
|
41
|
+
w: str,
|
|
42
|
+
base_income: str,
|
|
43
|
+
incidence: pd.Series,
|
|
44
|
+
name: str,
|
|
45
|
+
total: Optional[float] = None,
|
|
46
|
+
ratio: Optional[float] = None,
|
|
47
|
+
verbose: Optional[bool] = True,
|
|
48
|
+
) -> None:
|
|
48
49
|
"""Add a custom tax based on incidence analysis driven by percentiles.
|
|
49
50
|
|
|
50
51
|
:param df: DataFrame.
|
|
51
52
|
:param segment_income: Income measure used to segment tax units into
|
|
52
|
-
|
|
53
|
+
quantiles.
|
|
53
54
|
:param w: Weight used to segment into quantiles (either s006 or XTOT_m).
|
|
54
55
|
:param base_income: Income measure by which incidence is multiplied to
|
|
55
|
-
|
|
56
|
+
estimate liability.
|
|
56
57
|
:param incidence: pandas Series indexed on the floor of an income
|
|
57
58
|
percentile, with values for the tax rate.
|
|
58
59
|
:param name: Name of the column to add.
|
|
@@ -60,13 +61,12 @@ def add_custom_tax(
|
|
|
60
61
|
liabilities are calculated only based on the incidence schedule.
|
|
61
62
|
(Default value = None)
|
|
62
63
|
:param ratio: Ratio to adjust the tax by, compared to the original tax.
|
|
63
|
-
This acts as a multiplier for the incidence argument.
|
|
64
|
-
|
|
64
|
+
This acts as a multiplier for the incidence argument. (Default value =
|
|
65
|
+
None)
|
|
65
66
|
:param verbose: Whether to print the tax adjustment factor if needed.
|
|
66
67
|
Defaults to True.
|
|
67
68
|
:returns: Nothing. Adds the column name to df representing the tax
|
|
68
69
|
liability. df is also sorted by segment_income.
|
|
69
|
-
|
|
70
70
|
"""
|
|
71
71
|
if ratio is not None:
|
|
72
72
|
incidence = incidence * ratio
|
|
@@ -95,14 +95,14 @@ def add_custom_tax(
|
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
def add_vat(
|
|
98
|
-
df,
|
|
99
|
-
segment_income="tpc_eci",
|
|
100
|
-
w="XTOT_m",
|
|
101
|
-
base_income="aftertax_income",
|
|
102
|
-
incidence=VAT_INCIDENCE,
|
|
103
|
-
name="vat",
|
|
104
|
-
**kwargs
|
|
105
|
-
):
|
|
98
|
+
df: pd.DataFrame,
|
|
99
|
+
segment_income: Optional[str] = "tpc_eci",
|
|
100
|
+
w: Optional[str] = "XTOT_m",
|
|
101
|
+
base_income: Optional[str] = "aftertax_income",
|
|
102
|
+
incidence: Optional[pd.Series] = VAT_INCIDENCE,
|
|
103
|
+
name: Optional[str] = "vat",
|
|
104
|
+
**kwargs,
|
|
105
|
+
) -> None:
|
|
106
106
|
"""Add value added tax based on incidence estimate from Tax Policy Center.
|
|
107
107
|
|
|
108
108
|
:param df: DataFrame with columns for tpc_eci, XTOT_m, and aftertax_income.
|
|
@@ -111,11 +111,9 @@ def add_vat(
|
|
|
111
111
|
:param w: Default value = "XTOT_m")
|
|
112
112
|
:param base_income: Default value = "aftertax_income")
|
|
113
113
|
:param incidence: Default value = VAT_INCIDENCE)
|
|
114
|
-
:param name: Default value = "vat")
|
|
115
|
-
|
|
116
|
-
:returns: Nothing. Adds vat to df.
|
|
117
|
-
df is also sorted by tpc_eci.
|
|
118
|
-
|
|
114
|
+
:param name: Default value = "vat") :param **kwargs: Other arguments passed
|
|
115
|
+
to add_custom_tax().
|
|
116
|
+
:returns: Nothing. Adds vat to df. df is also sorted by tpc_eci.
|
|
119
117
|
"""
|
|
120
118
|
add_custom_tax(
|
|
121
119
|
df, segment_income, w, base_income, incidence, name, **kwargs
|
|
@@ -123,14 +121,14 @@ def add_vat(
|
|
|
123
121
|
|
|
124
122
|
|
|
125
123
|
def add_carbon_tax(
|
|
126
|
-
df,
|
|
127
|
-
segment_income="tpc_eci",
|
|
128
|
-
w="XTOT_m",
|
|
129
|
-
base_income="aftertax_income",
|
|
130
|
-
incidence=CARBON_TAX_INCIDENCE,
|
|
131
|
-
name="carbon_tax",
|
|
132
|
-
**kwargs
|
|
133
|
-
):
|
|
124
|
+
df: pd.DataFrame,
|
|
125
|
+
segment_income: Optional[str] = "tpc_eci",
|
|
126
|
+
w: Optional[str] = "XTOT_m",
|
|
127
|
+
base_income: Optional[str] = "aftertax_income",
|
|
128
|
+
incidence: Optional[pd.Series] = CARBON_TAX_INCIDENCE,
|
|
129
|
+
name: Optional[str] = "carbon_tax",
|
|
130
|
+
**kwargs,
|
|
131
|
+
) -> None:
|
|
134
132
|
"""Add carbon tax based on incidence estimate from the US Treasury
|
|
135
133
|
Department.
|
|
136
134
|
|
|
@@ -140,11 +138,9 @@ def add_carbon_tax(
|
|
|
140
138
|
:param w: Default value = "XTOT_m")
|
|
141
139
|
:param base_income: Default value = "aftertax_income")
|
|
142
140
|
:param incidence: Default value = CARBON_TAX_INCIDENCE)
|
|
143
|
-
:param name: Default value = "carbon_tax")
|
|
144
|
-
|
|
145
|
-
:returns: Nothing. Adds carbon_tax to df.
|
|
146
|
-
df is also sorted by tpc_eci.
|
|
147
|
-
|
|
141
|
+
:param name: Default value = "carbon_tax") :param **kwargs: Other arguments
|
|
142
|
+
passed to add_custom_tax().
|
|
143
|
+
:returns: Nothing. Adds carbon_tax to df. df is also sorted by tpc_eci.
|
|
148
144
|
"""
|
|
149
145
|
add_custom_tax(
|
|
150
146
|
df, segment_income, w, base_income, incidence, name, **kwargs
|
|
@@ -152,14 +148,14 @@ def add_carbon_tax(
|
|
|
152
148
|
|
|
153
149
|
|
|
154
150
|
def add_ftt(
|
|
155
|
-
df,
|
|
156
|
-
segment_income="tpc_eci",
|
|
157
|
-
w="XTOT_m",
|
|
158
|
-
base_income="aftertax_income",
|
|
159
|
-
incidence=FTT_INCIDENCE,
|
|
160
|
-
name="ftt",
|
|
161
|
-
**kwargs
|
|
162
|
-
):
|
|
151
|
+
df: pd.DataFrame,
|
|
152
|
+
segment_income: Optional[str] = "tpc_eci",
|
|
153
|
+
w: Optional[str] = "XTOT_m",
|
|
154
|
+
base_income: Optional[str] = "aftertax_income",
|
|
155
|
+
incidence: Optional[pd.Series] = FTT_INCIDENCE,
|
|
156
|
+
name: Optional[str] = "ftt",
|
|
157
|
+
**kwargs,
|
|
158
|
+
) -> None:
|
|
163
159
|
"""Add financial transaction tax based on incidence estimate from Tax
|
|
164
160
|
Policy Center.
|
|
165
161
|
|
|
@@ -169,11 +165,9 @@ def add_ftt(
|
|
|
169
165
|
:param w: Default value = "XTOT_m")
|
|
170
166
|
:param base_income: Default value = "aftertax_income")
|
|
171
167
|
:param incidence: Default value = FTT_INCIDENCE)
|
|
172
|
-
:param name: Default value = "ftt")
|
|
173
|
-
|
|
174
|
-
:returns: Nothing. Adds ftt to df.
|
|
175
|
-
df is also sorted by tpc_eci.
|
|
176
|
-
|
|
168
|
+
:param name: Default value = "ftt") :param **kwargs: Other arguments passed
|
|
169
|
+
to add_custom_tax().
|
|
170
|
+
:returns: Nothing. Adds ftt to df. df is also sorted by tpc_eci.
|
|
177
171
|
"""
|
|
178
172
|
add_custom_tax(
|
|
179
173
|
df, segment_income, w, base_income, incidence, name, **kwargs
|
microdf/income_measures.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
1
3
|
import microdf as mdf
|
|
2
4
|
|
|
3
5
|
# See
|
|
@@ -5,7 +7,7 @@ import microdf as mdf
|
|
|
5
7
|
# for a comparison of income measures used here.
|
|
6
8
|
|
|
7
9
|
|
|
8
|
-
def cash_income(df):
|
|
10
|
+
def cash_income(df: pd.DataFrame) -> pd.Series:
|
|
9
11
|
"""Calculates income after taxes and cash transfers.
|
|
10
12
|
|
|
11
13
|
Defined as aftertax_income minus non-cash benefits.
|
|
@@ -22,7 +24,6 @@ def cash_income(df):
|
|
|
22
24
|
* vet_ben
|
|
23
25
|
* wic_ben
|
|
24
26
|
:returns: A pandas Series with the cash income for each row in df.
|
|
25
|
-
|
|
26
27
|
"""
|
|
27
28
|
return (
|
|
28
29
|
df.aftertax_income
|
|
@@ -38,27 +39,25 @@ def cash_income(df):
|
|
|
38
39
|
)
|
|
39
40
|
|
|
40
41
|
|
|
41
|
-
def tpc_eci(df):
|
|
42
|
+
def tpc_eci(df: pd.DataFrame) -> pd.Series:
|
|
42
43
|
"""Approximates Tax Policy Center's Expanded Cash Income measure.
|
|
43
44
|
|
|
44
45
|
Subtracts WIC, housing assistance, veteran's benefits, Medicare, and
|
|
45
|
-
Medicaid from expanded_income. ECI adds income measures not modeled in
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
Medicaid from expanded_income. ECI adds income measures not modeled in Tax-
|
|
47
|
+
Calculator, so these are ignored and will create a discrepancy compared to
|
|
48
|
+
TPC's ECI.
|
|
48
49
|
|
|
49
50
|
:param df: DataFrame with columns from Tax-Calculator.
|
|
50
51
|
:returns: pandas Series with TPC's ECI.
|
|
51
|
-
|
|
52
52
|
"""
|
|
53
53
|
return df.expanded_income - df[mdf.ECI_REMOVE_COLS].sum(axis=1)
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
def market_income(df):
|
|
57
|
-
"""Approximates CBO's market income concept, which is income
|
|
58
|
-
|
|
56
|
+
def market_income(df: pd.DataFrame) -> pd.Series:
|
|
57
|
+
"""Approximates CBO's market income concept, which is income before social
|
|
58
|
+
insurance, means-tested transfers, and taxes.
|
|
59
59
|
|
|
60
60
|
:param df: DataFrame with expanded_income and benefits.
|
|
61
61
|
:returns: pandas Series of the same length as df.
|
|
62
|
-
|
|
63
62
|
"""
|
|
64
63
|
return df.expanded_income - df[mdf.BENS].sum(axis=1)
|
microdf/inequality.py
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
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
|
-
def gini(
|
|
9
|
+
def gini(
|
|
10
|
+
df: pd.DataFrame,
|
|
11
|
+
col: str,
|
|
12
|
+
w: Optional[str] = None,
|
|
13
|
+
negatives: Optional[str] = None,
|
|
14
|
+
groupby: Optional[Union[str, List[str]]] = None,
|
|
15
|
+
) -> float:
|
|
7
16
|
"""Calculates Gini index.
|
|
8
17
|
|
|
9
18
|
:param df: DataFrame.
|
|
@@ -18,10 +27,14 @@ def gini(df, col, w=None, negatives=None, groupby=None):
|
|
|
18
27
|
Defaults to None, which leaves negative values as they are.
|
|
19
28
|
:param groupby: Column, or list of columns, to group by.
|
|
20
29
|
:returns: A float, the Gini index.
|
|
21
|
-
|
|
22
30
|
"""
|
|
23
31
|
|
|
24
|
-
def _gini(
|
|
32
|
+
def _gini(
|
|
33
|
+
df: pd.DataFrame,
|
|
34
|
+
col: str,
|
|
35
|
+
w: Optional[str] = None,
|
|
36
|
+
negatives: Optional[str] = None,
|
|
37
|
+
) -> float:
|
|
25
38
|
# Requires float numpy arrays (not pandas Series or lists) to work.
|
|
26
39
|
x = np.array(df[col]).astype("float")
|
|
27
40
|
if negatives == "zero":
|
|
@@ -50,7 +63,13 @@ def gini(df, col, w=None, negatives=None, groupby=None):
|
|
|
50
63
|
return df.groupby(groupby).apply(lambda x: _gini(x, col, w, negatives))
|
|
51
64
|
|
|
52
65
|
|
|
53
|
-
def top_x_pct_share(
|
|
66
|
+
def top_x_pct_share(
|
|
67
|
+
df: pd.DataFrame,
|
|
68
|
+
col: str,
|
|
69
|
+
top_x_pct: float,
|
|
70
|
+
w: Optional[str] = None,
|
|
71
|
+
groupby: Optional[Union[str, List[str]]] = None,
|
|
72
|
+
) -> float:
|
|
54
73
|
"""Calculates top x% share.
|
|
55
74
|
|
|
56
75
|
:param df: DataFrame.
|
|
@@ -59,10 +78,11 @@ def top_x_pct_share(df, col, top_x_pct, w=None, groupby=None):
|
|
|
59
78
|
:param w: Column representing weight in df.
|
|
60
79
|
:param groupby: Column, or list of columns, to group by.
|
|
61
80
|
:returns: The share of w-weighted val held by the top x%.
|
|
62
|
-
|
|
63
81
|
"""
|
|
64
82
|
|
|
65
|
-
def _top_x_pct_share(
|
|
83
|
+
def _top_x_pct_share(
|
|
84
|
+
df: pd.DataFrame, col: str, top_x_pct: float, w: Optional[str] = None
|
|
85
|
+
) -> float:
|
|
66
86
|
threshold = mdf.weighted_quantile(df, col, w, 1 - top_x_pct)
|
|
67
87
|
top_x_pct_sum = mdf.weighted_sum(df[df[col] >= threshold], col, w)
|
|
68
88
|
total_sum = mdf.weighted_sum(df, col, w)
|
|
@@ -75,7 +95,13 @@ def top_x_pct_share(df, col, top_x_pct, w=None, groupby=None):
|
|
|
75
95
|
)
|
|
76
96
|
|
|
77
97
|
|
|
78
|
-
def bottom_x_pct_share(
|
|
98
|
+
def bottom_x_pct_share(
|
|
99
|
+
df: pd.DataFrame,
|
|
100
|
+
col: str,
|
|
101
|
+
bottom_x_pct: float,
|
|
102
|
+
w: Optional[str] = None,
|
|
103
|
+
groupby: Optional[Union[str, List[str]]] = None,
|
|
104
|
+
) -> float:
|
|
79
105
|
"""Calculates bottom x% share.
|
|
80
106
|
|
|
81
107
|
:param df: DataFrame.
|
|
@@ -84,12 +110,16 @@ def bottom_x_pct_share(df, col, bottom_x_pct, w=None, groupby=None):
|
|
|
84
110
|
:param w: Column representing weight in df.
|
|
85
111
|
:param groupby: Column, or list of columns, to group by.
|
|
86
112
|
:returns: The share of w-weighted val held by the bottom x%.
|
|
87
|
-
|
|
88
113
|
"""
|
|
89
114
|
return 1 - top_x_pct_share(df, col, 1 - bottom_x_pct, w, groupby)
|
|
90
115
|
|
|
91
116
|
|
|
92
|
-
def bottom_50_pct_share(
|
|
117
|
+
def bottom_50_pct_share(
|
|
118
|
+
df: pd.DataFrame,
|
|
119
|
+
col: str,
|
|
120
|
+
w: Optional[str] = None,
|
|
121
|
+
groupby: Optional[Union[str, List[str]]] = None,
|
|
122
|
+
) -> float:
|
|
93
123
|
"""Calculates bottom 50% share.
|
|
94
124
|
|
|
95
125
|
:param df: DataFrame.
|
|
@@ -97,12 +127,16 @@ def bottom_50_pct_share(df, col, w=None, groupby=None):
|
|
|
97
127
|
:param w: Column representing weight in df.
|
|
98
128
|
:param groupby: Column, or list of columns, to group by.
|
|
99
129
|
:returns: The share of w-weighted val held by the bottom 50%.
|
|
100
|
-
|
|
101
130
|
"""
|
|
102
131
|
return bottom_x_pct_share(df, col, 0.5, w, groupby)
|
|
103
132
|
|
|
104
133
|
|
|
105
|
-
def top_50_pct_share(
|
|
134
|
+
def top_50_pct_share(
|
|
135
|
+
df: pd.DataFrame,
|
|
136
|
+
col: str,
|
|
137
|
+
w: Optional[str] = None,
|
|
138
|
+
groupby: Optional[Union[str, List[str]]] = None,
|
|
139
|
+
) -> float:
|
|
106
140
|
"""Calculates top 50% share.
|
|
107
141
|
|
|
108
142
|
:param df: DataFrame.
|
|
@@ -110,12 +144,16 @@ def top_50_pct_share(df, col, w=None, groupby=None):
|
|
|
110
144
|
:param w: Column representing weight in df.
|
|
111
145
|
:param groupby: Column, or list of columns, to group by.
|
|
112
146
|
:returns: The share of w-weighted val held by the top 50%.
|
|
113
|
-
|
|
114
147
|
"""
|
|
115
148
|
return top_x_pct_share(df, col, 0.5, w, groupby)
|
|
116
149
|
|
|
117
150
|
|
|
118
|
-
def top_10_pct_share(
|
|
151
|
+
def top_10_pct_share(
|
|
152
|
+
df: pd.DataFrame,
|
|
153
|
+
col: str,
|
|
154
|
+
w: Optional[str] = None,
|
|
155
|
+
groupby: Optional[Union[str, List[str]]] = None,
|
|
156
|
+
) -> float:
|
|
119
157
|
"""Calculates top 10% share.
|
|
120
158
|
|
|
121
159
|
:param df: DataFrame.
|
|
@@ -123,12 +161,16 @@ def top_10_pct_share(df, col, w=None, groupby=None):
|
|
|
123
161
|
:param w: Column representing weight in df.
|
|
124
162
|
:param groupby: Column, or list of columns, to group by.
|
|
125
163
|
:returns: The share of w-weighted val held by the top 10%.
|
|
126
|
-
|
|
127
164
|
"""
|
|
128
165
|
return top_x_pct_share(df, col, 0.1, w, groupby)
|
|
129
166
|
|
|
130
167
|
|
|
131
|
-
def top_1_pct_share(
|
|
168
|
+
def top_1_pct_share(
|
|
169
|
+
df: pd.DataFrame,
|
|
170
|
+
col: str,
|
|
171
|
+
w: Optional[str] = None,
|
|
172
|
+
groupby: Optional[Union[str, List[str]]] = None,
|
|
173
|
+
) -> float:
|
|
132
174
|
"""Calculates top 1% share.
|
|
133
175
|
|
|
134
176
|
:param df: DataFrame.
|
|
@@ -136,12 +178,16 @@ def top_1_pct_share(df, col, w=None, groupby=None):
|
|
|
136
178
|
:param w: Column representing weight in df.
|
|
137
179
|
:param groupby: Column, or list of columns, to group by.
|
|
138
180
|
:returns: The share of w-weighted val held by the top 1%.
|
|
139
|
-
|
|
140
181
|
"""
|
|
141
182
|
return top_x_pct_share(df, col, 0.01, w, groupby)
|
|
142
183
|
|
|
143
184
|
|
|
144
|
-
def top_0_1_pct_share(
|
|
185
|
+
def top_0_1_pct_share(
|
|
186
|
+
df: pd.DataFrame,
|
|
187
|
+
col: str,
|
|
188
|
+
w: Optional[str] = None,
|
|
189
|
+
groupby: Optional[Union[str, List[str]]] = None,
|
|
190
|
+
) -> float:
|
|
145
191
|
"""Calculates top 0.1% share.
|
|
146
192
|
|
|
147
193
|
:param df: DataFrame.
|
|
@@ -149,21 +195,24 @@ def top_0_1_pct_share(df, col, w=None, groupby=None):
|
|
|
149
195
|
:param w: Column representing weight in df.
|
|
150
196
|
:param groupby: Column, or list of columns, to group by.
|
|
151
197
|
:returns: The share of w-weighted val held by the top 0.1%.
|
|
152
|
-
|
|
153
198
|
"""
|
|
154
199
|
return top_x_pct_share(df, col, 0.001, w, groupby)
|
|
155
200
|
|
|
156
201
|
|
|
157
|
-
def t10_b50(
|
|
202
|
+
def t10_b50(
|
|
203
|
+
df: pd.DataFrame,
|
|
204
|
+
col: str,
|
|
205
|
+
w: Optional[str] = None,
|
|
206
|
+
groupby: Optional[Union[str, List[str]]] = None,
|
|
207
|
+
) -> float:
|
|
158
208
|
"""Calculates ratio between the top 10% and bottom 50% shares.
|
|
159
209
|
|
|
160
210
|
:param df: DataFrame.
|
|
161
211
|
:param col: Name of column in df representing value.
|
|
162
212
|
:param w: Column representing weight in df.
|
|
163
213
|
:param groupby: Column, or list of columns, to group by.
|
|
164
|
-
:returns: The share of w-weighted val held by the top 10% divided by
|
|
165
|
-
|
|
166
|
-
|
|
214
|
+
:returns: The share of w-weighted val held by the top 10% divided by the
|
|
215
|
+
share of w-weighted val held by the bottom 50%.
|
|
167
216
|
"""
|
|
168
217
|
t10 = top_10_pct_share(df, col, w, groupby)
|
|
169
218
|
b50 = bottom_50_pct_share(df, col, w, groupby)
|
microdf/io.py
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import io
|
|
2
2
|
import zipfile
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
import pandas as pd
|
|
5
|
+
import requests
|
|
5
6
|
|
|
6
7
|
HEADER = {
|
|
7
|
-
"User-Agent":
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
}
|
|
8
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) "
|
|
9
|
+
+ "AppleWebKit/537.36 (KHTML, like Gecko) "
|
|
10
|
+
+ "Chrome/50.0.2661.102 Safari/537.36"
|
|
11
|
+
}
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def read_stata_zip(url: str, **kwargs) -> pd.DataFrame:
|
|
@@ -19,11 +19,9 @@ def read_stata_zip(url: str, **kwargs) -> pd.DataFrame:
|
|
|
19
19
|
Pending native support in
|
|
20
20
|
https://github.com/pandas-dev/pandas/issues/26599.
|
|
21
21
|
|
|
22
|
-
:param url: URL string of .zip file containing a single
|
|
23
|
-
|
|
24
|
-
:param **kwargs: Arguments passed to pandas.read_stata().
|
|
22
|
+
:param url: URL string of .zip file containing a single .dta file. :param
|
|
23
|
+
**kwargs: Arguments passed to pandas.read_stata().
|
|
25
24
|
:returns: DataFrame.
|
|
26
|
-
|
|
27
25
|
"""
|
|
28
26
|
r = requests.get(url, headers=HEADER)
|
|
29
27
|
data = io.BytesIO(r.content)
|