Getting Started with trendseries

library(trendseries)
library(dplyr)
library(ggplot2)

theme_series <- theme_minimal(paper = "#fefefe") +
  theme(
    legend.position = "bottom",
    panel.grid.minor = element_blank(),
    # Use colors
    palette.colour.discrete = c(
      "#024873FF",
      "#BF4F26FF",
      "#D98825FF",
      "#D9AA1EFF",
      "#A2A637FF"
    )
  )

What is trendseries?

The trendseries package helps you extract trends from economic time series data. Think of trends as the underlying direction of your data, stripped of short-term fluctuations and noise.

This vignette will walk you through the basics, starting with the simplest possible example and gradually building up to more complex analyses.

Purpose

The main purpose of trendseries is to simplify the workflow of trend extraction, especially when working with data frames and grouped data. It provides two main functions:

Tabular data frames are the most common format for time series data in R, but most trend extraction methods are designed for ts objects. trendseries bridges this gap, allowing you to work directly with data frames without cumbersome conversions. trendseries was designed to work fluidly with tidyverse tools like dplyr and ggplot2.

This package was designed with economic time series in mind, in this sense, it includes methods commonly used in economics (e.g., Hodrick-Prescott filter) as well as general-purpose smoothing methods (e.g., LOESS, moving averages).

Your First Trend Analysis

Let’s start with the simplest possible example: extracting a trend from Brazilian GDP construction data.

# Load the data
data("gdp_construction", package = "trendseries")

# Take a quick look
head(gdp_construction)
#> # A tibble: 6 × 2
#>   date       index
#>   <date>     <dbl>
#> 1 1995-01-01 100  
#> 2 1995-04-01 100  
#> 3 1995-07-01 100  
#> 4 1995-10-01 100  
#> 5 1996-01-01  97.8
#> 6 1996-04-01 101.

This dataset contains quarterly observations of Brazil’s construction sector GDP. Let’s extract a trend using the Hodrick-Prescott (HP) filter, one of the most common methods in economics.

# Extract trend using HP filter
gdp_with_trend <- augment_trends(
  gdp_construction,
  value_col = "index",
  methods = "hp"
)
#> Auto-detected quarterly (4 obs/year)
#> Computing HP filter (two-sided) with lambda = 1600

# View the result
head(gdp_with_trend)
#> # A tibble: 6 × 3
#>   date       index trend_hp
#>   <date>     <dbl>    <dbl>
#> 1 1995-01-01 100       101.
#> 2 1995-04-01 100       101.
#> 3 1995-07-01 100       102.
#> 4 1995-10-01 100       103.
#> 5 1996-01-01  97.8     103.
#> 6 1996-04-01 101.      104.

That’s it! The augment_trends() function added a new column called trend_hp to your data. The original data stays intact, and you get a new column with the trend.

Visualizing Your First Trend

Let’s see what this trend looks like.

# Prepare data for plotting
plot_data <- gdp_with_trend |>
  select(date, index, trend_hp) |>
  tidyr::pivot_longer(
    cols = c(index, trend_hp),
    names_to = "series",
    values_to = "value"
  ) |>
  mutate(
    series = case_when(
      series == "index" ~ "Data (original)",
      series == "trend_hp" ~ "HP Filter Trend"
    )
  )

# Create the plot
ggplot(plot_data, aes(x = date, y = value, color = series)) +
  geom_line(linewidth = 0.8) +
  labs(
    title = "Brazil GDP Construction: Original vs Trend",
    x = "Date",
    y = "Construction Index",
    color = NULL
  ) +
  theme_series

Notice how the trend smooths out the short-term fluctuations in the original data. This makes it easier to see the long-term direction of the construction sector.

What if my data is a sinlge time series?

If your data is already a ts object, you can use the extract_trends() function directly. This is useful for quick analyses or when working with single series.

gdp <- ts(
  gdp_construction$index,
  frequency = 4,
  start = c(1996, 1)
)

gdp_trend_hp <- extract_trends(gdp, "hp")
#> Computing HP filter (two-sided) with lambda = 1600

Trying Different Trend Methods

The HP filter is just one way to extract trends. Let’s compare it with two other popular methods.

# Extract multiple trends at once
gdp_comparison <- gdp_construction |>
  augment_trends(
    value_col = "index",
    methods = c("hp", "loess", "ma")
  )
