Demystifying ICP Purchasing Power Parity calculations using Python
This page in:
New results from the ICP including purchasing power parities (PPP), price level indexes and PPPbased expenditures for reference year 2017 are now available at icp.worldbank.org. This blog series, edited by Edie Purdie, covers all aspects of the ICP and explores the use made of these data by researchers, policymakers, economists, data scientists and others. We encourage users to share their data applications and findings in this blog series via [email protected].
Every few years, results from the International Comparison Program (ICP) make headlines around the world as they shed new light on the size and structure of the global economy. Underlying these results are purchasing power parities (PPPs) for countries across the globe. PPPs are exchange rates or conversion factors that enable us to convert one currency into the other while also—and this part is key—adjusting for the fact that prices of the same goods and services may differ between countries.
In this blog we describe the calculation steps and the main formulas needed to estimate ICP PPPs using a workedout coded example. To help with the task, we use mock average price data and Python, a free, opensource programming language with an evergrowing community of users around the world.
Details of ICP PPP calculations have been publicly available for years. The methodology section of the ICP website and Chapter 5 of the ICP 2017 report Purchasing Power Parities and the Size of World Economies provide a general account of the calculation of ICP PPPs. The ICP PPP eLearning course also provides an overview of the calculation process. Furthermore, Deaton and Heston (2010) provides details on the choice of formulas used in the calculations and their properties as they apply to ICP PPPs. However, ICP PPP calculations are not the most intuitive concept, and providing another way to access this information will be a helpful refresher to some, and a useful initiation to those who are programmingadept but newcomers to ICP PPPs.
Example of estimating PPPs using Python
What follows are code excerpts, using a Jupyter notebook, sequentially organized to understand how ICP PPPs are estimated. Click each tab to see the Python code applied at each step. The full notebook with the entire code set in an executable online environment is available here (no installations are required, but it may take some time to load the first few times).
Input Data
We start by loading the input dataset containing mock average price data and other relevant countrylevel information. A review of the price and expenditure data required to estimate PPPs is provided in the World Bank Data Blog How does the ICP measure price levels across the world?
## Load libraries
import pandas as pd
import numpy as np
import statsmodels.api as sm
# Load price data
data="price_data.csv"
prices=pd.read_csv(data)
prices # Show full dataset
This mock dataset contains four countries ('country') and three basic headings ('bh'): garment; rice; and pork. 'Basic headings' in the ICP literature refer to detailed expenditure categories containing similar item varieties, for example the 'Rice' basic heading contains several rice varieties. It is also the lowest level of aggregation for which PPPs are first calculated. The different item varieties in each basic heading are noted under the 'item' column, for example, within 'garment' there are three item varieties, identified as 'garment 1', 'garment 2', and 'garment 3'. Finally, an average price in the local currency unit of each country is reported for each item ('price') and information on the relative importance of each item in a country's consumption at the basic heading level is included for each item priced in the importance column ('imp'). Following the guidelines provided by the ICP Technical Advisory Group, countries assign a weight of '3' to items identified as 'important' within a given basic heading and a weight of '1' to items deemed unimportant
It should be highlighted that in practice the full ICP classification consists of 155 basic headings with the number of items within each varying from one basic heading to another. Also, not all countries are able to report prices for all items. These two realities are reflected in the example: some basic headings contain more items than others, and prices for some items are missing in some countries.
Basic heading PPPs
PPPs are first estimated at the basic heading level resulting in a set of several PPPs per country, one PPP for each basic heading per country.
The estimation procedure involves averaging price relatives for individual items from different countries within each basic heading to obtain basic headinglevel PPPs. This is done via a regression method known as the weighted country product dummy (CPDW).
The CPDW is carried out within each basic heading by regressing the logarithm of the observed country item prices on item dummies (one for each item) and country dummies (one for each country other than the numeraire). The CPDW method also incorporates the country reported itemlevel importance indicators discussed earlier with the idea of "downweighting" unrepresentative items during the calculation.
numeraire = 'country2'
This refers to the currency against which all the estimated PPP values will be compared. In the case of the global PPP results, the numeraire is the US dollar. In this case, we select the currency of 'country2' as the numeraire and say that 'country2' is the base or reference country.
for bh in prices.bh.unique():
tempdf=prices[prices.bh == bh]
X=tempdf.loc[:, [x for x in tempdf.columns if x.startswith(('c_', 'i_'))]]
y=np.log(tempdf['price'])
wts=tempdf['imp']
wts_cpd=sm.WLS(y, X,weights=wts) res=wts_cpd.fit() res_eparams=np.exp(res.params)
print("\n","Basic Heading:", bh, "\n")
print('Exponentiated Parameters:',"\n",
res_eparams)
l_coef.append(res_eparams)
l_bh.append(bh)
coef = np.array(l_coef, dtype=float)
coef = np.round(coef,4) # round to 4 decimals
cols = list(X) #store column heads of X as a list
coef[coef == 1] = np.nan #%% replace PPPs that were exp(0)=1 with 'np.nan'
Basic Heading PPPs  
bh 
country2 
country1 
country3 
country4 
garment  1.0  9.7435  20.3606  0.0947 
pork  1.0  13.8749  18.9851  0.0917 
rice  1.0  14.0847  10.5113  0.0672 
Abovebasic heading PPPs
Next, PPPs estimated at the basic heading level are aggregated using national accounts expenditure values in local currency units for each country as weights.
The aggregation method involves constructing bilateral PPPs for each pair of countries, using basic headinglevel national accounts expenditure values as weights from each country in turn. First, a Laspeyrestype bilateral PPP is calculated between each pair of countries and then a Paaschetype bilateral PPP. The geometric mean of the Laspeyres and Paaschetype bilateral PPPs gives us the Fishertype bilateral PPP between each pair of countries in the dataset.
#Load basic heading expenditure values
#Should contain bh and countries with prefix c
code="bhdata_exp.csv"
df_bh=pd.read_csv(code,index_col="icp_bh")
#Sort cols with numeraire as col1
def sorting(first_col, df):
columns = df.columns.tolist()
columns.remove(first_col)
columns.insert(0,first_col)
return df.reindex(columns, axis=1)
df_bhexp=sorting(c_numeraire,df_bh)
#sort rows alphabetically
df_bhexp=df_bhexp.sort_values('icp_bh')
Laspeyrestype bilateral PPPs:  

