--- title: "Use Case: Time Series Analysis" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Use Case: Time Series Analysis} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") ``` ## Overview LOESS provides flexible trend extraction from time series without parametric assumptions. --- ## Basic Trend Extraction `fraction = 0.1` sizes the neighbourhood as 10% of the data at each evaluation point — narrow enough to follow a slowly varying trend without smearing periodic variation. Three robustness `iterations` down-weight noise spikes so they cannot bias the fitted curve; this is especially important when the signal-to-noise ratio is low or when occasional outliers are expected. ```{r use_case_time_series_1} library(rfastloess) set.seed(42) t <- seq(0, 100, length.out = 500) trend <- 10 + 0.5 * t + 3 * sin(t / 10) noise <- rnorm(500, sd = 3) y <- trend + noise model <- Loess(fraction = 0.1, iterations = 3) result <- fit(model, t, y) plot(t, y, col = "gray", pch = ".", xlab = "Time", ylab = "Value", main = "Trend Extraction") lines(result$x, result$y, col = "blue", lwd = 2) legend("topleft", c("Observed", "Trend (LOESS)"), pch = c(1, NA), lty = c(NA, 1), col = c("gray", "blue")) ``` --- ## Seasonal Decomposition Setting `outputs = "residuals"` stores `observed − smoothed` alongside the smooth. A slightly wider `fraction = 0.4` produces a smoother baseline trend, so short-duration oscillations end up in the residuals rather than being absorbed into the trend component. The residual series is then ready for spectral analysis, seasonality detection, or change-point methods. ```{r use_case_time_series_2} library(rfastloess) set.seed(42) t <- 1:120 trend <- 100 + 0.5 * t seasonal <- 15 * sin(2 * pi * t / 12) noise <- rnorm(120, sd = 5) y <- trend + seasonal + noise model <- Loess(fraction = 0.4, iterations = 2, outputs = "residuals") result <- fit(model, t, y) residual <- result$residuals par(mfrow = c(2, 1), mar = c(4, 4, 2, 0.5)) plot(t, y, type = "l", col = "gray", main = "Original + LOESS Trend", xlab = "Month", ylab = "Value") lines(result$x, result$y, col = "blue", lwd = 2) plot(t, residual, type = "l", col = "darkgreen", main = "Detrended Residual (Seasonal Component)", xlab = "Month", ylab = "Residual") abline(h = 0, lty = 2, col = "red") par(mfrow = c(1, 1)) ``` --- ## Irregular Time Grids ```{r use_case_time_series_3} library(rfastloess) set.seed(42) t_irregular <- c(sort(runif(200, 0, 50)), sort(runif(50, 50, 100))) y_irregular <- sin(t_irregular / 10) + rnorm(length(t_irregular), sd = 0.5) model <- Loess(fraction = 0.2, iterations = 2) result <- fit(model, t_irregular, y_irregular) plot(t_irregular, y_irregular, pch = 16, cex = 0.4, col = "gray", xlab = "Time", ylab = "Value", main = "Irregularly Sampled Time Series") lines(result$x, result$y, col = "blue", lwd = 2) ``` --- ## Forecasting with Prediction Intervals Prediction intervals widen the uncertainty band to include both the uncertainty in the fitted curve (confidence interval) and the expected scatter of new observations around it. `fraction = 0.2` offers a balance between local detail and stable interval width — too small a fraction produces jagged interval edges; too large a fraction underestimates local variance near turning points. ```{r use_case_time_series_4} library(rfastloess) set.seed(42) t <- seq(0, 100, length.out = 500) y <- 10 + 0.3 * t + sin(t / 5) + rnorm(500, sd = 2) model <- Loess( fraction = 0.2, iterations = 3, intervals = intervals_opts(confidence = 0.95, prediction = 0.95) ) result <- fit(model, t, y) plot(t, y, pch = ".", col = "gray", xlab = "Time", ylab = "Value", main = "Trend with 95% Prediction Interval") polygon(c(result$x, rev(result$x)), c(result$prediction_upper, rev(result$prediction_lower)), col = rgb(0, 0, 1, 0.15), border = NA) lines(result$x, result$y, col = "blue", lwd = 2) ``` --- ## Handling Missing Data LOESS naturally handles irregular time sampling: ```{r use_case_time_series_5} library(rfastloess) set.seed(42) t_irregular <- sort(runif(200, 0, 100)) y_irregular <- 10 + t_irregular * 0.3 + rnorm(200, sd = 2) model <- Loess(fraction = 0.2) result <- fit(model, t_irregular, y_irregular) cat("Smoothed y[0]:", result$y[1], "\n") ``` --- ## Multi-Scale Analysis Use different fractions to extract features at different scales: ```{r use_case_time_series_6} library(rfastloess) set.seed(42) t <- seq(0, 100, length.out = 500) trend_true <- 10 + 0.5 * t + 3 * sin(t / 10) y <- trend_true + rnorm(length(t), sd = 3) fractions <- c(0.05, 0.2, 0.5) plot(t, y, col = "gray", pch = ".", xlab = "Time", ylab = "Value", main = "Multi-Scale LOESS") for (f in fractions) { model <- Loess(fraction = f) result <- fit(model, t, y) lines(result$x, result$y, lwd = 2) } legend("topleft", paste0("fraction=", fractions), lty = 1) ``` --- ## Gene Expression Time Course Biological application: ```{r use_case_time_series_7} library(rfastloess) set.seed(42) hours <- seq(0, 24, by = 0.5) expression <- 100 * (1 + 0.5 * sin(hours * pi / 12)) + rnorm(length(hours), sd = 10) model <- Loess( fraction = 0.3, iterations = 3, outputs = "diagnostics", intervals = intervals_opts(confidence = 0.95) ) result <- fit(model, hours, expression) cat("R2:", result$diagnostics$r_squared, "\n") ``` --- ## Choosing Fraction for Time Series | Data Type | Recommended Fraction | Rationale | | --- | --- | --- | | Daily data (years) | 0.3–0.5 | Capture annual trends | | Hourly data (days) | 0.1–0.2 | Capture daily patterns | | Sensor data (minutes) | 0.05–0.1 | Preserve short-term features | | Noisy data | Higher | Reduce noise impact | | Clean data | Lower | Preserve detail | --- ## See Also - `vignette("use-case-real-time", package = "rfastloess")` — For streaming time series - `vignette("cross-validation", package = "rfastloess")` — Optimal fraction selection - `vignette("degree", package = "rfastloess")` — Degree 2 for curved trends - `vignette("boundary", package = "rfastloess")` — Edge bias in trend extraction - `?Loess` — Full parameter reference ```{r session-info} sessionInfo() ```