Automated Time-Series Preprocessing in R
Overview
atspR is an R package that provides an automated, modular preprocessing pipeline for time-series data covering the full journey from raw sensor data to cross-validated, model-ready train and test sets.
The package is designed for students and domain practitioners (agronomists, environmental scientists, irrigation engineers) who work primarily in R and need a reliable, leakage-free preprocessing workflow without having to manage multiple packages or worry about the correct sequencing of steps.
Why atspR?
| Problem | How atspR solves it |
|---|---|
| Scalers applied before splitting → data leakage | Scaler fitted on TRAIN only, applied to both sets |
| Random split invalidates time-series models | Temporal split — train always precedes test chronologically |
| Irregular sensor timestamps break time-series assumptions |
fill_time_gaps() snaps data onto a regular time grid, flagging missing points as NA
|
| Silent NA removal with no record | Full report of what was dropped or imputed, and why |
| Multi-package workflow, easy to sequence incorrectly | Single function ts_preprocess() runs all 8 steps in order |
| One-size-fits-all model choice |
cross_validate() supports multiple model types (Linear Regression (lm) / Generalized Additive Model (gam) / Random Forest (rf)/ Decision Tree (dt)) |
| Error messages with no guidance | Every automated decision explained in plain-language output |
Pipeline
Raw data
│
├─ (Stage 1) combine_datetime() Merge date + time columns → POSIXct (optional)
├─ (Stage 2) fill_time_gaps() Insert placeholder rows for missing timestamps
│
└─ ts_preprocess() ────────────────── 8 steps ──────────────────────────────
│ (Stage 3) standardize_na() Convert sentinel values to NA
│ (Stage 4) coerce_numeric() Parse character columns to numeric
│ (Stage 5) split_data() Temporal train/test split
│ (Stage 6) missing_analysis() Summarise NAs; decide DROP or IMPUTE
│ (Stage 7) handle_missing() Drop rows or interpolate (linear/KNN)
│ (Stage 8) visualize_data() Scatter plots per variable vs time
│ (Stage 9) scale_data() MinMax / Z-score / Robust (auto)
│ (Stage 10) cross_validate() Walk-forward k-fold CV (optional) — choose one :
| Linear Regression (lm)/ Generalized Additive Model (gam)/
│ Random Forest (rf)/ Decision Tree (dt)
│
└─ ts_export() Write all outputs to CSV
Installation
# Install from GitHub
# install.packages("devtools")
devtools::install_github("EasternTechFusion/atspR")Quick Start
Note: Column names must not contain parentheses
()or the package will fail to parse them. For example, renameWaterLevel()toWaterLevelbefore passing the data toatspRfunctions.
Univariate
Sample data
| DateTime | WaterLevel |
|---|---|
| 2024-01-01 08:00 | 3.31 |
| 2024-01-01 08:10 | 3.37 |
| 2024-01-01 08:20 | 3.46 |
| 2024-01-01 08:33 | 3.53 |
| 2024-01-01 08:42 | 3.56 |
| 2024-01-01 08:59 | 3.58 |
| 2024-01-01 09:10 | 3.60 |
| 2024-01-01 09:24 | 3.62 |
Raw sensor readings arrive at irregular intervals (roughly every 10 minutes) —
fill_time_gaps()snaps them onto a regular 10-minute grid, insertingNAfor any grid point with no matching reading.
This dataset has only one variable (
WaterLevel), sots_preprocess()runs in univariate mode — no exogenous features are used.
library(atspR)
df$datetime <- as.POSIXct(df$datetime,
format = "%Y-%m-%d %H:%M:%S")
gap <- fill_time_gaps(df,
time_col = "DateTime",
n = 10,
unit = "min")
result <- ts_preprocess(data = gap$data,
train_ratio = 0.8,
impute_method = "linear",
lags = 24,
model_type = "lm",
target_col = "WaterLevel",
k_folds = 5)Multivariate
Case 1: Date + Time in separate columns
Sample data
| Date | Time | Temp | Humid | Solar | WindSpeed | RainFall | VPD |
|---|---|---|---|---|---|---|---|
| 2024-01-01 | 08:00 | 25.12 | 80.04 | 14.0591 | 0.0000 | 0.0000 | 0.2758 |
| 2024-01-01 | 09:00 | NA | 82.38 | 18.3274 | 0.5200 | 0.0000 | 0.3124 |
| 2024-01-01 | 11:00 | 26.26 | 85.03 | 22.7810 | 1.1400 | 0.0000 | 0.3892 |
| 2024-01-01 | 12:00 | 27.07 | 83.57 | NA | 1.3300 | 0.1200 | 0.4015 |
Hour 10:00 is missing entirely —
fill_time_gaps()will insert it as a row ofNA.
library(atspR)
df <- combine_datetime(my_data,
date_col = "Date",
time_col = "Time",
new_col = "datetime",
time_type = "string")
gap <- fill_time_gaps(df,
time_col = "datetime",
n = 1,
unit = "hour")
result <- ts_preprocess(data = gap$data,
train_ratio = 0.8,
target_col = "VPD",
k_folds = 5)Case 2: Datetime in one column
Sample data
| DateTime | Temp | Humid | Solar | WindSpeed | RainFall | VPD |
|---|---|---|---|---|---|---|
| 2024-01-01 08:00 | 25.12 | 80.04 | 14.0591 | 0.0000 | 0.0000 | 0.2758 |
| 2024-01-01 09:00 | NA | 82.38 | 18.3274 | 0.5200 | 0.0000 | 0.3124 |
| 2024-01-01 11:00 | 26.26 | 85.03 | 22.7810 | 1.1400 | 0.0000 | 0.3892 |
| 2024-01-01 12:00 | 27.07 | 83.57 | NA | 1.3300 | 0.1200 | 0.4015 |
Hour 10:00 is missing entirely —
fill_time_gaps()will insert it as a row ofNA.
library(atspR)
df$datetime <- as.POSIXct(df$datetime,
format = "%Y-%m-%d %H:%M:%S")
gap <- fill_time_gaps(df,
time_col = "datetime",
n = 1,
unit = "hour")
result <- ts_preprocess(data = gap$data,
train_ratio = 0.8,
target_col = "VPD",
k_folds = 5)Case 3: Date only (daily data)
Sample data
| date | Temp | Humid | Solar | WindSpeed | RainFall | VPD |
|---|---|---|---|---|---|---|
| 2024-01-01 | 28.38 | 75.04 | 14.0591 | 0.0000 | 0.0000 | 0.275835 |
| 2024-01-02 | 29.16 | 72.05 | NA | 0.5200 | 0.0000 | 0.312410 |
| 2024-01-04 | 27.89 | 78.03 | 22.7810 | 1.1400 | 2.4000 | 0.389200 |
| 2024-01-05 | 26.50 | 80.54 | 19.4320 | 0.8800 | 0.0000 | 0.301770 |
2024-01-03 is missing entirely —
fill_time_gaps()will insert it as a row ofNA.
library(atspR)
gap <- fill_time_gaps(df,
time_col = "date",
n = 1,
unit = "day")
result <- ts_preprocess(data = gap$data,
train_ratio = 0.8,
target_col = "VPD",
k_folds = 5)Cross-Validation
atspR uses walk-forward validation — the correct approach for time-series data. Each validation fold is always preceded only by past data, so no future information leaks into training.
Seed(20%) Fold1 Fold2 Fold3 Fold4 Fold5
[─────────][─────][─────][─────][─────][─────]
Fold 1: train = seed → val = fold1
Fold 2: train = seed + fold1 → val = fold2
Fold 3: train = seed + fold1+2 → val = fold3
...