country2 
country1 
country3 
country4 
country2 
1.000 
0.072 
0.056 
12.639 
country1 
12.467 
1.000 
0.640 
172.687 
country3 
14.229 
1.294 
1.000 
185.295 
country4 
0.078 
0.006 
0.005 
1.000 
Paaschetype bilateral PPPs:  

country2 
country1 
country3 
country4 
country2 
1.000 
0.080 
0.070 
12.889 
country1 
13.857 
1.000 
0.773 
156.306 
country3 
17.899 
1.563 
1.000 
203.452 
country4 
0.079 
0.006 
0.005 
1.000 
Fishertype bilateral PPPs:  

country2 
country1 
country3 
country4 
country2 
1.000 
0.076 
0.063 
12.763 
country1 
13.143 
1.000 
0.703 
164.292 
country3 
15.959 
1.422 
1.000 
194.162 
country4 
0.078 
0.006 
0.005 
1.000 
As a last step, the GiniÉltetőKövesSzulc (GEKS) method is applied to the matrix of Fishertype bilateral PPPs. GEKS PPPs are calculated between each country relative to the numeraire or base country. To this end, the first step is to divide each country row of the Fishertype bilateral PPP matrix by the row of the numeraire country. Each row will then contain two direct PPPs (each country to itself and directly to the numeraire country) and n−2 indirect PPPs (each country to the numeraire country via each of the other third countries), where n equals the total number of countries in the dataset. Finally, the GEKS PPP for each country relative to the numeraire is given by the geometric mean of the direct and indirect PPPs in each respective country row.
GEKS PPPs are considered 'multilateral' because the GEKS procedure uses both direct and indirect PPPs and thus takes into account the relative prices between all the countries as a group. The GEKS method is needed to make the Fishertype bilateral PPPs transitive and base countryinvariant. Transitivity means that the PPP between any two countries should be the same whether it is computed directly or indirectly through a third country. Base countryinvariant means that the PPPs between any two countries should be the same regardless of the choice of base or numeraire country.
#Calculate GEKS multilateral ppps
##requires the earlier nangmean function
geks = np.zeros(shape) # zero 'country x country' matrix
nrow=len(geks) # gets the number of rows
ncol=len(geks[0])
for row in range(nrow):
for col in range(ncol):
geks[row][col]= nangmean(fi[row]/fi[col])
geks_vec = np.zeros(shape=(1,len(df_bhexp.columns)))
# as we need a vec tor of ppps, not a matrix
j=len(geks_vec[0])
for col in range(j):#..one PPP per country, or col of bhexp df
geks_vec[:,col]=nangmean(geks[col,0]/geks[0,0]) #ge
omean over each row, w/ each col rebased to country in col1
geks_ppp = np.array(geks_vec)
GEKS Multilateral PPPs:  
country2 
country1 
country3 
country4 
1.0  12.568  16.405  0.08 
In the above example we showcased the main steps to calculate PPPs. Information about the overall ICP methodology is provided on the ICP website.
Join the Conversation