#> Auto-detected quarterly (4 obs/year)
#> Computing HP filter (two-sided) with lambda = 1600
#> Computing loess trend with span = 0.75
#> Computing 2x4-period MA (auto-adjusted for even-window centering)

# View the first few rows
gdp_comparison |>
  select(date, index, starts_with("trend_")) |>
  head()
#> # A tibble: 6 × 5
#>   date       index trend_hp trend_loess trend_ma
#>   <date>     <dbl>    <dbl>       <dbl>    <dbl>
#> 1 1995-01-01 100       101.        109.     NA  
#> 2 1995-04-01 100       101.        109.     99.7
#> 3 1995-07-01 100       102.        108.     99.6
#> 4 1995-10-01 100       103.        107.    101. 
#> 5 1996-01-01  97.8     103.        107.    102. 
#> 6 1996-04-01 101.      104.        106.    103.

Now let’s visualize all three methods:

# Prepare data for plotting
comparison_plot <- gdp_comparison |>
  select(date, index, starts_with("trend_")) |>
  tidyr::pivot_longer(
    cols = c(index, starts_with("trend_")),
    names_to = "method",
    values_to = "value"
  ) |>
  mutate(
    method = case_when(
      method == "index" ~ "Data (original)",
      method == "trend_hp" ~ "HP Filter",
      method == "trend_loess" ~ "LOESS",
      method == "trend_ma" ~ "Moving Average"
    )
  )

# Plot
ggplot(comparison_plot, aes(x = date, y = value, color = method)) +
  geom_line(linewidth = 0.8) +
  labs(
    title = "Comparing Different Trend Extraction Methods",
    subtitle = "Same data, different methods",
    x = "Date",
    y = "Construction Index",
    color = "Method"
  ) +
  theme_series
#> Warning: Removed 4 rows containing missing values or values outside the scale range
#> (`geom_line()`).

Each method produces a slightly different trend. The HP filter and LOESS are quite similar, while the moving average is slightly more responsive to changes in the data.

Working with Monthly Data

So far we’ve used quarterly data (4 observations per year). Let’s try monthly data, which is more common in economic analysis.

# Load monthly vehicle production data
data("vehicles", package = "trendseries")

# Look at recent data (last 4 years)
recent_vehicles <- vehicles |>
  slice_tail(n = 48)

head(recent_vehicles)
#> # A tibble: 6 × 2
#>   date       production
#>   <date>          <dbl>
#> 1 2021-08-01     178900
#> 2 2021-09-01     156803
#> 3 2021-10-01     170178
#> 4 2021-11-01     176322
#> 5 2021-12-01     219446
#> 6 2022-01-01     136105

The process is exactly the same - augment_trends() automatically detects the frequency.

# Extract trend from monthly data
vehicles_with_trend <- vehicles |>
  augment_trends(
    value_col = "production",
    methods = "hp"
  )
#> Auto-detected monthly (12 obs/year)
#> Computing HP filter (two-sided) with lambda = 14400

vehicles_with_trend <- vehicles_with_trend |>
  tidyr::pivot_longer(
    cols = c(production, trend_hp),
    names_to = "series",
    values_to = "value"
  ) |>
  mutate(
    series = ifelse(series == "production", "Original", "HP Trend"),
    # To make sure the trend is plotted on top of the original series
    # configure levels accordingly
    series = factor(series, levels = c("Original", "HP Trend"))
  )

ggplot(vehicles_with_trend, aes(x = date, y = value, color = series)) +
  geom_line(linewidth = 0.8) +
  labs(
    title = "Brazil Vehicle Production: Monthly Data",
    subtitle = "Last 4 years of data",
    x = "Date",
    y = "Production (thousands of units)",
    color = NULL
  ) +
  theme_series

Understanding Parameters: Window and Smoothing

Different trend methods accept different parameters. The trendseries package simplifies this with two main parameters:

Let’s experiment with the window parameter for a moving average.

# Try different window sizes
vehicles_windows <- recent_vehicles |>
  augment_trends(
    value_col = "production",
    methods = "ma",
    window = 6
  ) |>
  rename(trend_ma_6m = trend_ma)
#> Auto-detected monthly (12 obs/year)
#> Computing 2x6-period MA (auto-adjusted for even-window centering)

