Code
kbli <- read_csv("data/kbli.csv")Many environmental data are time series. Temperature records. Streamflow. Tree rings. CO2 measured at Mauna Loa. All of it indexed to time, all of it carrying a signature of the processes that generated it.
But it goes further than that. Even data sets that aren’t obviously temporal, like a soil sample or a species survey or a water chemistry measurement, exist in time. They were collected at a moment, and the system that produced them has a history. Time is the dimension that almost every environmental process moves through, and most of those processes have structure in time: cycles, trends, memory, responses to forcing. Learning to read that structure is part of what it means to do environmental science.
It also matters for getting the statistics right. Temporal dependence, the fact that observations close together in time tend to be more similar than observations far apart, violates the independence assumption that underlies most standard statistical methods. Ignore it, and your standard errors are too small, your p-values too optimistic, your conclusions too confident. Time series analysis gives you the tools to account for that dependence rather than pretend it isn’t there.
And beyond the bookkeeping, temporal structure is information. A seasonal cycle tells you something about the drivers of a system. A long-term trend tells you something has changed. Lagged correlations between two series can reveal how one system influences another. The goal of this book is to help you extract that information, work with it carefully, and communicate what it means.
This is a hands-on book. You will write code, look at data, fit models, and interpret results. Each chapter builds on what came before. The math shows up when it needs to, but the emphasis throughout is on doing time series analysis and understanding what you’re doing, not on deriving things for their own sake.
George Edward Pelham Box FRS need a proper introduction. Box was a British statistician who spent most of his career at the University of Wisconsin, and a fair amount of what’s ahead traces back to him one way or another. The systematic way of identifying and fitting autoregressive and moving-average models, the one you’ll meet in the ARMA chapter, comes from a 1970 book he wrote with Gwilym Jenkins; people still call the whole approach Box-Jenkins. The transformation you reach for when a series’ variance needs stabilizing before you trust it carries his name too, alongside David Cox.
None of that is why I keep bringing him up with students, though. In a 1976 paper for the Journal of the American Statistical Association, Box wrote: all models are wrong, but some are useful. (The cleaner two-clause version everyone quotes actually comes from a later book he wrote with Norman Draper, but it’s the same point.) He wasn’t being glib. Every model simplifies whatever produced your data, so asking whether a model is true doesn’t get you anywhere. Asking whether it’s useful for the question in front of you does. Keep that distinction handy. You’ll watch a beautifully fitting AR(20) explain nothing, an impressive R² turn out to be nearly automatic, and a forecast prove itself only once it’s been checked against data it never saw. Every time, Box’s line is the reason we keep asking the second question instead of stopping at the first.
Most of the chapters follow a natural progression. Each one builds on the last, and working through them in order is the right call. But scattered through the book you’ll find Asides: short detours that dig into something that doesn’t quite fit the main flow. An Aside might work through the math behind a method, explain how R handles something under the hood, or fill in background that makes the surrounding chapters make more sense.
You can skip the Asides and still follow the core material. But they’re there because the questions they answer are the ones that actually come up when you’re working through a chapter and start wondering why something works the way it does. If that’s how your brain works, the Asides are for you.
The chapters fall into four parts, each with its own job.
Get oriented: how R represents a time series, and how to pull one apart into trend, season, and noise before anything harder starts.
plot() and summary() depending on the class of the object you give them. Useful background for the whole book.A series’ own past is the first kind of dependence you’ll meet. This part builds the vocabulary for it: the ACF and PACF to see it, stationarity to say when it holds steady, and ARMA to model it directly.
pacf().Once you can describe a series’ own memory, this part asks what that buys you: whether two series move together, whether a slope or a trend can be trusted, and whether any of it predicts something you haven’t already seen.
Ccf Normalizes. Reproduces R’s cross-correlation normalization by hand.Trade the clock for the cycle. Smooth a series to see its slow-moving pattern, then decompose it into the frequencies it’s built from.
This isn’t a book for absolute beginners, and a few things will go more smoothly if you’ve seen them before. None of it is a hard prerequisite, but here’s what I’m assuming.
R basics. You should be comfortable enough in R to read a data file, poke at a data frame, understand functions, and not panic when something throws an error. You don’t need to be an expert. If you can follow along and look things up when you’re stuck, you’re ready.
Some statistics. We lean on the standard introductory toolkit throughout: correlation, standard errors, p-values, and linear models. You don’t need to have aced a theory course, but the ideas should feel familiar rather than brand new. If “fit a linear model and look at the residuals” means nothing to you, a stats class first will make this book a lot more rewarding.
Matrix algebra (gently). It comes up mostly in the Asides, when I want to show what a method is actually doing under the hood. If you’ve never multiplied two matrices, the main chapters will still work fine. The Asides are there if you want to look deeper.
One more thing about the code. We store data as tsibble objects, tidy tables with a visible time index, and wrangle them with ordinary tidyverse verbs: dplyr for manipulation, ggplot2 for plots, the pipe to chain it together. But a lot of the actual modeling machinery, arima.sim, lm, spectrum, comes from base R, since that’s where most of the classic time series tools live, and we build several of them from scratch before reaching for the canned version. If tidyverse syntax is new to you, don’t let it slow you down. The place to start is tidyverse.org1, and from there R for Data Science (Wickham et al. 2023) is the canonical resource.
You are going to learn to see the world in time. By the end of this book you will be able to look at a time series, describe its structure, fit a model, check your assumptions, and say something about what the data suggest.
The people who do this work well are not necessarily the ones who find it easiest. They’re the ones who run the code when it breaks, read the error messages, ask questions, and keep going.
So: set up your project, download the data, and let’s get to work.
The book is built around R, so you’ll need a working R installation. The code here was written and tested with R version 4.5.2 (2025-10-31). You should be reasonably up to date on your versions of R, RStudio, and relevant packages. If you’re not sure, run:
Do it now, and anytime it occurs to you. It’s almost always the right thing to do.
To follow along with the examples, you’ll want a working RStudio project.
Create a new RStudio project
Go to File → New Project → New Directory → New Project. Give it a name (something like timeseries-analysis) and choose where to save it.
Download the data/ folder
The datasets used in the examples are bundled into a single data.zip. Download it and unzip it inside your project directory.
You can download the data directly:
https://timeseries.andybunn.org/data.zip
Once it’s unzipped, your folder structure should look something like this:
timeseries-analysis/
├── data/
│ ├── kbli.csv
│ ├── nooksack.csv
│ └── ...
└── timeseries-analysis.RprojUse relative paths in your code
Use paths like "data/kbli.csv" rather than full file paths. This keeps the code portable. It will run on any machine without modification.
Save your files in the project root
Keep your working files in the project’s root directory. Quarto (.qmd) and R Markdown (.Rmd) are close cousins; the syntax is nearly identical and either works fine here. After working through the decomposition chapter, your structure might look like this:
timeseries-analysis/
├── data/
│ ├── kbli.csv
│ ├── nooksack.csv
│ └── ...
├── decompositionWork.qmd
└── timeseries-analysis.Rproj