Time

Time Series

Arvind V.

2022-12-15

Slides and Tutorials

TimeSeries Wrangling   Time Series Analysis-WIP 

“Remember that sometimes not getting what you want is a wonderful stroke of luck.”

— Dalai Lama XIV

Setting up R Packages

library(tidyverse)
library(mosaic)
library(ggformula) # Our Formula based graphing package
library(skimr)
library(fpp3)

# Wrangling
# library(lubridate)  # Deal with dates. Loads with tidyverse
# library(tsibble) # loads with ffp3
# library(tsibbledata) # loads with fpp3

# devtools::install_github("FinYang/tsdl")
library(tsdl)
library(TSstudio)
library(timetk)
library(tsbox)
library(gghighlight) # Highlight specific parts of charts
library(ggtime) # Mitchell Ohara-Wild June 2025
library(ggrepel) # Repel overlapping text labels in ggplot2
library(marquee) # Add text labels across a ggplot2 chart

The fpp3 packages loads a good few other packages:

 [1] "cli"         "crayon"      "dplyr"       "fable"       "feasts"     
 [6] "ggplot2"     "lubridate"   "purrr"       "rstudioapi"  "tibble"     
[11] "tidyr"       "tsibble"     "tsibbledata"

Plot Fonts and Theme

Code
library(systemfonts)
library(showtext)
## Clean the slate
systemfonts::clear_local_fonts()
systemfonts::clear_registry()
##
showtext_opts(dpi = 96) # set DPI for showtext
sysfonts::font_add(
  family = "Alegreya",
  regular = "../../../../../../fonts/Alegreya-Regular.ttf",
  bold = "../../../../../../fonts/Alegreya-Bold.ttf",
  italic = "../../../../../../fonts/Alegreya-Italic.ttf",
  bolditalic = "../../../../../../fonts/Alegreya-BoldItalic.ttf"
)

sysfonts::font_add(
  family = "Roboto Condensed",
  regular = "../../../../../../fonts/RobotoCondensed-Regular.ttf",
  bold = "../../../../../../fonts/RobotoCondensed-Bold.ttf",
  italic = "../../../../../../fonts/RobotoCondensed-Italic.ttf",
  bolditalic = "../../../../../../fonts/RobotoCondensed-BoldItalic.ttf"
)
showtext_auto(enable = TRUE) # enable showtext
##
theme_custom <- function() {
  theme_bw(base_size = 10) +

    # theme(panel.widths = unit(11, "cm"),
    #       panel.heights = unit(6.79, "cm")) + # Golden Ratio

    theme(
      plot.margin = margin_auto(t = 1, r = 2, b = 1, l = 1, unit = "cm"),
      plot.background = element_rect(
        fill = "bisque",
        colour = "black",
        linewidth = 1
      )
    ) +

    theme_sub_axis(
      title = element_text(
        family = "Roboto Condensed",
        size = 10
      ),
      text = element_text(
        family = "Roboto Condensed",
        size = 8
      )
    ) +

    theme_sub_legend(
      text = element_text(
        family = "Roboto Condensed",
        size = 6
      ),
      title = element_text(
        family = "Alegreya",
        size = 8
      )
    ) +

    theme_sub_plot(
      title = element_text(
        family = "Alegreya",
        size = 14, face = "bold"
      ),
      title.position = "plot",
      subtitle = element_text(
        family = "Alegreya",
        size = 10
      ),
      caption = element_text(
        family = "Alegreya",
        size = 6
      ),
      caption.position = "plot"
    )
}

## Use available fonts in ggplot text geoms too!
ggplot2::update_geom_defaults(geom = "text", new = list(
  family = "Roboto Condensed",
  face = "plain",
  size = 3.5,
  color = "#2b2b2b"
))
ggplot2::update_geom_defaults(geom = "label", new = list(
  family = "Roboto Condensed",
  face = "plain",
  size = 3.5,
  color = "#2b2b2b"
))

