Nothing
make_monthly_df <- function(n = 12, start = "2020-01-01", value = seq_len(n)) {
tibble::tibble(
date = seq.Date(as.Date(start), by = "month", length.out = n),
value = value
)
}
test_that("errors when data has no 'date' column", {
df <- tibble::tibble(x = 1:5)
expect_error(
compute_rolling_value(df, .f = ~ mean(.x$x), period = "month", periods = 3),
"date"
)
})
test_that("errors when 'date' column is not Date class", {
df <- tibble::tibble(date = as.character(Sys.Date() + 0:4), value = 1:5)
expect_error(
compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "month",
periods = 3
),
"Date"
)
})
test_that("errors when period is not a single string", {
df <- make_monthly_df(5)
expect_error(
compute_rolling_value(df, .f = ~ mean(.x$value), period = 123, periods = 3)
)
expect_error(
compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = c("month", "year"),
periods = 3
)
)
})
test_that("returns a numeric vector with length equal to nrow(data)", {
df <- make_monthly_df(12)
result <- compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "month",
periods = 3
)
expect_type(result, "double")
expect_length(result, nrow(df))
})
test_that("min_obs defaults to periods; early windows return NA", {
df <- make_monthly_df(6)
result <- compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "month",
periods = 3
)
# First two windows have fewer than 3 rows → NA
expect_true(is.na(result[1]))
expect_true(is.na(result[2]))
# Third window onwards should be non-NA
expect_false(any(is.na(result[3:6])))
})
test_that("custom min_obs produces fewer leading NAs", {
df <- make_monthly_df(6)
result <- compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "month",
periods = 3,
min_obs = 1
)
# Even the first row (1 observation) should be non-NA
expect_false(any(is.na(result)))
})
test_that("min_obs larger than periods makes more windows NA", {
df <- make_monthly_df(6)
# periods = 3 but min_obs = 5 → only windows with ≥5 rows produce values
result <- compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "month",
periods = 6,
min_obs = 5
)
expect_true(is.na(result[1]))
expect_true(is.na(result[4]))
expect_false(is.na(result[5]))
})
test_that("rows with NA values are dropped before applying .f", {
df <- make_monthly_df(6)
df$value[2] <- NA
df$value[3] <- NA
# Window for row 4 spans rows 2-4; after dropping NAs only row 4 remains
result <- compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "month",
periods = 3,
min_obs = 1
)
expect_type(result, "double")
# Row 2 has NA in value → after complete cases the window for row 2 (rows 1-2)
# has only row 1 → min_obs = 1 so still non-NA
expect_false(is.na(result[1]))
})
test_that("window returns NA when complete cases < min_obs due to NAs", {
df <- make_monthly_df(6)
df$value[1] <- NA
df$value[2] <- NA
df$value[3] <- NA
result <- compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "month",
periods = 3,
min_obs = 2
)
# Windows covering rows 1-3 have 0 complete rows → NA
expect_true(is.na(result[1]))
expect_true(is.na(result[2]))
expect_true(is.na(result[3]))
})
test_that("rolling mean with periods = 1 equals the original values", {
df <- make_monthly_df(5, value = c(10, 20, 30, 40, 50))
result <- compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "month",
periods = 1,
min_obs = 1
)
expect_equal(result, c(10, 20, 30, 40, 50))
})
test_that("rolling mean with periods = 3 computes correct values", {
df <- make_monthly_df(5, value = c(10, 20, 30, 40, 50))
result <- compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "month",
periods = 3,
min_obs = 1
)
expect_equal(result[1], 10) # window: 10
expect_equal(result[2], mean(c(10, 20))) # window: 10, 20
expect_equal(result[3], mean(c(10, 20, 30))) # window: 10, 20, 30
expect_equal(result[4], mean(c(20, 30, 40))) # window: 20, 30, 40
expect_equal(result[5], mean(c(30, 40, 50))) # window: 30, 40, 50
})
test_that("rolling sum works correctly", {
df <- make_monthly_df(5, value = c(1, 2, 3, 4, 5))
result <- compute_rolling_value(
df,
.f = ~ sum(.x$value),
period = "month",
periods = 3,
min_obs = 1
)
expect_equal(result[3], 6)
expect_equal(result[4], 9)
expect_equal(result[5], 12)
})
test_that("rolling sd works correctly", {
set.seed(42)
df <- make_monthly_df(6, value = c(1, 3, 2, 5, 4, 6))
result <- compute_rolling_value(
df,
.f = ~ sd(.x$value),
period = "month",
periods = 3,
min_obs = 2
)
expect_equal(result[3], sd(c(1, 3, 2)))
expect_equal(result[5], sd(c(2, 5, 4)))
})
test_that("accepts purrr-style formula (.f = ~ ...)", {
df <- make_monthly_df(5, value = c(1, 2, 3, 4, 5))
result <- compute_rolling_value(
df,
.f = ~ sum(.x$value),
period = "month",
periods = 2,
min_obs = 1
)
expect_equal(result[4], 7)
})
test_that("accepts a regular function for .f", {
my_fn <- function(d) max(d$value)
df <- make_monthly_df(4, value = c(10, 5, 20, 3))
result <- compute_rolling_value(
df,
.f = my_fn,
period = "month",
periods = 3,
min_obs = 1
)
expect_equal(result[3], 20)
expect_equal(result[4], 20)
})
test_that("works with period = 'quarter'", {
# 8 months → 3 quarters (Q1: Jan-Mar, Q2: Apr-Jun, Q3: Jul-Aug)
df <- tibble::tibble(
date = seq.Date(as.Date("2020-01-01"), by = "month", length.out = 8),
value = 1:8
)
result <- compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "quarter",
periods = 2,
min_obs = 1
)
expect_length(result, 3)
expect_type(result, "double")
})
test_that("works with period = 'year'", {
df <- tibble::tibble(
date = seq.Date(as.Date("2018-06-01"), by = "quarter", length.out = 12),
value = 1:12
)
result <- compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "year",
periods = 2,
min_obs = 1
)
expect_length(result, 4)
expect_type(result, "double")
})
test_that("works with multiple columns (e.g., regression residuals)", {
set.seed(99)
df <- tibble::tibble(
date = seq.Date(as.Date("2020-01-01"), by = "month", length.out = 24),
y = rnorm(24),
x = rnorm(24)
)
result <- compute_rolling_value(
df,
.f = ~ {
fit <- lm(y ~ x, data = .x)
tail(fit$residuals, 1)
},
period = "month",
periods = 12,
min_obs = 6
)
expect_length(result, 24)
expect_true(all(is.na(result[1:5])))
expect_false(any(is.na(result[12:24])))
})
test_that("single-row data frame works", {
df <- make_monthly_df(1, value = 42)
result <- compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "month",
periods = 1,
min_obs = 1
)
expect_equal(result, 42)
})
test_that("single-row data frame returns NA when min_obs > 1", {
df <- make_monthly_df(1, value = 42)
result <- compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "month",
periods = 3,
min_obs = 2
)
expect_true(is.na(result))
})
test_that("all-NA value column returns all NAs", {
df <- make_monthly_df(5, value = rep(NA_real_, 5))
result <- compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "month",
periods = 3,
min_obs = 1
)
expect_true(all(is.na(result)))
})
test_that("periods = nrow(data) uses full history for last row", {
df <- make_monthly_df(5, value = c(2, 4, 6, 8, 10))
result <- compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "month",
periods = 5,
min_obs = 5
)
# Only the last row has a full 5-period window
expect_true(all(is.na(result[1:4])))
expect_equal(result[5], mean(c(2, 4, 6, 8, 10)))
})
test_that("data_options with non-default date column works", {
df <- tibble::tibble(
month = seq.Date(as.Date("2020-01-01"), by = "month", length.out = 12),
value = seq_len(12)
)
result <- compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "month",
periods = 3,
min_obs = 1,
data_options = data_options(date = "month")
)
expect_type(result, "double")
expect_length(result, 12)
expect_false(any(is.na(result)))
})
test_that("non-default date column produces same results as default", {
dates <- seq.Date(as.Date("2020-01-01"), by = "month", length.out = 6)
vals <- c(10, 20, 30, 40, 50, 60)
df_default <- tibble::tibble(date = dates, value = vals)
df_custom <- tibble::tibble(trade_date = dates, value = vals)
res_default <- compute_rolling_value(
df_default,
.f = ~ mean(.x$value),
period = "month",
periods = 3,
min_obs = 1
)
res_custom <- compute_rolling_value(
df_custom,
.f = ~ mean(.x$value),
period = "month",
periods = 3,
min_obs = 1,
data_options = data_options(date = "trade_date")
)
expect_equal(res_default, res_custom)
})
test_that("errors when mapped date column is absent from data", {
df <- make_monthly_df(6)
expect_error(
compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "month",
periods = 3,
data_options = data_options(date = "month")
),
"month"
)
})
test_that("errors when mapped date column is not of class Date", {
df <- tibble::tibble(
month = as.character(seq.Date(
as.Date("2020-01-01"),
by = "month",
length.out = 6
)),
value = 1:6
)
expect_error(
compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "month",
periods = 3,
data_options = data_options(date = "month")
),
"Date"
)
})
test_that("errors when data_options$date is NULL", {
df <- make_monthly_df(6)
opts <- data_options()
opts$date <- NULL
expect_error(
compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "month",
periods = 3,
data_options = opts
),
"data_options"
)
})
test_that("errors when data_options$date is NA", {
df <- make_monthly_df(6)
opts <- data_options()
opts$date <- NA_character_
expect_error(
compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "month",
periods = 3,
data_options = opts
),
"data_options"
)
})
test_that("errors when data_options$date is not character", {
df <- make_monthly_df(6)
opts <- data_options()
opts$date <- 42
expect_error(
compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "month",
periods = 3,
data_options = opts
),
"data_options"
)
})
test_that("errors when data_options$date has length > 1", {
df <- make_monthly_df(6)
opts <- data_options()
opts$date <- c("date", "month")
expect_error(
compute_rolling_value(
df,
.f = ~ mean(.x$value),
period = "month",
periods = 3,
data_options = opts
),
"data_options"
)
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.