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 +25 -17
- pydeflate/core/api.py +404 -0
- pydeflate/core/deflator.py +171 -0
- pydeflate/core/exchange.py +237 -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 +278 -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.1.dist-info → pydeflate-2.0.0.dist-info}/LICENSE +1 -1
- pydeflate-2.0.0.dist-info/METADATA +287 -0
- pydeflate-2.0.0.dist-info/RECORD +25 -0
- {pydeflate-1.4.1.dist-info → pydeflate-2.0.0.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.1.dist-info/METADATA +0 -305
- pydeflate-1.4.1.dist-info/RECORD +0 -22
- /pydeflate/{get_data → core}/__init__.py +0 -0
pydeflate/deflate/deflator.py
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
"""Defines the deflator class"""
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
|
|
5
|
-
import numpy as np
|
|
6
|
-
import pandas as pd
|
|
7
|
-
|
|
8
|
-
from pydeflate.get_data.deflate_data import Data
|
|
9
|
-
from pydeflate.get_data.exchange_data import Exchange
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@dataclass
|
|
13
|
-
class Deflator:
|
|
14
|
-
base_year: int
|
|
15
|
-
exchange_obj: Exchange
|
|
16
|
-
deflator_obj: Data
|
|
17
|
-
deflator_method: str
|
|
18
|
-
source_currency: str = "LCU"
|
|
19
|
-
target_currency: str = "USA"
|
|
20
|
-
to_current: bool = False
|
|
21
|
-
_xe_df: pd.DataFrame | None = None
|
|
22
|
-
_xe_deflator_df: pd.DataFrame | None = None
|
|
23
|
-
_price_deflator_df: pd.DataFrame | None = None
|
|
24
|
-
"""A class to produce deflators based on the given parameters.
|
|
25
|
-
|
|
26
|
-
Deflators take into account changes in prices and the evolution of currency exchange
|
|
27
|
-
values. They compensate for both factors in order to produce a standard unit of
|
|
28
|
-
measurement, regardless of the year of the flow.
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
base_year: the year in which the deflator equals 100 (if the source and target
|
|
32
|
-
currency are equal).
|
|
33
|
-
Exchange: the exchange rate object.
|
|
34
|
-
price: the price data oject.
|
|
35
|
-
source_currency: the ISO3 code of the country which uses the source currency
|
|
36
|
-
target_currency: the ISO3 code of the country which used the target currency
|
|
37
|
-
_deflator_df: the resulting DataFrame containing the deflators data.
|
|
38
|
-
"""
|
|
39
|
-
|
|
40
|
-
def __post_init__(self):
|
|
41
|
-
# Exchange deflator DataFrame, including any currency conversion
|
|
42
|
-
self._xe_deflator_df = self.exchange_obj.exchange_deflator(
|
|
43
|
-
source_iso=self.source_currency,
|
|
44
|
-
target_iso=self.target_currency,
|
|
45
|
-
base_year=self.base_year,
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
# Price deflator DataFrame
|
|
49
|
-
self._price_deflator_df = self.deflator_obj.get_deflator(
|
|
50
|
-
base_year=self.base_year, method=self.deflator_method
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
def get_deflator(self) -> pd.DataFrame:
|
|
54
|
-
"""Get the deflator DataFrame"""
|
|
55
|
-
|
|
56
|
-
# Merge the price and currency exchange deflators
|
|
57
|
-
df = self._price_deflator_df.merge(
|
|
58
|
-
self._xe_deflator_df,
|
|
59
|
-
on=["iso_code", "year"],
|
|
60
|
-
how="left",
|
|
61
|
-
suffixes=("_def", "_xe"),
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
# Calculate the deflator
|
|
65
|
-
df = (
|
|
66
|
-
df.assign(
|
|
67
|
-
value=round(100 * df.value_def / df.value_xe, 6)
|
|
68
|
-
if not self.to_current
|
|
69
|
-
else round(100 * df.value_xe / df.value_def, 6)
|
|
70
|
-
)
|
|
71
|
-
.filter(["iso_code", "year", "value"], axis=1)
|
|
72
|
-
.assign(value=lambda d: d.value.replace(0, np.nan))
|
|
73
|
-
.dropna(subset=["value"])
|
|
74
|
-
.reset_index(drop=True)
|
|
75
|
-
.rename(columns={"value": "deflator"})
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
return df
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from abc import ABC, abstractmethod
|
|
4
|
-
from dataclasses import dataclass
|
|
5
|
-
|
|
6
|
-
import pandas as pd
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@dataclass
|
|
10
|
-
class Data(ABC):
|
|
11
|
-
_data: pd.DataFrame | None = None
|
|
12
|
-
_available_methods: dict | None = None
|
|
13
|
-
"""Abstract class defining the basic structure and functionality of Data classes.
|
|
14
|
-
|
|
15
|
-
Data classes store the price data from different sources.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
@abstractmethod
|
|
19
|
-
def update(self, **kwargs) -> None:
|
|
20
|
-
"""Update underlying data"""
|
|
21
|
-
|
|
22
|
-
@abstractmethod
|
|
23
|
-
def load_data(self, **kwargs) -> None:
|
|
24
|
-
"""Load required data to construct deflator"""
|
|
25
|
-
|
|
26
|
-
def available_methods(self) -> dict:
|
|
27
|
-
"""Return a dictionary of available methods with their functions"""
|
|
28
|
-
return self._available_methods
|
|
29
|
-
|
|
30
|
-
def get_method(self, method: str) -> pd.DataFrame:
|
|
31
|
-
"""Return the data for a given method"""
|
|
32
|
-
if self._data is None:
|
|
33
|
-
self.load_data()
|
|
34
|
-
|
|
35
|
-
if method not in self._data.indicator.unique():
|
|
36
|
-
raise ValueError(f"Invalid method {method=}")
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
self._data.loc[lambda d: d.indicator == method]
|
|
40
|
-
.filter(["iso_code", "year", "value"], axis=1)
|
|
41
|
-
.sort_values(["iso_code", "year"])
|
|
42
|
-
.reset_index(drop=True)
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
def get_deflator(self, base_year: int, method: str | None = None) -> pd.DataFrame:
|
|
46
|
-
if method is None:
|
|
47
|
-
raise ValueError("method must be specified")
|
|
48
|
-
|
|
49
|
-
if method not in self.available_methods():
|
|
50
|
-
raise ValueError(f"method must be one of {self.available_methods().keys()}")
|
|
51
|
-
|
|
52
|
-
if self._data is None:
|
|
53
|
-
self.load_data()
|
|
54
|
-
|
|
55
|
-
d_ = self.get_method(self._available_methods[method])
|
|
56
|
-
|
|
57
|
-
# get the data for the selected base year.
|
|
58
|
-
d_base = d_.query(f"year.dt.year == {base_year}")
|
|
59
|
-
|
|
60
|
-
# If base year is not valid, raise error
|
|
61
|
-
if len(d_base) == 0:
|
|
62
|
-
raise ValueError(f"No currency exchange data for {base_year=}")
|
|
63
|
-
|
|
64
|
-
# Merge the exchange data and the base year data
|
|
65
|
-
d_ = d_.merge(d_base, how="left", on=["iso_code"], suffixes=("", "_base"))
|
|
66
|
-
|
|
67
|
-
# Calculate the deflator
|
|
68
|
-
d_.value = round(100 * d_.value / d_.value_base, 6)
|
|
69
|
-
|
|
70
|
-
return d_.filter(["iso_code", "year", "value"], axis=1)
|
|
@@ -1,371 +0,0 @@
|
|
|
1
|
-
from abc import ABC, abstractmethod
|
|
2
|
-
from dataclasses import dataclass
|
|
3
|
-
|
|
4
|
-
import pandas as pd
|
|
5
|
-
|
|
6
|
-
from pydeflate.get_data.imf_data import IMF
|
|
7
|
-
from pydeflate.get_data.wb_data import END, START, update_world_bank_data
|
|
8
|
-
from pydeflate.pydeflate_config import PYDEFLATE_PATHS, logger
|
|
9
|
-
from pydeflate.utils import emu
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def _exchange_ratio(source_xe: pd.DataFrame, target_xe: pd.DataFrame) -> pd.DataFrame:
|
|
13
|
-
return (
|
|
14
|
-
source_xe.merge(
|
|
15
|
-
target_xe,
|
|
16
|
-
how="left",
|
|
17
|
-
on=["year", "iso_code"],
|
|
18
|
-
suffixes=("_source", "_target"),
|
|
19
|
-
)
|
|
20
|
-
.assign(value=lambda d: d.value_source / d.value_target)
|
|
21
|
-
.drop(["value_source", "value_target"], axis=1)
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def _calculate_deflator(xe: pd.DataFrame, base_year: int) -> pd.DataFrame:
|
|
26
|
-
# get the data for the selected base year.
|
|
27
|
-
xe_base = xe.query(f"year.dt.year == {base_year}")
|
|
28
|
-
|
|
29
|
-
# If base year is not valid, raise error
|
|
30
|
-
if len(xe_base) == 0:
|
|
31
|
-
raise ValueError(f"No currency exchange data for {base_year=}")
|
|
32
|
-
|
|
33
|
-
# Merge the exchange data and the base year data
|
|
34
|
-
xe = xe.merge(xe_base, how="left", on=["iso_code"], suffixes=("", "_base"))
|
|
35
|
-
|
|
36
|
-
# Calculate the deflator
|
|
37
|
-
xe.value = round(100 * xe.value / xe.value_base, 6)
|
|
38
|
-
|
|
39
|
-
return xe.filter(["iso_code", "year", "value"], axis=1)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@dataclass
|
|
43
|
-
class Exchange(ABC):
|
|
44
|
-
"""An abstract class to update, load and return exchange rate data.
|
|
45
|
-
|
|
46
|
-
Attributes:
|
|
47
|
-
method: the method to use to calculate the exchange rate.
|
|
48
|
-
|
|
49
|
-
"""
|
|
50
|
-
|
|
51
|
-
method: str | None
|
|
52
|
-
_data: pd.DataFrame | None = None
|
|
53
|
-
|
|
54
|
-
@abstractmethod
|
|
55
|
-
def update(self, **kwargs) -> None:
|
|
56
|
-
"""Update underlying data"""
|
|
57
|
-
|
|
58
|
-
@abstractmethod
|
|
59
|
-
def load_data(self, **kwargs) -> None:
|
|
60
|
-
"""Load required data to construct deflator"""
|
|
61
|
-
|
|
62
|
-
@abstractmethod
|
|
63
|
-
def usd_exchange_rate(self, direction: str = "lcu_to_uds") -> pd.DataFrame:
|
|
64
|
-
"""Get the exchange rate of a currency to USD (or vice versa)
|
|
65
|
-
|
|
66
|
-
Args:
|
|
67
|
-
direction: the direction of the exchange rate. Either "lcu_to_usd"
|
|
68
|
-
or "usd_to_lcu".
|
|
69
|
-
|
|
70
|
-
Returns:
|
|
71
|
-
A dataframe with the exchange rate of the currency to USD (or vice versa).
|
|
72
|
-
"""
|
|
73
|
-
|
|
74
|
-
def exchange_rate(self, currency_iso: str) -> pd.DataFrame:
|
|
75
|
-
"""Get an exchange rate for a given ISO
|
|
76
|
-
|
|
77
|
-
Args:
|
|
78
|
-
currency_iso: the iso_code of the currency to get the exchange rate for.
|
|
79
|
-
|
|
80
|
-
Returns:
|
|
81
|
-
A dataframe with the exchange rate for the specified currency.
|
|
82
|
-
"""
|
|
83
|
-
|
|
84
|
-
# get usd exchange rates
|
|
85
|
-
d_ = self.usd_exchange_rate()
|
|
86
|
-
|
|
87
|
-
# filter the exchange rate of the chosen currency
|
|
88
|
-
target_iso = d_.query(f"iso_code == '{currency_iso}'")
|
|
89
|
-
|
|
90
|
-
# If currency is not valid, raise error
|
|
91
|
-
if len(target_iso) == 0:
|
|
92
|
-
raise ValueError(f"No currency exchange data for {currency_iso=}")
|
|
93
|
-
|
|
94
|
-
# merge the two datasets and calculate new exchange rate
|
|
95
|
-
return (
|
|
96
|
-
pd.merge(d_, target_iso, how="left", on=["year"], suffixes=("", "_xe"))
|
|
97
|
-
.assign(value=lambda d: d.value / d.value_xe)
|
|
98
|
-
.drop(columns=["value_xe", "iso_code_xe"])
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
def exchange_deflator(
|
|
102
|
-
self, source_iso: str, target_iso: str, base_year: int
|
|
103
|
-
) -> pd.DataFrame:
|
|
104
|
-
"""Get an exchange rate deflator for a given ISO
|
|
105
|
-
|
|
106
|
-
Args:
|
|
107
|
-
source_iso: the iso_code in which the "original" data is based.
|
|
108
|
-
The default should be "LCU".
|
|
109
|
-
target_iso: the iso_code of the currency to get the exchange rate for.
|
|
110
|
-
base_year: the base year to calculate the deflator.
|
|
111
|
-
|
|
112
|
-
Returns:
|
|
113
|
-
A dataframe with the exchange rate deflator for the specified currency.
|
|
114
|
-
|
|
115
|
-
"""
|
|
116
|
-
# Get exchange data based on the source currency. If LCU set to None
|
|
117
|
-
source_xe = (
|
|
118
|
-
self.exchange_rate(currency_iso=source_iso)
|
|
119
|
-
if source_iso != "LCU"
|
|
120
|
-
else self.exchange_rate(currency_iso="USA").assign(value=1)
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
# Get exchange data based on the target currency. If LCU set to None
|
|
124
|
-
target_xe = (
|
|
125
|
-
self.exchange_rate(currency_iso=target_iso)
|
|
126
|
-
if target_iso != "LCU"
|
|
127
|
-
else self.exchange_rate(currency_iso="USA").assign(value=1)
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
# calculate conversion ratio
|
|
131
|
-
exchange_ratio = _exchange_ratio(source_xe=source_xe, target_xe=target_xe)
|
|
132
|
-
|
|
133
|
-
# get the data for the selected base year.
|
|
134
|
-
if target_iso != "LCU":
|
|
135
|
-
xe = self.exchange_rate(currency_iso=target_iso)
|
|
136
|
-
elif source_iso != "LCU":
|
|
137
|
-
xe = self.exchange_rate(currency_iso=source_iso)
|
|
138
|
-
else:
|
|
139
|
-
xe = self.exchange_rate(currency_iso="USA").assign(value=1)
|
|
140
|
-
|
|
141
|
-
# Calculate the deflator
|
|
142
|
-
xe = _calculate_deflator(xe=xe, base_year=base_year)
|
|
143
|
-
|
|
144
|
-
# Merge the conversion ratio
|
|
145
|
-
xe = xe.merge(
|
|
146
|
-
exchange_ratio,
|
|
147
|
-
how="left",
|
|
148
|
-
on=["year", "iso_code"],
|
|
149
|
-
suffixes=("_xe", "_xe_ratio"),
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
# Convert deflators to currency of interest
|
|
153
|
-
xe = xe.assign(value=lambda d: d.value_xe * d.value_xe_ratio)
|
|
154
|
-
|
|
155
|
-
return xe.filter(["year", "iso_code", "value"], axis=1)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
@dataclass
|
|
159
|
-
class ExchangeOECD(Exchange):
|
|
160
|
-
"""The OECD exchange rate data
|
|
161
|
-
|
|
162
|
-
Attributes:
|
|
163
|
-
method: the method to use to calculate the exchange rate. For this source,
|
|
164
|
-
the only valid method is “implied”.
|
|
165
|
-
"""
|
|
166
|
-
|
|
167
|
-
method: str = "implied"
|
|
168
|
-
_load_try_count: int = 0
|
|
169
|
-
|
|
170
|
-
@staticmethod
|
|
171
|
-
def update(**kwargs) -> None:
|
|
172
|
-
"""Update the DAC1 data, which is the source for the OECD exchange rates."""
|
|
173
|
-
from pydeflate.get_data import oecd_data
|
|
174
|
-
|
|
175
|
-
oecd_data.update_dac1()
|
|
176
|
-
|
|
177
|
-
def load_data(self, **kwargs) -> None:
|
|
178
|
-
"""Load the OECD DAC price deflators data.
|
|
179
|
-
|
|
180
|
-
If the data is not found, it will be downloaded.
|
|
181
|
-
DAC deflators are transformed into price deflators by using the
|
|
182
|
-
implied exchange rate information from the OECD DAC data.
|
|
183
|
-
|
|
184
|
-
The deflators that are loaded is therefore *not* the DAC deflator,
|
|
185
|
-
but the price deflator used to produce the DAC deflators.
|
|
186
|
-
|
|
187
|
-
"""
|
|
188
|
-
try:
|
|
189
|
-
self._data = pd.read_feather(
|
|
190
|
-
PYDEFLATE_PATHS.data / "pydeflate_dac1.feather"
|
|
191
|
-
)
|
|
192
|
-
except FileNotFoundError:
|
|
193
|
-
logger.info("Data not found, downloading...")
|
|
194
|
-
self.update()
|
|
195
|
-
self.load_data()
|
|
196
|
-
return
|
|
197
|
-
|
|
198
|
-
def usd_exchange_rate(self, direction: str = "lcu_usd") -> pd.DataFrame:
|
|
199
|
-
"""Get the exchange rate of a currency to USD (or vice versa)
|
|
200
|
-
|
|
201
|
-
Args:
|
|
202
|
-
direction: the direction of the exchange rate. Either "lcu_to_usd"
|
|
203
|
-
or "usd_to_lcu".
|
|
204
|
-
|
|
205
|
-
Returns:
|
|
206
|
-
A dataframe with the exchange rate of the currency to USD (or vice versa).
|
|
207
|
-
"""
|
|
208
|
-
|
|
209
|
-
# If data is not loaded, load it
|
|
210
|
-
if self._data is None:
|
|
211
|
-
self.load_data()
|
|
212
|
-
|
|
213
|
-
# Filter the data to only include the required columns, rename the value column.
|
|
214
|
-
d_ = self._data[["iso_code", "year", "exchange"]].rename(
|
|
215
|
-
columns={"exchange": "value"}
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
# if the direction is lcu to usd, return the data
|
|
219
|
-
if direction == "lcu_usd":
|
|
220
|
-
return d_
|
|
221
|
-
# if the direction is usd to lcu, invert the exchange rate.
|
|
222
|
-
elif direction == "usd_lcu":
|
|
223
|
-
d_.value = 1 / d_.value
|
|
224
|
-
return d_
|
|
225
|
-
# if the direction is not valid, raise error
|
|
226
|
-
else:
|
|
227
|
-
raise ValueError("Direction must be 'lcu_usd' or 'usd_lcu'")
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
@dataclass
|
|
231
|
-
class ExchangeWorldBank(Exchange):
|
|
232
|
-
"""The World Bank exchange rate data
|
|
233
|
-
|
|
234
|
-
Attributes:
|
|
235
|
-
method: the method to use to calculate the exchange rate. For this source,
|
|
236
|
-
the valid methods are "yearly_average" and "effective_exchange".
|
|
237
|
-
"""
|
|
238
|
-
|
|
239
|
-
method: str = "yearly_average"
|
|
240
|
-
_load_try_count: int = 0
|
|
241
|
-
|
|
242
|
-
def __post_init__(self):
|
|
243
|
-
"""Check that the method is valid"""
|
|
244
|
-
if self.method not in ["yearly_average", "effective_exchange"]:
|
|
245
|
-
raise ValueError("Method must be 'yearly_average' or 'effective_exchange'")
|
|
246
|
-
|
|
247
|
-
def update(self) -> None:
|
|
248
|
-
"""Update the World Bank data"""
|
|
249
|
-
update_world_bank_data()
|
|
250
|
-
|
|
251
|
-
def load_data(self, **kwargs) -> None:
|
|
252
|
-
"""Load the World Bank data"""
|
|
253
|
-
# method mapping
|
|
254
|
-
method_map = {
|
|
255
|
-
"yearly_average": "PA.NUS.FCRF",
|
|
256
|
-
"effective_exchange": "PX.REX.REER",
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if self._load_try_count < 1:
|
|
260
|
-
# Try to load the data from disk
|
|
261
|
-
try:
|
|
262
|
-
self._data = (
|
|
263
|
-
pd.read_csv(
|
|
264
|
-
PYDEFLATE_PATHS.data
|
|
265
|
-
/ f"{method_map[self.method]}_{START}-{END}_.csv",
|
|
266
|
-
parse_dates=["date"],
|
|
267
|
-
)
|
|
268
|
-
.rename(columns={"date": "year"})
|
|
269
|
-
.drop(columns=["indicator_code"])
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
# If the data is not found, download it. Reload the data to the object.
|
|
273
|
-
except FileNotFoundError:
|
|
274
|
-
logger.info("World Bank data not found, downloading...")
|
|
275
|
-
self._load_try_count = +1
|
|
276
|
-
self.update()
|
|
277
|
-
self.load_data()
|
|
278
|
-
else:
|
|
279
|
-
raise FileNotFoundError("Could not load World Bank data")
|
|
280
|
-
|
|
281
|
-
def usd_exchange_rate(self, direction: str = "lcu_usd") -> pd.DataFrame:
|
|
282
|
-
"""Get the exchange rate of a currency to USD (or vice versa)
|
|
283
|
-
|
|
284
|
-
Args:
|
|
285
|
-
direction: the direction of the exchange rate. Either "lcu_to_usd"
|
|
286
|
-
or "usd_to_lcu".
|
|
287
|
-
|
|
288
|
-
Returns:
|
|
289
|
-
A dataframe with the exchange rate of the currency to USD (or vice versa).
|
|
290
|
-
|
|
291
|
-
"""
|
|
292
|
-
|
|
293
|
-
if direction not in ["lcu_usd", "usd_lcu"]:
|
|
294
|
-
raise ValueError("Direction must be 'lcu_usd' or 'usd_lcu'")
|
|
295
|
-
|
|
296
|
-
# If data is not loaded, load it
|
|
297
|
-
if self._data is None:
|
|
298
|
-
self.load_data()
|
|
299
|
-
|
|
300
|
-
# Load the data into a temporary variable
|
|
301
|
-
df = self._data.copy(deep=True)
|
|
302
|
-
|
|
303
|
-
# Find the "Euro" data. This is done given that some countries are missing
|
|
304
|
-
# exchange rates, but they are part of the Euro area.
|
|
305
|
-
eur = df.loc[df.iso_code == "EMU"].dropna().set_index("year")["value"].to_dict()
|
|
306
|
-
|
|
307
|
-
# Euro area countries without exchange rates
|
|
308
|
-
eur_mask = (df.iso_code.isin(emu())) & (df.value.isna())
|
|
309
|
-
|
|
310
|
-
# Assign EURO exchange rate to euro are countries from year euro adopted
|
|
311
|
-
df.loc[eur_mask, "value"] = df.year.map(eur)
|
|
312
|
-
|
|
313
|
-
# If the direction is lcu to usd, return the data
|
|
314
|
-
|
|
315
|
-
if direction == "lcu_usd":
|
|
316
|
-
return df
|
|
317
|
-
# if the direction is usd to lcu, invert the exchange rate.
|
|
318
|
-
|
|
319
|
-
df.value = 1 / df.value
|
|
320
|
-
return df
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
@dataclass
|
|
324
|
-
class ExchangeIMF(Exchange):
|
|
325
|
-
"""The IMF exchange rate data
|
|
326
|
-
|
|
327
|
-
Attributes:
|
|
328
|
-
method: the method to use to calculate the exchange rate. For this source,
|
|
329
|
-
the only valid method is “implied”.
|
|
330
|
-
|
|
331
|
-
"""
|
|
332
|
-
|
|
333
|
-
method: str = "implied"
|
|
334
|
-
_imf: IMF | None = None
|
|
335
|
-
|
|
336
|
-
def __post_init__(self):
|
|
337
|
-
"""Load an IMF object to manage getting the data"""
|
|
338
|
-
if self._imf is None:
|
|
339
|
-
self._imf = IMF()
|
|
340
|
-
|
|
341
|
-
def update(self, **kwargs) -> None:
|
|
342
|
-
"""Update the IMF data"""
|
|
343
|
-
self._imf.update()
|
|
344
|
-
|
|
345
|
-
def load_data(self, **kwargs) -> None:
|
|
346
|
-
"""Load the IMF data"""
|
|
347
|
-
|
|
348
|
-
# if no direction is passed, default to lcu to usd
|
|
349
|
-
direction = "lcu_usd" if "direction" not in kwargs else kwargs["direction"]
|
|
350
|
-
self._data = self._imf.implied_exchange(direction=direction)
|
|
351
|
-
|
|
352
|
-
def usd_exchange_rate(self, direction: str = "lcu_usd") -> pd.DataFrame:
|
|
353
|
-
"""Get the exchange rate of a currency to USD (or vice versa)
|
|
354
|
-
|
|
355
|
-
Args:
|
|
356
|
-
direction: the direction of the exchange rate. Either "lcu_to_usd"
|
|
357
|
-
or "usd_to_lcu".
|
|
358
|
-
|
|
359
|
-
Returns:
|
|
360
|
-
A dataframe with the exchange rate of the currency to USD (or vice versa).
|
|
361
|
-
|
|
362
|
-
"""
|
|
363
|
-
|
|
364
|
-
if direction not in ["lcu_usd", "usd_lcu"]:
|
|
365
|
-
raise ValueError("Direction must be 'lcu_usd' or 'usd_lcu'")
|
|
366
|
-
|
|
367
|
-
# load the data
|
|
368
|
-
self.load_data(direction=direction)
|
|
369
|
-
|
|
370
|
-
# return the data
|
|
371
|
-
return self._data
|
pydeflate/get_data/imf_data.py
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
|
|
3
|
-
import pandas as pd
|
|
4
|
-
from bblocks import WorldEconomicOutlook, set_bblocks_data_path
|
|
5
|
-
|
|
6
|
-
from pydeflate.get_data.deflate_data import Data
|
|
7
|
-
from pydeflate.pydeflate_config import PYDEFLATE_PATHS
|
|
8
|
-
from pydeflate.tools.update_data import update_update_date
|
|
9
|
-
|
|
10
|
-
set_bblocks_data_path(PYDEFLATE_PATHS.data)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@dataclass
|
|
14
|
-
class IMF(Data):
|
|
15
|
-
_weo: WorldEconomicOutlook = None
|
|
16
|
-
"""An object to download and return the latest IMF WEO data for several indicators"""
|
|
17
|
-
|
|
18
|
-
def __post_init__(self):
|
|
19
|
-
self._weo = WorldEconomicOutlook()
|
|
20
|
-
self._available_methods = {
|
|
21
|
-
"Consumer price index": "PCPI",
|
|
22
|
-
"Consumer price index, end of period": "PCPIE",
|
|
23
|
-
"Gross domestic product, deflator": "NGDP_D",
|
|
24
|
-
"gdp": "NGDP_D",
|
|
25
|
-
"pcpi": "PCPI",
|
|
26
|
-
"pcpie": "PCPIE",
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
def update(self) -> None:
|
|
30
|
-
"""Update the stored WEO data, using WEO package."""
|
|
31
|
-
self._weo.update_data(reload_data=False, year=None, release=None)
|
|
32
|
-
update_update_date(source="IMF")
|
|
33
|
-
|
|
34
|
-
def load_data(
|
|
35
|
-
self, latest_y: int | None = None, latest_r: int | None = None
|
|
36
|
-
) -> None:
|
|
37
|
-
"""Loading WEO as a clean dataframe
|
|
38
|
-
|
|
39
|
-
Args:
|
|
40
|
-
latest_y: passed only optional to override the behaviour to get the latest
|
|
41
|
-
release year for the WEO.
|
|
42
|
-
latest_r: passed only optionally to override the behaviour to get the latest
|
|
43
|
-
released value (1 or 2).
|
|
44
|
-
"""
|
|
45
|
-
# load the data for available indicators
|
|
46
|
-
indicators = list(self._available_methods.values()) + ["NGDP", "NGDPD"]
|
|
47
|
-
self._weo.load_data(indicators)
|
|
48
|
-
|
|
49
|
-
# get the data into a dataframe
|
|
50
|
-
self._data = self._weo.get_data(keep_metadata=False)
|
|
51
|
-
|
|
52
|
-
def implied_exchange(self, direction: str = "lcu_usd") -> pd.DataFrame:
|
|
53
|
-
"""Get the implied exchange rate used by the IMF
|
|
54
|
-
Args:
|
|
55
|
-
direction: the direction of the exchange rate, either lcu_usd for
|
|
56
|
-
local currency units per usd, or usd_lcu for the opposite.
|
|
57
|
-
"""
|
|
58
|
-
|
|
59
|
-
# USD data
|
|
60
|
-
usd = self.get_method("NGDPD")
|
|
61
|
-
|
|
62
|
-
# LCU data
|
|
63
|
-
lcu = self.get_method("NGDP")
|
|
64
|
-
|
|
65
|
-
# Merge datasets
|
|
66
|
-
d_ = usd.merge(lcu, on=["iso_code", "year"], suffixes=("_usd", "_lcu"))
|
|
67
|
-
|
|
68
|
-
# Calculate implied exchange rate
|
|
69
|
-
if direction == "lcu_usd":
|
|
70
|
-
d_["value"] = d_["value_lcu"] / d_["value_usd"]
|
|
71
|
-
elif direction == "usd_lcu":
|
|
72
|
-
d_["value"] = d_["value_usd"] / d_["value_lcu"]
|
|
73
|
-
else:
|
|
74
|
-
raise ValueError(f"direction must be lcu_usd or usd_lcu, not {direction}")
|
|
75
|
-
|
|
76
|
-
return d_.filter(["iso_code", "year", "value"], axis=1)
|