ggplot2::update_geom_defaults(geom = "marquee", new = list(
  family = "Roboto Condensed",
  face = "plain",
  size = 3.5,
  color = "#2b2b2b"
))
ggplot2::update_geom_defaults(geom = "text_repel", new = list(
  family = "Roboto Condensed",
  face = "plain",
  size = 3.5,
  color = "#2b2b2b"
))
ggplot2::update_geom_defaults(geom = "label_repel", new = list(
  family = "Roboto Condensed",
  face = "plain",
  size = 3.5,
  color = "#2b2b2b"
))

## Set the theme
ggplot2::theme_set(new = theme_custom())

## tinytable options
options("tinytable_tt_digits" = 2)
options("tinytable_format_num_fmt" = "significant_cell")
options(tinytable_html_mathjax = TRUE)


## Set defaults for flextable
flextable::set_flextable_defaults(font.family = "Roboto Condensed")

What graphs will we see today?

Variable #1 Variable #2 Chart Names Chart Shape
Quant Qual Line Chart, CandleStick Plot, Heatmap

What kind of Data Variables will we choose?

Error in style_tt(., color = "black", fontsize = 0.75, line = "tblr", : could not find function "style_tt"

Inspiration

Shown below are the temperatures over time in two US cities:

Where would need ACs in all rooms? And heaters?

Introduction

Any metric that is measured over regular time intervals forms a time series. Analysis of Time Series is commercially important because of industrial need and relevance, especially with respect to Forecasting (Weather data, sports scores, population growth figures, stock prices, demand, sales, supply…).

What can we do with Time Series? As with other datasets, we have to begin by answering fundamental questions, such as:

  1. What are the types of time series?
  2. How do we visualize time series?
  3. How might we summarize time series to get aggregate numbers, say by week, month, quarter or year?
  4. How do we decompose the time series into level, trend, and seasonal components?
  5. How might we make a model of the underlying process that creates these time series?
  6. How do we make useful forecasts with the data we have?

We will first look at the multiple data formats for time series in R. Alongside we will look at the R packages that work with these formats and create graphs and measures using those objects. Then we examine data wrangling of time series, where we look at packages that offer dplyr-like ability to group and summarize time series using the time variable. We will finally look at obtaining the components of the time series and try our hand at modelling and forecasting.

Time Series Formats, Conversion, and Plotting

There are multiple formats for time series data. The ones that we are likely to encounter most are:

  • The ts format: We may simply have a single series of measurements that are made over time, stored as a numerical vector. The stats::ts() function will convert a numeric vector into an R time series ts object, which is the most basic time series object in R. The base-R ts object is used by established packages forecast and is also supported by newer packages such as tsbox.

  • The tibble format: the simplest and most familiar data format is of course the standard tibble/data frame, with or without an explicit time column/variable to indicate that the other variables vary with time. The standard tibble object is used by many packages, e.g. timetk & modeltime.

  • The tsibble format: this is a new format for time series analysis. The special tsibble object (“time series tibble”) is used by fable, feasts and others from the tidyverts set of packages.

There are many other time-oriented data formats too…probably too many, such a tibbletime and TimeSeries objects. For now the best way to deal with these, should you encounter them, is to convert them (Using the package tsbox) to a tibble or a tsibble and work with these.

Figure 1: Time Series Data Standards

To start, we will use simple ts data first, and then do another with a “vanilla” tibble format that we can plot as is. We will then look at a tibbledata that does have a time-oriented variable. We will then perform conversion to tsibble format to plot it, and then a final example with a ground-up tsibble dataset.

Base-R ts format data

There are a few datasets in base R that are in ts format already.

This can be easily plotted using base R:

One can see that there is an upward trend and also seasonal variations that also increase over time. This is an example of a multiplicative time series, which we will discuss later.

Let us take data that is “time oriented” but not in ts format. We use the command ts to convert a numeric vector to ts format: the syntax of ts() is:

Syntax: objectName <- ts(data, start, end, frequency), where,

  • data : represents the data vector
  • start : represents the first observation in time series
  • end : represents the last observation in time series
  • frequency : represents number of observations per unit time. For example 1=annual, 4=quarterly, 12=monthly, 7=weekly, etc.

We will pick simple numerical vector data ( i.e. not a time series ) ChickWeight:

We see that the weights of a young chick specimen #1 increases over time.

tibble data

The ts data format can handle only one time series; in the above example, we could not have plotted the weight of two chicks, if we had wanted to. If we want to plot/analyze multiple time series, based on say Qualitative variables, (e.g. sales figures over time across multiple products and locations) we need other data formats. Using the familiar tibble structure opens up new possibilities.

  • We can have multiple time series within a tibble (think of numerical time-series data like GDP, Population, Imports, Exports for multiple countries as with the gapminder1data we saw earlier).

gapminder data

country year gdpPercap pop lifeExp continent
Afghanistan 1952 779.4453 8425333 28.801 Asia
Afghanistan 1957 820.8530 9240934 30.332 Asia
Afghanistan 1962 853.1007 10267083 31.997 Asia
Afghanistan 1967 836.1971 11537966 34.020 Asia
Afghanistan 1972 739.9811 13079460 36.088 Asia
  • It also allows for data processing with dplyr such as filtering and summarizing.

Let us read and inspect in the US births data from 2000 to 2014. Download this data by clicking on the icon below, and saving the downloaded file in a sub-folder called data inside your project.

Read this data in and inspect it.

This is just a tibble containing a single data variable births that varies over time. All other variables, although depicting time, are numerical columns and not explicitly time columns. There are no Qualitative variables (yet!).

Plotting tibble-oriented time data

Small Multiples using gghighlight

Instead of looking at multiple overlapping time series graphs, we could split these up into small multiples or facets and still retain the overall picture that is offered by the overlapping graphs. The trick here is the highlight one of the graphs at a time, while keeping all other graphs in the background. We can do this with the gghighlight package.

ggplot2::theme_set(new = theme_custom())

births_2000_2014_monthly
###
births_2000_2014_monthly %>% ggplot() +
  geom_line(aes(
    y = mean_monthly_births,
    x = year,
    group = month
  )) +
  labs(
    x = "Year", y = "Mean Monthly Births over the Years",
    title = "Mean Births by Month",
    caption = "Using gghighlight package"
  ) +

  ### Add highlighting
  gghighlight(
    use_direct_label = F,
    unhighlighted_params = list(colour = alpha("grey85", 1))
  ) +

  ### Add faceting
  facet_wrap(vars(month))

ggplot2::theme_set(new = theme_custom())

births_2000_2014_weekly
###
births_2000_2014_weekly %>% ggplot() +
  geom_line(aes(y = mean_daily_births, x = year, group = day_of_week)) +
  labs(
    x = "Year", y = "Mean Daily Births over the Years",
    title = "Mean Births by Day of Week",
    caption = "Using gghighlight package"
  ) +

  ### Add highlighting
  gghighlight(
    use_direct_label = F,
    unhighlighted_params = list(colour = alpha("grey85", 1))
  ) +

  ### Add faceting
  facet_wrap(vars(day_of_week))

Why are fewer babies born on weekends?

Looks like an interesting story here…there are significantly fewer births on average on Sat and Sun, over the years! Why? Should we watch Grey’s Anatomy ?

And more births in September? That should be a no-brainer!! 😂

Important

Note that this is still using just tibble data, without converting it into a time series format. So far we are simply treating the year/month/day variables are simple variables and using dplyr to group and summarize. We have not created an explicit time or date variable.

Plotting tibble time-series

Now, we can convert the time-oriented columns in this dataset into a single date variable, giving us a proper tibble time-series:

births_tibble_timeseries <-
  births_2000_2014 %>%
  mutate(date = lubridate::make_date(year, month, date_of_month)) %>%
  ## Drop off the individual columns ( year, month, day_of_month)
  select(date, births)

births_tibble_timeseries

Note that we have a proper date formatted column, as desired. This is a single time series, but if we had other Qualitative variables such as say city, we could easily have had multiple series here. We can plot this with ggformula/ggplot as we have done before, and with now with timetk:

ggplot2::theme_set(new = theme_custom())

births_tibble_timeseries %>%
  timetk::plot_time_series(
    .date_var = date,
    .value = births,
    .interactive = FALSE,
    .title = "Births over Time",
    .x_lab = "Time",
    .y_lab = "Births"
  )

tsibble data

Finally, we have tsibble (“time series tibble”) format data, which contains three main components:

  • an index variable that defines time;
  • a set of key variables, usually categorical, that define sets of observations, over time. This allows for each combination of the categorical variables to define a separate time series.
  • a set of quantitative variables, that represent the quantities that vary with time (i.e index)

Here is Robert Hyndman’s video introducing tsibbles:

The package tsibbledata contains several ready made tsibble format data. Run data(package = "tsibbledata") in your Console to find out about these.

Let us try PBS, which is a dataset containing Monthly Medicare prescription data in Australia.

data(PBS, package = "tsibbledata")
PBS
glimpse(PBS)
Rows: 67,596
Columns: 9
Key: Concession, Type, ATC1, ATC2 [336]
$ Month      <mth> 1991 Jul, 1991 Aug, 1991 Sep, 1991 Oct, 1991 Nov, 1991 Dec,…
$ Concession <chr> "Concessional", "Concessional", "Concessional", "Concession…
$ Type       <chr> "Co-payments", "Co-payments", "Co-payments", "Co-payments",…
$ ATC1       <chr> "A", "A", "A", "A", "A", "A", "A", "A", "A", "A", "A", "A",…
$ ATC1_desc  <chr> "Alimentary tract and metabolism", "Alimentary tract and me…
$ ATC2       <chr> "A01", "A01", "A01", "A01", "A01", "A01", "A01", "A01", "A0…
$ ATC2_desc  <chr> "STOMATOLOGICAL PREPARATIONS", "STOMATOLOGICAL PREPARATIONS…
$ Scripts    <dbl> 18228, 15327, 14775, 15380, 14371, 15028, 11040, 15165, 168…
$ Cost       <dbl> 67877.00, 57011.00, 55020.00, 57222.00, 52120.00, 54299.00,…

Data Dictionary

Note

Data Description: This is a large-ish dataset.Run PBS in your console)

  • 67K observations
  • 336 combinations of key variables (Concession, Type, ATC1, ATC2) which are categorical, as foreseen.
  • Data appears to be monthly, as indicated by the 1M.
  • the time index variable is called Month, formatted as yearmonth, a new type of variable introduced in the tsibble package.

