---
title: "Ragged-Edge Nowcasting with bridgr"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Ragged-Edge Nowcasting with bridgr}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 6,
  fig.height = 3.5
)
set.seed(7)
library(bridgr)
```

# Why Ragged Edges Matter

Mixed-frequency nowcasting usually happens at the ragged edge: the
lower-frequency target has not been released yet, and the higher-frequency
indicators are only partially observed for the current target period.

`bridgr` handles that situation through `indic_predict`, which controls
how the missing high-frequency tail is completed before the target
equation is estimated or forecast.

The currently supported options are:

- `"last"`: extend the latest available high-frequency observation.
- `"mean"`: extend the mean of the latest available high-frequency block.
- `"auto.arima"`: fit an automatic ARIMA model to each indicator and
  forecast the missing observations.
- `"ets"`: fit an ETS model to each indicator and forecast the missing
  observations.
- `"direct"`: do not forecast the indicator at all. Instead, align the
  latest observed complete high-frequency blocks directly to the target
  periods and average them within each target period.

# A Ragged-Edge Example

We start from the package's quarterly GDP growth series and monthly
barometer, then remove the last quarterly GDP observation and the last
two monthly indicator observations. That creates a clean nowcast setup:
the final quarter is now forecasted rather than estimated, and the
monthly indicator is only partially observed for that forecast quarter.

```{r data}
gdp_growth <- suppressMessages(tsbox::ts_na_omit(tsbox::ts_pc(gdp)))
gdp_nowcast <- gdp_growth |>
  dplyr::slice_head(n = nrow(gdp_growth) - 1)

baro_ragged <- baro |>
  dplyr::slice_head(n = nrow(baro) - 2)

tail(gdp_growth)
tail(gdp_nowcast)
tail(baro_ragged)
```

The target history now ends one quarter earlier, while the monthly
indicator contains only a partial block for the quarter we want to
nowcast.

# Comparing Indicator Completion Rules

```{r compare-methods}
predict_methods <- c("last", "mean", "auto.arima", "ets")

models <- lapply(
  predict_methods,
  function(method) {
    mf_model(
      target = gdp_nowcast,
      indic = baro_ragged,
      indic_predict = method,
      indic_aggregators = "mean",
      target_lags = 1,
      h = 1
    )
  }
)
names(models) <- predict_methods

forecast_df <- dplyr::bind_rows(
  lapply(predict_methods, function(method) {
    fc <- forecast(models[[method]])
    dplyr::tibble(
      method = method,
      forecast_time = fc$time,
      forecast = as.numeric(fc$mean)
    )
  })
)

forecast_df
```

The model specification is the same in each case. The only difference is
how the missing monthly observations are completed before the indicator is
aggregated to the quarterly frequency.

## Visualizing the completed indicator paths

The plot below shows the tail of the observed monthly indicator together
with the two extrapolated months that each `indic_predict` rule produces
for the partially observed forecast quarter.

```{r extension-plot}
last_obs <- max(baro_ragged$time)
last_obs_value <- baro_ragged$values[baro_ragged$time == last_obs]
future_months <- seq(last_obs, by = "month", length.out = 3)[-1]

observed_tail <- baro_ragged |>
  dplyr::slice_tail(n = 9) |>
  dplyr::transmute(
    time = .data$time,
    value = .data$values,
    method = "observed"
  )

extended <- dplyr::bind_rows(
  lapply(predict_methods, function(method) {
    indicator_tbl <- models[[method]]$indic
    extension <- indicator_tbl |>
      dplyr::filter(.data$time %in% future_months) |>
      dplyr::transmute(
        time = .data$time,
        value = .data$values,
        method = method
      )
    # join to the last observed point for a continuous line
    dplyr::bind_rows(
      dplyr::tibble(time = last_obs, value = last_obs_value, method = method),
      extension
    )
  })
)

ggplot2::ggplot(
  dplyr::bind_rows(observed_tail, extended),
  ggplot2::aes(x = .data$time, y = .data$value, color = .data$method)
) +
  ggplot2::geom_line(linewidth = 0.8) +
  ggplot2::geom_point(size = 1.6) +
  ggplot2::labs(
    title = "Indicator completion at the ragged edge",
    x = NULL,
    y = "KOF barometer",
    color = NULL
  ) +
  theme_bridgr()
```

The differences across rules are exactly what their names suggest:
`"last"` holds the last value flat, `"mean"` extends the mean of the
recent block, and `auto.arima` / `ets` produce model-based paths.

# Direct Alignment

`"direct"` takes a different route. The indicator is not forecasted.
Instead, `bridgr` assigns the latest complete high-frequency blocks
*backward* to the target periods and averages them within each target
period.

```{r direct}
direct_model <- mf_model(
  target = gdp_nowcast,
  indic = baro_ragged,
  indic_predict = "direct",
  h = 1
)

forecast(direct_model)
```

This is especially useful when you want to avoid a separate indicator
forecasting step and prefer to work only with observed high-frequency
data.

# Explicit Missing Values

Real-time indicator data sometimes contain explicit `NA` cells inside
the sample, for example because of late releases or reporting holidays.
Implicit ragged edges (a shorter indicator tail with no `NA` cells) are
always supported through `indic_predict`. By default `bridgr` errors on
explicit `NA` values; the `missing` argument controls what to do instead.

`missing = "impute"` interpolates linearly inside each series and is the
most generally useful choice. The example below introduces a single
internal gap and re-fits the model under both `"error"` (default,
captured here) and `"impute"`.

```{r missing-impute, warning = TRUE}
baro_internal_na <- baro_ragged
internal_gap <- seq(nrow(baro_internal_na) - 14, nrow(baro_internal_na) - 13)
baro_internal_na$values[internal_gap] <- NA_real_

invisible(tryCatch(
  mf_model(
    target = gdp_nowcast,
    indic = baro_internal_na,
    indic_predict = "last",
    indic_aggregators = "mean",
    h = 1
  ),
  error = function(e) message("Error captured: ", conditionMessage(e))
))

imputed_model <- mf_model(
  target = gdp_nowcast,
  indic = baro_internal_na,
  indic_predict = "last",
  indic_aggregators = "mean",
  h = 1,
  missing = "impute"
)

forecast(imputed_model)
```

`missing = "drop"` removes `NA` rows entirely and is useful when those
rows do not break the per-period completeness required by the inferred
frequency ladder — for example when they sit beyond the estimation
sample. In most ragged-edge workflows `indic_predict` is the better
mechanism for handling trailing gaps.

# When to Use Which Option

As a rough guide:

- Use `"last"` when a simple carry-forward rule is acceptable.
- Use `"mean"` when you want a stable deterministic fill based on the
  latest high-frequency block.
- Use `"auto.arima"` or `"ets"` when the indicator has meaningful
  time-series structure and a separate extrapolation step is sensible.
- Use `"direct"` when you want direct alignment without indicator
  forecasting and want to rely only on the latest observed complete
  high-frequency blocks.

The good part is that the downstream workflow does not change. The same
`summary()` and `forecast()` interface works across all of these
nowcasting choices.