# Add 12-month window
vehicles_windows <- vehicles_windows |>
  augment_trends(
    value_col = "production",
    methods = "ma",
    window = 12
  )
#> Auto-detected monthly (12 obs/year)
#> Computing 2x12-period MA (auto-adjusted for even-window centering)

# Visualize
vehicles_windows <- vehicles_windows |>
  select(date, production, trend_ma_6m, trend_ma) |>
  tidyr::pivot_longer(
    cols = c(production, trend_ma_6m, trend_ma),
    names_to = "method",
    values_to = "value"
  ) |>
  mutate(
    method = case_when(
      method == "production" ~ "Data (original)",
      method == "trend_ma_6m" ~ "MA (6-month)",
      method == "trend_ma" ~ "MA (12-month)"
    )
  )

ggplot(vehicles_windows, aes(x = date, y = value, color = method)) +
  geom_line(linewidth = 0.8) +
  labs(
    title = "Window Size Comparison",
    subtitle = "Larger windows = smoother trends",
    x = "Date",
    y = "Production (thousands)",
    color = NULL
  ) +
  theme_series
#> Warning: Removed 18 rows containing missing values or values outside the scale range
#> (`geom_line()`).

Key insight: Larger windows produce smoother trends but are slower to react to changes. Smaller windows track the data more closely but may still include some noise.

For monthly data: - Short-term trend: window = 3 to 6 - Medium-term trend: window = 12 (one year) - Long-term trend: window = 24 or more

Working with Multiple Series

A common task is comparing trends across different time-series. Let’s look at economic activity and vehicle production together. augment_trends() can handle this easily with the group_vars argument.

data("ibcbr", package = "trendseries")

series <- full_join(ibcbr, vehicles, by = "date")

series <- series |>
  filter(date >= as.Date("2010-01-01")) |>
  tidyr::pivot_longer(
    cols = c(index, production),
    names_to = "indicator",
    values_to = "value"
  ) |>
  # Normalize to Jan 2010 = 100 for comparison
  mutate(
    norm_index = value / first(value) * 100,
    .by = indicator
  )

series <- augment_trends(
  series,
  value_col = "norm_index",
  methods = "hp",
  group_vars = "indicator"
)
#> Auto-detected monthly (12 obs/year)
#> Computing HP filter (two-sided) with lambda = 14400
#> Auto-detected monthly (12 obs/year)
#> Computing HP filter (two-sided) with lambda = 14400
# Plot trends only
series |>
  ggplot(aes(x = date, color = indicator)) +
  geom_line(aes(y = norm_index), alpha = 0.4) +
  geom_line(aes(y = trend_hp), linewidth = 1) +
  labs(
    title = "Economic Indicators: HP Filter Trends",
    subtitle = "Normalized to first observation = 100",
    x = "Date",
    y = "Index (normalized)",
    color = "Indicator"
  ) +
  theme_series

This shows both series moved together during the pandemic period but diverged afterwards, with economic activity recovering more strongly than vehicle production.

Available Trend Methods

The trendseries package supports many trend extraction methods. Here are the most commonly used:

Economic/Econometric Filters

  • hp: Hodrick-Prescott filter (default, widely used in macroeconomics)
  • bk: Baxter-King bandpass filter (isolates business cycles)
  • cf: Christiano-Fitzgerald filter (asymmetric bandpass)
  • hamilton: Hamilton regression filter (recent alternative to HP)

Moving Averages

  • ma: Simple moving average (easiest to understand)
  • ewma: Exponentially weighted moving average (more weight on recent data)
  • wma: Weighted moving average (emphasizes recent observations)
  • zlema: Zero-lag exponential moving average (reduced lag)
  • triangular: Triangular moving average (double-smoothed)

Smoothing Methods

  • loess: Local polynomial regression (flexible, data-adaptive)
  • spline: Smoothing splines (very smooth)
  • stl: Seasonal-trend decomposition (handles seasonality)

Advanced Methods

  • sg: Savitzky-Golay filter (preserves features like peaks)
  • kalman: Kalman smoother (optimal under certain assumptions)
  • kernel: Kernel regression smoother (nonparametric)

Recommendation for beginners: Start with hp for general use, ma when you want something simple and interpretable, and stl when your data has clear seasonal patterns.

Quick Reference: Common Patterns

Here are some ready-to-use patterns for common scenarios:

Basic trend extraction