Note that there are multiple Quantitative variables (Scripts,Cost), each sliced into 336 time-series, a feature which is not supported in the ts format, but is supported in a tsibble. The Qualitative Variables are described below. (Type help("PBS") in your Console.)

The data is dis-aggregated/grouped using four keys:
- Concession: Concessional scripts are given to pensioners, unemployed, dependents, and other card holders
- Type: Co-payments are made until an individual’s script expenditure hits a threshold ($290.00 for concession, $1141.80 otherwise). Safety net subsidies are provided to individuals exceeding this amount.
- ATC1: Anatomical Therapeutic Chemical index (level 1). 15 types
- ATC2: Anatomical Therapeutic Chemical index (level 2). 84 types, nested inside ATC1.

Code
PBS %>%
  DT::datatable(
    caption = htmltools::tags$caption(
      style = "caption-side: top; text-align: left; color: black; font-size: 150%;",
      "PBS Dataset (Clean)"
    ),
    options = list(pageLength = 10, autoWidth = TRUE)
  ) %>%
  DT::formatStyle(
    columns = names(PBS),
    fontFamily = "Roboto Condensed",
    fontSize = "12px"
  )
Table 1: PBS Clean Dynamic Data Table

Let us simply plot Cost over time:

This basic plot is quite messy. Other than an overall rising trend and more vigorous variations pointing to a multiplicative process, we cannot say more. There is simply too much happening here and it is now time (sic!) for us to look at summaries of the data using dplyr-like verbs.

