Nothing
# Tests for panel data utility functions
# Staggered panel setup shared across blocks
make_staggered_panel <- function(n = 30, seed = 42) {
set.seed(seed)
id <- rep(seq_len(n), each = 4)
t <- rep(1:4, n)
y <- rnorm(n * 4)
g <- rep(sample(c(0, 2, 3), n, replace = TRUE), each = 4)
treat <- as.integer(t >= g & g > 0)
data.frame(id = id, t = t, y = y, treat = treat, group = g)
}
# get_group and check_staggered: basic structural checks
test_that("get_group and check_staggered work correctly", {
dta <- make_staggered_panel()
dta$group_calc <- get_group(dta, idname = "id", tname = "t", treatname = "treat")
# group should match original group assignment
expect_equal(dta$group_calc, dta$group)
# all group values should be 0, 2, or 3
expect_true(all(dta$group_calc %in% c(0, 2, 3)))
# treatment reversal (1->0) is not staggered
dta_rev <- data.frame(id = c(1, 1, 2, 2), t = c(1, 2, 1, 2), treat = c(0, 1, 1, 0))
expect_false(check_staggered(dta_rev, idname = "id", treatname = "treat"))
})
# get_lagYi and get_first_difference: each unit should have exactly one NA (first period)
test_that("get_lagYi and get_first_difference produce correct NA pattern", {
dta <- make_staggered_panel()
lag_y <- get_lagYi(dta, idname = "id", yname = "y", tname = "t")
fd <- get_first_difference(dta, idname = "id", yname = "y", tname = "t")
expect_equal(sum(is.na(lag_y)), 30L)
expect_equal(sum(is.na(fd)), 30L)
expect_true(is.numeric(lag_y))
expect_true(is.numeric(fd))
})
# panel2cs2 and time_invariant_to_panel: shape and column checks
test_that("panel2cs2 and time_invariant_to_panel return correct structure", {
set.seed(42)
id <- rep(seq_len(20), each = 2)
t <- rep(1:2, 20)
y <- rnorm(40)
dta2 <- data.frame(id = id, t = t, y = y)
out <- panel2cs2(dta2, yname = "y", idname = "id", tname = "t")
expect_equal(nrow(out), 20L)
expect_true(all(c(".y0", ".y1", ".dy") %in% names(out)))
expect_equal(out$.dy, out$.y1 - out$.y0)
# time_invariant_to_panel repeats each unit value for all its periods
x_unit <- rnorm(20)
x_panel <- time_invariant_to_panel(x_unit, dta2, idname = "id")
expect_equal(length(x_panel), 40L)
expect_equal(length(unique(x_panel[1:2])), 1L)
})
# get_lagYi lags along tname, not along row order, and returns values aligned
# with the rows of the input (this is what dplyr::lag(order_by=) provided)
test_that("get_lagYi respects time ordering and input row order", {
dta <- data.frame(
id = c(1, 1, 1, 2, 2, 2),
t = c(3, 1, 2, 2, 3, 1),
y = c(30, 10, 20, 200, 300, 100)
)
expect_equal(get_lagYi(dta, "id", "y", "t"), c(20, NA, 10, 100, 200, NA))
expect_equal(get_lagYi(dta, "id", "y", "t", nlags = 2), c(10, NA, NA, NA, 100, NA))
expect_equal(get_first_difference(dta, "id", "y", "t"), c(10, NA, 10, 100, 100, NA))
})
# the unit-level getters expand one value per unit back out to nT
test_that("unit-level getters expand to one value per row", {
dta <- data.frame(
id = rep(1:2, each = 3),
t = rep(1:3, 2),
y = c(1, 2, 3, 10, 20, 30),
group = rep(c(0, 3), each = 3)
)
expect_equal(get_Yi1(dta, "id", "y", "t", "group"), c(1, 1, 1, 10, 10, 10))
expect_equal(get_Yit(dta, 2, "id", "y", "t"), c(2, 2, 2, 20, 20, 20))
expect_equal(get_Yibar(dta, "id", "y"), c(2, 2, 2, 20, 20, 20))
# group 0 units fall back to the last period / overall mean
expect_equal(get_YiGmin1(dta, "id", "y", "t", "group"), c(3, 3, 3, 20, 20, 20))
expect_equal(get_Yibar_pre(dta, "id", "y", "t", "group"), c(2, 2, 2, 15, 15, 15))
})
# none of these should modify the caller's data, including data.table input
test_that("panel getters do not modify their input", {
dta <- data.table::data.table(
id = rep(1:2, each = 2), t = rep(1:2, 2), y = c(1, 2, 3, 4), treat = c(0, 1, 0, 0)
)
before <- data.table::copy(dta)
invisible(get_lagYi(dta, "id", "y", "t"))
invisible(get_first_difference(dta, "id", "y", "t"))
invisible(get_group(dta, "id", "t", "treat"))
invisible(get_Yibar(dta, "id", "y"))
expect_equal(dta, before)
})
# get_principal_components: shape and argument handling
test_that("get_principal_components returns the expected shape", {
set.seed(42)
n <- 20
nperiods <- 4
dta <- data.frame(
id = rep(seq_len(n), each = nperiods),
t = rep(seq_len(nperiods), n),
x1 = rnorm(n * nperiods),
x2 = rnorm(n * nperiods)
)
pcs <- get_principal_components(~ x1 + x2, dta, idname = "id", tname = "t")
# one column per (variable, period) pair, one row per observation
expect_equal(dim(pcs), c(n * nperiods, 2L * nperiods))
expect_equal(names(pcs), c(paste0("x1_PC", 1:4), paste0("x2_PC", 1:4)))
# principal components are unit-specific, so constant within a unit
expect_equal(length(unique(pcs$x1_PC1[1:nperiods])), 1L)
wide <- get_principal_components(~ x1 + x2, dta, "id", "t", ret_wide = TRUE, ret_id = TRUE)
expect_equal(nrow(wide), n)
expect_equal(wide$.id, sort(unique(dta$id)))
two <- get_principal_components(~ x1 + x2, dta, "id", "t", n_components = 2)
expect_equal(names(two), c(paste0("x1_PC", 1:2), paste0("x2_PC", 1:2)))
})
# dcast() (used internally) sorts rows by id, which can differ from the order
# units first appear in the data; the long-format output must realign to each
# row's own unit rather than assume that order, or units' PCs get swapped.
test_that("get_principal_components long format aligns by id when units are unsorted", {
set.seed(7)
n <- 6
nperiods <- 3
dta <- data.frame(
id = rep(sample(seq_len(n)), each = nperiods),
t = rep(seq_len(nperiods), n),
x1 = rnorm(n * nperiods)
)
wide <- get_principal_components(~x1, dta, "id", "t", ret_wide = TRUE, ret_id = TRUE)
long <- get_principal_components(~x1, dta, "id", "t")
expected <- wide$x1_PC1[match(dta$id, wide$.id)]
expect_equal(long$x1_PC1, expected)
})
# The vectorised getters must stay in step with the exported *_inner functions,
# which remain the reference definition of what each one computes. Units are
# deliberately left in an unsorted, first-appearance row order (not sorted by
# id) so this also catches getters that silently assume sorted input.
test_that("vectorised getters agree with the *_inner functions", {
by_unit <- function(df, fun) {
vals <- sapply(split(df, df$id), fun)
unname(vals[as.character(df$id)])
}
set.seed(11)
for (i in 1:25) {
n <- sample(2:8, 1)
nper <- sample(2:5, 1)
ids <- sample(seq_len(3 * n), n)
dta <- expand.grid(t = seq_len(nper), id = ids)[, c("id", "t")]
dta$y <- round(rnorm(nrow(dta)), 3)
# groups include 0 (never treated) and 1 (treated from the first period,
# so no pre-treatment periods at all)
gs <- sample(c(0, seq_len(nper)), n, replace = TRUE)
dta$group <- gs[match(dta$id, ids)]
dta$treat <- as.integer(dta$t >= dta$group & dta$group > 0)
expect_equal(
get_group(dta, "id", "t", "treat"),
by_unit(dta, function(d) get_group_inner(d, "t", "treat"))
)
expect_equal(
get_Yi1(dta, "id", "y", "t", "group"),
by_unit(dta, function(d) get_Yi1_inner(d, "y", "t", "group"))
)
expect_equal(
get_Yibar(dta, "id", "y"),
by_unit(dta, function(d) get_Yibar_inner(d, "y"))
)
expect_equal(
get_YiGmin1(dta, "id", "y", "t", "group"),
by_unit(dta, function(d) get_YiGmin1_inner(d, "y", "t", "group"))
)
expect_equal(
get_Yibar_pre(dta, "id", "y", "t", "group"),
by_unit(dta, function(d) get_Yibar_pre_inner(d, "y", "t", "group"))
)
expect_equal(
get_Yit(dta, 1, "id", "y", "t"),
by_unit(dta, function(d) get_Yit_inner(d, 1, "y", "t"))
)
expect_equal(
check_staggered(dta, "id", "treat"),
all(by_unit(dta, function(d) check_staggered_inner(d, "treat")))
)
}
})
# a unit that is never observed in period tp now yields NA rather than
# silently shortening the returned vector
test_that("get_Yit returns NA for units not observed in period tp", {
dta <- data.frame(id = c(1, 1, 2), t = c(1, 2, 1), y = c(10, 20, 30))
expect_equal(get_Yit(dta, 2, "id", "y", "t"), c(20, 20, NA))
})
# check_staggered asks whether treatment is absorbing: units may switch on at
# different times, but no unit may ever switch back off
test_that("check_staggered identifies absorbing treatment", {
panel <- function(treat) {
data.frame(id = rep(1:2, each = 4), t = rep(1:4, 2), treat = treat)
}
# units adopting in different periods is the canonical staggered design
expect_true(check_staggered(panel(c(0, 1, 1, 1, 0, 0, 1, 1)), "id", "treat"))
# common adoption timing is still absorbing
expect_true(check_staggered(panel(c(0, 1, 1, 1, 0, 1, 1, 1)), "id", "treat"))
# so are always-treated and never-treated units
expect_true(check_staggered(panel(c(1, 1, 1, 1, 0, 0, 0, 0)), "id", "treat"))
# a single unit switching back off is enough to fail
expect_false(check_staggered(panel(c(0, 1, 0, 1, 0, 0, 1, 1)), "id", "treat"))
expect_false(check_staggered(panel(c(0, 1, 1, 1, 0, 1, 1, 0)), "id", "treat"))
})
# without tname the rows are assumed to be in time order; with it they are
# sorted first, so a scrambled panel is still read correctly
test_that("check_staggered sorts by tname when it is supplied", {
dta <- data.frame(id = rep(1:2, each = 3), t = rep(1:3, 2),
treat = c(0, 0, 1, 0, 1, 1))
scrambled <- dta[c(3, 1, 2, 5, 6, 4), ]
expect_true(check_staggered(dta, "id", "treat"))
expect_true(check_staggered(scrambled, "id", "treat", tname = "t"))
# rows out of time order look like a reversal if tname is not given
expect_false(check_staggered(scrambled, "id", "treat"))
# the inner function behaves the same way on a single unit
one <- scrambled[scrambled$id == 1, ]
expect_true(check_staggered_inner(one, "treat", tname = "t"))
expect_false(check_staggered_inner(one, "treat"))
})
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.