microdf-python 0.4.5__py3-none-any.whl → 1.0.0__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 +0 -146
- microdf/tests/test_microseries_dataframe.py +0 -18
- microdf_python-1.0.0.dist-info/METADATA +75 -0
- microdf_python-1.0.0.dist-info/RECORD +10 -0
- microdf/_optional.py +0 -85
- microdf/agg.py +0 -90
- microdf/concat.py +0 -29
- microdf/constants.py +0 -40
- microdf/custom_taxes.py +0 -174
- microdf/income_measures.py +0 -63
- microdf/inequality.py +0 -219
- microdf/io.py +0 -30
- microdf/poverty.py +0 -136
- microdf/tax.py +0 -78
- microdf/taxcalc.py +0 -161
- microdf/tests/test_compare.py +0 -48
- microdf/tests/test_decile_rank.py +0 -91
- microdf/tests/test_inequality.py +0 -20
- microdf/tests/test_io.py +0 -10
- microdf/tests/test_optional_dependency.py +0 -54
- microdf/tests/test_poverty.py +0 -73
- microdf/tests/test_quantile_chg.py +0 -14
- microdf/tests/test_tax.py +0 -54
- microdf/tests/test_taxcalc.py +0 -25
- microdf/tests/test_utils.py +0 -39
- microdf/tests/test_weighted.py +0 -74
- microdf/ubi.py +0 -46
- microdf/utils.py +0 -73
- microdf/weighted.py +0 -235
- microdf_python-0.4.5.dist-info/METADATA +0 -47
- microdf_python-0.4.5.dist-info/RECORD +0 -35
- {microdf_python-0.4.5.dist-info → microdf_python-1.0.0.dist-info}/WHEEL +0 -0
- {microdf_python-0.4.5.dist-info → microdf_python-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {microdf_python-0.4.5.dist-info → microdf_python-1.0.0.dist-info}/top_level.txt +0 -0
microdf/__init__.py
CHANGED
|
@@ -1,156 +1,10 @@
|
|
|
1
|
-
from .agg import agg, combine_base_reform, pctchg_base_reform
|
|
2
|
-
from .concat import concat
|
|
3
|
-
from .constants import (
|
|
4
|
-
BENS,
|
|
5
|
-
ECI_REMOVE_COLS,
|
|
6
|
-
HOUSING_CASH_SHARE,
|
|
7
|
-
MCAID_CASH_SHARE,
|
|
8
|
-
MCARE_CASH_SHARE,
|
|
9
|
-
MED_BENS,
|
|
10
|
-
OTHER_CASH_SHARE,
|
|
11
|
-
SNAP_CASH_SHARE,
|
|
12
|
-
SSI_CASH_SHARE,
|
|
13
|
-
TANF_CASH_SHARE,
|
|
14
|
-
VET_CASH_SHARE,
|
|
15
|
-
WIC_CASH_SHARE,
|
|
16
|
-
)
|
|
17
|
-
from .custom_taxes import (
|
|
18
|
-
CARBON_TAX_INCIDENCE,
|
|
19
|
-
FTT_INCIDENCE,
|
|
20
|
-
VAT_INCIDENCE,
|
|
21
|
-
add_carbon_tax,
|
|
22
|
-
add_custom_tax,
|
|
23
|
-
add_ftt,
|
|
24
|
-
add_vat,
|
|
25
|
-
)
|
|
26
|
-
from .income_measures import cash_income, market_income, tpc_eci
|
|
27
|
-
from .inequality import (
|
|
28
|
-
bottom_50_pct_share,
|
|
29
|
-
bottom_x_pct_share,
|
|
30
|
-
gini,
|
|
31
|
-
t10_b50,
|
|
32
|
-
top_0_1_pct_share,
|
|
33
|
-
top_1_pct_share,
|
|
34
|
-
top_10_pct_share,
|
|
35
|
-
top_50_pct_share,
|
|
36
|
-
top_x_pct_share,
|
|
37
|
-
)
|
|
38
|
-
from .io import read_stata_zip
|
|
39
1
|
from .microdataframe import MicroDataFrame, MicroDataFrameGroupBy
|
|
40
2
|
from .microseries import MicroSeries, MicroSeriesGroupBy
|
|
41
|
-
from .poverty import (
|
|
42
|
-
deep_poverty_gap,
|
|
43
|
-
deep_poverty_rate,
|
|
44
|
-
fpl,
|
|
45
|
-
poverty_gap,
|
|
46
|
-
poverty_rate,
|
|
47
|
-
squared_poverty_gap,
|
|
48
|
-
)
|
|
49
|
-
from .tax import mtr, tax_from_mtrs
|
|
50
|
-
from .taxcalc import (
|
|
51
|
-
add_weighted_metrics,
|
|
52
|
-
calc_df,
|
|
53
|
-
n65,
|
|
54
|
-
recalculate,
|
|
55
|
-
static_baseline_calc,
|
|
56
|
-
)
|
|
57
|
-
from .ubi import ubi_or_bens
|
|
58
|
-
from .utils import (
|
|
59
|
-
cartesian_product,
|
|
60
|
-
dedup_list,
|
|
61
|
-
flatten,
|
|
62
|
-
listify,
|
|
63
|
-
ordinal_label,
|
|
64
|
-
)
|
|
65
|
-
from .weighted import (
|
|
66
|
-
add_weighted_quantiles,
|
|
67
|
-
quantile_chg,
|
|
68
|
-
weight,
|
|
69
|
-
weighted_mean,
|
|
70
|
-
weighted_median,
|
|
71
|
-
weighted_quantile,
|
|
72
|
-
weighted_sum,
|
|
73
|
-
)
|
|
74
3
|
|
|
75
4
|
name = "microdf"
|
|
76
5
|
__version__ = "0.1.0"
|
|
77
6
|
|
|
78
7
|
__all__ = [
|
|
79
|
-
# agg.py
|
|
80
|
-
"combine_base_reform",
|
|
81
|
-
"pctchg_base_reform",
|
|
82
|
-
"agg",
|
|
83
|
-
# concat.py
|
|
84
|
-
"concat",
|
|
85
|
-
# constants.py
|
|
86
|
-
"BENS",
|
|
87
|
-
"ECI_REMOVE_COLS",
|
|
88
|
-
"HOUSING_CASH_SHARE",
|
|
89
|
-
"MCAID_CASH_SHARE",
|
|
90
|
-
"MCARE_CASH_SHARE",
|
|
91
|
-
"MED_BENS",
|
|
92
|
-
"OTHER_CASH_SHARE",
|
|
93
|
-
"SNAP_CASH_SHARE",
|
|
94
|
-
"SSI_CASH_SHARE",
|
|
95
|
-
"TANF_CASH_SHARE",
|
|
96
|
-
"VET_CASH_SHARE",
|
|
97
|
-
"WIC_CASH_SHARE",
|
|
98
|
-
# custom_taxes.py
|
|
99
|
-
"CARBON_TAX_INCIDENCE",
|
|
100
|
-
"FTT_INCIDENCE",
|
|
101
|
-
"VAT_INCIDENCE",
|
|
102
|
-
"add_custom_tax",
|
|
103
|
-
"add_vat",
|
|
104
|
-
"add_carbon_tax",
|
|
105
|
-
"add_ftt",
|
|
106
|
-
# income_measures.py
|
|
107
|
-
"cash_income",
|
|
108
|
-
"tpc_eci",
|
|
109
|
-
"market_income",
|
|
110
|
-
# inequality.py
|
|
111
|
-
"gini",
|
|
112
|
-
"top_x_pct_share",
|
|
113
|
-
"bottom_x_pct_share",
|
|
114
|
-
"bottom_50_pct_share",
|
|
115
|
-
"top_10_pct_share",
|
|
116
|
-
"top_1_pct_share",
|
|
117
|
-
"top_0_1_pct_share",
|
|
118
|
-
"top_50_pct_share",
|
|
119
|
-
"t10_b50",
|
|
120
|
-
# io.py
|
|
121
|
-
"read_stata_zip",
|
|
122
|
-
# poverty.py
|
|
123
|
-
"fpl",
|
|
124
|
-
"poverty_rate",
|
|
125
|
-
"deep_poverty_rate",
|
|
126
|
-
"poverty_gap",
|
|
127
|
-
"squared_poverty_gap",
|
|
128
|
-
"deep_poverty_gap",
|
|
129
|
-
# tax.py
|
|
130
|
-
"mtr",
|
|
131
|
-
"tax_from_mtrs",
|
|
132
|
-
# taxcalc.py
|
|
133
|
-
"static_baseline_calc",
|
|
134
|
-
"add_weighted_metrics",
|
|
135
|
-
"n65",
|
|
136
|
-
"calc_df",
|
|
137
|
-
"recalculate",
|
|
138
|
-
# ubi.py
|
|
139
|
-
"ubi_or_bens",
|
|
140
|
-
# utils.py
|
|
141
|
-
"ordinal_label",
|
|
142
|
-
"dedup_list",
|
|
143
|
-
"listify",
|
|
144
|
-
"flatten",
|
|
145
|
-
"cartesian_product",
|
|
146
|
-
# weighted.py
|
|
147
|
-
"weight",
|
|
148
|
-
"weighted_sum",
|
|
149
|
-
"weighted_mean",
|
|
150
|
-
"weighted_quantile",
|
|
151
|
-
"weighted_median",
|
|
152
|
-
"add_weighted_quantiles",
|
|
153
|
-
"quantile_chg",
|
|
154
8
|
# microseries.py
|
|
155
9
|
"MicroSeries",
|
|
156
10
|
"MicroSeriesGroupBy",
|
|
@@ -105,24 +105,6 @@ def test_multiple_groupby() -> None:
|
|
|
105
105
|
assert (df.groupby(["x", "y"]).z.sum() == np.array([5, 6])).all()
|
|
106
106
|
|
|
107
107
|
|
|
108
|
-
def test_concat() -> None:
|
|
109
|
-
df1 = mdf.MicroDataFrame({"x": [1, 2]}, weights=[3, 4])
|
|
110
|
-
df2 = mdf.MicroDataFrame({"y": [5, 6]}, weights=[7, 8])
|
|
111
|
-
# Verify that pd.concat returns DataFrame (probably no way to fix this).
|
|
112
|
-
pd_long = pd.concat([df1, df2])
|
|
113
|
-
assert isinstance(pd_long, pd.DataFrame)
|
|
114
|
-
assert not isinstance(pd_long, mdf.MicroDataFrame)
|
|
115
|
-
# Verify that mdf.concat works.
|
|
116
|
-
mdf_long = mdf.concat([df1, df2])
|
|
117
|
-
assert isinstance(mdf_long, mdf.MicroDataFrame)
|
|
118
|
-
# Weights should be preserved.
|
|
119
|
-
assert mdf_long.weights.equals(pd.concat([df1.weights, df2.weights]))
|
|
120
|
-
# Verify it works horizontally too (take the first set of weights).
|
|
121
|
-
mdf_wide = mdf.concat([df1, df2], axis=1)
|
|
122
|
-
assert isinstance(mdf_wide, mdf.MicroDataFrame)
|
|
123
|
-
assert mdf_wide.weights.equals(df1.weights)
|
|
124
|
-
|
|
125
|
-
|
|
126
108
|
def test_set_index() -> None:
|
|
127
109
|
d = mdf.MicroDataFrame(dict(x=[1, 2, 3]), weights=[4, 5, 6])
|
|
128
110
|
assert d.x.__class__ == MicroSeries
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: microdf-python
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Weighted pandas DataFrames and Series for survey microdata
|
|
5
|
+
Author-email: Max Ghenis <max@ubicenter.org>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: numpy
|
|
11
|
+
Requires-Dist: pandas
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: codecov; extra == "dev"
|
|
14
|
+
Requires-Dist: flake8; extra == "dev"
|
|
15
|
+
Requires-Dist: flake8-pyproject; extra == "dev"
|
|
16
|
+
Requires-Dist: black; extra == "dev"
|
|
17
|
+
Requires-Dist: docformatter; extra == "dev"
|
|
18
|
+
Requires-Dist: isort; extra == "dev"
|
|
19
|
+
Requires-Dist: linecheck; extra == "dev"
|
|
20
|
+
Requires-Dist: pytest; extra == "dev"
|
|
21
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
22
|
+
Requires-Dist: setuptools; extra == "dev"
|
|
23
|
+
Provides-Extra: docs
|
|
24
|
+
Requires-Dist: jupyter_book; extra == "docs"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
[](https://github.com/PolicyEngine/microdf/actions)
|
|
28
|
+
[](https://codecov.io/gh/PolicyEngine/microdf)
|
|
29
|
+
|
|
30
|
+
# microdf
|
|
31
|
+
Weighted pandas DataFrames and Series for survey microdata analysis.
|
|
32
|
+
|
|
33
|
+
## Overview
|
|
34
|
+
microdf provides `MicroDataFrame` and `MicroSeries` classes that extend pandas functionality with integrated weighting support, essential for accurate survey data analysis.
|
|
35
|
+
|
|
36
|
+
## Key Features
|
|
37
|
+
- **MicroDataFrame**: A pandas DataFrame with an integrated weight column
|
|
38
|
+
- **MicroSeries**: A pandas Series with integrated weights
|
|
39
|
+
- **Weighted operations**: All aggregations (sum, mean, median, etc.) automatically use weights
|
|
40
|
+
- **Inequality metrics**: Built-in Gini coefficient calculation
|
|
41
|
+
- **Poverty analysis**: Integrated poverty rate and gap calculations
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
Install with:
|
|
45
|
+
|
|
46
|
+
pip install microdf-python
|
|
47
|
+
|
|
48
|
+
Or for development:
|
|
49
|
+
|
|
50
|
+
pip install git+https://github.com/PolicyEngine/microdf.git
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
```python
|
|
54
|
+
import microdf as mdf
|
|
55
|
+
import pandas as pd
|
|
56
|
+
|
|
57
|
+
# Create sample data with weights
|
|
58
|
+
df = pd.DataFrame({
|
|
59
|
+
'income': [10_000, 20_000, 30_000, 40_000, 50_000],
|
|
60
|
+
'weights': [1, 2, 3, 2, 1]
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
# Create a MicroDataFrame
|
|
64
|
+
mdf_df = mdf.MicroDataFrame(df, weights='weights')
|
|
65
|
+
|
|
66
|
+
# All operations are weight-aware
|
|
67
|
+
print(mdf_df.income.mean()) # Weighted mean
|
|
68
|
+
print(mdf_df.income.gini()) # Gini coefficient
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Questions
|
|
72
|
+
Contact the maintainer, Max Ghenis (max@policyengine.org).
|
|
73
|
+
|
|
74
|
+
## Citation
|
|
75
|
+
You may cite the source of your analysis as "microdf release #.#.#, author's calculations."
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
microdf/__init__.py,sha256=ldRvhE8t4cD7qlxQ5yLndMnWEUYpDw4m-jCDV_psXkY,319
|
|
2
|
+
microdf/microdataframe.py,sha256=5jYQ76PYjndACJO6hsJCaCL0g0clAsQbWltVcPtJ7yU,18202
|
|
3
|
+
microdf/microseries.py,sha256=URFT9bqry5Q_qezl3ge9lW8FSbQ6qg0XRaFQwN0IkwQ,22130
|
|
4
|
+
microdf/tests/conftest.py,sha256=u-EMyX1-u_nM-YO0RJYCzYHQDXxUI2WQE6GkyJlErqg,150
|
|
5
|
+
microdf/tests/test_microseries_dataframe.py,sha256=QSxdJ0ZfXO1XZgER9LRqDt5SGpVGyME03gefixH6spk,8855
|
|
6
|
+
microdf_python-1.0.0.dist-info/licenses/LICENSE,sha256=uPs-ASYnzlldpf2z8jeRgQFeEH3FLhSuX0rw0OKWoDU,1067
|
|
7
|
+
microdf_python-1.0.0.dist-info/METADATA,sha256=mC0MB2zQwVaqrPXfmh9mIc1KX5lmSO1zS9ilCvZQQg0,2483
|
|
8
|
+
microdf_python-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
microdf_python-1.0.0.dist-info/top_level.txt,sha256=T2WFPTygQQMdS3GF8YpZ12DKfMGrspbZ3r7z-e3KfiM,8
|
|
10
|
+
microdf_python-1.0.0.dist-info/RECORD,,
|
microdf/_optional.py
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import distutils.version
|
|
2
|
-
import importlib
|
|
3
|
-
import types
|
|
4
|
-
import warnings
|
|
5
|
-
from typing import Optional, Union
|
|
6
|
-
|
|
7
|
-
# Adapted from:
|
|
8
|
-
# https://github.com/pandas-dev/pandas/blob/master/pandas/compat/_optional.py
|
|
9
|
-
|
|
10
|
-
VERSIONS = {
|
|
11
|
-
"taxcalc": "2.0.0",
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def _get_version(module: types.ModuleType) -> str:
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
:param module: types.ModuleType:
|
|
19
|
-
:param module: types.ModuleType:
|
|
20
|
-
|
|
21
|
-
"""
|
|
22
|
-
version = getattr(module, "__version__", None)
|
|
23
|
-
if version is None:
|
|
24
|
-
# xlrd uses a capitalized attribute name
|
|
25
|
-
version = getattr(module, "__VERSION__", None)
|
|
26
|
-
|
|
27
|
-
if version is None:
|
|
28
|
-
raise ImportError(f"Can't determine version for {module.__name__}")
|
|
29
|
-
return version
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def import_optional_dependency(
|
|
33
|
-
name: str,
|
|
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.
|
|
41
|
-
|
|
42
|
-
:param name: The module name. This should be top-level only, so that the
|
|
43
|
-
version may be checked.
|
|
44
|
-
:type name: str
|
|
45
|
-
:param extra: Additional text to include in the ImportError message.
|
|
46
|
-
:type extra: str
|
|
47
|
-
:param raise_on_missing: Whether to raise if the optional dependency is
|
|
48
|
-
not found. When False and the module is not present, None is returned.
|
|
49
|
-
:type raise_on_missing: bool, default True
|
|
50
|
-
:param on_version: What to do when a dependency's version is too old.
|
|
51
|
-
* raise : Raise an ImportError
|
|
52
|
-
* warn : Warn that the version is too old. Returns None
|
|
53
|
-
* ignore: Return the module, even if the version is too old.
|
|
54
|
-
It's expected that users validate the version locally when
|
|
55
|
-
:type on_version: str {'raise', 'warn'}
|
|
56
|
-
"""
|
|
57
|
-
msg = (
|
|
58
|
-
f"Missing optional dependency '{name}'. {extra} "
|
|
59
|
-
f"Use pip or conda to install {name}."
|
|
60
|
-
)
|
|
61
|
-
try:
|
|
62
|
-
module = importlib.import_module(name)
|
|
63
|
-
except ImportError:
|
|
64
|
-
if raise_on_missing:
|
|
65
|
-
raise ImportError(msg) from None
|
|
66
|
-
else:
|
|
67
|
-
return None
|
|
68
|
-
|
|
69
|
-
minimum_version = VERSIONS.get(name)
|
|
70
|
-
if minimum_version:
|
|
71
|
-
version = _get_version(module)
|
|
72
|
-
if distutils.version.LooseVersion(version) < minimum_version:
|
|
73
|
-
assert on_version in {"warn", "raise", "ignore"}
|
|
74
|
-
msg = (
|
|
75
|
-
f"microdf requires version '{minimum_version}' or newer of "
|
|
76
|
-
f"'{name}' "
|
|
77
|
-
f"(version '{version}' currently installed)."
|
|
78
|
-
)
|
|
79
|
-
if on_version == "warn":
|
|
80
|
-
warnings.warn(msg, UserWarning)
|
|
81
|
-
return None
|
|
82
|
-
elif on_version == "raise":
|
|
83
|
-
raise ImportError(msg)
|
|
84
|
-
|
|
85
|
-
return module
|
microdf/agg.py
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
|
-
import pandas as pd
|
|
4
|
-
|
|
5
|
-
import microdf as mdf
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def combine_base_reform(
|
|
9
|
-
base: pd.DataFrame,
|
|
10
|
-
reform: pd.DataFrame,
|
|
11
|
-
base_cols: Optional[list] = None,
|
|
12
|
-
cols: Optional[list] = None,
|
|
13
|
-
reform_cols: Optional[list] = None,
|
|
14
|
-
) -> pd.DataFrame:
|
|
15
|
-
"""Combine base and reform with certain columns.
|
|
16
|
-
|
|
17
|
-
:param base: Base DataFrame. Index must match reform.
|
|
18
|
-
:type base: pd.DataFrame
|
|
19
|
-
:param reform: Reform DataFrame. Index must match base.
|
|
20
|
-
:type reform: pd.DataFrame
|
|
21
|
-
:param base_cols: Columns in base to keep.
|
|
22
|
-
:type base_cols: list, optional
|
|
23
|
-
:param cols: Columns to keep from both base and reform.
|
|
24
|
-
:type cols: list, optional
|
|
25
|
-
:param reform_cols: Columns in reform to keep.
|
|
26
|
-
:type reform_cols: list, optional
|
|
27
|
-
:returns: DataFrame with columns for base ("_base") and reform ("_reform").
|
|
28
|
-
:rtype: pd.DataFrame
|
|
29
|
-
"""
|
|
30
|
-
all_base_cols = mdf.listify([base_cols] + [cols])
|
|
31
|
-
all_reform_cols = mdf.listify([reform_cols] + [cols])
|
|
32
|
-
return base[all_base_cols].join(
|
|
33
|
-
reform[all_reform_cols], lsuffix="_base", rsuffix="_reform"
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def pctchg_base_reform(combined: pd.DataFrame, metric: str) -> pd.Series:
|
|
38
|
-
"""Calculates the percentage change in a metric for a combined dataset.
|
|
39
|
-
|
|
40
|
-
:param combined: Combined DataFrame with _base and _reform columns.
|
|
41
|
-
:type combined: pd.DataFrame
|
|
42
|
-
:param metric: String of the column to calculate the difference. Must exist
|
|
43
|
-
as metric_m_base and metric_m_reform in combined.
|
|
44
|
-
:type metric: str
|
|
45
|
-
:returns: Series with percentage change.
|
|
46
|
-
:rtype: pd.Series
|
|
47
|
-
"""
|
|
48
|
-
return combined[metric + "_m_reform"] / combined[metric + "_m_base"] - 1
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def agg(
|
|
52
|
-
base: pd.DataFrame,
|
|
53
|
-
reform: pd.DataFrame,
|
|
54
|
-
groupby: str,
|
|
55
|
-
metrics: list,
|
|
56
|
-
base_metrics: Optional[list] = None,
|
|
57
|
-
reform_metrics: Optional[list] = None,
|
|
58
|
-
) -> pd.DataFrame:
|
|
59
|
-
"""Aggregates differences between base and reform.
|
|
60
|
-
|
|
61
|
-
:param base: Base DataFrame. Index must match reform.
|
|
62
|
-
:type base: pd.DataFrame
|
|
63
|
-
:param reform: Reform DataFrame. Index must match base.
|
|
64
|
-
:type reform: pd.DataFrame
|
|
65
|
-
:param groupby: Variable in base to group on.
|
|
66
|
-
:type groupby: str
|
|
67
|
-
:param metrics: List of variables to agg and calculate the % change of.
|
|
68
|
-
These should have associated weighted columns ending in _m in base and
|
|
69
|
-
reform.
|
|
70
|
-
:type metrics: list
|
|
71
|
-
:param base_metrics: List of variables from base to sum.
|
|
72
|
-
:type base_metrics: Optional[list]
|
|
73
|
-
:param reform_metrics: List of variables from reform to sum.
|
|
74
|
-
:type reform_metrics: Optional[list]
|
|
75
|
-
:returns: DataFrame with groupby and metrics, and _pctchg metrics.
|
|
76
|
-
:rtype: pd.DataFrame
|
|
77
|
-
"""
|
|
78
|
-
metrics = mdf.listify(metrics)
|
|
79
|
-
metrics_m = [i + "_m" for i in metrics]
|
|
80
|
-
combined = combine_base_reform(
|
|
81
|
-
base,
|
|
82
|
-
reform,
|
|
83
|
-
base_cols=mdf.listify([groupby, base_metrics]),
|
|
84
|
-
cols=mdf.listify(metrics_m),
|
|
85
|
-
reform_cols=mdf.listify(reform_metrics),
|
|
86
|
-
)
|
|
87
|
-
grouped = combined.groupby(groupby).sum()
|
|
88
|
-
for metric in metrics:
|
|
89
|
-
grouped[metric + "_pctchg"] = pctchg_base_reform(grouped, metric)
|
|
90
|
-
return grouped
|
microdf/concat.py
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import inspect
|
|
2
|
-
|
|
3
|
-
import pandas as pd
|
|
4
|
-
|
|
5
|
-
import microdf as mdf
|
|
6
|
-
from microdf.microdataframe import MicroDataFrame
|
|
7
|
-
|
|
8
|
-
|
|
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.
|
|
13
|
-
|
|
14
|
-
:return: MicroDataFrame with concatenated weights.
|
|
15
|
-
:rtype: mdf.MicroDataFrame
|
|
16
|
-
"""
|
|
17
|
-
# Extract args with respect to pd.concat.
|
|
18
|
-
pd_args = inspect.getcallargs(pd.concat, *args, **kwargs)
|
|
19
|
-
objs = pd_args["objs"]
|
|
20
|
-
axis = pd_args["axis"]
|
|
21
|
-
# Create result, starting with pd.concat.
|
|
22
|
-
res = mdf.MicroDataFrame(pd.concat(*args, **kwargs))
|
|
23
|
-
# Assign weights depending on axis.
|
|
24
|
-
if axis == 0:
|
|
25
|
-
res.weights = pd.concat([obj.weights for obj in objs])
|
|
26
|
-
else:
|
|
27
|
-
# If concatenating horizontally, use the first set of weights.
|
|
28
|
-
res.weights = objs[0].weights
|
|
29
|
-
return res
|
microdf/constants.py
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# Constants for share of each benefit that is cash.
|
|
2
|
-
HOUSING_CASH_SHARE = 0.0
|
|
3
|
-
MCAID_CASH_SHARE = 0.0
|
|
4
|
-
MCARE_CASH_SHARE = 0.0
|
|
5
|
-
# https://github.com/open-source-economics/taxdata/issues/148
|
|
6
|
-
# https://docs.google.com/spreadsheets/d/1g_YdFd5idgLL764G0pZBiBnIlnCBGyxBmapXCOZ1OV4
|
|
7
|
-
OTHER_CASH_SHARE = 0.35
|
|
8
|
-
SNAP_CASH_SHARE = 0.0
|
|
9
|
-
SSI_CASH_SHARE = 1.0
|
|
10
|
-
TANF_CASH_SHARE = 0.25
|
|
11
|
-
# https://github.com/open-source-economics/C-TAM/issues/62.
|
|
12
|
-
VET_CASH_SHARE = 0.48
|
|
13
|
-
WIC_CASH_SHARE = 0.0
|
|
14
|
-
|
|
15
|
-
# Columns to remove from expanded_income to approximate TPC's Expanded Cash
|
|
16
|
-
# Income.
|
|
17
|
-
ECI_REMOVE_COLS = [
|
|
18
|
-
"wic_ben",
|
|
19
|
-
"housing_ben",
|
|
20
|
-
"vet_ben",
|
|
21
|
-
"mcare_ben",
|
|
22
|
-
"mcaid_ben",
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
# Benefits.
|
|
26
|
-
BENS = [
|
|
27
|
-
"housing_ben",
|
|
28
|
-
"mcaid_ben",
|
|
29
|
-
"mcare_ben",
|
|
30
|
-
"vet_ben",
|
|
31
|
-
"other_ben",
|
|
32
|
-
"snap_ben",
|
|
33
|
-
"ssi_ben",
|
|
34
|
-
"tanf_ben",
|
|
35
|
-
"wic_ben",
|
|
36
|
-
"e02400", # Social Security (OASDI).
|
|
37
|
-
"e02300", # Unemployment insurance.
|
|
38
|
-
]
|
|
39
|
-
|
|
40
|
-
MED_BENS = ["mcaid_ben", "mcare_ben", "vet_ben"]
|
microdf/custom_taxes.py
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
"""Functions and data for estimating taxes outside the income tax system.
|
|
2
|
-
|
|
3
|
-
Examples include value added tax, financial transaction tax, and carbon tax.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from typing import Optional
|
|
7
|
-
|
|
8
|
-
import numpy as np
|
|
9
|
-
import pandas as pd
|
|
10
|
-
|
|
11
|
-
import microdf as mdf
|
|
12
|
-
|
|
13
|
-
# Source:
|
|
14
|
-
# https://www.taxpolicycenter.org/briefing-book/who-would-bear-burden-vat
|
|
15
|
-
VAT_INCIDENCE = pd.Series(
|
|
16
|
-
index=[-1, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99, 99.9],
|
|
17
|
-
data=[3.9, 3.9, 3.6, 3.6, 3.6, 3.6, 3.6, 3.4, 3.4, 3.2, 2.8, 2.5, 2.5],
|
|
18
|
-
)
|
|
19
|
-
VAT_INCIDENCE /= 100
|
|
20
|
-
|
|
21
|
-
# Source: Table 5 in
|
|
22
|
-
# https://www.treasury.gov/resource-center/tax-policy/tax-analysis/Documents/WP-115.pdf
|
|
23
|
-
CARBON_TAX_INCIDENCE = pd.Series(
|
|
24
|
-
index=[-1, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99, 99.9],
|
|
25
|
-
data=[0.8, 1.2, 1.4, 1.5, 1.6, 1.7, 1.8, 1.8, 1.8, 1.8, 1.6, 1.4, 0.7],
|
|
26
|
-
)
|
|
27
|
-
CARBON_TAX_INCIDENCE /= 100
|
|
28
|
-
|
|
29
|
-
# Source: Figure 1 in
|
|
30
|
-
# https://www.taxpolicycenter.org/sites/default/files/alfresco/publication-pdfs/2000587-financial-transaction-taxes.pdf
|
|
31
|
-
FTT_INCIDENCE = pd.Series(
|
|
32
|
-
index=[-1, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99, 99.9],
|
|
33
|
-
data=[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.2, 0.2, 0.2, 0.3, 0.4, 0.8, 1.0],
|
|
34
|
-
)
|
|
35
|
-
FTT_INCIDENCE /= 100
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def add_custom_tax(
|
|
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:
|
|
49
|
-
"""Add a custom tax based on incidence analysis driven by percentiles.
|
|
50
|
-
|
|
51
|
-
:param df: DataFrame.
|
|
52
|
-
:param segment_income: Income measure used to segment tax units into
|
|
53
|
-
quantiles.
|
|
54
|
-
:param w: Weight used to segment into quantiles (either s006 or XTOT_m).
|
|
55
|
-
:param base_income: Income measure by which incidence is multiplied to
|
|
56
|
-
estimate liability.
|
|
57
|
-
:param incidence: pandas Series indexed on the floor of an income
|
|
58
|
-
percentile, with values for the tax rate.
|
|
59
|
-
:param name: Name of the column to add.
|
|
60
|
-
:param total: Total amount the tax should generate. If not provided,
|
|
61
|
-
liabilities are calculated only based on the incidence schedule.
|
|
62
|
-
(Default value = None)
|
|
63
|
-
:param ratio: Ratio to adjust the tax by, compared to the original tax.
|
|
64
|
-
This acts as a multiplier for the incidence argument. (Default value =
|
|
65
|
-
None)
|
|
66
|
-
:param verbose: Whether to print the tax adjustment factor if needed.
|
|
67
|
-
Defaults to True.
|
|
68
|
-
:returns: Nothing. Adds the column name to df representing the tax
|
|
69
|
-
liability. df is also sorted by segment_income.
|
|
70
|
-
"""
|
|
71
|
-
if ratio is not None:
|
|
72
|
-
incidence = incidence * ratio
|
|
73
|
-
assert total is None, "ratio and total cannot both be provided."
|
|
74
|
-
df.sort_values(segment_income, inplace=True)
|
|
75
|
-
income_percentile = 100 * df[w].cumsum() / df[w].sum()
|
|
76
|
-
tu_incidence = incidence.iloc[
|
|
77
|
-
pd.cut(
|
|
78
|
-
income_percentile,
|
|
79
|
-
# Add a right endpoint. Should be 100 but sometimes a decimal
|
|
80
|
-
# gets added.
|
|
81
|
-
bins=incidence.index.tolist() + [101],
|
|
82
|
-
labels=False,
|
|
83
|
-
)
|
|
84
|
-
].values
|
|
85
|
-
df[name] = np.maximum(0, tu_incidence * df[base_income])
|
|
86
|
-
if total is not None:
|
|
87
|
-
initial_total = mdf.weighted_sum(df, name, "s006")
|
|
88
|
-
if verbose:
|
|
89
|
-
print(
|
|
90
|
-
"Multiplying tax by "
|
|
91
|
-
+ str(round(total / initial_total, 2))
|
|
92
|
-
+ "."
|
|
93
|
-
)
|
|
94
|
-
df[name] *= total / initial_total
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def add_vat(
|
|
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
|
-
"""Add value added tax based on incidence estimate from Tax Policy Center.
|
|
107
|
-
|
|
108
|
-
:param df: DataFrame with columns for tpc_eci, XTOT_m, and aftertax_income.
|
|
109
|
-
:param Other: arguments: Args to add_custom_tax with VAT defaults.
|
|
110
|
-
:param segment_income: Default value = "tpc_eci")
|
|
111
|
-
:param w: Default value = "XTOT_m")
|
|
112
|
-
:param base_income: Default value = "aftertax_income")
|
|
113
|
-
:param incidence: Default value = VAT_INCIDENCE)
|
|
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.
|
|
117
|
-
"""
|
|
118
|
-
add_custom_tax(
|
|
119
|
-
df, segment_income, w, base_income, incidence, name, **kwargs
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def add_carbon_tax(
|
|
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:
|
|
132
|
-
"""Add carbon tax based on incidence estimate from the US Treasury
|
|
133
|
-
Department.
|
|
134
|
-
|
|
135
|
-
:param df: DataFrame with columns for tpc_eci, XTOT_m, and aftertax_income.
|
|
136
|
-
:param Other: arguments: Args to add_custom_tax with carbon tax defaults.
|
|
137
|
-
:param segment_income: Default value = "tpc_eci")
|
|
138
|
-
:param w: Default value = "XTOT_m")
|
|
139
|
-
:param base_income: Default value = "aftertax_income")
|
|
140
|
-
:param incidence: Default value = CARBON_TAX_INCIDENCE)
|
|
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.
|
|
144
|
-
"""
|
|
145
|
-
add_custom_tax(
|
|
146
|
-
df, segment_income, w, base_income, incidence, name, **kwargs
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def add_ftt(
|
|
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:
|
|
159
|
-
"""Add financial transaction tax based on incidence estimate from Tax
|
|
160
|
-
Policy Center.
|
|
161
|
-
|
|
162
|
-
:param df: DataFrame with columns for tpc_eci, XTOT_m, and aftertax_income.
|
|
163
|
-
:param Other: arguments: Args to add_custom_tax with FTT defaults.
|
|
164
|
-
:param segment_income: Default value = "tpc_eci")
|
|
165
|
-
:param w: Default value = "XTOT_m")
|
|
166
|
-
:param base_income: Default value = "aftertax_income")
|
|
167
|
-
:param incidence: Default value = FTT_INCIDENCE)
|
|
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.
|
|
171
|
-
"""
|
|
172
|
-
add_custom_tax(
|
|
173
|
-
df, segment_income, w, base_income, incidence, name, **kwargs
|
|
174
|
-
)
|