We will do that in the Section 1.

Time Series Heatmaps

How about a heatmap? We can cook up a categorical variable based on the number of births (low, fine, high) and use that to create a heatmap:

ggplot2::theme_set(new = theme_custom())

births_2000_2014 %>%
  mutate(birthrate = case_when(
    births >= 10000 ~ "high",
    births <= 8000 ~ "low",
    TRUE ~ "fine"
  )) %>%
  mutate(birthrate = base::factor(birthrate,
    labels = c("high", "fine", "low"),
    ordered = TRUE
  )) %>%
  gf_tile(
    data = .,
    year ~ month,
    fill = ~birthrate,
    color = "black"
  ) %>%
  gf_labs(title = "Heatmap as a Time Series Representation") %>%
  gf_theme(scale_x_time(
    breaks = 1:12,
    labels = c(
      "Jan", "Feb", "Mar", "Apr",
      "May", "Jun", "Jul", "Aug",
      "Sep", "Oct", "Nov", "Dec"
    )
  )) %>%
  gf_theme(scale_fill_brewer(
    name = "Birth Rate", type = "qual", palette = "OrRd",
    direction = -1
  ))

Note how both X and Y axis seem to be a time-oriented variable in a heatmap!

Your Turn

  1. Choose some of the datasets in the tsdl and in the tsibbledata packages. (Install and load them first! ) Plot basic, filtered and model-based graphs for these and interpret.

