#' Generate a World Bank API URL that will return all results for a given indicator in JSON format
#'
#' results and returns JSON format
#'
#' @name url_all_results
#'
#' @usage url_all_results(original_url)
#'
#' @param original_url A World Bank API URL. E.g. "https://api.worldbank.org/v2/country".
#'
#' @return A character vector
#'
#' @import dplyr stringr
#'
#' @export
#'
#' @examples
#'
#' # Provide a World Bank API URL and `url_all_results` will convert it into one with all results
#' # for that indicator
#' \dontrun{
#' original_url <- "https://api.worldbank.org/v2/country" # Note: no ?format=json on url
#' url_all_results(original_url)
#' }
#' # "https://api.worldbank.org/v2/country?format=json&per_page=304"
#'
#'
#'
url_all_results <- function(original_url) {
# Append "?format=json" if url doesn't already have it
if(!grepl("\\?", original_url)) {
original_url <- paste0(original_url, "?format=json")
}
total_results <- original_url %>% fromJSON %>% .[[1]] %>% .$total
cat("Generating URL to request all", total_results, "results\n")
url_with_all_results <- paste0(original_url, "&per_page=", total_results)
url_with_all_results
}
#' Show available country codes
#'
#' `show_countries` calls the World Bank API and retrieves a list of available countries and regions
#' @name show_countries
#'
#' @usage show_countries()
#'
#' @return A data.frame of countries available to query using the World Bank API
#'
#' @import dplyr stringr
#' @importFrom jsonlite fromJSON
#'
#' @export
#'
#' @examples
#'
#' # Simply call show_countries() to receive a dataframe of all countries (and regions) and their
#' # iso2Code
#'
#' # show_countries()
#' # iso2Code country_name
#' # 1 AW Aruba
#' # 2 AF Afghanistan
#' # 3 A9 Africa
#' # 4 AO Angola
#' # Etc
#'
#'
show_countries <- function() {
countries_url <- "https://api.worldbank.org/v2/country?format=json" %>% url_all_results(.)
countries <- countries_url %>% fromJSON(.)
countries <- data.frame(countries[[2]]$iso2Code, countries[[2]]$name, stringsAsFactors = FALSE)
colnames(countries) <- c("iso2Code", "country_name")
return(countries)
}
#' Determines whether country input is a country name or iso2Code
#'
#' Determines whether a string is a country name, an iso2Code, or invalid (not a World Bank API
#' country/region)
#' @name country_input_type
#'
#' @usage country_input_type(country_input, countries_dataframe)
#'
#' @param country_input A country/region the user wishes to validate (string) E.g. "Australia".
#' @param countries_dataframe A dataframe containing available iso2Code and country_name
#' (see show_countries()).
#'
#' @return A character vector
#'
#' @import dplyr stringr
#' @importFrom jsonlite fromJSON
#'
#' @export
#'
#' @examples
#'
#' \dontrun{
#' # Assign so as to save on API calls - recommended
#' countries_dataframe <- show_countries()
#'
#' country <- "Australia"
#' country_input_type(country, countries_dataframe)
#' # [1] "country_name"
#'
#' country <- "AU"
#' country_input_type(country, countries_dataframe)
#' # [1] "iso2Code"
#'
#' country <- "something other than a valid country name or iso2Code"
#' country_input_type(country, countries_dataframe)
#' # [1] "invalid"
#' }
country_input_type <- function(country_input, countries_dataframe) {
# Logic: if the country_input is found among the iso2Codes, assume it's an iso2Code,
# if the country_input if not found in iso2Codes, then check to see if it's found among
# country_names,
# and if not, return 'invalid'
if (which(countries_dataframe$iso2Code %in% country_input) %>% length > 0) {
country_input_type_string <- "iso2Code"
} else if (which(countries_dataframe$country_name %in% country_input) %>% length > 0) {
country_input_type_string <- "country_name"
} else {
country_input_type_string <- "invalid"
}
return(country_input_type_string)
}
#' Convert any country input into its iso2Code
#'
#' `convert_to_iso2Code` accepts the type of country input and the country, and returns the relevant iso2Code
#' @name convert_to_iso2Code
#'
#' @usage convert_to_iso2Code(country_input_type_string, country, countries_dataframe)
#'
#' @param country_input_type_string Either "country_name" or "iso2Code" - use country_input_type(country, countries_dataframe) to determine or assign manually.
#' @param country A country/region name or iso2Code.
#' @param countries_dataframe The output of show_countries()
#'
#' @return A character vector containing a valid iso2Code
#'
#' @import dplyr stringr
#' @importFrom jsonlite fromJSON
#'
#' @export
#'
#' @examples
#'
#' \dontrun{
#'
#' # Assign so as to save on API calls (recommended)
#' countries_dataframe <- show_countries()
#' country <- "Australia"
#' country_input_type_string <- country_input_type(country, countries_dataframe)
#' convert_to_iso2Code(country_input_type_string, country, countries_dataframe)
#' # [1] "AU"
#'
#' country <- "AU"
#' country_input_type_string <- country_input_type(country, countries_dataframe)
#' convert_to_iso2Code(country_input_type_string, country, countries_dataframe)
#' # [1] "AU"
#'
#'
#' }
#'
convert_to_iso2Code <- function(country_input_type_string, country, countries_dataframe) {
if(country_input_type_string == "iso2Code") { country <- country }
if(country_input_type_string == "invalid") { stop(paste0("'", country, "'", " is not a valid country input - select a valid country from show_countries()")) }
if(country_input_type_string == "country_name") {
index_of_country_in_countries <- which(countries_dataframe$country_name %in% country)
country <- countries_dataframe$iso2Code[index_of_country_in_countries]
}
return(country)
}
#' Retrieve historical inflation data
#'
#' Retrieve inflation data for any country/region (using iso2Code or country_name)
#' @name retrieve_inflation_data
#'
#' @usage retrieve_inflation_data(country, countries_dataframe)
#'
#' @param country A country_name or iso2code (see show_countries() for complete list of available inputs).
#' @param countries_dataframe The output from show_countries(). It is optional, but if not provided, it will be retrieved via the API.
#'
#' @return A data.frame containing inflation data from World Bank API for specified country
#'
#' @import dplyr stringr
#' @importFrom jsonlite fromJSON
#'
#' @export
#'
#' @examples
#' \dontrun{
#' # Retrieve inflation data for any country (or iso2Code)
#' country <- "AU"
#' inflation_dataframe <- retrieve_inflation_data(country)
#'
#' country <- "Australia"
#' countries_dataframe <- show_countries()
#' inflation_dataframe <- retrieve_inflation_data(country, countries_dataframe)
#' }
#' # inflation_dataframe
#' # indicator.id indicator.value country.id country.value value
#' # FP.CPI.TOTL.ZG Inflation, consumer prices (annual %) AU Australia <NA>
#' # FP.CPI.TOTL.ZG Inflation, consumer prices (annual %) AU Australia 1.94864
#' # FP.CPI.TOTL.ZG Inflation, consumer prices (annual %) AU Australia 1.27699
#' # FP.CPI.TOTL.ZG Inflation, consumer prices (annual %) AU Australia 1.50836
#' # Etc
#'
#'
retrieve_inflation_data <- function(country, countries_dataframe) {
if(missing(countries_dataframe)) {
cat("Validating iso2Code for", country, "\n")
countries_dataframe <- show_countries()
}
# Ensure we have an iso2Code
country_input_type_string <- country_input_type(country, countries_dataframe)
country <- convert_to_iso2Code(country_input_type_string, country, countries_dataframe)
cat("Retrieving inflation data for", country, "\n")
# Works
# https://api.worldbank.org/v2/country/AU/indicator/FP.CPI.TOTL.ZG?format=json
inflation_url <- paste0("https://api.worldbank.org/v2/country/", country, "/indicator/FP.CPI.TOTL.ZG")
inflation_url <- inflation_url %>% url_all_results
inflation_data <- inflation_url %>% fromJSON(.)
inflation_data <- inflation_data[[2]]
return(inflation_data)
}
#' Convert nominal prices into real prices
#'
#' Inflate/deflate prices from any year to any year, using World Bank inflation data and assumptions only where necessary.
#' Typically used for converting past (nominal) values into current (real) values. This uses World Bank inflation data where available,
#' but allows for both historical and future assumptions in extrapolation.
#' @name adjust_for_inflation
#'
#' @usage adjust_for_inflation(price, from_date, country, to_date, inflation_dataframe,
#' countries_dataframe, extrapolate_future_method, future_averaging_period, future_rate,
#' extrapolate_past_method, past_averaging_period, past_rate)
#'
#' @param price A price (or prices).
#' @param from_date A date(s) from which the prices will be converted.
#' @param country A country or region in whose currency the prices are denominated.
#' @param to_date A date(s) to which the prices will be converted.
#' @param inflation_dataframe The R object (list) representing the JSON retrieved by calling retrieve_inflation_data().
#' @param countries_dataframe The R object (data.frame) representing the JSON retreived by calling show_countries().
#' @param extrapolate_future_method The extrapolation method that shall be used if extrapolation into the future is required. Options are 'average' or 'rate'.
#' @param future_averaging_period The number of recent periods to average in order to extrapolate forward (if 'average' is method being used).
#' @param future_rate An assumed rate of inflation to use for extrapolating forward (if 'rate' is method being used).
#' @param extrapolate_past_method The extrapolation method that shall be used if extrapolation from the earliest available data to some even earlier period is required.
#' @param past_averaging_period The number of periods back from the earliest available inflation data for a given country to average in order to extrapolate into the past (if 'average' is method being used).
#' @param past_rate An assumed rate of inflation to use for extrapolating from the earliest available data to some even earlier period (if 'rate' is method being used).
#'
#' @return A vector of inflation-adjusted prices
#'
#' @import dplyr stringr
#' @importFrom jsonlite fromJSON
#' @importFrom purrr map_df
#' @importFrom stats na.omit
#'
#' @export
#'
#'
#' @examples
#' \dontrun{
#' # Assign these variables once
#' country <- "AU"
#' inflation_dataframe <- retrieve_inflation_data(country)
#' countries_dataframe <- show_countries()
#'
#' # Convert $100 from 2005 into 2017 dollars
#'
#' adjust_for_inflation(100, 2005, country, to_date = 2017,
#' inflation_dataframe = inflation_dataframe,
#' countries_dataframe = countries_dataframe)
#'
#' # [1] 133.9861 # i.e. $100 in 2005 had the same purchasing power as $133.99 in 2017
#' }
#'
adjust_for_inflation <- function(price,
from_date,
country,
to_date,
inflation_dataframe,
countries_dataframe,
extrapolate_future_method, # exf_method
future_averaging_period, #exf_period
future_rate, # exf_rate
extrapolate_past_method, #exp_method
past_averaging_period, # exp_period
past_rate) { # exp_rate
# Later, it would be great to include a parameter for 'extrapolate = TRUE' - this could project for earlier and later dates, rather than returning NA
#----- Deal with missing parameters and define functions -----#
if(missing(extrapolate_future_method)) { extrapolate_future <- FALSE } else { extrapolate_future <- TRUE }
if(missing(extrapolate_past_method)) { extrapolate_past <- FALSE } else { extrapolate_past <- TRUE }
if(extrapolate_future) {
if(missing(extrapolate_future_method)) { stop("'extrapolate_future_method' must be specified (it can take values 'average' or 'rate')") }
if(!extrapolate_future_method %in% c("average", "rate")) { stop("'extrapolate_future_method' must be either 'average' or 'rate'")}
if(extrapolate_future_method == 'average') { if(missing(future_averaging_period)) {
stop("Please specify how many years' average to use when extrapolating forward ('future_averaging_period'
can take any positive integer or 'all' to use an average of all available years' data")}}
if(extrapolate_future_method == 'rate') {
if(missing(future_rate)) { stop("Please specify the assumed rate of inflation for future periods (i.e. using 'future_rate' parameter)")}
}
}
if(extrapolate_past) {
if(missing(extrapolate_past_method)) { stop("'extrapolate_past_method' must be specified (it can take values 'average' or 'rate')") }
if(!extrapolate_past_method %in% c("average", "rate")) { stop("'extrapolate_past_method' must be either 'average' or 'rate'")}
if(extrapolate_past_method == 'average') { if(missing(past_averaging_period)) {
stop("Please specify how many years' average to use when extrapolating forward ('past_averaging_period'
can take any positive integer or 'all' to use an average of all available years' data")}}
if(extrapolate_past_method == 'rate') {
if(missing(past_rate)) { stop("Please specify the assumed rate of inflation for past periods (i.e. using 'past_rate' parameter)")}
}
}
# If no to_date is provided, assume conversion into present day dollars is intended
if(missing(to_date)) {
to_date <- rep(Sys.Date(), length(price)) %>% substr(., 1, 4) %>% as.integer
} else { to_date <- to_date %>% substr(., 1, 4) %>% as.integer }
from_date <- substr(from_date, 1, 4) %>% as.integer # Ensure to / from are just years (for now)
to_date <- substr(to_date, 1, 4) %>% as.integer # Ensure to / from are just years (for now)
# Validate that there are as many dates as prices (or just one date)
if(!(length(price) == length(from_date) | length(from_date) == 1)) {
stop("from_date must be a date or a vector of dates of the same length as price")
}
if(missing(countries_dataframe)) {
message("Retrieving countries data")
# Determine country input type
countries_dataframe <- show_countries()
}
country_input_type_string <- country_input_type(country, countries_dataframe)
country <- convert_to_iso2Code(country_input_type_string, country, countries_dataframe)
# 'country' is iso2Code from here on
name_of_country <- which(countries_dataframe$iso2Code %in% country) %>% countries_dataframe$country_name[.]
if(missing(inflation_dataframe)) { inflation_dataframe <- retrieve_inflation_data(country, countries_dataframe = countries_dataframe) }
inflation_dataframe <- inflation_dataframe %>% .[ , c("value", "date")]
inflation_dataframe$date <- inflation_dataframe$date %>% as.integer
inflation_dataframe$value <- inflation_dataframe$value %>% as.numeric
#----- Extrapolation logic -----#
# Extrapolating future
available_inflation_data <- inflation_dataframe %>% na.omit
max_year_requested <- max(c(to_date, from_date)) # from_date included here for edge case where
# going from future from date to future to_date AND where from_date > to_date
max_year_available_without_extrapolation <- max(available_inflation_data$date)
if(extrapolate_future & (max_year_requested > max_year_available_without_extrapolation)) {
if(extrapolate_future_method == 'average'){
# Take one more than requested as we are looking for a rate (difference between years)
number_of_years_to_use_for_average <- min(future_averaging_period, nrow(available_inflation_data))
years_to_extrapolate <- seq(max_year_available_without_extrapolation + 1, max_year_requested, 1)
average_inflation <- available_inflation_data[1:number_of_years_to_use_for_average, "value"] %>% as.numeric %>% mean
extrapolated_dataframe <- data.frame(value=rep(average_inflation, length(years_to_extrapolate)), years_to_extrapolate, stringsAsFactors = FALSE) %>% map_df(rev) %>% as.data.frame
colnames(extrapolated_dataframe) <- c("value", "date")
inflation_dataframe <- rbind(extrapolated_dataframe, available_inflation_data)
} # End future / average
if(extrapolate_future_method == 'rate'){
years_to_extrapolate <- seq(max_year_available_without_extrapolation + 1, max_year_requested, 1)
average_inflation <- future_rate
extrapolated_dataframe <- data.frame(value=rep(average_inflation, length(years_to_extrapolate)), years_to_extrapolate, stringsAsFactors = FALSE) %>% map_df(rev) %>% as.data.frame
colnames(extrapolated_dataframe) <- c("value", "date")
inflation_dataframe <- rbind(extrapolated_dataframe, available_inflation_data)
} # End future / rate
} # End outermost else for extrapolating future
# Extrapolating past
min_year_requested <- min(to_date, from_date) # Note that this is still the 'to_date'; taking whatever values are provided into a date in the past
min_year_available_without_extrapolation <- min(available_inflation_data$date)
if(extrapolate_past & (min_year_requested >= min_year_available_without_extrapolation)) {
warning(paste0("Past extrapolation not required as data available for ", country, " back to ", min_year_available_without_extrapolation))
}
if(extrapolate_past & (min_year_requested < min_year_available_without_extrapolation)) {
if(extrapolate_past_method == 'average'){
# Take one more than requested as we are looking for a rate (difference between years)
number_of_years_to_use_for_average <- min(past_averaging_period, nrow(available_inflation_data))
years_to_extrapolate <- seq(min_year_available_without_extrapolation - 1, min_year_requested, - 1) # Note these are in reverse
average_inflation <- available_inflation_data[(nrow(available_inflation_data)-number_of_years_to_use_for_average + 1):nrow(available_inflation_data), "value"] %>% as.numeric %>% mean
extrapolated_dataframe <- data.frame(value=rep(average_inflation, length(years_to_extrapolate)), years_to_extrapolate, stringsAsFactors = FALSE)
colnames(extrapolated_dataframe) <- c("value", "date")
inflation_dataframe <- rbind(available_inflation_data, extrapolated_dataframe)
} # End past / average
if(extrapolate_past_method == 'rate'){
years_to_extrapolate <- seq(min_year_available_without_extrapolation - 1, min_year_requested, -1)
average_inflation <- past_rate
extrapolated_dataframe <- data.frame(value=rep(average_inflation, length(years_to_extrapolate)), years_to_extrapolate, stringsAsFactors = FALSE)
colnames(extrapolated_dataframe) <- c("value", "date")
inflation_dataframe <- rbind(available_inflation_data, extrapolated_dataframe)
} # End past / rate
} # End outermost else for extrapolating past
# If dates are outside of avaiable data and no extrapolation is set
if(max(to_date) > max(available_inflation_data$date) & !extrapolate_future) {
stop(paste0("'to_date' (", to_date, ") is/contains a later date than the latest available data (",
max(available_inflation_data$date), ").\nTry setting 'extrapolate_future' to TRUE or using an earlier 'to_date'"))
}
if(min(to_date) < min(available_inflation_data$date) & !extrapolate_past) {
stop(paste0("'to_date' (", from_date, ") is/contains an earlier date than the earliest available data (",
min(available_inflation_data$date), ").\nTry setting 'extrapolate_past' to TRUE or using a later 'to_date'"))
}
# This function takes a single from value and single to value and creates a multiplier
# Note that we don't pass inflation_dataframe object to this as it confuses mapply/sapply and they
# don't understand what to do with it
# from_input <- from_date; to_input <- to_date
make_multiplier <- function(from_input, to_input) {
# Note diligent use of inequalities (rightly) prevent current year's inflation being applied
# https://economics.stackexchange.com/questions/28972/convention-standard-methodology-for-calculating-real-prices
# if(length(from_date) == 1) { from_input <- rep(from_input, length(price))}
# if(length(to_date) == 1) { to_input <- rep(to_input, length(price))}
inflation_dataframe %>%
filter(date > from_input & date <= to_input | date < from_input & date >= to_input ) %>%
.$value %>% {. / 100} %>% {. + 1} %>% { ifelse(from_input < to_input, prod(.), { 1 / prod(.) }) }
}
# from_input <- from_date
# to_input <- to_date
#
# mapply(make_multiplier, from_input = from_date, to_input = to_date)
multipliers <- mapply(make_multiplier, from_input = from_date, to_input = to_date)
real_price <- price * multipliers
real_price
}
#' @rdname adjust_for_inflation
#' @export
afi <- adjust_for_inflation
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.