Skip to contents

rdss is the companion package to Research Design in the Social Sciences: Declaration, Diagnosis, and Redesign by Graeme Blair, Alexander Coppock, and Macartan Humphreys (Princeton University Press, 2023). The book declares its designs with DeclareDesign. Most declarations need nothing beyond DeclareDesign, but some call an estimator from another package whose output DeclareDesign cannot read directly, or build a model or inquiry that takes more than a line to write. rdss holds those pieces, along with the datasets the book analyzes and the helpers that format its tables and figures.

This article lists which book sections use which rdss functions, says what each needs installed, and works through one declaration from start to finish.

Installing

Each helper wraps an estimator from another package, and rdss lists those packages as suggested rather than required, so installing rdss does not install all of them. Install the ones the sections you are reading need; the table below names them. Two are not on CRAN:

Functions by book section

Book section rdss functions Also needs
Bayesian formalizations tidy_stan() (deprecated: use broom.mixed::tidy()) rstanarm, broom.mixed
Choosing an answer strategy clingingsmith_etal, foos_etal (data)
Multilevel regression and poststratification post_stratification_helper() lme4, marginaleffects
Process tracing process_tracing_estimator() CausalQueries
Difference-in-differences lag_by_group(), did_multiplegt_tidy() DIDmultiplegt, polars
Regression discontinuity designs rdrobust_helper(), tidy.rdrobust() rdrobust
Conjoint experiments conjoint_assignment(), conjoint_measurement(), conjoint_inquiries(), tidy.amce() cjoint
Experiments over networks get_exposure_AS(), estimator_AS_tidy(), fairfax (data) interference (GitHub), spdep
Meta-analysis rma_helper(), rma_mu_tau() metafor
Discovery using causal forests causal_forest_handler(), best_predictor() grf

The remaining datasets (bonilla_tillery, la_voter_file, and lapop_brazil) are for the book’s exercises. format_num(), add_parens(), make_se_entry(), and make_interval_entry() format numbers for tables; theme_dd(), dd_palette(), and hex_add_alpha() give figures the book’s look.

A worked example: a conjoint experiment

The book’s conjoint declaration (declaration 17.5) shows how the helpers slot into a design. Subjects each see three tasks, and each task pits two candidate profiles against each other. Profiles vary in gender, party, and region, and subjects choose the profile with the higher utility. The utility function encodes the conjecture under study: women gain support only in the North and East, and the Right only in the North and South.

library(rdss)
library(DeclareDesign)
library(cjoint)
library(dplyr)

levels_list <-
  list(
    gender = c("Man", "Woman"),
    party = c("Left", "Right"),
    region = c("North", "South", "East", "West")
  )

conjoint_utility <- function(data) {
  data |>
    mutate(
      U = 0.25 * (gender == "Woman") * (region %in% c("North", "East")) +
        0.5 * (party == "Right") * (region %in% c("North", "South")) + uij
    )
}

Three rdss helpers fill the steps that would otherwise each take a page of code. conjoint_inquiries() computes the true average marginal component effect (AMCE) of every attribute level from the utility function. conjoint_assignment() draws the attributes of every profile at random. conjoint_measurement() applies the utility function and records which profile in each task is chosen. The estimator is cjoint::amce(), which DeclareDesign can use directly because rdss supplies a tidy() method for its output.

declaration_17.5 <-
  declare_model(
    subject = add_level(N = 500),
    task = add_level(N = 3, task = 1:N),
    profile = add_level(N = 2, profile = 1:2, uij = rnorm(N, sd = 1))
  ) +
  declare_inquiry(
    handler = conjoint_inquiries,
    levels_list = levels_list,
    utility_fn = conjoint_utility
  ) +
  declare_assignment(handler = conjoint_assignment, levels_list = levels_list) +
  declare_measurement(handler = conjoint_measurement, utility_fn = conjoint_utility) +
  declare_estimator(
    choice ~ gender + party + region,
    respondent.id = "subject",
    .method = amce
  )

One draw of the data has a row per profile, with the chosen profile marked:

set.seed(343)
draw_data(declaration_17.5) |>
  head()
#> # A tibble: 6 × 9
#>   subject  task profile    uij gender party region      U choice
#>   <chr>   <int>   <int>  <dbl> <fct>  <fct> <fct>   <dbl>  <dbl>
#> 1 001         1       1 -1.40  Woman  Left  North  -1.15       0
#> 2 001         1       2  0.523 Man    Left  North   0.523      1
#> 3 001         2       1  0.142 Man    Right East    0.142      1
#> 4 001         2       2 -0.847 Woman  Left  South  -0.847      0
#> 5 001         3       1 -0.412 Man    Left  South  -0.412      1
#> 6 001         3       2 -1.47  Man    Left  South  -1.47       0

The inquiries are the true AMCEs, one per non-reference level:

draw_estimands(declaration_17.5)
#> # A tibble: 5 × 5
#>   attribute reference level inquiry     estimand
#>   <chr>     <chr>     <chr> <chr>          <dbl>
#> 1 gender    Man       Woman genderWoman   0.0360
#> 2 party     Left      Right partyRight    0.0740
#> 3 region    North     South regionSouth  -0.0300
#> 4 region    North     East  regionEast   -0.0667
#> 5 region    North     West  regionWest   -0.105

Diagnosing the design compares the amce() estimates with those targets over many simulated runs of the study:

set.seed(343)
diagnosis <- diagnose_design(declaration_17.5, sims = 100, bootstrap_sims = FALSE)
diagnosis
#> Research design diagnosis
#>   100 simulations
#>   estimates matched to inquiries on attribute, level (no estimator named an inquiry)
#> 
#>            Design     Inquiry Estimator N Sims Mean Estimand Mean Estimate
#>  declaration_17.5 genderWoman estimator    100          0.03          0.03
#>  declaration_17.5  partyRight estimator    100          0.07          0.07
#>  declaration_17.5  regionEast estimator    100         -0.07         -0.07
#>  declaration_17.5 regionSouth estimator    100         -0.03         -0.04
#>  declaration_17.5  regionWest estimator    100         -0.10         -0.10
#>   Bias SD Estimate RMSE Power Coverage
#>  -0.00        0.02 0.02  0.43     0.90
#>  -0.00        0.02 0.02  0.97     0.91
#>  -0.00        0.03 0.03  0.75     0.91
#>  -0.00        0.03 0.03  0.32     0.93
#>  -0.00        0.02 0.02  0.99     0.95

Bias is near zero for every level. Power is not: the design detects the larger effects almost every time and the smaller ones in well under half of runs, which is the kind of finding that should send a researcher back to N before fielding the study.

The book’s declarations and diagnoses

get_rdss_file() downloads the book’s saved declarations and diagnosis objects from its Dataverse archive, so you can inspect a diagnosis without rerunning a long simulation. Its help page lists the available names.

diagnosis_9.1 <- get_rdss_file("diagnosis_9.1")