Wait, But Why?

  • Many datasets show quantities varying over time. These are called time-series data.
  • The X-axis in these cases becomes a time axis.
  • Time-series data come in many different formats!
  • The time-aspect in a dataset creates for two dimensions of data-aggregation and averaging: One based on factors as before, and a new one based on intervals of time
  • We are interested in decomposing a time-series into averages, trends, seasonal components, and random variations
  • We are also interested in modelling a time-series as additive or multiplicative time-series, using techniques such as Holt-Winters, and ARIMA
  • And of course we are interested in forecasting!

Conclusion

We have seen a good few data formats for time series, and how to work with them and plot them.

In the Tutorial Section 1, we will explore:

  • wrangling with Time series to produce grouped and filtered aggregates/summaries and plots with these
  • how to decompose time series into periodic and aperiodic components, which can be used to make business decisions.
  • Producing Interactive Plots for Time Series
  • modelling and forecasting of time series.

References

  1. Robert Hyndman, Forecasting: Principles and Practice (Third Edition).available online
  2. Time Series Analysis at Our Coding Club
  3. The Nuclear Threat—The Shadow Peace, part 1
  4. 11 Ways to Visualize Changes Over Time – A Guide
  5. What is seasonal adjustment and why is it used?
  6. The start-at-zero rule
R Package Citations
Package Version Citation
fpp3 1.0.2 Hyndman (2025)
gghighlight 0.5.0 Yutani (2025)
timetk 2.9.1 Dancho and Vaughan (2025)
tsbox 0.4.2 Sax (2021)
tsdl 0.1.0 Hyndman and Yang (2025)
tsibble 1.1.6 Wang, Cook, and Hyndman (2020)
tsibbledata 0.4.1 O’Hara-Wild et al. (2022)
TSstudio 0.1.7 Krispin (2023)
Dancho, Matt, and Davis Vaughan. 2025. timetk: A Tool Kit for Working with Time Series. https://doi.org/10.32614/CRAN.package.timetk.
Hyndman, Rob. 2025. Fpp3: Data for Forecasting: Principles and Practice (3rd Edition). https://doi.org/10.32614/CRAN.package.fpp3.
Hyndman, Rob, and Yangzhuoran Yang. 2025. tsdl: Time Series Data Library. https://github.com/FinYang/tsdl.
Krispin, Rami. 2023. TSstudio: Functions for Time Series Analysis and Forecasting. https://doi.org/10.32614/CRAN.package.TSstudio.
O’Hara-Wild, Mitchell, Rob Hyndman, Earo Wang, and Rakshitha Godahewa. 2022. tsibbledata: Diverse Datasets for tsibble. https://doi.org/10.32614/CRAN.package.tsibbledata.
Sax, Christoph. 2021. tsbox: Class-Agnostic Time Series in in R. https://docs.ropensci.org/tsbox/.
Wang, Earo, Dianne Cook, and Rob J Hyndman. 2020. “A New Tidy Data Structure to Support Exploration and Modeling of Temporal Data.” Journal of Computational and Graphical Statistics 29 (3): 466–78. https://doi.org/10.1080/10618600.2019.1695624.
Yutani, Hiroaki. 2025. gghighlight: Highlight Lines and Points in ggplot2. https://doi.org/10.32614/CRAN.package.gghighlight.