---
title: "wbstats"
author: "Jesse Piburn and Mauricio Vargas Sepulveda"
output:
  litedown::html_format:
    options:
      toc: true
      number_sections: true
vignette: >
  %\VignetteIndexEntry{02 - Setup}
  %\VignetteEngine{litedown::vignette}
  %\VignetteEncoding{UTF-8}
editor:
  markdown:
    wrap: sentence
---

# Programmatic Access to Data and Statistics from the World Bank API

You can install the latest release version from CRAN with

```r
install.packages("wbstats")
```

or

The latest development version from github with

```r
remotes::install_github("pachadotdev/wbstats")
```

# Introduction

The World Bank^[<https://www.worldbank.org/ext/en/home>] 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 `grep` style 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

```r
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.

```r
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

```r
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

```r
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.

```r
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

```r
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

```r
# '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

```r
# 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()`

```r
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

```r
# 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.

```r
# 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](https://www.jstatsoft.org/article/view/v059i10).
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

```r
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`

```r
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

```r
# 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.

```r
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 `numeric`s. 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()`

```r
# 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.
