pydeflate 1.4.2__py3-none-any.whl → 2.0.1__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.
- pydeflate/__init__.py +25 -17
- pydeflate/core/api.py +404 -0
- pydeflate/core/deflator.py +171 -0
- pydeflate/core/exchange.py +239 -0
- pydeflate/core/source.py +54 -0
- pydeflate/deflate/deflators.py +228 -0
- pydeflate/deflate/legacy_deflate.py +109 -0
- pydeflate/exchange/__init__.py +0 -0
- pydeflate/exchange/exchangers.py +147 -0
- pydeflate/pydeflate_config.py +25 -16
- pydeflate/sources/__init__.py +0 -0
- pydeflate/sources/common.py +287 -0
- pydeflate/sources/dac.py +137 -0
- pydeflate/sources/imf.py +203 -0
- pydeflate/sources/world_bank.py +186 -0
- pydeflate/utils.py +55 -22
- {pydeflate-1.4.2.dist-info → pydeflate-2.0.1.dist-info}/LICENSE +1 -1
- pydeflate-2.0.1.dist-info/METADATA +287 -0
- pydeflate-2.0.1.dist-info/RECORD +25 -0
- {pydeflate-1.4.2.dist-info → pydeflate-2.0.1.dist-info}/WHEEL +1 -1
- pydeflate/deflate/deflate.py +0 -324
- pydeflate/deflate/deflator.py +0 -78
- pydeflate/get_data/deflate_data.py +0 -70
- pydeflate/get_data/exchange_data.py +0 -371
- pydeflate/get_data/imf_data.py +0 -76
- pydeflate/get_data/oecd_data.py +0 -146
- pydeflate/get_data/wb_data.py +0 -75
- pydeflate/tools/__init__.py +0 -2
- pydeflate/tools/exchange.py +0 -171
- pydeflate/tools/update_data.py +0 -69
- pydeflate-1.4.2.dist-info/METADATA +0 -305
- pydeflate-1.4.2.dist-info/RECORD +0 -22
- /pydeflate/{get_data → core}/__init__.py +0 -0
pydeflate/__init__.py
CHANGED
|
@@ -1,34 +1,42 @@
|
|
|
1
1
|
__author__ = """Jorge Rivera"""
|
|
2
|
-
__version__ = "
|
|
2
|
+
__version__ = "2.0.1"
|
|
3
3
|
|
|
4
|
-
from pydeflate.deflate.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
from pydeflate.deflate.deflators import (
|
|
5
|
+
oecd_dac_deflate,
|
|
6
|
+
wb_cpi_deflate,
|
|
7
|
+
wb_gdp_deflate,
|
|
8
|
+
wb_gdp_linked_deflate,
|
|
9
|
+
imf_cpi_deflate,
|
|
10
|
+
imf_gdp_deflate,
|
|
11
|
+
imf_cpi_e_deflate,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from pydeflate.deflate.legacy_deflate import deflate
|
|
15
|
+
from pydeflate.exchange.exchangers import oecd_dac_exchange, wb_exchange, imf_exchange
|
|
16
|
+
from pydeflate.pydeflate_config import setup_logger
|
|
9
17
|
|
|
10
18
|
|
|
11
19
|
def set_pydeflate_path(path):
|
|
12
20
|
from pathlib import Path
|
|
13
21
|
from pydeflate.pydeflate_config import PYDEFLATE_PATHS
|
|
14
|
-
from bblocks import set_bblocks_data_path
|
|
15
22
|
|
|
16
23
|
"""Set the path to the data folder."""
|
|
17
24
|
global PYDEFLATE_PATHS
|
|
18
25
|
|
|
19
26
|
PYDEFLATE_PATHS.data = Path(path).resolve()
|
|
20
|
-
set_bblocks_data_path(PYDEFLATE_PATHS.data)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
# check that data is fresh enough
|
|
24
|
-
warn_updates()
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
__all__ = [
|
|
28
|
-
"deflate",
|
|
29
|
-
"exchange",
|
|
30
|
-
"update_all_data",
|
|
31
30
|
"set_pydeflate_path",
|
|
32
|
-
"
|
|
33
|
-
"
|
|
31
|
+
"oecd_dac_deflate",
|
|
32
|
+
"oecd_dac_exchange",
|
|
33
|
+
"wb_cpi_deflate",
|
|
34
|
+
"wb_gdp_deflate",
|
|
35
|
+
"wb_gdp_linked_deflate",
|
|
36
|
+
"wb_exchange",
|
|
37
|
+
"imf_cpi_deflate",
|
|
38
|
+
"imf_gdp_deflate",
|
|
39
|
+
"imf_cpi_e_deflate",
|
|
40
|
+
"imf_exchange",
|
|
41
|
+
"deflate",
|
|
34
42
|
]
|
pydeflate/core/api.py
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
from pydeflate.core.deflator import ExchangeDeflator, PriceDeflator
|
|
4
|
+
from pydeflate.core.exchange import Exchange
|
|
5
|
+
from pydeflate.core.source import Source
|
|
6
|
+
from pydeflate.sources.common import AvailableDeflators
|
|
7
|
+
from pydeflate.utils import (
|
|
8
|
+
create_pydeflate_year,
|
|
9
|
+
merge_user_and_pydeflate_data,
|
|
10
|
+
get_unmatched_pydeflate_data,
|
|
11
|
+
get_matched_pydeflate_data,
|
|
12
|
+
flag_missing_pydeflate_data,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def resolve_common_currencies(currency: str, source: str) -> str:
|
|
17
|
+
mapping = {
|
|
18
|
+
"USD": "USA",
|
|
19
|
+
"EUR": "EMU",
|
|
20
|
+
"GBP": "GBR",
|
|
21
|
+
"JPY": "JPN",
|
|
22
|
+
"CAD": "CAN",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if source == "DAC":
|
|
26
|
+
mapping["EUR"] = "EUI"
|
|
27
|
+
|
|
28
|
+
return mapping.get(currency, currency)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _base_operation(
|
|
32
|
+
base_obj,
|
|
33
|
+
data: pd.DataFrame,
|
|
34
|
+
entity_column: str,
|
|
35
|
+
year_column: str,
|
|
36
|
+
value_column: str,
|
|
37
|
+
target_value_column: str | None = None,
|
|
38
|
+
year_format: str | None = None,
|
|
39
|
+
exchange: bool = False,
|
|
40
|
+
reversed_: bool = False,
|
|
41
|
+
):
|
|
42
|
+
"""Perform deflation or exchange rate adjustment on input data using pydeflate data.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
base_obj (BaseExchange | BaseDeflate): The base object containing pydeflate data.
|
|
46
|
+
data (pd.DataFrame): Data to be adjusted.
|
|
47
|
+
entity_column (str): Column with entity or country identifiers.
|
|
48
|
+
year_column (str): Column with year information.
|
|
49
|
+
value_column (str): Column with values to be adjusted.
|
|
50
|
+
target_value_column (str | None, optional): Column to store adjusted values. Defaults to `value_column`.
|
|
51
|
+
year_format (str, optional): Format of the year. Defaults to "%Y".
|
|
52
|
+
exchange (bool, optional): Whether to perform an exchange rate adjustment (True)
|
|
53
|
+
or deflation (False).
|
|
54
|
+
reversed_ (bool, optional): If True, perform the operation in reverse.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
pd.DataFrame: DataFrame with adjusted values and original columns preserved.
|
|
58
|
+
"""
|
|
59
|
+
# Make a copy of the input data to avoid modifying the original data
|
|
60
|
+
data = data.copy(deep=True)
|
|
61
|
+
|
|
62
|
+
target_value_column = target_value_column or value_column
|
|
63
|
+
|
|
64
|
+
# Keep track of original columns to return data in the same order.
|
|
65
|
+
cols = (
|
|
66
|
+
[*data.columns, target_value_column]
|
|
67
|
+
if target_value_column not in data.columns
|
|
68
|
+
else data.columns
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Merge pydeflate data to the input data
|
|
72
|
+
base_obj._merge_pydeflate_data(
|
|
73
|
+
data=data,
|
|
74
|
+
entity_column=entity_column,
|
|
75
|
+
year_column=year_column,
|
|
76
|
+
year_format=year_format,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Flag missing data
|
|
80
|
+
flag_missing_pydeflate_data(base_obj._unmatched_data)
|
|
81
|
+
x = base_obj._merged_data[value_column]
|
|
82
|
+
y = base_obj._merged_data[
|
|
83
|
+
"pydeflate_EXCHANGE" if exchange else "pydeflate_deflator"
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
# Apply the correct operation based on `exchange` and `reversed`
|
|
87
|
+
if (exchange and not reversed_) or (not exchange and reversed_):
|
|
88
|
+
base_obj._merged_data[target_value_column] = (x * y).round(6)
|
|
89
|
+
else:
|
|
90
|
+
base_obj._merged_data[target_value_column] = (x / y).round(6)
|
|
91
|
+
|
|
92
|
+
return base_obj._merged_data[cols]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class BaseExchange:
|
|
96
|
+
"""Performs currency exchange rate conversions within data, using pydeflate.
|
|
97
|
+
|
|
98
|
+
Provides methods to merge pydeflate exchange rate data with user data and apply
|
|
99
|
+
exchange rate adjustments, allowing for straightforward currency conversions
|
|
100
|
+
within a dataset.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
def __init__(
|
|
104
|
+
self,
|
|
105
|
+
exchange_source: Source,
|
|
106
|
+
source_currency: str,
|
|
107
|
+
target_currency: str,
|
|
108
|
+
use_source_codes: bool = False,
|
|
109
|
+
):
|
|
110
|
+
"""Initialize a BaseExchange instance with exchange rate data and identifiers.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
exchange_source (Source): Data source for exchange rates.
|
|
114
|
+
source_currency (str): Original currency for conversion.
|
|
115
|
+
target_currency (str): Target currency for conversion.
|
|
116
|
+
use_source_codes (bool, optional): Use source-specific entity codes.
|
|
117
|
+
Defaults to False.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
# Try to accept common currencies by their country codes
|
|
121
|
+
source_currency = resolve_common_currencies(
|
|
122
|
+
source_currency, exchange_source.name
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
target_currency = resolve_common_currencies(
|
|
126
|
+
target_currency, exchange_source.name
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
self.exchange_rates = Exchange(
|
|
130
|
+
source=exchange_source,
|
|
131
|
+
source_currency=source_currency,
|
|
132
|
+
target_currency=target_currency,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
self._idx = [
|
|
136
|
+
"pydeflate_year",
|
|
137
|
+
"pydeflate_entity_code" if use_source_codes else "pydeflate_iso3",
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
self._load_pydeflate_data()
|
|
141
|
+
|
|
142
|
+
def _load_pydeflate_data(self) -> None:
|
|
143
|
+
"""Load and prepare pydeflate exchange rate data."""
|
|
144
|
+
self.pydeflate_data = (
|
|
145
|
+
self.exchange_rates.exchange_data.set_index(self._idx)
|
|
146
|
+
.dropna(how="any")
|
|
147
|
+
.reset_index()
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def _merge_pydeflate_data(
|
|
151
|
+
self,
|
|
152
|
+
data: pd.DataFrame,
|
|
153
|
+
entity_column: str,
|
|
154
|
+
year_column: str,
|
|
155
|
+
year_format: str | None = None,
|
|
156
|
+
) -> None:
|
|
157
|
+
"""Merge pydeflate exchange rate data into input data by year and entity.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
data (pd.DataFrame): Input DataFrame to merge with pydeflate data.
|
|
161
|
+
entity_column (str): Column name for entity or country codes.
|
|
162
|
+
year_column (str): Column name for year information.
|
|
163
|
+
year_format (str, optional): Year format. Defaults to '%Y'.
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
# Convert the year to an integer
|
|
167
|
+
data = create_pydeflate_year(
|
|
168
|
+
data=data, year_column=year_column, year_format=year_format
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Merge data to the input data based on year and entity
|
|
172
|
+
merged_data = merge_user_and_pydeflate_data(
|
|
173
|
+
data=data,
|
|
174
|
+
pydeflate_data=self.pydeflate_data,
|
|
175
|
+
entity_column=entity_column,
|
|
176
|
+
ix=self._idx,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# store unmatched data
|
|
180
|
+
self._unmatched_data = get_unmatched_pydeflate_data(merged_data=merged_data)
|
|
181
|
+
|
|
182
|
+
# store matched data
|
|
183
|
+
self._merged_data = get_matched_pydeflate_data(merged_data=merged_data)
|
|
184
|
+
|
|
185
|
+
def exchange(
|
|
186
|
+
self,
|
|
187
|
+
data: pd.DataFrame,
|
|
188
|
+
entity_column: str,
|
|
189
|
+
year_column: str,
|
|
190
|
+
value_column: str,
|
|
191
|
+
target_value_column: str | None = None,
|
|
192
|
+
year_format: str | None = None,
|
|
193
|
+
reversed_: bool = False,
|
|
194
|
+
):
|
|
195
|
+
"""Apply exchange rate conversion to input data.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
data (pd.DataFrame): Data to apply exchange rate adjustment.
|
|
199
|
+
entity_column (str): Column with entity identifiers.
|
|
200
|
+
year_column (str): Column with year information.
|
|
201
|
+
value_column (str): Column with values to adjust.
|
|
202
|
+
target_value_column (str | None, optional): Column for adjusted values. Defaults to `value_column`.
|
|
203
|
+
year_format (str, optional): Format of the year. Defaults to "%Y".
|
|
204
|
+
reversed_ (bool, optional): If True, perform the operation in reverse. defaults to False.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
pd.DataFrame: DataFrame with exchange rate-adjusted values.
|
|
208
|
+
"""
|
|
209
|
+
return _base_operation(
|
|
210
|
+
base_obj=self,
|
|
211
|
+
exchange=True,
|
|
212
|
+
data=data,
|
|
213
|
+
entity_column=entity_column,
|
|
214
|
+
year_column=year_column,
|
|
215
|
+
value_column=value_column,
|
|
216
|
+
target_value_column=target_value_column,
|
|
217
|
+
year_format=year_format,
|
|
218
|
+
reversed_=reversed_,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class BaseDeflate:
|
|
223
|
+
"""Deflating monetary values over time with exchange rates and price indices.
|
|
224
|
+
|
|
225
|
+
Combines price deflators, exchange rate deflators, and pydeflate data to allow
|
|
226
|
+
for adjustments of monetary values across different time periods, enabling accurate
|
|
227
|
+
economic comparisons over time.
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
def __init__(
|
|
231
|
+
self,
|
|
232
|
+
deflator_source: Source,
|
|
233
|
+
exchange_source: Source,
|
|
234
|
+
base_year: int,
|
|
235
|
+
price_kind: AvailableDeflators,
|
|
236
|
+
source_currency: str,
|
|
237
|
+
target_currency: str,
|
|
238
|
+
use_source_codes: bool = False,
|
|
239
|
+
to_current: bool = False,
|
|
240
|
+
):
|
|
241
|
+
"""Initialize a BaseDeflate instance with deflator and exchange rate sources.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
deflator_source (Source): Data source for price deflators.
|
|
245
|
+
exchange_source (Source): Data source for exchange rates.
|
|
246
|
+
base_year (int): Reference year for base value adjustments.
|
|
247
|
+
price_kind (AvailableDeflators): Type of deflator (e.g., CPI).
|
|
248
|
+
source_currency (str): Currency of the input data.
|
|
249
|
+
target_currency (str): Currency for conversion.
|
|
250
|
+
use_source_codes (bool, optional): Use source-specific entity codes. Defaults to False.
|
|
251
|
+
to_current (bool, optional): If True, adjust to current year values. Defaults to False.
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
# Try to accept common currencies by their country codes
|
|
255
|
+
source_currency = resolve_common_currencies(
|
|
256
|
+
source_currency, deflator_source.name
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
target_currency = resolve_common_currencies(
|
|
260
|
+
target_currency, deflator_source.name
|
|
261
|
+
)
|
|
262
|
+
self.exchange_rates = Exchange(
|
|
263
|
+
source=exchange_source,
|
|
264
|
+
source_currency=source_currency,
|
|
265
|
+
target_currency=target_currency,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
self.exchange_deflator = ExchangeDeflator(
|
|
269
|
+
source=self.exchange_rates, base_year=base_year
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
self.price_deflator = PriceDeflator(
|
|
273
|
+
source=deflator_source, kind=price_kind, base_year=base_year
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
self._idx = [
|
|
277
|
+
"pydeflate_year",
|
|
278
|
+
"pydeflate_entity_code" if use_source_codes else "pydeflate_iso3",
|
|
279
|
+
]
|
|
280
|
+
|
|
281
|
+
self.use_source_codes = use_source_codes
|
|
282
|
+
self.to_current = to_current
|
|
283
|
+
|
|
284
|
+
self.__post_init__()
|
|
285
|
+
|
|
286
|
+
def __post_init__(self):
|
|
287
|
+
"""Post-initialization process to merge deflator, exchange, and pydeflate data."""
|
|
288
|
+
|
|
289
|
+
# Merge deflator and exchange rate data
|
|
290
|
+
data = self._merge_components(
|
|
291
|
+
df=self.price_deflator.deflator_data,
|
|
292
|
+
other=self.exchange_deflator.deflator_data,
|
|
293
|
+
).pipe(self._merge_components, other=self.exchange_rates.exchange_data)
|
|
294
|
+
|
|
295
|
+
# drop where necessary data is missing
|
|
296
|
+
data = data.set_index(self._idx).dropna(how="any").reset_index()
|
|
297
|
+
|
|
298
|
+
# Calculate price-exchange deflator
|
|
299
|
+
data["pydeflate_deflator"] = self._calculate_deflator_value(
|
|
300
|
+
data[f"pydeflate_{self.price_deflator.price_kind}"],
|
|
301
|
+
data["pydeflate_EXCHANGE_D"],
|
|
302
|
+
data[f"pydeflate_EXCHANGE"],
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
self.pydeflate_data = data
|
|
306
|
+
|
|
307
|
+
def _calculate_deflator_value(
|
|
308
|
+
self, price_def: pd.Series, exchange_def: pd.Series, exchange_rate: pd.Series
|
|
309
|
+
):
|
|
310
|
+
"""Compute the combined deflator value using price deflator, exchange deflator, and rates.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
price_def (pd.Series): Series of price deflator values.
|
|
314
|
+
exchange_def (pd.Series): Series of exchange deflator values.
|
|
315
|
+
exchange_rate (pd.Series): Series of exchange rates.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
pd.Series: Series with combined deflator values.
|
|
319
|
+
"""
|
|
320
|
+
return (
|
|
321
|
+
(exchange_def * exchange_rate) / price_def
|
|
322
|
+
if self.to_current
|
|
323
|
+
else price_def / (exchange_def * exchange_rate)
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
def _merge_components(self, df: pd.DataFrame, other: pd.DataFrame):
|
|
327
|
+
"""Combine data components, merging deflator and exchange rate information.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
df (pd.DataFrame): Main DataFrame for merging.
|
|
331
|
+
other (pd.DataFrame): Additional data to merge into `df`.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
pd.DataFrame: Merged DataFrame without duplicate columns.
|
|
335
|
+
"""
|
|
336
|
+
merged = df.merge(other, how="outer", on=self._idx, suffixes=("", "_ex"))
|
|
337
|
+
return merged.drop(columns=merged.filter(regex=f"_ex$").columns, axis=1)
|
|
338
|
+
|
|
339
|
+
def _merge_pydeflate_data(
|
|
340
|
+
self,
|
|
341
|
+
data: pd.DataFrame,
|
|
342
|
+
entity_column: str,
|
|
343
|
+
year_column: str,
|
|
344
|
+
year_format: str | None = None,
|
|
345
|
+
) -> None:
|
|
346
|
+
"""Merge pydeflate deflator data into the input data by year and entity.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
data (pd.DataFrame): Input DataFrame to merge with pydeflate data.
|
|
350
|
+
entity_column (str): Column for entity or country identifiers.
|
|
351
|
+
year_column (str): Column for year information.
|
|
352
|
+
year_format (str, optional): Format of the year. Defaults to '%Y'.
|
|
353
|
+
"""
|
|
354
|
+
|
|
355
|
+
# Convert the year to an integer
|
|
356
|
+
data = create_pydeflate_year(
|
|
357
|
+
data=data, year_column=year_column, year_format=year_format
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
# Merge data to the input data based on year and entity
|
|
361
|
+
merged_data = merge_user_and_pydeflate_data(
|
|
362
|
+
data=data,
|
|
363
|
+
pydeflate_data=self.pydeflate_data,
|
|
364
|
+
entity_column=entity_column,
|
|
365
|
+
ix=self._idx,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
# store unmatched data
|
|
369
|
+
self._unmatched_data = get_unmatched_pydeflate_data(merged_data=merged_data)
|
|
370
|
+
|
|
371
|
+
# store matched data
|
|
372
|
+
self._merged_data = get_matched_pydeflate_data(merged_data=merged_data)
|
|
373
|
+
|
|
374
|
+
def deflate(
|
|
375
|
+
self,
|
|
376
|
+
data: pd.DataFrame,
|
|
377
|
+
entity_column: str,
|
|
378
|
+
year_column: str,
|
|
379
|
+
value_column: str,
|
|
380
|
+
target_value_column: str | None = None,
|
|
381
|
+
year_format: str | None = None,
|
|
382
|
+
):
|
|
383
|
+
"""Apply deflation adjustment to input data using pydeflate deflator rates.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
data (pd.DataFrame): Data for deflation adjustment.
|
|
387
|
+
entity_column (str): Column with entity identifiers.
|
|
388
|
+
year_column (str): Column with year information.
|
|
389
|
+
value_column (str): Column with values to deflate.
|
|
390
|
+
target_value_column (str | None, optional): Column to store deflated values. Defaults to `value_column`.
|
|
391
|
+
year_format (str, optional): Format of the year. Defaults to "%Y".
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
pd.DataFrame: DataFrame with deflated values.
|
|
395
|
+
"""
|
|
396
|
+
return _base_operation(
|
|
397
|
+
base_obj=self,
|
|
398
|
+
data=data,
|
|
399
|
+
entity_column=entity_column,
|
|
400
|
+
year_column=year_column,
|
|
401
|
+
value_column=value_column,
|
|
402
|
+
target_value_column=target_value_column,
|
|
403
|
+
year_format=year_format,
|
|
404
|
+
)
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
from pydeflate.core.exchange import Exchange
|
|
7
|
+
from pydeflate.core.source import Source
|
|
8
|
+
from pydeflate.sources.common import AvailableDeflators
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class Deflator:
|
|
13
|
+
"""A class to manage and process deflator data.
|
|
14
|
+
|
|
15
|
+
This class holds deflators and provides methods to apply exchange rates
|
|
16
|
+
for conversions between source and target currencies.
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
source: Source | Exchange
|
|
21
|
+
deflator_type: Literal["price", "exchange"]
|
|
22
|
+
price_kind: AvailableDeflators | None
|
|
23
|
+
base_year: int
|
|
24
|
+
deflator_data: pd.DataFrame = field(default_factory=pd.DataFrame)
|
|
25
|
+
|
|
26
|
+
def __post_init__(self):
|
|
27
|
+
"""Processes the deflator data based on the deflator type.
|
|
28
|
+
|
|
29
|
+
Initializes the `deflator_data` attribute by fetching and processing
|
|
30
|
+
the appropriate deflator data from the source.
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
ValueError: If the combination of `deflator_type` and `price_kind` is invalid.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
# If price deflator, fetch the price deflator data
|
|
37
|
+
if self.deflator_type == "price":
|
|
38
|
+
if self.price_kind is None:
|
|
39
|
+
raise ValueError(
|
|
40
|
+
"`price_kind` must be specified when `deflator_type` is 'price'."
|
|
41
|
+
)
|
|
42
|
+
self.deflator_data = self.source.price_deflator(kind=self.price_kind)
|
|
43
|
+
|
|
44
|
+
# If exchange deflator, fetch the exchange deflator data
|
|
45
|
+
elif self.deflator_type == "exchange":
|
|
46
|
+
if self.price_kind is not None:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
"`price_kind` should be None when `deflator_type` is 'exchange'."
|
|
49
|
+
)
|
|
50
|
+
self.deflator_data = self.source.deflator()
|
|
51
|
+
self.source = self.source.source
|
|
52
|
+
|
|
53
|
+
else:
|
|
54
|
+
raise ValueError(f"Invalid deflator type: {self.deflator_type}")
|
|
55
|
+
|
|
56
|
+
# Rebase the deflator data to the specified base year
|
|
57
|
+
self.rebase_deflator()
|
|
58
|
+
|
|
59
|
+
def _extract_base_year_values(self, value_column: str) -> pd.DataFrame:
|
|
60
|
+
"""Extracts the base year values from the deflator data."""
|
|
61
|
+
|
|
62
|
+
# Extract base year values
|
|
63
|
+
base_year_values = (
|
|
64
|
+
self.deflator_data[self.deflator_data["pydeflate_year"] == self.base_year]
|
|
65
|
+
.rename(columns={value_column: "base_year_value"})
|
|
66
|
+
.drop(columns=["pydeflate_year"])
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
if base_year_values.empty:
|
|
70
|
+
raise ValueError(f"No data found for base year {self.base_year}.")
|
|
71
|
+
return base_year_values
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def _ensure_deflator_suffix(value_column: str) -> str:
|
|
75
|
+
"""Ensures that the value column ends with the '_D' suffix."""
|
|
76
|
+
return value_column if value_column.endswith("_D") else value_column + "_D"
|
|
77
|
+
|
|
78
|
+
def _get_deflator_value_column(self) -> str:
|
|
79
|
+
"""Identifies the value column in the deflator data."""
|
|
80
|
+
# Identify the value column
|
|
81
|
+
value_column = self.deflator_data.columns.difference(self.source._idx).tolist()
|
|
82
|
+
|
|
83
|
+
if len(value_column) != 1:
|
|
84
|
+
raise ValueError("Invalid deflator data format.")
|
|
85
|
+
|
|
86
|
+
return value_column[0]
|
|
87
|
+
|
|
88
|
+
def rebase_deflator(self) -> None:
|
|
89
|
+
"""Rebases the deflator data to the specified base year.
|
|
90
|
+
|
|
91
|
+
Adjusts the deflator values so that the base year values are set to 100.
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
ValueError: If `deflator_data` is empty, has an invalid format, or lacks data for the base year.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
# Check if deflator data is empty
|
|
98
|
+
if self.deflator_data.empty:
|
|
99
|
+
raise ValueError("No deflator data found.")
|
|
100
|
+
|
|
101
|
+
# Identify the value column
|
|
102
|
+
value_column = self._get_deflator_value_column()
|
|
103
|
+
|
|
104
|
+
# Extract base year values
|
|
105
|
+
base_year_values = self._extract_base_year_values(value_column)
|
|
106
|
+
|
|
107
|
+
# Merge base year values back into the main DataFrame
|
|
108
|
+
rebased = self.deflator_data.merge(
|
|
109
|
+
base_year_values,
|
|
110
|
+
on=[c for c in self.source._idx if c != "pydeflate_year"],
|
|
111
|
+
how="left",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# if value column doesn't end in _D, add it
|
|
115
|
+
original_value_column = value_column
|
|
116
|
+
value_column = self._ensure_deflator_suffix(value_column)
|
|
117
|
+
|
|
118
|
+
# Rebase the deflator values
|
|
119
|
+
rebased[value_column] = (
|
|
120
|
+
100 * rebased[original_value_column] / rebased["base_year_value"]
|
|
121
|
+
).round(6)
|
|
122
|
+
|
|
123
|
+
# Update the deflator data
|
|
124
|
+
self.deflator_data = rebased.drop(columns=["base_year_value"])
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class ExchangeDeflator(Deflator):
|
|
128
|
+
"""Manages and processes exchange rate deflators.
|
|
129
|
+
|
|
130
|
+
Using exchange rate data (local currency to USD) provided by the source.
|
|
131
|
+
It normalizes exchange rate deflators to a specified base year, allowing
|
|
132
|
+
consistent comparisons over time.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
source (Source): The source from which to fetch exchange rate data.
|
|
136
|
+
base_year (int): The base year to rebase the deflator data.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
def __init__(self, source: Exchange, base_year: int):
|
|
140
|
+
super().__init__(
|
|
141
|
+
source=source,
|
|
142
|
+
deflator_type="exchange",
|
|
143
|
+
price_kind=None,
|
|
144
|
+
base_year=base_year,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class PriceDeflator(Deflator):
|
|
149
|
+
"""Manages and processes price deflators data.
|
|
150
|
+
|
|
151
|
+
It fetches price deflator data from the provided source and normalizes it to a specified
|
|
152
|
+
base year, allowing for inflation-adjusted comparisons over time.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
source (Source): The source from which to fetch price deflator data.
|
|
156
|
+
kind (AvailableDeflators): The type of price deflator to apply (e.g., GDP deflator or CPI).
|
|
157
|
+
base_year (int): The base year to rebase the deflator data.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
def __init__(
|
|
161
|
+
self,
|
|
162
|
+
source: Source,
|
|
163
|
+
kind: AvailableDeflators,
|
|
164
|
+
base_year: int,
|
|
165
|
+
):
|
|
166
|
+
super().__init__(
|
|
167
|
+
source=source,
|
|
168
|
+
deflator_type="price",
|
|
169
|
+
price_kind=kind,
|
|
170
|
+
base_year=base_year,
|
|
171
|
+
)
|