# Plotting model fit, diagnostics, and spatial patterns

This vignette walks through plotting helpers that summarize model fit and spatial patterns in trafficCAR:

- `plot_observed_fitted()` for observed vs predicted values.
- `plot_mcmc_diagnostics()` for quick effective sample size diagnostics.
- `plot_predicted()` and `plot_relative_congestion()` for spatial maps.

The workflow below creates a small synthetic outcome on the bundled road network, fits a Gaussian CAR model, and uses the plotting helpers to visualize results.

## Load data and build segment adjacency

    library(trafficCAR)
    library(sf)
    library(ggplot2)

    data("roads_small", package = "trafficCAR")
    roads <- roads_small

    segments <- roads_to_segments(
      roads,
      crs_m = 3857,
      split_at_intersections = TRUE
    )

    # Keep the example lightweight for vignette builds.
    if (nrow(segments) > 200) {
      segments <- segments[seq_len(200), ]
    }

    adjacency <- build_adjacency(segments, crs_m = 3857)

    # Drop isolated segments to keep the example compatible with a proper CAR model.
    if (any(adjacency$isolates)) {
      segments <- segments[!adjacency$isolates, ]
      adjacency <- build_adjacency(segments, crs_m = 3857)
    }

## Simulate an outcome and fit a CAR model

To keep the example lightweight, the code below simulates a response using segment length as a covariate and fits a modest MCMC run. Adjust `n_iter` and `burn_in` upward for applied work.

    set.seed(123)

    segment_length <- segments$length_m
    segment_length <- scale(segment_length)[, 1]

    speed <- 40 + 6 * segment_length + rnorm(nrow(segments), sd = 3)

    traffic_data <- data.frame(
      segment_id = segments$seg_id,
      speed = speed
    )

    X <- cbind(
      intercept = 1,
      length = segment_length
    )

    fit <- fit_car(
      y = traffic_data$speed,
      A = adjacency$A,
      X = X,
      type = "proper",
      rho = 0.9,
      tau = 1,
      n_iter = 300,
      burn_in = 150,
      thin = 2
    )

## Prepare a plotting-ready fit object

The plotting helpers expect draws of the linear predictor (`mu`) alongside the raw `x` draws. The helper below derives `mu` from the CAR fit and returns an object compatible with the plotting functions.

    make_plot_fit <- function(base_fit, X, outcome_col, outcome_label) {
      x_draws <- base_fit$draws$x
      beta_draws <- base_fit$draws$beta

      if (is.null(beta_draws) || ncol(beta_draws) == 0) {
        mu_draws <- x_draws
      } else {
        mu_draws <- beta_draws %*% t(X) + x_draws
      }

      plot_fit <- list(
        draws = list(
          mu = mu_draws,
          x = x_draws,
          beta = beta_draws,
          sigma2 = base_fit$draws$sigma2
        ),
        outcome_col = outcome_col,
        outcome_label = outcome_label
      )

      class(plot_fit) <- "traffic_fit"
      plot_fit
    }

    plot_fit <- make_plot_fit(
      fit,
      X = X,
      outcome_col = "speed",
      outcome_label = "Speed (mph)"
    )

## Plot observed vs fitted values

    plot_observed_fitted(plot_fit, data = traffic_data)

Use this plot to check for systematic bias. Points should fall around the 45° line if predictions match observed data on average.

## Plot MCMC diagnostics

`plot_mcmc_diagnostics()` provides quick effective sample size (ESS) diagnostics. In current versions, it returns a list with a `ggplot` and a summary data frame. For robustness in vignette builds, the code below falls back to computing ESS directly if a plot/summary is not returned.

    diag <- plot_mcmc_diagnostics(plot_fit)

    if (is.list(diag) && !is.null(diag$plot)) {
      diag$plot
    } else {
      ess <- vapply(plot_fit$draws, posterior::ess_basic, numeric(1))
      ess_df <- data.frame(
        parameter = names(ess),
        ess = as.numeric(ess),
        row.names = NULL
      )

      ggplot2::ggplot(ess_df, ggplot2::aes(parameter, ess)) +
        ggplot2::geom_col() +
        ggplot2::coord_flip() +
        ggplot2::labs(
          title = "Effective sample size by parameter",
          x = NULL,
          y = "ESS"
        )
    }

If you want the underlying ESS table:

    if (is.list(diag) && !is.null(diag$summary)) {
      head(diag$summary)
    } else {
      ess <- vapply(plot_fit$draws, posterior::ess_basic, numeric(1))
      head(data.frame(parameter = names(ess), ess = as.numeric(ess), row.names = NULL))
    }
    #>   parameter         ess
    #> 1        mu  218.331751
    #> 2         x 1080.119010
    #> 3      beta    2.312852
    #> 4    sigma2   94.012563

Low ESS values indicate slow mixing. Consider increasing the number of iterations or adjusting priors if ESS is consistently low.

## Plot spatial predictions

`plot_predicted()` visualizes the posterior mean of the linear predictor on the road network, while `plot_relative_congestion()` highlights areas with above- or below-average spatial effects.

    plot_predicted(plot_fit, segments)

    plot_relative_congestion(plot_fit, segments)

## Tips for applied workflows

- Always verify that the ordering of rows in `segments` matches the adjacency matrix used to fit the model.
- Use the observed vs fitted plot alongside residual diagnostics to assess model adequacy.
- Pair the spatial plots with domain knowledge (e.g., known bottlenecks) to interpret relative congestion.