The IMF’s APIallows machine access to macroeconomic data covering more than 180 countries. Using python, it is easy to retrieve data from the API’s JSON RESTful Web Service.
The example below retrieves monthly (frequency: M)
import price index data (indicator: PMP_IX) for the U.K.
(reference area: GB), from the International Financial
Statistics (IFS) series. The request returns the base year
of the index (the year in which values are indexed to 100), the
observation values, and the time period for each value, in this case the
year and month. The request is generated by combining the base url of
the IMF API, the CompactData method, and the specific code
for the series and each dimension of its data. Part 2 covers how to
obtain codes and dimension information.
import requests
url = 'http://dataservices.imf.org/REST/SDMX_JSON.svc/'
key = 'CompactData/IFS/M.GB.PMP_IX' # adjust codes here
# Navigate to series in API-returned JSON data
data = (requests.get(f'{url}{key}').json()
['CompactData']['DataSet']['Series'])
print(data['Obs'][-1]) # Print latest observation## {'@TIME_PERIOD': '2019-06', '@OBS_VALUE': '107.3385518591'}
Next, we’ll use pandas to clean up the data obtained above, save it as a csv file, and produce a simple line plot.
import pandas as pd
baseyr = data['@BASE_YEAR']
# Create pandas dataframe from the observations
data_list = [[obs.get('@TIME_PERIOD'), obs.get('@OBS_VALUE')]
for obs in data['Obs']]
df = pd.DataFrame(data_list, columns=['date', 'value'])
df = df.set_index(pd.to_datetime(df['date']))['value'].astype('float')
# Save cleaned dataframe as a csv file
df.to_csv('UK_import_price_index.csv', header=True)We visually inspect the results by producing a line plot.
# Title and text with recent value
title = f'U.K. Import Prices (index, {baseyr})'
recentdt = df.index[-1].strftime('%B %Y')
recentval = round(df[-1], 1)## <string>:1: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
recent = f'Most recent: {recentdt}: {recentval}'
source = 'Source: IMF IFS'
# Basic plot
plot = df.plot(title=title, colormap='Set1')
plot = plot.set_xlabel(f'{recent}; {source}')The IMF’s CompactData method, combined with codes for
the series, frequency, area, and indicator, returns a JSON structured
dataset. The codes and method are explained in more detail as
follows:
Method: CompactData retrieves data,
DataStructure retrieves series information, and
GenericMetadata returns the metadata;
Series: The broad group of indicators, in this case International
Financial Statistics IFS;
Frequency: For example monthly M, quarterly Q, or
annually A;
Area: The country, region, or set of countries, for example
GB for the U.K., or GB+US for the U.K. and the
U.S.;
Indicator: The code for the indicator of interest–IFS includes
more than 2,500. In the example above, the code of interest is
PMP_IX; and
Date Range (Optional): Use this to limit the data range returned,
for example ?startPeriod=2010&endPeriod=2017 otherwise
the full set of data is returned.
The order in which codes are combined is referred to as the
dimensions of the data, in the IFS case:
{Method}/{Series}/{Frequency}.{Area}.{Indicator}.{Date Range}
Here we take a look at techniques for finding the right codes to make
successful API requests using the requests package.
The Dataflow method offers JSON formatted information on
which series are available through the API. To find the series of
interest (for example Direction of Trade Statistics DOT) with Python, we
can search a dictionary with the series names and their IDs.
import requests
url = 'http://dataservices.imf.org/REST/SDMX_JSON.svc/'
key = 'Dataflow' # Method with series information
search_term = 'Trade' # Term to find in series names
series_list = requests.get(f'{url}{key}').json()\
['Structure']['Dataflows']['Dataflow']
# Use dict keys to navigate through results:
for series in series_list:
if search_term in series['Name']['#text']:
print(f"{series['Name']['#text']}: {series['KeyFamilyRef']['KeyFamilyID']}")## Direction of Trade Statistics (DOTS), 2020 Q1: DOT_2020Q1
## Direction of Trade Statistics (DOTS), 2018 Q3: DOT_2018Q3
## Direction of Trade Statistics (DOTS), 2018 Q2: DOT_2018Q2
## Direction of Trade Statistics (DOTS), 2019 Q1: DOT_2019Q1
## Direction of Trade Statistics (DOTS), 2017 Q2: DOT_2017Q2
## Direction of Trade Statistics (DOTS), 2018 Q1: DOT_2018Q1
## Direction of Trade Statistics (DOTS), 2019 Q4: DOT_2019Q4
## Direction of Trade Statistics (DOTS), 2017 Q4: DOT_2017Q4
## Direction of Trade Statistics (DOTS), 2019 Q2: DOT_2019Q2
## Direction of Trade Statistics (DOTS), 2020 Q2: DOT_2020Q2
## Direction of Trade Statistics (DOTS), 2018 Q4: DOT_2018Q4
## Direction of Trade Statistics (DOTS), 2019 Q3: DOT_2019Q3
## Direction of Trade Statistics (DOTS), 2017 Q3: DOT_2017Q3
## Direction of Trade Statistics (DOTS), 2017 Q1: DOT_2017Q1
## Direction of Trade Statistics (DOTS), 2020 Q4: DOT_2020Q4
## Direction of Trade Statistics (DOTS), 2020 Q3: DOT_2020Q3
## Direction of Trade Statistics (DOTS), 2021 Q1: DOT_2021Q1
## Direction of Trade Statistics (DOTS), 2022 Q3: DOT_2022Q3
## Direction of Trade Statistics (DOTS), 2023 Q1: DOT_2023Q1
## Direction of Trade Statistics (DOTS), 2022 Q2: DOT_2022Q2
## Direction of Trade Statistics (DOTS), 2022 Q1: DOT_2022Q1
## Direction of Trade Statistics (DOTS), 2021 Q4: DOT_2021Q4
## Direction of Trade Statistics (DOTS), 2022 Q4: DOT_2022Q4
## Direction of Trade Statistics (DOTS), 2021 Q2: DOT_2021Q2
## Direction of Trade Statistics (DOTS), 2021 Q3: DOT_2021Q3
## Commodity Terms of Trade: PCTOT
## Direction of Trade Statistics (DOTS): DOT
The exact format of the key in our API request is determined by the structure of the series. Direction of Trade Statistics, which are grouped by importer and exporter pair rather than by country exemplifies the need to first determine series structure.
The dimensions of the data are found with the DataStructure method
and series specific, so that the full key becomes
DataStructure/DOT
key = 'DataStructure/DOT' # Method / series
dimension_list = (requests.get(f'{url}{key}').json()
['Structure']['KeyFamilies']['KeyFamily']
['Components']['Dimension'])
for n, dimension in enumerate(dimension_list):
print('Dimension {}: {}'.format(n+1, dimension['@codelist']))## Dimension 1: CL_FREQ
## Dimension 2: CL_AREA_DOT
## Dimension 3: CL_INDICATOR_DOT
## Dimension 4: CL_COUNTERPART_AREA_DOT
In this case, the dimensions correspond to: 1) frequency, 2) country
or reference area 1, 3) indicator (such as total exports), and 4)
country or reference area 2. That is, the monthly value of goods exports
from Italy to France would be M.IT.TXG_FOB.FR.
The codes which correspond to the dimensions identified above are
combined, in order, and separated by periods, to complete the API
request url. To find the list of possible codes for each dimension, we
can use the CodeList method, shown below for dimension 3, indicators
CL_INDICATOR_DOT.
# Example: codes for third dimension, which is 2 in python
key = f"CodeList/{dimension_list[2]['@codelist']}"
code_list = requests.get(f'{url}{key}').json()\
['Structure']['CodeLists']['CodeList']['Code']
for code in code_list:
print(f"{code['Description']['#text']}: {code['@value']}")## Goods, Value of Exports, Free on board (FOB), US Dollars: TXG_FOB_USD
## Goods, Value of Imports, Cost, Insurance, Freight (CIF), US Dollars: TMG_CIF_USD
## Goods, Value of Imports, Free on board (FOB), US Dollars: TMG_FOB_USD
## Goods, Value of Trade Balance, US Dollars: TBG_USD
Once a series has been identified, it can be a challenge to determine which combination of codes returns valid data. Often an indicator has data available at only one frequency–whichever frequency of compilation occurs in the source country, though there are exceptions.
The number of indicators varies by series. In the case of International Financial Statistics (IFS), there are more than 2500 indicators, while there are four indicators in the Direction of Trade Statistics series. The same search technique used to find the series names can be used to filter IFS indicators.
Here we obtain the metadata related to our request from thefirst part of the code, and show how to make a slightly more advanced request, which includes more than one element in a dimension. Specifically, we calculate the U.K.’s share of goods imports that originate in the EU.
import requests
url = 'http://dataservices.imf.org/REST/SDMX_JSON.svc/'
key = 'GenericMetadata/IFS/M.GB.PMP_IX'
metadata = requests.get(f'{url}{key}').json()
country = metadata['GenericMetadata']['MetadataSet']\
['AttributeValueSet'][1]['ReportedAttribute']\
[1]['ReportedAttribute'][3]['Value']['#text']
indicator = metadata['GenericMetadata']['MetadataSet']\
['AttributeValueSet'][2]['ReportedAttribute']\
[1]['ReportedAttribute'][4]['Value']['#text']
print(f'Country: {country}; Indicator: {indicator}')## Country: United Kingdom; Indicator: Index
The GenericMetadata method returns several options for
country and indicator names, so it may be helpful to step into the JSON
data and examine what is available directly.
Perhaps we are interested in the share of goods the U.K. imports from
the European Union, which requires information on both trade between the
U.K and the EU, and on total U.K. trade. The API allows us to combine
along a single dimension by adding ‘+’. For example, searching for the
dimension 4 (CL_COUNTERPART_AREA_DOT) code using the
technique described in section 4 returns B0 as the code for
the EU, and W00 as the code for the world (total
trade).
Pandas is used for calculations and to make a simple line plot of the result.
import pandas as pd
# key includes two partners, B0 and W00 for EU and world
key = 'CompactData/DOT/M.GB.TMG_CIF_USD.B0+W00'
# Retrieve data from IMF API
data = requests.get(f'{url}{key}').json()
# Convert results to pandas dataframe
df = pd.DataFrame({s['@COUNTERPART_AREA'] : {pd.to_datetime(i['@TIME_PERIOD']) :
round(float(i['@OBS_VALUE']), 1) for i in s['Obs']}
for s in data['CompactData']['DataSet']['Series']})
# 12 month moving average of EU share of total
eu_share = (df['B0'].div(df['W00']) * 100).rolling(12).mean()
# Create a line plot and print most recent value as x label
title = "U.K. imports of goods: European Union share of total"
recent = f"{eu_share.index[-1].strftime('%B %Y')}: {eu_share[-1].round(1)}%"## <string>:1: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`