Skip to content

Commit 2ebb34f

Browse files
committed
Remove pandas-datareader
Use JSON/pandas solution for downloading World Bank indicator data. * Add function `download_world_bank_indicator`. * Add unit test. * Update requirements.
1 parent 8680668 commit 2ebb34f

File tree

4 files changed

+92
-12
lines changed

4 files changed

+92
-12
lines changed

climada/util/finance.py

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
__all__ = ["net_present_value", "income_group", "gdp"]
2323

24+
import json
2425
import logging
2526
import shutil
2627
import warnings
@@ -31,7 +32,6 @@
3132
import pandas as pd
3233
import requests
3334
from cartopy.io import shapereader
34-
from pandas_datareader import wb
3535

3636
from climada.util.constants import SYSTEM_DIR
3737
from climada.util.files_handler import download_file
@@ -181,6 +181,68 @@ def gdp(cntry_iso, ref_year, shp_file=None, per_capita=False):
181181
return close_year, close_val
182182

183183

184+
def download_world_bank_indicator(
185+
country_code: str, indicator: str, parse_dates: bool = False
186+
):
187+
"""Download indicator data from the World Bank API for all years or dates on record
188+
189+
Parameters
190+
----------
191+
country_code : str
192+
The country code in ISO alpha 3
193+
indicator : str
194+
The ID of the indicator in the World Bank API
195+
parse_dates : bool
196+
Whether the dates of the indicator data should be parsed as datetime objects.
197+
If ``False`` (default), they will be parsed as ``int`` (this only works for
198+
yearly data).
199+
200+
Returns
201+
-------
202+
pd.Series
203+
A series with the values of the indicator for all dates (years) on record
204+
"""
205+
# Download data from API
206+
raw_data = []
207+
pages = np.inf
208+
page = 1
209+
while page <= pages:
210+
response = requests.get(
211+
f"https://api.worldbank.org/v2/countries/{country_code}/indicators/"
212+
f"{indicator}?format=json&page={page}"
213+
)
214+
json_data = json.loads(response.text)
215+
print(json_data)
216+
217+
# Check if we received an error message
218+
try:
219+
if json_data[0]["message"][0]["id"] == "120":
220+
raise RuntimeError(
221+
"Error requesting data from the World Bank API. Did you use the "
222+
"correct country code and indicator ID?"
223+
)
224+
# If no, we should be fine
225+
except KeyError:
226+
pass
227+
228+
# Update the data
229+
pages = json_data[0]["pages"]
230+
page = page + 1
231+
raw_data.append(json_data[1])
232+
233+
# Create dataframe
234+
data = pd.concat([pd.DataFrame.from_records(rec) for rec in raw_data])
235+
236+
# Parse dates and rename value column
237+
if parse_dates:
238+
data["date"] = pd.DatetimeIndex(data["date"])
239+
else:
240+
data["date"] = data["date"].astype("int")
241+
242+
# Only return indicator data (with a proper name)
243+
return data.set_index("date")["value"].rename(data["indicator"].iloc[0]["value"])
244+
245+
184246
def world_bank(cntry_iso, ref_year, info_ind):
185247
"""Get country's GDP from World Bank's data at a given year, or
186248
closest year value. If no data, get the natural earth's approximation.
@@ -204,18 +266,14 @@ def world_bank(cntry_iso, ref_year, info_ind):
204266
IOError, KeyError, IndexError
205267
"""
206268
if info_ind != "INC_GRP":
207-
with warnings.catch_warnings():
208-
warnings.simplefilter("ignore")
209-
cntry_gdp = wb.download(
210-
indicator=info_ind, country=cntry_iso, start=1960, end=2030
211-
)
212-
years = np.array(
213-
[int(year) for year in cntry_gdp.index.get_level_values("year")]
269+
cntry_gdp = download_world_bank_indicator(
270+
indicator=info_ind, country_code=cntry_iso, parse_dates=False
214271
)
272+
years = cntry_gdp.index
215273
sort_years = np.abs(years - ref_year).argsort()
216274
close_val = cntry_gdp.iloc[sort_years].dropna()
217-
close_year = int(close_val.iloc[0].name[1])
218-
close_val = float(close_val.iloc[0].values)
275+
close_year = close_val.index[0]
276+
close_val = float(close_val.iloc[0])
219277
else: # income group level
220278
fn_ig = SYSTEM_DIR.joinpath("OGHIST.xls")
221279
dfr_wb = pd.DataFrame()

climada/util/test/test_finance.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
from climada.util.finance import (
2828
_gdp_twn,
29+
download_world_bank_indicator,
2930
gdp,
3031
income_group,
3132
nat_earth_adm0,
@@ -137,6 +138,29 @@ def test_wb_esp_1950_pass(self):
137138
self.assertEqual(wb_year, ref_year)
138139
self.assertAlmostEqual(wb_val, ref_val)
139140

141+
def test_download_wb_data(self):
142+
"""Test downloading data via the API"""
143+
# Unfortunate reference test
144+
data = download_world_bank_indicator("ESP", "NY.GDP.MKTP.CD")
145+
self.assertAlmostEqual(data[1960], 12424514013.7604)
146+
self.assertEqual(data.name, "GDP (current US$)")
147+
148+
# Check parsing dates
149+
data = download_world_bank_indicator("ESP", "NY.GDP.MKTP.CD", parse_dates=True)
150+
self.assertEqual(data.index[-1], np.datetime64("1960-01-01"))
151+
152+
# Check errors raised
153+
with self.assertRaisesRegex(
154+
RuntimeError,
155+
"Did you use the correct country code",
156+
):
157+
download_world_bank_indicator("Spain", "NY.GDP.MKTP.CD")
158+
with self.assertRaisesRegex(
159+
RuntimeError,
160+
"Did you use the correct country code",
161+
):
162+
download_world_bank_indicator("ESP", "BogusIndicator")
163+
140164

141165
class TestWealth2GDP(unittest.TestCase):
142166
"""Test Wealth to GDP factor extraction"""

requirements/env_climada.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ dependencies:
2020
- openpyxl>=3.1
2121
- osm-flex>=1.1
2222
- pandas>=2.1,<2.2 # 2.2 is not compatible with the default pytables=3.7 and yields a very high deprecation warning number through geopandas
23-
- pandas-datareader>=0.10
2423
- pathos>=0.3
2524
- pint>=0.24
2625
- pip

setup.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@
7777
"openpyxl",
7878
"overpy",
7979
"pandas",
80-
"pandas-datareader",
8180
"pathos",
8281
"peewee",
8382
"pillow",

0 commit comments

Comments
 (0)