pydeflate 1.4.2__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.
@@ -0,0 +1,237 @@
1
+ from dataclasses import dataclass, field
2
+
3
+ import pandas as pd
4
+
5
+ from pydeflate.core.source import Source
6
+ from pydeflate.sources.common import compute_exchange_deflator
7
+
8
+
9
+ @dataclass
10
+ class Exchange:
11
+ """A class to manage and process exchange rate data for currency conversions.
12
+
13
+ This class holds exchange rate data and provides methods to apply exchange rates
14
+ for conversions between source and target currencies.
15
+
16
+ Attributes:
17
+ source (Source): An instance of the Source class to fetch exchange rate data.
18
+ source_currency (str): The source currency code (default is 'LCU' - Local Currency Unit).
19
+ target_currency (str): The target currency code (default is 'USA' - US Dollar).
20
+ exchange_data (pd.DataFrame): DataFrame holding the exchange rate data.
21
+ """
22
+
23
+ source: Source
24
+ source_currency: str = "LCU"
25
+ target_currency: str = "USA"
26
+ exchange_data: pd.DataFrame = field(default_factory=pd.DataFrame)
27
+
28
+ def __post_init__(self):
29
+ """Initialize the Exchange object and process the exchange rate data."""
30
+ # Load and filter the relevant columns from the exchange rate data
31
+ self.exchange_data = self.source.lcu_usd_exchange()
32
+
33
+ if self.source_currency == self.target_currency:
34
+ self.exchange_data = self.exchange_rate("LCU", self.target_currency)
35
+ self.exchange_data["pydeflate_EXCHANGE"] = 1
36
+ else:
37
+ self.exchange_data = self.exchange_rate(
38
+ self.source_currency, self.target_currency
39
+ )
40
+
41
+ def _get_exchange_rate(self, currency):
42
+ """Helper function to fetch exchange rates for a given currency."""
43
+ exchange_rate = self.exchange_data.loc[
44
+ self.exchange_data["pydeflate_iso3"] == currency
45
+ ]
46
+ return exchange_rate
47
+
48
+ def _convert_exchange(self, to_: str) -> pd.DataFrame:
49
+ """Converts exchange rates based on the target currency.
50
+
51
+ This method retrieves exchange rates for a given target currency, merges the
52
+ target exchange rates with the base exchange rates, and computes the final
53
+ exchange rate by dividing the base exchange rate by the target exchange rate.
54
+
55
+ Args:
56
+ to_ (str): The target currency code.
57
+
58
+ Returns:
59
+ pd.DataFrame: A DataFrame with the adjusted exchange rates for the target currency.
60
+
61
+ Raises:
62
+ ValueError: If no exchange rate data is available for the target currency.
63
+ """
64
+
65
+ if to_ == "LCU":
66
+ return self.exchange_data.copy().assign(pydeflate_EXCHANGE=1)
67
+
68
+ usd_rate = self.exchange_data.copy()
69
+ target_exchange = self._get_exchange_rate(to_)
70
+
71
+ if target_exchange.empty:
72
+ raise ValueError(f"No currency exchange data for {to_=}")
73
+
74
+ merged = pd.merge(
75
+ usd_rate,
76
+ target_exchange,
77
+ how="left",
78
+ on=["pydeflate_year"],
79
+ suffixes=("", "_to"),
80
+ ).assign(
81
+ pydeflate_EXCHANGE=lambda d: d.pydeflate_EXCHANGE / d.pydeflate_EXCHANGE_to,
82
+ )
83
+
84
+ return merged.drop(columns=merged.filter(regex="_to$").columns, axis=1)
85
+
86
+ def exchange_rate(self, from_currency: str, to_currency: str):
87
+ """Calculates the exchange rates between the source and target currencies.
88
+
89
+ Args:
90
+ from_currency (str): The source currency code.
91
+ to_currency (str): The target currency code.
92
+
93
+ Returns:
94
+ None
95
+ """
96
+ # Get exchange data based on the source currency.
97
+ source = self._convert_exchange(to_=from_currency)
98
+
99
+ # Get exchange data based on the target currency.
100
+ target = self._convert_exchange(to_=to_currency)
101
+
102
+ # Merge the source and target exchange data and compute the final exchange rate.
103
+ merged = pd.merge(
104
+ source,
105
+ target,
106
+ how="left",
107
+ on=["pydeflate_year", "pydeflate_entity_code", "pydeflate_iso3"],
108
+ suffixes=("", "_to"),
109
+ ).assign(
110
+ pydeflate_EXCHANGE=lambda d: d.pydeflate_EXCHANGE / d.pydeflate_EXCHANGE_to
111
+ )
112
+
113
+ # Drop unnecessary columns
114
+ merged = merged.drop(columns=merged.filter(regex="_to$").columns, axis=1)
115
+
116
+ # Compute the exchange rate deflator
117
+ merged = compute_exchange_deflator(
118
+ merged,
119
+ exchange="pydeflate_EXCHANGE",
120
+ year="pydeflate_year",
121
+ grouper=["pydeflate_entity_code", "pydeflate_iso3"],
122
+ )
123
+
124
+ return merged
125
+
126
+ def exchange(
127
+ self,
128
+ data: pd.DataFrame,
129
+ value_column: str,
130
+ entity_column: str,
131
+ year_column: str,
132
+ year_format: str = "%Y",
133
+ use_source_codes: bool = False,
134
+ ) -> pd.DataFrame:
135
+ """Apply the exchange rate to the given data to convert it to the target currency.
136
+
137
+ Args:
138
+ data (pd.DataFrame): The input DataFrame containing values to be exchanged.
139
+ value_column (str): The column name containing the values to be converted.
140
+ entity_column (str): The column name containing the entity or country codes.
141
+ year_column (str): The column name containing the year information.
142
+ year_format (str, optional): The format of the year (default is '%Y').
143
+ use_source_codes (bool, optional): Whether to use source entity codes
144
+ instead of ISO3 (default is False).
145
+
146
+ Returns:
147
+ pd.DataFrame: DataFrame with the values converted to the target currency.
148
+ """
149
+
150
+ # Convert the year to an integer
151
+ data["pydeflate_year"] = pd.to_datetime(
152
+ data[year_column], format=year_format
153
+ ).dt.year
154
+
155
+ # exchange columns
156
+ exchange_cols = [
157
+ "pydeflate_year",
158
+ "pydeflate_entity_code",
159
+ "pydeflate_iso3",
160
+ "pydeflate_EXCHANGE",
161
+ ]
162
+
163
+ # Merge exchange rate data to the input data based on year and entity
164
+ merged_data = data.merge(
165
+ self.exchange_data.filter(exchange_cols),
166
+ how="left",
167
+ left_on=["pydeflate_year", entity_column],
168
+ right_on=[
169
+ "pydeflate_year",
170
+ "pydeflate_entity_code" if use_source_codes else "pydeflate_iso3",
171
+ ],
172
+ )
173
+
174
+ # Apply the exchange rate to convert the value column
175
+ merged_data[value_column] = (
176
+ merged_data[value_column] / merged_data["pydeflate_EXCHANGE"]
177
+ )
178
+
179
+ # Drop all columns that start with pydeflate_ merging and return the result
180
+ return merged_data.filter(regex="^(?!pydeflate_)")
181
+
182
+ def deflator(self) -> pd.DataFrame:
183
+ """Get the exchange rate deflator data.
184
+
185
+ Returns:
186
+ pd.DataFrame: DataFrame with the exchange rate deflator data.
187
+ """
188
+
189
+ return self.exchange_data.filter(
190
+ [
191
+ "pydeflate_year",
192
+ "pydeflate_entity_code",
193
+ "pydeflate_iso3",
194
+ "pydeflate_EXCHANGE_D",
195
+ ]
196
+ )
197
+
198
+ def merge_deflator(
199
+ self,
200
+ data: pd.DataFrame,
201
+ entity_column: str,
202
+ year_column: str,
203
+ year_format: str = "%Y",
204
+ use_source_codes: bool = False,
205
+ ) -> pd.DataFrame:
206
+ """Merge the deflator data to the input data based on year and entity.
207
+
208
+ Args:
209
+ data (pd.DataFrame): The input DataFrame
210
+ entity_column (str): The column name containing the entity or country codes.
211
+ year_column (str): The column name containing the year information.
212
+ year_format (str, optional): The format of the year (default is '%Y').
213
+ use_source_codes (bool, optional): Whether to use source entity codes
214
+ instead of ISO3 (default is False).
215
+
216
+ Returns:
217
+ pd.DataFrame: DataFrame with the deflator data merged to the input data.
218
+
219
+ """
220
+
221
+ # Convert the year to an integer
222
+ data["pydeflate_year"] = pd.to_datetime(
223
+ data[year_column], format=year_format
224
+ ).dt.year
225
+
226
+ # Merge exchange rate data to the input data based on year and entity
227
+ merged_data = data.merge(
228
+ self.deflator(),
229
+ how="left",
230
+ left_on=["pydeflate_year", entity_column],
231
+ right_on=[
232
+ "pydeflate_year",
233
+ "pydeflate_entity_code" if use_source_codes else "pydeflate_iso3",
234
+ ],
235
+ )
236
+
237
+ return merged_data.filter(regex="^(?!pydeflate_)")
@@ -0,0 +1,54 @@
1
+ from dataclasses import dataclass, field
2
+
3
+ import pandas as pd
4
+
5
+ from pydeflate.sources.common import AvailableDeflators
6
+ from pydeflate.sources.dac import read_dac
7
+ from pydeflate.sources.imf import read_weo
8
+ from pydeflate.sources.world_bank import read_wb
9
+
10
+
11
+ @dataclass
12
+ class Source:
13
+ name: str
14
+ reader: callable
15
+ update: bool = False
16
+ data: pd.DataFrame = field(default_factory=pd.DataFrame)
17
+ _idx = ["pydeflate_year", "pydeflate_entity_code", "pydeflate_iso3"]
18
+
19
+ def __post_init__(self):
20
+ self.data = self.reader(self.update)
21
+ self.validate()
22
+
23
+ def validate(self):
24
+ if self.data.empty:
25
+ raise ValueError(f"No data found for {self.name}")
26
+
27
+ # check all columns start with pydeflate_
28
+ if not all(col.startswith("pydeflate_") for col in self.data.columns):
29
+ raise ValueError(f"Invalid data format for {self.name}")
30
+
31
+ def lcu_usd_exchange(self) -> pd.DataFrame:
32
+ return self.data.filter(self._idx + ["pydeflate_EXCHANGE"])
33
+
34
+ def price_deflator(self, kind: AvailableDeflators = "NGDP_D") -> pd.DataFrame:
35
+
36
+ if f"pydeflate_{kind}" not in self.data.columns:
37
+ raise ValueError(f"No deflator data found for {kind} in {self.name} data.")
38
+
39
+ return self.data.filter(self._idx + [f"pydeflate_{kind}"])
40
+
41
+
42
+ class IMF(Source):
43
+ def __init__(self, update: bool = False):
44
+ super().__init__(name="IMF", reader=read_weo, update=update)
45
+
46
+
47
+ class WorldBank(Source):
48
+ def __init__(self, update: bool = False):
49
+ super().__init__(name="World Bank", reader=read_wb, update=update)
50
+
51
+
52
+ class DAC(Source):
53
+ def __init__(self, update: bool = False):
54
+ super().__init__(name="DAC", reader=read_dac, update=update)
@@ -0,0 +1,228 @@
1
+ from functools import wraps
2
+
3
+ import pandas as pd
4
+
5
+ from pydeflate.core.api import BaseDeflate
6
+ from pydeflate.core.source import DAC, WorldBank, IMF
7
+
8
+
9
+ def _generate_docstring(source_name: str, price_kind: str) -> str:
10
+ """Generate docstring for each decorated deflation function."""
11
+ return (
12
+ f"Deflate a DataFrame using the {source_name} deflator source ({price_kind}).\n\n"
13
+ f"This function applies deflation adjustments to a DataFrame using the {source_name} {price_kind} deflator.\n\n"
14
+ "Args:\n"
15
+ " data (pd.DataFrame): The input DataFrame containing data to deflate.\n"
16
+ " base_year (int): The base year for calculating deflation adjustments.\n"
17
+ " source_currency (str, optional): The source currency code. Defaults to 'USA'.\n"
18
+ " target_currency (str, optional): The target currency code. Defaults to 'USA'.\n"
19
+ " id_column (str, optional): Column with entity identifiers. Defaults to 'iso_code'.\n"
20
+ " year_column (str, optional): Column with year information. Defaults to 'year'.\n"
21
+ " use_source_codes (bool, optional): Use source-specific entity codes. Defaults to False.\n"
22
+ " value_column (str, optional): Column with values to deflate. Defaults to 'value'.\n"
23
+ " target_value_column (str, optional): Column to store deflated values. Defaults to 'value'.\n"
24
+ " to_current (bool, optional): Adjust values to current-year values if True. Defaults to False.\n"
25
+ " year_format (str | None, optional): Format of the year in `year_column`. Defaults to None.\n"
26
+ " update_deflators (bool, optional): Update the deflator data before deflation. Defaults to False.\n\n"
27
+ "Returns:\n"
28
+ " pd.DataFrame: DataFrame with deflated values in the `target_value_column`.\n"
29
+ )
30
+
31
+
32
+ def _deflator(deflator_source_cls, price_kind):
33
+ """Decorator to create deflate wrappers with specific deflator source and price kind."""
34
+
35
+ def decorator(func):
36
+ @wraps(func)
37
+ def wrapper(
38
+ data: pd.DataFrame,
39
+ *,
40
+ base_year: int,
41
+ source_currency: str = "USA",
42
+ target_currency: str = "USA",
43
+ id_column: str = "iso_code",
44
+ year_column: str = "year",
45
+ use_source_codes: bool = False,
46
+ value_column: str = "value",
47
+ target_value_column: str = "value",
48
+ to_current: bool = False,
49
+ year_format: str | None = None,
50
+ update_deflators: bool = False,
51
+ ):
52
+ # Validate input parameters
53
+ if not isinstance(data, pd.DataFrame):
54
+ raise ValueError("The 'data' parameter must be a pandas DataFrame.")
55
+ if not isinstance(base_year, int):
56
+ raise ValueError("The 'base_year' parameter must be an integer.")
57
+ if id_column not in data.columns:
58
+ raise ValueError(
59
+ f"The id_column '{id_column}' is not in the DataFrame."
60
+ )
61
+ if year_column not in data.columns:
62
+ raise ValueError(
63
+ f"The year_column '{year_column}' is not in the DataFrame."
64
+ )
65
+ if value_column not in data.columns:
66
+ raise ValueError(
67
+ f"The value_column '{value_column}' is not in the DataFrame."
68
+ )
69
+
70
+ # Copy the data to avoid modifying the original
71
+ to_deflate = data.copy()
72
+
73
+ # Initialize the deflator source
74
+ source = deflator_source_cls(update=update_deflators)
75
+
76
+ # Create a deflator object
77
+ deflator = BaseDeflate(
78
+ base_year=base_year,
79
+ deflator_source=source,
80
+ exchange_source=source,
81
+ source_currency=source_currency,
82
+ target_currency=target_currency,
83
+ price_kind=price_kind,
84
+ use_source_codes=use_source_codes,
85
+ to_current=to_current,
86
+ )
87
+
88
+ # Deflate the data
89
+ return deflator.deflate(
90
+ data=to_deflate,
91
+ entity_column=id_column,
92
+ year_column=year_column,
93
+ value_column=value_column,
94
+ target_value_column=target_value_column,
95
+ year_format=year_format,
96
+ )
97
+
98
+ # Add the deflator source and price kind to the function
99
+ wrapper.__doc__ = _generate_docstring(deflator_source_cls.__name__, price_kind)
100
+ return wrapper
101
+
102
+ return decorator
103
+
104
+
105
+ @_deflator(DAC, "NGDP_D")
106
+ def oecd_dac_deflate(
107
+ data: pd.DataFrame,
108
+ *,
109
+ base_year: int,
110
+ source_currency: str = "USA",
111
+ target_currency: str = "USA",
112
+ id_column: str = "iso_code",
113
+ year_column: str = "year",
114
+ use_source_codes: bool = False,
115
+ value_column: str = "value",
116
+ target_value_column: str = "value",
117
+ to_current: bool = False,
118
+ year_format: str | None = None,
119
+ update_deflators: bool = False,
120
+ ) -> pd.DataFrame: ...
121
+
122
+
123
+ @_deflator(WorldBank, "NGDP_D")
124
+ def wb_gdp_deflate(
125
+ data: pd.DataFrame,
126
+ *,
127
+ base_year: int,
128
+ source_currency: str = "USA",
129
+ target_currency: str = "USA",
130
+ id_column: str = "iso_code",
131
+ year_column: str = "year",
132
+ use_source_codes: bool = False,
133
+ value_column: str = "value",
134
+ target_value_column: str = "value",
135
+ to_current: bool = False,
136
+ year_format: str | None = None,
137
+ update_deflators: bool = False,
138
+ ): ...
139
+
140
+
141
+ @_deflator(WorldBank, "NGDP_D")
142
+ def wb_gdp_linked_deflate(
143
+ data: pd.DataFrame,
144
+ *,
145
+ base_year: int,
146
+ source_currency: str = "USA",
147
+ target_currency: str = "USA",
148
+ id_column: str = "iso_code",
149
+ year_column: str = "year",
150
+ use_source_codes: bool = False,
151
+ value_column: str = "value",
152
+ target_value_column: str = "value",
153
+ to_current: bool = False,
154
+ year_format: str | None = None,
155
+ update_deflators: bool = False,
156
+ ): ...
157
+
158
+
159
+ @_deflator(WorldBank, "CPI")
160
+ def wb_cpi_deflate(
161
+ data: pd.DataFrame,
162
+ *,
163
+ base_year: int,
164
+ source_currency: str = "USA",
165
+ target_currency: str = "USA",
166
+ id_column: str = "iso_code",
167
+ year_column: str = "year",
168
+ use_source_codes: bool = False,
169
+ value_column: str = "value",
170
+ target_value_column: str = "value",
171
+ to_current: bool = False,
172
+ year_format: str | None = None,
173
+ update_deflators: bool = False,
174
+ ): ...
175
+
176
+
177
+ @_deflator(IMF, "NGDP_D")
178
+ def imf_gdp_deflate(
179
+ data: pd.DataFrame,
180
+ *,
181
+ base_year: int,
182
+ source_currency: str = "USA",
183
+ target_currency: str = "USA",
184
+ id_column: str = "iso_code",
185
+ year_column: str = "year",
186
+ use_source_codes: bool = False,
187
+ value_column: str = "value",
188
+ target_value_column: str = "value",
189
+ to_current: bool = False,
190
+ year_format: str | None = None,
191
+ update_deflators: bool = False,
192
+ ): ...
193
+
194
+
195
+ @_deflator(IMF, "PCPI")
196
+ def imf_cpi_deflate(
197
+ data: pd.DataFrame,
198
+ *,
199
+ base_year: int,
200
+ source_currency: str = "USA",
201
+ target_currency: str = "USA",
202
+ id_column: str = "iso_code",
203
+ year_column: str = "year",
204
+ use_source_codes: bool = False,
205
+ value_column: str = "value",
206
+ target_value_column: str = "value",
207
+ to_current: bool = False,
208
+ year_format: str | None = None,
209
+ update_deflators: bool = False,
210
+ ): ...
211
+
212
+
213
+ @_deflator(IMF, "PCPIE")
214
+ def imf_cpi_e_deflate(
215
+ data: pd.DataFrame,
216
+ *,
217
+ base_year: int,
218
+ source_currency: str = "USA",
219
+ target_currency: str = "USA",
220
+ id_column: str = "iso_code",
221
+ year_column: str = "year",
222
+ use_source_codes: bool = False,
223
+ value_column: str = "value",
224
+ target_value_column: str = "value",
225
+ to_current: bool = False,
226
+ year_format: str | None = None,
227
+ update_deflators: bool = False,
228
+ ): ...
@@ -0,0 +1,109 @@
1
+ import warnings
2
+
3
+ import pandas as pd
4
+ from pandas.util._decorators import deprecate_kwarg
5
+
6
+ from pydeflate.core.api import BaseDeflate
7
+ from pydeflate.core.source import DAC, WorldBank, IMF
8
+
9
+
10
+ @deprecate_kwarg(old_arg_name="method", new_arg_name="deflator_method")
11
+ @deprecate_kwarg(old_arg_name="source", new_arg_name="deflator_source")
12
+ @deprecate_kwarg(old_arg_name="iso_column", new_arg_name="id_column")
13
+ @deprecate_kwarg(old_arg_name="source_col", new_arg_name="source_column")
14
+ @deprecate_kwarg(old_arg_name="target_col", new_arg_name="target_column")
15
+ def deflate(
16
+ df: pd.DataFrame,
17
+ base_year: int,
18
+ deflator_source: str,
19
+ deflator_method: str,
20
+ exchange_source: str,
21
+ exchange_method: str,
22
+ source_currency: str = "USA",
23
+ target_currency: str = "USA",
24
+ id_column: str = "iso_code",
25
+ id_type: str = "ISO3",
26
+ date_column: str = "date",
27
+ source_column: str = "value",
28
+ target_column: str = "value",
29
+ to_current: bool = False,
30
+ ) -> pd.DataFrame:
31
+ """DEPRECATED: Use pydeflate's updated API for deflation adjustments.
32
+
33
+ This legacy function is provided for backwards compatibility only.
34
+ Please refer to the latest documentation for using pydeflate's updated API.
35
+
36
+ Args:
37
+ df (pd.DataFrame): DataFrame with flow data to be deflated.
38
+ base_year (int): Target year for base value adjustments.
39
+ deflator_source (str): Source of deflator data, e.g., 'oecd_dac', 'wb', 'imf'.
40
+ deflator_method (str): Method for deflator calculation, e.g., 'gdp', 'cpi'.
41
+ exchange_source (str): Source for exchange rates.
42
+ exchange_method (str): Method for exchange rate calculation.
43
+ source_currency (str): ISO3 code for the source currency.
44
+ target_currency (str): ISO3 code for the target currency.
45
+ id_column (str): Column for entity identifiers, e.g., 'iso_code'.
46
+ id_type (str): Type of ID classification; default is 'ISO3'.
47
+ date_column (str): Column for date information.
48
+ source_column (str): Column with original monetary values.
49
+ target_column (str): Column to store deflated values.
50
+ to_current (bool): If True, convert to current prices.
51
+
52
+ Returns:
53
+ pd.DataFrame: DataFrame with deflated values.
54
+ """
55
+ warnings.warn(
56
+ "The `deflate` function is deprecated and will be removed in future versions. "
57
+ "Please check the latest documentation for updated methods.",
58
+ DeprecationWarning,
59
+ )
60
+
61
+ if id_type != "ISO3":
62
+ raise ValueError(
63
+ "Only ISO3 ID classification is supported in this version.\n"
64
+ "You can use bblocks to convert to ISO3."
65
+ )
66
+
67
+ price_kind = {
68
+ "oecd_dac": "NGDP_D",
69
+ "dac_deflator": "NGDP_D",
70
+ "gdp": "NGDP_D",
71
+ "cpi": "CPI",
72
+ }
73
+
74
+ # Mapping of string identifiers to classes and price kinds
75
+ deflator_source_map = {
76
+ "oecd_dac": DAC,
77
+ "dac": DAC,
78
+ "wb": WorldBank,
79
+ "world_bank": WorldBank,
80
+ "imf": IMF,
81
+ }
82
+
83
+ deflator_source = deflator_source_map[deflator_source.lower()]()
84
+ exchange_source = deflator_source_map[exchange_source.lower()]()
85
+ deflator_method = price_kind.get(deflator_method.lower(), deflator_method).upper()
86
+
87
+ # Copy the data to avoid modifying the original
88
+ to_deflate = df.copy()
89
+
90
+ # Create a deflator object
91
+ deflator = BaseDeflate(
92
+ base_year=base_year,
93
+ deflator_source=deflator_source,
94
+ exchange_source=exchange_source,
95
+ source_currency=source_currency,
96
+ target_currency=target_currency,
97
+ price_kind=deflator_method,
98
+ to_current=to_current,
99
+ )
100
+
101
+ # Deflate the data
102
+ return deflator.deflate(
103
+ data=to_deflate,
104
+ entity_column=id_column,
105
+ year_column=date_column,
106
+ value_column=source_column,
107
+ target_value_column=target_column,
108
+ year_format=None,
109
+ )
File without changes