# Single method, quarterly data
data |>
  augment_trends(value_col = "your_column", methods = "hp")

# Single series, monthly data
extract_trends(your_ts_data, method = "loess")

Compare multiple methods

data |>
  augment_trends(
    value_col = "your_column",
    methods = c("hp", "loess", "ma")
  )

Control smoothness

# Smoother HP filter (higher lambda)
data |>
  augment_trends(
    value_col = "your_column",
    methods = "hp",
    smoothing = 3200  # vs default 1600 for quarterly
  )

# Longer moving average window
data |>
  augment_trends(
    value_col = "your_column",
    methods = "ma",
    window = 24  # 2-year window for monthly data
  )

Grouped data

# Apply trend to multiple series at once
multi_series_data |>
  group_by(country) |>
  augment_trends(value_col = "gdp", methods = "hp") |>
  ungroup()

# Or using group_vars argument
multi_series_data |>
  augment_trends(
    value_col = "gdp",
    methods = "hp",
    group_vars = "country"
  )

Next Steps

Now that you understand the basics, you can:

  1. Explore specific methods in depth: Check out the “Moving Averages” vignette for detailed analysis of MA methods.
  2. Learn about economic filters: See the “Economic Filters” vignette for HP, BK, and CF filters.
  3. Try advanced methods: Explore STL decomposition, Kalman filtering, and more in the “Advanced Methods” vignette.
  4. Read the function documentation: ?augment_trends and ?extract_trends.

Built-in Datasets

The package includes several economic datasets for practice.

Brazilian Economic Data

All series are monthly except for gdp_construction. - ibcbr: Central Bank Economic Activity Index - smooth, good for learning - vehicles: Vehicle production - cyclical, interesting patterns - electric: Electricity consumption - seasonal patterns - oil_derivatives: Oil derivatives production - gdp_construction: GDP construction index - smooth, great for first examples

International Data

  • retail_households: UK household goods retail (monthly)
  • retail_autofuel: UK automotive fuel retail (monthly)
  • coffee_arabica: Coffee prices (daily)
  • coffee_robusta: Coffee prices (daily)

FAQ

How is trendseries easier than the traditional workflow?

Time series have a specific structure in R (ts) and most filtering methods are designed for ts objects. However, datasets come as data frames with date columns, which can make applying filters cumbersome.

The usual workflow involves (1) converting individual columns to ts, (2) applying the filter, (3) and then converting back to a data.frame. This can be cumbersome, especially when working with multiple series or grouped data. Merging back the results with the original data can also be error-prone due to misalignment of dates and additional NA values introduced by some filters.

For instance, to replicate the first example in this vignette, without trendseries, you would have to first convert the data frame to a ts object, mannually inputing both frequency and start parameters:

gdp_cons <- ts(
  gdp_construction$index,
  frequency = 4,
  start = c(1996, 1)
)

# Or, using lubridate to extract year and month
gdp_cons <- ts(
  gdp_construction$index,
  frequency = 4,
  start = c(lubridate::year(min(gdp_construction$date)),
            lubridate::quarter(min(gdp_construction$date)))
)

Then apply the HP filter using the mFilter package.

gdp_trend_hp <- mFilter::hpfilter(gdp_cons, 1600)

And finally, convert it back to a data.frame and merge with the original data.

# Convert back to data frame using tsbox
trend_df <- tsbox::ts_df(gdp_trend_hp$trend)
names(trend_df) <- c("date", "trend_hp")

# Join with original data
gdp_manual <- left_join(gdp_construction, trend_df, by = "date")

What are the alternatives to trendseries?

The closest alternative to trendseries is the tsibble/fable ecosystem, which provides a model() function for applying models — including some trend extraction methods — to grouped time series. Like trendseries, these packages integrate well with tidyverse tools and pipes.

However, fable was designed primarily for forecasting, which means its trend extraction capabilities are more limited. They also lack some popular methods commonly used by economists, such as the HP filter and the Hamilton filter.

Additionally, these packages require using the tsibble data structure, which pulls users away from the familiar data.frame/tibble format. For users working with just a few time series and relying on R’s built-in ts functionality, the tsibble structure can feel unnecessarily complex.

Acknowledgements

This package was inspired by the need for a simpler workflow for trend extraction in R. It builds upon many existing packages, including:

Getting Help

If you run into issues: