1 IMF API with Python: An example

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'}

2 Pandas to clean the data and save to csv

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)

3 Visual Inspection with a basic plot

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}')

4 Variations: breaking down the request

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}

5 Finding dimensions and codes

Here we take a look at techniques for finding the right codes to make successful API requests using the requests package.

5.1 The series list

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

5.2 Finding the dimensions of the series

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.

5.3 Finding the codes for each dimension

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

5.4 Variations and notes

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.

6 Metadata and advanced requests

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.

6.1 Obtaining the metadata

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.

6.2 More complex requests

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]`
ax = eu_share.plot(title=title)
ax = ax.set_xlabel(recent)