02 - Setup
Programmatic Access to Data and Statistics from the World Bank API
You can install the latest release version from CRAN with
install.packages("wbstats")
or
The latest development version from github with
remotes::install_github("pachadotdev/wbstats")
Introduction
The World Bank1 is a tremendous source of global socio-economic data; spanning several decades and dozens of topics, it has the potential to shed light on numerous global issues. To help provide access to this rich source of information, The World Bank themselves, provide a well structured RESTful API. While this API is very useful for integration into web services and other high-level applications, it becomes quickly overwhelming for researchers who have neither the time nor the expertise to develop software to interface with the API. This leaves the researcher to rely on manual bulk downloads of spreadsheets of the data they are interested in. This too is can quickly become overwhelming, as the work is manual, time consuming, and not easily reproducible.
The goal of the wbstats package is to provide a bridge
between these alternatives and allow researchers to focus on their
research questions and not the question of accessing the data. The
wbstats package allows researchers to quickly search and
download the data of their particular interest in a programmatic and
reproducible fashion; this facilitates a seamless integration into their
workflow and allows analysis to be quickly rerun on different areas of
interest and with realtime access to the latest available data.
Highlighted features of the wbstats package:
- Uses version 2 of the World Bank API that provides access to more indicators and metadata than the previous API version
- Access to all annual, quarterly, and monthly data available in the API
- Support for searching and downloading data in multiple languages
- Returns data in either wide (default) or long format
- Support for Most Recent Value queries
- Support for
grepstyle searching for data descriptions and names - Ability to download data not only by country, but by aggregates as well, such as High Income or South Asia
Getting Started
Unless you know the country and indicator codes that you want to
download the first step would be searching for the data you are
interested in. wb_search() provides grep style
searching of all available indicators from the World Bank API and
returns the indicator information that matches your query.
To access what countries or regions are available you can use the
countries data frame from either wb_cachelist
or the saved return from wb_cache(). This data frame
contains relevant information regarding each country or region. More
information on how to use this for downloading data is covered
later.
Finding available data with wb_cachelist
For performance and ease of use, a cached version of useful
information is provided with the wbstats package. This data
is called wb_cachelist and provides a snapshot of available
countries, indicators, and other relevant information.
wb_cachelist is by default the the source from which
wb_search() and wb_data() uses to find
matching information. The structure of wb_cachelist is as
follows
library(wbstats)
str(wb_cachelist, max.level = 1)
# List of 8
# $ countries :Classes ‘data.table’ and 'data.frame': 295 obs. of 18 variables:
# $ indicators :Classes ‘data.table’ and 'data.frame': 28517 obs. of 8 variables:
# $ sources :Classes ‘data.table’ and 'data.frame': 71 obs. of 9 variables:
# $ topics :Classes ‘data.table’ and 'data.frame': 21 obs. of 3 variables:
# $ regions :Classes ‘data.table’ and 'data.frame': 43 obs. of 4 variables:
# $ income_levels:Classes ‘data.table’ and 'data.frame': 7 obs. of 3 variables:
# $ lending_types:Classes ‘data.table’ and 'data.frame': 4 obs. of 3 variables:
# $ languages :Classes ‘data.table’ and 'data.frame': 23 obs. of 3 variables:
Accessing updated available data with wb_cache()
For the most recent information on available data from the World Bank
API wb_cache() downloads an updated version of the
information stored in wb_cachelist.
wb_cachelist is simply a saved return of
wb_cache(lang = "en"). To use this updated information in
wb_search() or wb_data(), set the
cache parameter to the saved list returned
from wb_cache(). It is always a good idea to use this
updated information to insure that you have access to the latest
available information, such as newly added indicators or data sources.
There are also cases in which indicators that were previously available
from the API have been removed or deprecated.
library(wbstats)
# default language is english
new_cache <- wb_cache()
Search available data with wb_search()
wb_search() searches through the indicators
data frame to find indicators that match a search pattern. An example of
the structure of this data frame is below
str(wb_search("GDP per person employed"))
# Classes ‘data.table’ and 'data.frame': 2 obs. of 3 variables:
# $ indicator_id : chr "SL.GDP.PCAP.EM.KD" "SL.GDP.PCAP.EM.KD.ZG"
# $ indicator : chr "GDP per person employed (constant 2021 PPP $)" "GDP per person employed (annual % growth)"
# $ indicator_desc: chr "GDP per person employed is gross domestic product (GDP) divided by total employment in the economy. Purchasing "| __truncated__ "GDP per person employed is gross domestic product (GDP) divided by total employment in the economy."
By default the search is done over the indicator_id,
indicator, and indicator_desc fields and
returns the those 3 columns of the matching rows. The
indicator_id values are inputs into wb_data(),
the function for downloading the data.
To return all columns for the indicators data table, you
can set extra = TRUE as below
str(wb_search("GDP per person employed", extra = TRUE))
# Classes ‘data.table’ and 'data.frame': 2 obs. of 8 variables:
# $ indicator_id : chr "SL.GDP.PCAP.EM.KD" "SL.GDP.PCAP.EM.KD.ZG"
# $ indicator : chr "GDP per person employed (constant 2021 PPP $)" "GDP per person employed (annual % growth)"
# $ unit : logi NA NA
# $ indicator_desc: chr "GDP per person employed is gross domestic product (GDP) divided by total employment in the economy. Purchasing "| __truncated__ "GDP per person employed is gross domestic product (GDP) divided by total employment in the economy."
# $ source_org : chr "Staff estimates, World Bank (WB), note: Estimates are based on employment, population, GDP, and PPP data obtain"| __truncated__ "International Labour Organization, Key Indicators of the Labour Market database."
# $ topics :List of 2
# ..$ :'data.frame': 1 obs. of 2 variables:
# .. ..$ id : chr "10"
# .. ..$ value: chr "Social Protection & Labor"
# ..$ :'data.frame': 0 obs. of 0 variables
# $ source_id : int 2 11
# $ source : chr "World Development Indicators" "Africa Development Indicators"
Sometimes the tables are quite long because of a description. Use
data.table options to prevent full printing.
options(datatable.prettyprint.char = 25L)
unemploy_inds<- wb_search("unemployment")
head(unemploy_inds)
# indicator_id indicator indicator_desc
# <char> <char> <char>
# 1: fin37 Received government trans... The percentage of respond...
# 2: fin37.1 Received government trans... The percentage of respond...
# 3: fin37.2 Received government trans... The percentage of respond...
# 4: fin37.38 Received government trans... The percentage of respond...
# 5: fin37.38.1 Received government trans... The percentage of respond...
# 6: fin37.38.2 Received government trans... The percentage of respond...
Other fields can be searched by simply changing the
fields parameter. For example
blmbrg_vars <- wb_search("Bloomberg", fields = "source_org")
head(blmbrg_vars)
# indicator_id indicator indicator_desc
# <char> <char> <char>
# 1: GFDD.OM.02 Stock market return (%, y... Stock market return is th...
# 2: GFDD.SM.01 Stock price volatility Stock price volatility is...
Regular expressions are also supported
# 'poverty' OR 'unemployment' OR 'employment'
povemply_inds <- wb_search(pattern = "poverty|unemployment|employment")
head(povemply_inds)
# indicator_id indicator
# <char> <char>
# 1: 1.0.HCount.1.90usd Poverty Headcount ($1.90 ...
# 2: 1.0.HCount.2.5usd Poverty Headcount ($2.50 ...
# 3: 1.0.HCount.Mid10to50 Middle Class ($10-50 a da...
# 4: 1.0.HCount.Ofcl Official Moderate Poverty...
# 5: 1.0.HCount.Poor4uds Poverty Headcount ($4 a d...
# 6: 1.0.HCount.Vul4to10 Vulnerable ($4-10 a day) ...
# indicator_desc
# <char>
# 1: The poverty headcount ind...
# 2: The poverty headcount ind...
# 3: The poverty headcount ind...
# 4: The poverty headcount ind...
# 5: The poverty headcount ind...
# 6: The poverty headcount ind...
As well as any grep function argument
# contains "gdp" and NOT "trade"
gdp_no_trade_inds <- wb_search("^(?=.*gdp)(?!.*trade).*", perl = TRUE)
head(gdp_no_trade_inds)
# indicator_id indicator
# <char> <char>
# 1: 6.0.GDP_current GDP (current $)
# 2: 6.0.GDP_growth GDP growth (annual %)
# 3: 6.0.GDP_usd GDP (constant 2005 $)
# 4: 6.0.GDPpc_constant GDP per capita, PPP (cons...
# 5: BI.WAG.TOTL.GD.ZS Wage bill as a percentage...
# 6: BM.KLT.DINV.WD.GD.ZS Foreign direct investment...
# indicator_desc
# <char>
# 1: GDP is the sum of gross v...
# 2: Annual percentage growth ...
# 3: GDP is the sum of gross v...
# 4: GDP per capita based on p...
# 5:
# 6: Foreign direct investment...
The default cached data in wb_cachelist is in English.
To search indicators in a different language, you can download an
updated copy of wb_cachelist using wb_cache(),
with the lang parameter set to the language of interest and
then set this as the cache parameter in
wb_search(). Other languages are supported in so far as
they are supported by the original data sources. Some sources provide
full support for other languages, while some have very limited support.
If the data source does not have a translation for a certain field or
indicator then the result is NA, this may result in a
varying number matches depending upon the language you select. To see a
list of availabe languages call wb_languages()
wb_langs <- wb_languages()
Downloading data with wb_data()
Once you have found the set of indicators that you would like to
explore further, the next step is downloading the data with
wb_data(). The following examples are meant to highlight
the different ways in which wb_data() can be used and
demonstrate the major optional parameters.
The default value for the country parameter is a special
value of "countries_only", which as you might expect,
returns data on the selected indicator for only countries.
This is in contrast to country = "all" or
country = "regions_only" which would return data for
countries and regional aggregates together, or only regional aggregates,
respectively
# Population, total
pop_data <- wb_data("SP.POP.TOTL", start_date = 2000, end_date = 2002)
head(pop_data)
# iso2c iso3c country date SP.POP.TOTL unit obs_status footnote
# <char> <char> <char> <num> <num> <char> <char> <char>
# 1: AW ABW Aruba 2000 90588 <NA> <NA>
# 2: AW ABW Aruba 2001 91439 <NA> <NA>
# 3: AW ABW Aruba 2002 92074 <NA> <NA>
# 4: AF AFG Afghanistan 2000 20130327 <NA> <NA>
# 5: AF AFG Afghanistan 2001 20284307 <NA> <NA>
# 6: AF AFG Afghanistan 2002 21378117 <NA> <NA>
# last_updated
# <char>
# 1: 2026-07-13
# 2: 2026-07-13
# 3: 2026-07-13
# 4: 2026-07-13
# 5: 2026-07-13
# 6: 2026-07-13
If you are interested in only some subset of countries or regions you
can pass along the specific codes to the country parameter.
The country and region codes and names that can be passed to the
country parameter as well, most prominently the coded
values from the iso2c and iso3c from the
countries data frame in wb_cachelist or the
return of wb_cache(). Any values from the above columns can
mixed together and passed to the same call.
# you can mix different ids and they are case insensitive
# you can even use SpOnGeBoB CaSe if that's the kind of thing you're into
# iso3c, iso2c, country, region_iso3c, admin_region_iso3c, admin_region, income_level
example_geos <- c("ABW","AF", "albania", "SSF", "eca", "South Asia", "HiGh InCoMe")
pop_data <- wb_data("SP.POP.TOTL", country = example_geos, start_date = 2012, end_date = 2012)
pop_data
# iso2c iso3c country date SP.POP.TOTL unit
# <char> <char> <char> <num> <num> <char>
# 1: XD High income 2012 1342428399 <NA>
# 2: AW ABW Aruba 2012 104110 <NA>
# 3: AF AFG Afghanistan 2012 30560034 <NA>
# 4: AL ALB Albania 2012 2860708 <NA>
# 5: 7E ECA Europe & Central Asia (ex... 2012 233875199 <NA>
# 6: 8S SAS South Asia 2012 1483553073 <NA>
# 7: ZG SSF Sub-Saharan Africa 2012 944523292 <NA>
# obs_status footnote last_updated
# <char> <char> <char>
# 1: <NA> 2026-07-13
# 2: <NA> 2026-07-13
# 3: <NA> 2026-07-13
# 4: <NA> WB estimate by interpolat... 2026-07-13
# 5: <NA> 2026-07-13
# 6: <NA> 2026-07-13
# 7: <NA> 2026-07-13
As of wbstats 1.0 queries are now returned in wide
format. This was a request made by multiple users and is in line with
the principles of tidy data. If
you would like to return the data in a long format, you can set
return_wide = FALSE.
Now that each indicator is it’s own column, we can allow custom names for the indicators
my_indicators = c("pop" = "SP.POP.TOTL", "gdp" = "NY.GDP.MKTP.CD")
pop_gdp <- wb_data(my_indicators, start_date = 2010, end_date = 2012)
head(pop_gdp)
# iso2c iso3c country date gdp pop
# <char> <char> <char> <num> <num> <num>
# 1: AW ABW Aruba 2010 2453597207 101838
# 2: AW ABW Aruba 2011 2637859218 102591
# 3: AW ABW Aruba 2012 2615208380 104110
# 4: AF AFG Afghanistan 2010 15856668556 28284089
# 5: AF AFG Afghanistan 2011 17805098206 29347708
# 6: AF AFG Afghanistan 2012 19907329778 30560034
You’ll notice that when you query only one indicator, as in the first
two examples above, it returns the extra fields unit,
obs_status, footnote, and
last_updated, but when we queried multiple indicators at
once, as in our last example, they are dropped. This is because those
extra fields are tied to a specific observation of a single indicator
and when we have multiple indicator values in a single row, they are no
longer consistent with the tidy data format. If you would like that
information for multiple indicators, you can use
return_wide = FALSE
my_indicators = c("pop" = "SP.POP.TOTL", "gdp" = "NY.GDP.MKTP.CD")
pop_gdp_long <- wb_data(my_indicators, start_date = 2010, end_date = 2012, return_wide = FALSE)
head(pop_gdp_long)
# indicator_id indicator iso2c iso3c country date value
# <char> <char> <char> <char> <char> <num> <num>
# 1: SP.POP.TOTL Population, total AF AFG Afghanistan 2012 30560034
# 2: SP.POP.TOTL Population, total AF AFG Afghanistan 2011 29347708
# 3: SP.POP.TOTL Population, total AF AFG Afghanistan 2010 28284089
# 4: SP.POP.TOTL Population, total AL ALB Albania 2012 2860708
# 5: SP.POP.TOTL Population, total AL ALB Albania 2011 2905195
# 6: SP.POP.TOTL Population, total AL ALB Albania 2010 2913021
# unit obs_status last_updated
# <char> <char> <char>
# 1: <NA> <NA> 2026-07-13
# 2: <NA> <NA> 2026-07-13
# 3: <NA> <NA> 2026-07-13
# 4: <NA> <NA> 2026-07-13
# 5: <NA> <NA> 2026-07-13
# 6: <NA> <NA> 2026-07-13
Using mrv and mrnev
If you do not know the latest date an indicator you are interested in
is available for you country you can use the mrv instead of
start_date and end_date. mrv
stands for most recent value and takes a integer
corresponding to the number of most recent values you wish to return
# most recent gdp per captia estimates
gdp_capita <- wb_data("NY.GDP.PCAP.CD", mrv = 1)
head(gdp_capita)
# iso2c iso3c country date NY.GDP.PCAP.CD unit obs_status
# <char> <char> <char> <num> <num> <char> <char>
# 1: AW ABW Aruba 2025 NA <NA> <NA>
# 2: AF AFG Afghanistan 2025 NA <NA> <NA>
# 3: AO AGO Angola 2025 3129.477 <NA> <NA>
# 4: AL ALB Albania 2025 12998.148 <NA> <NA>
# 5: AD AND Andorra 2025 54291.503 <NA> <NA>
# 6: AE ARE United Arab Emirates 2025 NA <NA> <NA>
# footnote last_updated
# <char> <char>
# 1: <NA> 2026-07-13
# 2: <NA> 2026-07-13
# 3: <NA> 2026-07-13
# 4: <NA> 2026-07-13
# 5: <NA> 2026-07-13
# 6: <NA> 2026-07-13
Often it is the case that the latest available data is different from
country to country. There may be 2020 estimates for one location, while
another only has estimates up to 2019. This is especially true for
survey data. When you would like to return the latest avialble data for
each country regardless of its temporal misalignment, you can use the
mrnev instead of mrnev. mrnev
stands for most recent non empty value.
gdp_capita <- wb_data("NY.GDP.PCAP.CD", mrnev = 1)
head(gdp_capita)
# iso2c iso3c country date NY.GDP.PCAP.CD obs_status
# <char> <char> <char> <num> <num> <char>
# 1: AW ABW Aruba 2024 38590.5650 <NA>
# 2: AF AFG Afghanistan 2024 416.8711 <NA>
# 3: AO AGO Angola 2025 3129.4766 <NA>
# 4: AL ALB Albania 2025 12998.1479 <NA>
# 5: AD AND Andorra 2025 54291.5026 <NA>
# 6: AE ARE United Arab Emirates 2024 50273.5126 <NA>
# last_updated
# <char>
# 1: 2026-07-13
# 2: 2026-07-13
# 3: 2026-07-13
# 4: 2026-07-13
# 5: 2026-07-13
# 6: 2026-07-13
Dates
Because the majority of data available from the World Bank is at the
annual resolution, by default dates in wbstats are returned
as numerics. This default makes common tasks like filtering
easier. If you would like the date field to be of class
Date you can set date_as_class_date = TRUE
Some Sharp Corners
There are a few behaviors of the World Bank API that being aware of could help explain some potentially unexpected results. These results are known but no special actions are taken to mitigate them as they are the result of the API itself and artifically limiting the inputs or results could potentially causes problems or create unnecessary rescrictions in the future.
Searching in other languages
Not all data sources support all languages. If an indicator does not
have a translation for a particular language, the non-supported fields
will return as NA. This could potentially result in a
differing number of matching indicators from
wb_search()
# english
cache_en <- wb_cache()
sum(is.na(cache_en$indicators$indicator))
#> [1] 0
# spanish
cache_es <- wb_cache(lang = "es")
sum(is.na(cache_es$indicators$indicator))
#> [1] 14791
Legal
The World Bank Group, or any of its member instutions, do not support or endorse this software and are not libable for any findings or conclusions that come from the use of this software.