Author: Pietro Piu

# cycleTrendR in practice: Universal Trend and Cycle Analysis

#### Pietro Piu

# Introduction

cycleTrendR provides a unified framework for trend estimation, cycle detection, spectral analysis, bootstrap confidence intervals, and change-point detection across any type of time series.

Version 0.3.0 introduces a major enhancement:

- full support for multiple time formats through the argument dates_type;

- a universal internal time index (timenum);

- automatic switching between STL, Lomb–Scargle, Fourier, LOESS, GAM, and GAMM;

- compatibility with daily, sub-daily, and numeric time series.

This vignette demonstrates how to use cycleTrendR in three real-world scenarios:

1. Daily data (Date)

2. Wearable / PhysioNet data (POSIXct)

3. Neuroscience / spike trains (numeric time)

# Supported time formats

adaptive_cycle_trend_analysis() accepts three types of temporal indexing:

dates_type | Input class | Typical use case
--- | --- | ---
“date” | Date | Epidemiology, climate, economics
“posix” | POSIXct | Wearable sensors, HR, EDA, EEG
“numeric” | numeric | Spike trains, firing rate, …

The function automatically adapts:

- STL only for “date”

- Lomb–Scargle for “posix” and “numeric”

- Fourier harmonics in time units of timenum

- Trend estimation via LOESS, GAM, or GAMM

- Change-points in the same scale as the input time

# Example 1 — Daily data (Date)

    set.seed(1)

    dates <- as.Date("2020-01-01") + 1:200
    signal <- sin(2*pi*(1:200)/30) + rnorm(200, 0, 0.2)

    res_date <- adaptive_cycle_trend_analysis(
      signal = signal,
      dates = dates,
      dates_type = "date",
      trendmethod = "loess",
      usefourier = TRUE
    )
    #> Using original scale.
    #> Regular time index: STL (if dates_type='date') + LOESS/GAM(M)

    #> Auto seasonality: dominant period approx 28.57 samples -> seasonalfrequency=29
    #> loess.as proposed span (criterion=aicc) = 0.945
    #> Fourier selection: criterion=AICc -> K=1 (from 0..6)
    #> Warning in adaptive_cycle_trend_analysis(signal = signal, dates = dates, :
    #> Model-based CI not implemented for this configuration; switching to
    #> bootstrapiid.
    #> Bootstrap CI (bootstrapiid): 1000 resamples
    #> Warning in tseries::adf.test(signal, alternative = "stationary"): p-value
    #> smaller than printed p-value
    #> Warning in tseries::kpss.test(signal, null = "Level"): p-value greater than
    #> printed p-value

    res_date$Plot$Trend

    res_date$Plot$Spectrum

This example illustrates:

- STL decomposition

- automatic seasonality detection

- Fourier harmonics

- LOESS trend

- model-based or bootstrap CI

# Example 2 — PhysioNet in practice (POSIXct)

    # Example: synthetic POSIXct HR-like signal
    t <- as.POSIXct("2020-01-01 00:00:00") + seq(0, by = 60, length.out = 1000)
    hr <- 70 + 5*sin(2*pi*(1:1000)/200) + rnorm(1000, 0, 1)

    res_posix <- adaptive_cycle_trend_analysis(
      signal = hr,
      dates = t,
      dates_type = "posix",
      trendmethod = "gam",
      usefourier = TRUE
    )
    #> Using original scale.
    #> Regular time index: STL (if dates_type='date') + LOESS/GAM(M)
    #> No STL/seasonality for dates_type != 'date'.
    #> Fourier selection: criterion=AICc -> K=5 (from 0..6)
    #> Warning in adaptive_cycle_trend_analysis(signal = hr, dates = t, dates_type =
    #> "posix", : Model-based CI not implemented for this configuration; switching to
    #> bootstrapiid.
    #> Bootstrap CI (bootstrapiid): 1000 resamples

    res_posix$Plot$Trend

    res_posix$Plot$Spectrum

Key features:

- Lomb–Scargle periodogram

- GAM or GAMM trend

- Fourier in seconds

- change-points in POSIXct

- robust CI

This is the recommended workflow for:

- PhysioNet HR

- PhysioNet EDA

- accelerometry

- EEG timestamps

# Example 3 — Neuroscience in practice (numeric time)

    time <- seq(0, 10, length.out = 2000)
    spike_rate <- sin(2*pi*time*5) + rnorm(2000, 0, 0.2)

    res_num <- adaptive_cycle_trend_analysis(
      signal = spike_rate,
      dates = time,
      dates_type = "numeric",
      trendmethod = "loess",
      usefourier = TRUE
    )
    #> Using original scale.
    #> Regular time index: STL (if dates_type='date') + LOESS/GAM(M)
    #> No STL/seasonality for dates_type != 'date'.
    #> loess.as proposed span (criterion=aicc) = 0.949
    #> Fourier selection: criterion=AICc -> K=3 (from 0..6)
    #> Warning in adaptive_cycle_trend_analysis(signal = spike_rate, dates = time, :
    #> Model-based CI not implemented for this configuration; switching to
    #> bootstrapiid.
    #> Bootstrap CI (bootstrapiid): 1000 resamples
    #> Warning in tseries::adf.test(signal, alternative = "stationary"): p-value
    #> smaller than printed p-value
    #> Warning in tseries::kpss.test(signal, null = "Level"): p-value greater than
    #> printed p-value

    res_num$Plot$Trend

    res_num$Plot$Spectrum

This workflow is ideal for:

- spike trains

- firing rate

- LFP power

- simulated time series

- any non-calendar time axis

# Best practices

- Use “date” when the time axis is calendar-based

- Use “posix” for sub-daily physiological signals

- Use “numeric” for experimental or simulated time

- Prefer GAM/GAMM for long or noisy signals

- Use Fourier only when a dominant period is meaningful

- Use bootstrap CI when residuals show autocorrelation

# Session info

    ```r{r info}
    sessionInfo()