# If dplyr::select is in a package, using .data also prevents R CMD check from
# giving a NOTE about undefined global variables (provided that @importFrom
# rlang .data is inserted). Using rlang::.data (without @importFrom rlang .data)
# is another option. See Wickham, Hadley, R Packages, O'Reilly, 1st Edition,
# 2015, p. 89 for details
#' @importFrom rlang .data
#' @importFrom magrittr "%>%"
#----------------------------------------------------------------------------
# Reference: Fama, E., Gibbons, M. A Comparison of Inflation Forecasts.
# Journal of Monetary Economics, 1984, vol. 13, pp 327-348
#----------------------------------------------------------------------------
# Model 'Time-series' : EITS (expected inflation from time-series)
# Model 'Treasury Bills' : EITB (expected inflation from Tresuary Bills)
# Model 'Naive' : EINTB (expected inflation from naive Tresuary Bills)
inflation_series <- function(from_ = NA, to_ = NA, stop_on_join_NA,
src_dir =
'~/Desktop/UMich/Factor Warehouse/Uncompressed/',
src_file = c(CPI = 'FRED_CPI_US_M.csv',
TB = 'FF_3F_US_M.csv')){
if(is.na(src_file['CPI'])) {
stop("CPI source file argument is NA", call. = T)
}
if(is.na(src_file['TB'])) {
stop("TB (Treasury Bills) source file argument is NA", call. = T)
}
#----------------------------------------------------------------------------
# Date range validation and construction. Push back < from_ > by one month to
# allow T-Bill observed at end of t-1 without generating NA
stop_on_ym_range(.from = from_, .to = to_)
if(src_file['TB'] == 'FF_3F_US_M.csv') {
if(tsibble::yearmonth(from_) <= tsibble::yearmonth('1926-07')) {
stop("Argument from_ must be later than '1926-07' to allow lagged T-Bill",
call. = T)
}} else {
warning(stringr::str_glue("Unknow threshold date in file ",
src_file['TB']), call. = T)
}
if(src_file['CPI'] == 'FRED_CPI_US_M.csv') {
if(tsibble::yearmonth(from_) <= tsibble::yearmonth('1947-02')) {
stop(stringr::str_glue("Argument from_ must be later than '1947-02' to ",
"allow lagged CPI in inflation I(t) calculation.",
"\n",
"Also accouts for start date adjustment related ",
"to lagged T-Bill", .sep = ''),
call. = T)
}} else {
warning(stringr::str_glue("Unknow threshold date in file ",
src_file['CPI']), call. = T)
}
from_ <- as.character( tsibble::yearmonth(from_) - 1 )
date_range = expression_ym_range(.from = from_, .to = to_)
#----------------------------------------------------------------------------
# Time-series: Inflation (CPI)
# Path validation of source file
path_df_uncompressed <- stringr::str_glue(src_dir, '/', src_file['CPI'])
if( fs::file_exists(path_df_uncompressed) == F) {
stop(stringr::str_glue('Directory and/or file for ', src_file['CPI'],
' does not exist'), call. = T)
}
# Sourcing & processing
tbl.CPI <- readr::read_csv(path_df_uncompressed)
tbl.CPI <- dplyr::mutate(.data = tbl.CPI,
I_t = log(.data$CPI) -
log(dplyr::lag(x = .data$CPI,
n = 1,
default = NA)) ) %>%
dplyr::mutate( year = lubridate::year(.data$date),
month = lubridate::month(.data$date)) %>%
dplyr::mutate( year_month =
tsibble::yearmonth(as.character(.data$date))) %>%
tsibble::as_tsibble(index = .data$year_month) %>%
dplyr::select(.data$year, .data$month, .data$year_month,
.data$CPI, .data$I_t) %>%
tsibble::filter_index(eval(date_range))
stop_on_gap_duplicate(tbl.CPI)
#----------------------------------------------------------------------------
#----------------------------------------------------------------------------
# Time-series: Treasury Bill (TB)
# # Path validation of source file
path_df_uncompressed <- stringr::str_glue(src_dir, '/', src_file['TB'])
if( fs::file_exists(path_df_uncompressed) == F) {
stop(stringr::str_glue('Directory and/or file for ', src_file['TB'],
' does not exist'), call. = T)
}
# Sourcing & processing
tbl.TB <- readr::read_csv(path_df_uncompressed)
tbl.TB <- dplyr::mutate(.data = tbl.TB,
year_month_str = paste0(.data$year, '-',
.data$month, '-01')) %>%
dplyr::mutate(year_month = tsibble::yearmonth(.data$year_month_str)) %>%
tsibble::as_tsibble(index = .data$year_month) %>%
dplyr::select(.data$year, .data$month, .data$year_month, .data$rf) %>%
magrittr::set_colnames(c('year', 'month', 'year_month', 'TB_t')) %>%
tsibble::filter_index(eval(date_range))
stop_on_gap_duplicate(tbl.TB)
#----------------------------------------------------------------------------
#----------------------------------------------------------------------------
# Merge CPI and Treasury Bills time-series.
tbl <- dplyr::full_join(x = tbl.CPI,
y = dplyr::select(tbl.TB,
.data$year_month, .data$TB_t),
by = 'year_month')
# Add calendar date (end-of-month)
tbl <- dplyr::mutate(.data = tbl,
date = last_cal_day(yyyy = .data$year,
mm = .data$month),
.after = .data$month)
stop_on_gap_duplicate(tbl)
if(stop_on_join_NA == TRUE){
if( base::anyNA(tbl) ){
stop("Joined tsibble objects have NA's", call. = T)
}
} else {
if( base::anyNA(tbl) ){
msg <- stringr::str_glue(
"The following records obtained from joined objects have NA's,",
"which will be removed:", .sep = " ")
warning(msg, call. = T, immediate. = T)
get_incomplete_record(tbl, show = T, issue_warning = F)
tbl <- tidyr::drop_na(tbl)
stop_on_gap_duplicate(tbl)
}
}
# T-Bill observed at end of t-1 and ex-post real return (cf Section 2.2, (8))
tbl <- dplyr::mutate(tbl,
TB_t_1 = dplyr::lag(x = .data$TB_t, n = 1, default = NA)) %>%
dplyr::mutate(ex_post_real = .data$TB_t_1 - .data$I_t)
# Clean up first NA induced by < TB_t_1 > ( ...and <ex_post_real> ) while
# maintaining the original start date requested via argument < from_ >
tbl <- tidyr::drop_na(data = tbl)
stop_on_gap_duplicate(tbl)
return(tbl)
}
inflation_metrics <- function(obs, q = 1, conf_level = 0.95) {
# ---------------------------------------------------------------------------
# Validate
if( !tsibble::is_tsibble(obs) ) {
stop('Argument obs must be tsibble object')
}
nm <- c('year', 'month', 'year_month',
'CPI', 'I_t', 'TB_t', 'TB_t_1',
'ex_post_real')
if( !all( nm %in% names(obs) ) ){
stop('Tsibble object (argument) has missing variables', call. = )
}
# ---------------------------------------------------------------------------
augmented_stamp <- dplyr::select(obs,
.data$year, .data$month,
.data$date, .data$year_month,
.data$I_t,
.data$TB_t, .data$TB_t_1,
.data$ex_post_real)
# ---------------------------------------------------------------------------
# Econometrics
#
# Model 'Time-series' (cf. Section 2.1)
fit <- stats::arima(x = obs$I_t, order = c(0, 1, q),
include.mean = F, method = 'ML',
optim.control = list(maxit = 1000))
arima.ITS <- broom::tidy(fit, conf.int = TRUE)
tbl.ITS <- tibble::tibble(expected = obs$I_t - fit$residuals,
shock = fit$residuals,
model = base::as.factor('Time-series'))
tbl.ITS <- dplyr::bind_cols(augmented_stamp, tbl.ITS)
stop_on_gap_duplicate(tbl.ITS)
remove(fit)
# Model 'Treasury Bill' (cf. Section 2.2)
fit <- stats::arima(x = obs$ex_post_real, order = c(0, 1, q),
include.mean = F, method = 'ML',
optim.control = list(maxit = 1000))
arima.ITB <- broom::tidy(fit, conf.int = TRUE)
tbl.ITB <- tibble::tibble(
ER_t_1 = obs$ex_post_real - fit$residuals) %>%
dplyr::mutate(expected = obs$TB_t_1 - .data$ER_t_1) %>%
dplyr::mutate(shock = obs$I_t - .data$expected,
model = base::as.factor('Treasury Bills'))
tbl.ITB <- dplyr::select(tbl.ITB, -.data$ER_t_1)
tbl.ITB <- dplyr::bind_cols(augmented_stamp, tbl.ITB)
stop_on_gap_duplicate(tbl.ITB)
remove(fit)
# Model 'Naive' (cf. Section 2.3)
tbl.INTB <- tibble::tibble(
ERN_t_1 = zoo::rollmean(x = dplyr::lag(x = obs$ex_post_real,
n = 1,
default = NA),
k = 12,
align = 'right',
fill = NA)) %>%
dplyr::mutate(expected = obs$TB_t_1 - .data$ERN_t_1) %>%
dplyr::mutate(shock = obs$I_t - .data$expected,
model = base::as.factor('Naive'))
tbl.INTB <- dplyr::select(tbl.INTB, -.data$ERN_t_1)
tbl.INTB <- dplyr::bind_cols(augmented_stamp, tbl.INTB)
stop_on_gap_duplicate(tbl.INTB)
# ---------------------------------------------------------------------------
# Compile results
metrics <- dplyr::bind_rows(dplyr::as_tibble(tbl.ITB),
dplyr::as_tibble(tbl.INTB),
dplyr::as_tibble(tbl.ITS))
# ---------------------------------------------------------------------------
# Model diagnostic
diagnostic <- dplyr::group_by(.data = metrics, .data$model) %>%
tidyr::nest() %>%
dplyr::mutate(
ols = purrr::map(.data$data,
function(df) stats::lm(I_t ~ expected, data = df)))
diagnostic <- diagnostic %>%
dplyr::mutate(
ols.tidy = purrr::map(.data$ols,
function(df) broom::tidy(
df, conf.int = T,
conf.level = conf_level))) %>%
dplyr::mutate(ols.glance = purrr::map(.data$ols,
function(df) broom::glance(df)))
diagostic_param <- diagnostic %>%
dplyr::select(.data$model, .data$ols.tidy) %>%
tidyr::unnest(.data$ols.tidy)
diagostic_stats <- diagnostic %>%
dplyr::select(.data$model, .data$ols.glance) %>%
tidyr::unnest(.data$ols.glance) %>%
dplyr::select(.data$model, .data$adj.r.squared, .data$sigma,
.data$df.residual, .data$nobs)
return( list(metrics = metrics,
arima.ITB = arima.ITB,
arima.ITS = arima.ITS,
diagostic_param = diagostic_param,
diagostic_stats = diagostic_stats) )
}
inflation_validate_FamaGibbons <- function(metrics, show_plot = T){
# ---------------------------------------------------------------------------
# Integrity check and minor filter
metrics <- with_sub_Period(metrics, interval_type = 'Fama-Gibbons') %>%
dplyr::filter(.data$sub_period != 'Other')
nm <- c('model', 'sub_period', 'shock')
if( !all( nm %in% names(metrics) ) ){
msg <- stringr::str_glue(
'Metric object (argument) has missing variables.\n',
"'model', 'sub_period', 'shock' must be in the tibble.")
stop(msg, call. = )
}
# ---------------------------------------------------------------------------
# Compile diagnostic measures
diagnostic <- dplyr::group_by(.data = metrics,
.data$sub_period, .data$model) %>%
tidyr::nest() %>%
dplyr::filter(.data$sub_period != 'Other') %>%
dplyr::mutate(mean = purrr::map(.x = .data$data,
~ base::mean(.x$shock, na.rm = T)),
test = purrr::map(.x = .data$data,
~ stats::t.test(.x$shock)$statistic),
sd = purrr::map(.x = .data$data,
~ stats::sd(.x$shock, na.rm = T)),
rmse = purrr::map(.x = .data$data,
~ base::sqrt(base::mean(.x$shock^2,
na.rm = T)))) %>%
tidyr::unnest(cols = c(.data$mean, .data$test, .data$sd, .data$rmse)) %>%
dplyr::select(-.data$data) %>%
dplyr::select(.data$sub_period, .data$model,
.data$mean, .data$test, .data$sd, .data$rmse)
# ---------------------------------------------------------------------------
# Plot generation
if(show_plot) {
# Baseline plot
g <- ggplot2::ggplot(data = diagnostic,
ggplot2::aes(x = .data$sub_period,
y = .data$sd * 10000,
col = .data$model))
g <- g + ggplot2::geom_point(size = 3)
# Add original data from Fama, Gibbons (1984), Table 4
g <- g + ggplot2::geom_point(data = Fama_Gibbons_Tbl_4,
mapping = ggplot2::aes(
x = .data$sub_period,
y = .data$sd * 10000,
col = .data$model),
shape = 23, size = 3)
# y-title
g <- g + ggplot2::ylab(label = 'sd. x 10,000')
# x-text, x-title
g <- g + ggplot2::theme(
axis.text.x = ggplot2::element_text(angle = 90,vjust = 0.5, hjust=1))
g <- g + ggplot2::theme(axis.title.x = ggplot2::element_blank())
# Title, sub-title, caption
main_title <- stringr::str_glue(
'Standard Deviation of Monthly Forecast Error, ',
'by Sub-Period, In-Sample')
g <- g + ggplot2::labs(
title = main_title,
subtitle = "(Circle: Replicated Estimates | Diamond: Original Reference)",
caption = "Ref.: Fama, Gibbons (1984), Table 4, p. 338")
g <- g + ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5),
plot.subtitle = ggplot2::element_text(hjust = 0.5))
g <- g + ggplot2::theme(
plot.title = ggplot2::element_text(color = "black",
size = 12, face = "bold"),
plot.subtitle = ggplot2::element_text(color = "black",
size = 10),
plot.caption = ggplot2::element_text(color = "blue", face = "italic"))
# xy-text
g <- g + ggplot2::theme(
axis.text.x = ggplot2::element_text(face = 'bold'),
axis.text.y = ggplot2::element_text(face = 'bold'))
base::print(g)
}
# ---------------------------------------------------------------------------
return(diagnostic)
}
write_inflation_factor <- function(tsbl, hdl_str, dest_root_dir,
model_type, func_arg){
path_df_audit <- stringr::str_glue(dest_root_dir, '/Audit/', hdl_str, '.pdf')
path_df_series_csv <- stringr::str_glue(dest_root_dir, '/Uncompressed/',
hdl_str, '.csv')
tsbl <- remove_year_month_from(tsbl)
readr::write_csv(x = tsbl, path = path_df_series_csv)
# ----------------------------------------------------------------------------
# Auditing
caption_str <- 'Inflation Factor: File Paths'
tbl_path <- tibble::tibble(
format = c('pdf', 'csv'),
path_dir = c(fs::path_dir(path_df_audit),
fs::path_dir(path_df_series_csv))
) %>% xtable::xtable(caption = caption_str)
caption_str <- 'Inflation Factor: File Attributes'
tbl_attr <- tibble::tibble(
file = c( fs::path_file(path_df_series_csv)),
user = c( fs::file_info(path_df_series_csv)$user),
device_id =
c( as.character( fs::file_info(path_df_series_csv)$device_id )),
permissions =
c( as.character( fs::file_info(path_df_series_csv)$permissions )),
size = c( fs::file_info(path_df_series_csv)$size),
birth =
c( as.character( fs::file_info(path_df_series_csv)$birth_time) )
) %>% xtable::xtable(caption = caption_str)
caption_str <- 'Inflation Factor: File Time Stamps'
tbl_times <- tibble::tibble(
file = c( fs::path_file(path_df_series_csv)),
modification =
c( as.character( fs::file_info(path_df_series_csv)$modification_time )),
access =
c( as.character( fs::file_info(path_df_series_csv)$access_time) ),
change =
c( as.character( fs::file_info(path_df_series_csv)$change_time) )
) %>% xtable::xtable(caption = caption_str)
parsedHdl <- parseBuiltdHdl(hdl_str)
arsenal::write2pdf(object = list(tbl_path,
tbl_attr,
tbl_times,
stringr::str_glue('Derived Catalog Entry: ',
parsedHdl[1], '_',
parsedHdl[2], '_',
parsedHdl[3], '\n'),
stringr::str_glue(
'Proprietary: TRUE\n'),
stringr::str_glue(
'Build Type: Econometric\n'),
stringr::str_glue('Inflation Model Type: ',
model_type, '\n'),
'Function Call Parameters:\n',
utils::ls.str(func_arg),
'Tibble Tail:\n',
utils::tail(tsbl)),
file = path_df_audit, quiet = T)
# ----------------------------------------------------------------------------
# Set permission to 'r-' (read-only) for non-owners
fs::file_chmod(path_df_audit, mode = '644')
fs::file_chmod(path_df_series_csv, mode = '644')
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.