pydeflate 1.4.1__py3-none-any.whl → 2.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.
pydeflate/__init__.py CHANGED
@@ -1,34 +1,42 @@
1
1
  __author__ = """Jorge Rivera"""
2
- __version__ = "1.4.1"
2
+ __version__ = "2.0.0"
3
3
 
4
- from pydeflate.deflate.deflate import deflate
5
- from pydeflate.tools.exchange import exchange
6
- from pydeflate.tools.update_data import update_all_data, warn_updates
7
- from pydeflate.get_data.oecd_data import update_dac1
8
- from pydeflate import get_data
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
- "get_data",
33
- "update_dac1",
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
+ (10_000 * exchange_rate) / (price_def * exchange_def)
322
+ if self.to_current
323
+ else (price_def * exchange_def) / (10_000 * 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
+ )