Because all inputs to declare_design() are functions, it’s easy for you to provide custom functions instead of using our defaults.

Custom Population

You can use a custom function to generate your population entirely on your own, too:

my_population_function <- function(N) {
  data.frame(u = rnorm(N))
}

my_population_custom <- declare_population(
  handler = my_population_function, N = 100)

pop_custom <- my_population_custom()

head(pop_custom)
u
-0.35
0.61
0.74
0.48
0.33
-1.78

Custom Potential Outcomes

my_potential_outcomes_function <-
  function(data) {
    data$Y_Z_0 <- with(data, u)
    data$Y_Z_1 <- with(data, 0.25 + u)
    data
  }
my_potential_outcomes_custom <- declare_potential_outcomes(
  handler = my_potential_outcomes_function
)

pop_pos_custom <- my_potential_outcomes_custom(pop_custom)

head(pop_pos_custom[, c("u", "Y_Z_0", "Y_Z_1")])
u Y_Z_0 Y_Z_1
-0.35 -0.35 -0.10
0.61 0.61 0.86
0.74 0.74 0.99
0.48 0.48 0.73
0.33 0.33 0.58
-1.78 -1.78 -1.53

Custom Sampling

Again, you can still use a custom sampling function easily. In this case, the requirement is simply that the function takes population data and returns sampled data. You can also include inclusion weights if you wish in the function (as the default function does).

my_sampling_function <- function(data) {
     data$S <- rbinom(n = nrow(data),
            size = 1,
            prob = 0.1)
     data[data$S == 1, ]
}

my_sampling_custom <- declare_sampling(
  handler = my_sampling_function)

smp_custom <- my_sampling_custom(pop_pos)

nrow(smp_custom)

107

Custom Assignment

my_assignment_function <- function(data) {
  data$Z <- rbinom(n = nrow(data),
         size = 1,
         prob = 0.5)
  data
}
my_assignment_custom <- declare_assignment(
  handler = my_assignment_function)

table(my_assignment_custom(pop_pos)$Z)
0 1
504 496

Custom Estimand

Handlers can also make use of labels, if it’s declared as an argument to the custom handler function.

my_estimand_function <- function(data, label) {
    data.frame(
      estimand_label=label,
      estimand=with(data, median(Y_Z_1 - Y_Z_0))
    )
}
my_estimand_custom <- declare_estimand(
  handler = my_estimand_function, label = "medianTE")

my_estimand_custom(pop_pos)
estimand_label estimand
medianTE 0.82

Custom Estimator

Custom estimators are slightly different - the default handler itself calls a model function. To implement a new handler, you can use the tidy_estimator wrapper to handle the bookkeeping associated with estimator and estimand labeling.

my_estimator_function <- function(data){
  data.frame(est = with(data, mean(Y)))
}

my_estimator_custom <- 
  declare_estimator(handler = tidy_estimator(my_estimator_function), 
                    estimand = my_estimand)

my_estimator_custom(smp)
estimator_label est estimand_label
my_estimator 0.09 ATE