Time Series and Introduction to Spatial Data

Lecture 9

Shannon Gallagher

September 23, 2026

Code
source("_setup.R")
us <- ggplot2::map_data("state")
daily <- events |>
  mutate(day = as.Date(begin_dt)) |>
  count(day) |>
  complete(day = seq(min(day), max(day), by = "day"), fill = list(n = 0)) |>
  arrange(day) |>
  mutate(ma7 = as.numeric(stats::filter(n, rep(1 / 7, 7), sides = 2)))

Reminders, previously, and today…

  • use smoothing without hiding individual variation,
  • interpret lag and autocorrelation cautiously,
  • distinguish seasonality from reporting artifacts,
  • identify point, point-referenced, and areal spatial data.

Things of interest for time-series data

Raw daily counts show individual bursts. A moving average asks about local level.

Code
daily |>
  ggplot(aes(day)) +
  geom_line(aes(y = n), color = "gray75", linewidth = 0.4) +
  geom_line(aes(y = ma7), color = deep_gold, linewidth = 1) +
  scale_x_date(date_breaks = "2 months", date_labels = "%b") +
  labs(title = "Smoothing reveals the local level and hides daily extremes", x = NULL, y = "Event records")
Daily NOAA event counts with a centered seven-day moving average.

Moving Average Plots

Ask:

  • centered or trailing?
  • seven days, 30 days, or another window?
  • how are endpoints handled?
  • does smoothing match the operational decision?

A smoother is a summary, not repaired data.

Compare several windows

Code
daily_windows <- daily |>
  mutate(
    ma7 = as.numeric(stats::filter(n, rep(1 / 7, 7), sides = 2)),
    ma30 = as.numeric(stats::filter(n, rep(1 / 30, 30), sides = 2))
  ) |>
  pivot_longer(c(ma7, ma30), names_to = "window", values_to = "smooth")

ggplot(daily_windows, aes(day)) +
  geom_line(aes(y = n), color = "gray82", linewidth = 0.3) +
  geom_line(aes(y = smooth, color = window), linewidth = 1) +
  scale_color_manual(values = c(ma7 = deep_gold, ma30 = blue_gray), labels = c(ma7 = "7 days", ma30 = "30 days")) +
  scale_x_date(date_breaks = "2 months", date_labels = "%b") +
  labs(title = "Longer windows suppress more short-term variation", x = NULL, y = "Event records", color = "Window")
Daily NOAA counts with 7-day and 30-day moving averages.

How are moving averages computed?

For an odd window of length \(2h+1\),

\[ \widetilde{y}_t=\frac{1}{2h+1}\sum_{j=-h}^{h} y_{t+j}. \]

A centered average uses future and past observations, so it is appropriate for description but not for a real-time forecast at the current endpoint.

Checkpoint 1 · Choose a window

Choose a smoothing window for (a) daily staffing and (b) seasonal planning. Explain why the same window should not automatically serve both decisions.

Working with lags

A lagged value pairs today’s observation with an earlier one:

Code
daily |>
  mutate(yesterday = lag(n), last_week = lag(n, 7)) |>
  select(day, n, yesterday, last_week) |>
  slice(8:13)
# A tibble: 6 × 4
  day            n yesterday last_week
  <date>     <int>     <int>     <int>
1 2024-01-08   609       101       463
2 2024-01-09  1373       609        14
3 2024-01-10   246      1373        57
4 2024-01-11   328       246        40
5 2024-01-12   912       328        80
6 2024-01-13   617       912       317

Lag plots show serial dependence

Code
lagged <- daily |>
  mutate(lag1 = lag(n), lag7 = lag(n, 7))

ggplot(lagged, aes(lag1, n)) +
  geom_point(alpha = 0.3, color = deep_gold) +
  geom_smooth(method = "lm", se = FALSE, color = blue_gray) +
  labs(title = "Lag 1", x = "Yesterday's count", y = "Today's count")
ggplot(lagged, aes(lag7, n)) +
  geom_point(alpha = 0.3, color = deep_gold) +
  geom_smooth(method = "lm", se = FALSE, color = blue_gray) +
  labs(title = "Lag 7", x = "Count seven days ago", y = "Today's count")

Lag-1 and lag-7 scatterplots of daily NOAA event counts.

Lag-1 and lag-7 scatterplots of daily NOAA event counts.

Autocorrelation

Autocorrelation measures linear similarity between a series and a lagged version of itself.

Code
acf(daily$n, lag.max = 30, main = "Daily event records remain related across nearby days")
Autocorrelation plot of daily NOAA event counts.

This dependence reflects multi-day weather systems, reporting, seasonality, and other processes—not one simple mechanism.

Autocorrelation plots

  • The x-axis is lag \(k\).
  • The vertical segment is the sample correlation between \(y_t\) and \(y_{t-k}\).
  • Values near one indicate similar high/low movement at that lag.
  • Values near minus one indicate opposite movement.
  • Reference bands are rough sampling guides, not a causal threshold.

Autocorrelation plots and seasonality

Code
monthly <- events |>
  count(month) |>
  complete(month = 1:12, fill = list(n = 0)) |>
  arrange(month)

plot(monthly$month, monthly$n, type = "b", xaxt = "n",
     xlab = "Month", ylab = "Event records", main = "Monthly aggregation emphasizes the seasonal cycle")
axis(1, at = 1:12, labels = month.abb)
Autocorrelation plot of monthly NOAA event counts.

One year is enough to display a seasonal pattern but not enough to estimate a stable annual autocorrelation at lag 12.

Checkpoint 2 · Interpret a lag

If lag-1 autocorrelation is positive, what does that say? Write one defensible sentence and one causal sentence that the plot does not justify.

How should we think about spatial data?

  1. Point pattern: event locations themselves are the outcome.
  2. Point referenced: a measured value occurs at a location.
  3. Areal: a value summarizes a region such as a state or county.

The right display depends on which structure you have.

Latitude and Longitude

They are not ordinary x and y variables on a flat plane.

Earth is curved; a map projection converts the surface to a plane. Every projection distorts some combination of area, shape, distance, or direction.

Map Projections

  • A conformal projection preserves local angles and shapes.
  • An equal-area projection preserves relative areas.
  • An equidistant projection preserves selected distances.
  • No flat map preserves area, shape, distance, and direction everywhere.

For state-level choropleths, area distortion can alter the visual weight assigned to regions even when the data values are unchanged.

Point-Pattern data

The event locations are themselves the observations. Questions include:

  • Where are reports concentrated?
  • Do events appear clustered?
  • Does intensity vary over space?

Interpreting concentration requires exposure, geography, and reporting context.

Point-Referenced data

Code
events |>
  filter(event_type == "Hail", has_begin_coords, magnitude > 0,
         begin_lon >= -125, begin_lon <= -66, begin_lat >= 24, begin_lat <= 50) |>
  ggplot() +
  geom_polygon(data = us, aes(long, lat, group = group), fill = "gray95", color = "white") +
  geom_point(aes(begin_lon, begin_lat, size = magnitude), alpha = 0.35, color = deep_gold) +
  coord_quickmap() +
  labs(title = "A measured value is attached to each reported location", size = "Hail size", x = NULL, y = NULL) +
  theme_void()
Map of reported hail magnitudes at NOAA event locations.

Here the location is known and hail magnitude is the measured attribute.

Areal data

An areal value summarizes a region such as a state or county. Examples include total events, total damage, or a normalized rate.

The region is the observational unit. A choropleth should not be interpreted as showing within-region point locations.

Inference for Spatial Data

Nearby observations can be more similar than distant observations. That violates the independence assumption behind many familiar analyses.

Spatial inference requires choices about neighborhood, distance, trend, and covariance; a smooth map alone does not provide those choices.

Kriging

At a new location, kriging forms a weighted combination of nearby observed values. The weights reflect the fitted spatial covariance and the geometry of the observed sites.

The resulting surface is a model-based prediction, not observed data between stations. Prediction uncertainty should accompany the interpolated map.

NOAA point-pattern example

Code
us <- ggplot2::map_data("state")

events |>
  filter(has_begin_coords, begin_lon >= -125, begin_lon <= -66, begin_lat >= 24, begin_lat <= 50) |>
  slice_sample(n = 8000) |>
  ggplot() +
  geom_polygon(data = us, aes(long, lat, group = group), fill = "gray95", color = "white") +
  geom_point(aes(begin_lon, begin_lat), alpha = 0.08, size = 0.5, color = deep_gold) +
  coord_quickmap() +
  labs(title = "A point map mixes hazard, exposure, and reporting", x = NULL, y = NULL) +
  theme_void()
Map of a sample of geocoded NOAA event records in the contiguous United States.

Checkpoint 3 · Name the spatial claim

What does a dense cluster of points on this map mean? List three plausible explanations, then identify one denominator or additional dataset needed to distinguish them.

Recap and next steps

  • Smoothing trades local detail for a clearer level.
  • Autocorrelation describes dependence, not cause.
  • Historical NOAA trends require reporting caveats.
  • Point and areal spatial data support different claims.

Next: choropleths, normalization, and high-quality composition.