Chapter 2 MRP with Noncensus Variables
When our sample population is different than our target population, MRP can only adjust for the predictors included in the model. As these are restricted by the variables in the poststratification table, which in turn are limited by the questions asked in the census, the characteristics that we can use for poststratification are quite reduced. This is the reason researchers tend to use simple demographic and geographic variables, which unfortunately do not provide much help if the bias in the survey originates from non-response in voters of a certain party, for instance. As a potential solution, Kastellec et al. (2015) propose extending the postratification table using a survey that contains one or multiple non-census variables that could help adjusting for the differences between the sample and the target population. For instance, if our survey asked for partisanship, we could use the CCES to extend the poststratification table such as that it also contains this variable. The extension is done in two steps. First, we fit a multilevel model in which we try to predict partisanship in the CCES based on the variables available in the census. Second, we use this model to predict, for each cell in the original poststratification table, what proportion of subjects are Democrats, Republicans, or Independents. This extended poststratification table that contains partisanship will allow us to (a) generate MRP estimates that adjust for differential party nonresponse in the original survey; and/or (b) obtain estimates outcome of interest by party.
For this case study we will continue using the previous example of studying support for the right of employers to exclude abortion coverage.
# Read CCES data with the same outcome variable and predictors, but also including
# party. This is done by the clean_cces2 function (not shown, see Github code)
<- read_csv("data_public/chapter1/data/cces18_common_vv.csv.gz")
cces_all_df <- clean_cces2(cces_all_df, remove_nas = TRUE)
cces_all_df
# Read poststratification table
<- read_csv("data_public/chapter1/data/poststrat_df.csv")
poststrat_df
# Read state-level predictors and add them to the CCES and poststratification table
<- read_csv('data_public/chapter1/data/statelevel_predictors.csv')
statelevel_predictors_df <- left_join(cces_all_df, statelevel_predictors_df,
cces_all_df by = "state", keep = FALSE)
<- left_join(poststrat_df, statelevel_predictors_df,
poststrat_df by = "state", keep = FALSE)
2.1 Model-based Extension of the Poststratification Table
As we have described, we start fitting a multilevel model to predict partisanship as a function of the same demographic and geographic variables used in the standard MRP model, which will allow us to predict the proportion of Republicans, Democrats, and Independents in each row of the poststratification table. As there are three levels for partisanship, we use a Bayesian multinomial (i.e. unordered) logistic regression which can be fitted in brms
(currently, rstanarm
does not support multinomial logistic regression).
For this extension step we should use a survey that we think is to some degree representative with respect to the variable that we are trying to include in the poststratification table. In our example, if we extended our census-based poststratification table using a highly non-representative survey with respect to party, we would indeed generated a biased poststratification table and ultimately obtain compromised MRP estimates. In other words, this is our opportunity to bring outside information in order to generate a richer poststratification table that can adjust for potential biases in the main survey, so we need to make sure that the survey we use to extend the poststratification table is trustworthy with respect to the non-census variable.
In this example, we will use a 5,000-person sample of the CCES to extend the poststratification table to include partisanship, which is addressed in the CCES:
Generally speaking, do you think of yourself as a …? (Democrat, Republican, Independent, Other, Not Sure)
For simplicity, we included the few respondents that indicated “Other” or “Not Sure” as Independents.
# Setting seed to arbitrary number for reproducibility
set.seed(1010)
# Taking random sample from the CCES survey
<- cces_all_df %>% sample_n(5000)
cces_unbiased_df
<- brm(party ~ (1 | state) + (1 | eth) + (1 | age) + (1 | educ) + male +
fit_party 1 | male:eth) + (1 | educ:age) + (1 | educ:eth) +
(+ factor(region),
repvote family = "categorical",
data = cces_unbiased_df,
prior = c(prior(normal(0, 5), class = Intercept),
prior(normal(0, 1), class = b),
prior(exponential(0.5), class = sd, dpar = muIndependent),
prior(exponential(0.5), class = sd, dpar = muRepublican)),
control = list(adapt_delta = 0.9, max_treedepth = 10),
seed = 1010)
This model gives us, for each poststratification cell \(j\), an estimate for the proportion of Democrats (\(\hat{\theta}_{{\rm Democrat}, j}\)), Republicans (\(\hat{\theta}_{{\rm Republican}, j}\)), and Independents (\(\hat{\theta}_{{\rm Independent}, j}\)). We can multiply these quantities by the number of people in cell \(j\) to estimate the number of Democrats (\(N_j \: \hat{\theta}_{{\rm Democrat}, j}\)), Republicans (\(N_j \: \hat{\theta}_{{\rm Republican}, j}\)), and Independents (\(N_j \: \hat{\theta}_{{\rm Independent}, j}\)), obtaining an extended poststratification table in which each cell has been expanded into three. That is, if the original poststratification table had \(J\) rows (e.g. 12,000 in our case), the new one will have \(3 J\) (e.g. 36,000). There is, however, a certain complication that must be taken into account. The model-based estimates for the proportion of Democrats, Republicans, and Independents are not single numbers, but several draws from the posterior distribution that capture the uncertainty about these estimates. For instance, if we have 500 draws for \(\hat{\theta}_{{\rm Democrat}, j}\), \(\hat{\theta}_{{\rm Republican}, j}\), and \(\hat{\theta}_{{\rm Independent}, j}\), we can imagine 500 poststratification tables with different numbers for each cell.
# Use posterior_epred to predict partisanship for original poststratification table
<- brms::posterior_epred(fit_party, newdata = poststrat_df, ndraws = 500, transform = TRUE)
pred_mat
# Extend poststratification table
<- poststrat_df[rep(seq_len(nrow(poststrat_df)), each = 3), ]
poststrat_df_threefold $party <- rep(c("Democrat", "Republican", "Independent"), nrow(poststrat_df))
poststrat_df_threefold
# Calculate new numbers for the cells of the new poststratification table. K
# is a matrix containing 36000 rows (one for each cell of the poststratification table)
# and 500 columns (corresponding to the 500 draws).
<- apply(pred_mat, 1, function(x){as.vector(t(x))})
K_theta <- K_theta * rep(poststrat_df$n, each = 3) K
In sum, we started with a poststratification table with 12,000 rows. Here we can see the first three rows:
state | eth | male | age | educ | n |
---|---|---|---|---|---|
AL | White | -0.5 | 18-29 | No HS | 23948 |
AL | White | -0.5 | 18-29 | HS | 59378 |
AL | White | -0.5 | 18-29 | Some college | 104855 |
We have used a model-based approach to include partisanship in this poststratification table, that now has 36,000 rows (again, each row in the original table has been split into three). However, in order to consider the uncertainty in these model-based estimates we have actually built 500 different poststratification tables. Here we show the first 9 rows of one of these 500 poststratification tables:
state | eth | male | age | educ | party | n |
---|---|---|---|---|---|---|
AL | White | -0.5 | 18-29 | No HS | Democrat | 7139.0 |
AL | White | -0.5 | 18-29 | No HS | Republican | 8433.4 |
AL | White | -0.5 | 18-29 | No HS | Independent | 8375.6 |
AL | White | -0.5 | 18-29 | HS | Democrat | 16589.1 |
AL | White | -0.5 | 18-29 | HS | Republican | 21971.6 |
AL | White | -0.5 | 18-29 | HS | Independent | 20817.3 |
AL | White | -0.5 | 18-29 | Some college | Democrat | 32922.8 |
AL | White | -0.5 | 18-29 | Some college | Republican | 33892.6 |
AL | White | -0.5 | 18-29 | Some college | Independent | 38039.6 |
2.2 Adjusting for Nonresponse Bias
We have described how to extend the poststratification table by including partisanship. Now, we will use this poststratification table to adjust for differential party nonresponse.
2.2.1 Setting up example with an artificially nonrepresentative sample
To demostraty how non-census MRP can adjust for party, we will use a survey that is biased with respect to party. As we are already familiar with the CCES dataset, what we are going to do is to take a different sample of 5,000 respondents that simulates a high nonresponse rate among Republicans and, to a lesser degree, Independents.
# Random sample of 5,000 that weights by party
<- cces_all_df %>% sample_n(5000, weight = I((cces_all_df$party=="Democrat")*1 +
cces_biased_df $party=="Independent")*0.75 +
(cces_all_df$party=="Republican")*0.5)) (cces_all_df
Previously, we saw that the national average support for requiring companies to cover abortion in their insurance plans was around 43.4% according to the CCES. Comparatively, this biased sample of the CCES gives an estimate of 38.9%. This is not surprising, as missing Republicans and Independents in the survey should reduce support for the employers’ right to decline abortion coverage.
2.2.2 Standard MRP
We fit a standard MRP (i.e. without including party) on the nonrepresentative sample, using the same model as in the MRP introduction and the non-extended poststratification table.
<- stan_glmer(abortion ~ (1 | state) + (1 | eth) + (1 | age) + (1 | educ) + male +
fit_abortion_standard 1 | male:eth) + (1 | educ:age) + (1 | educ:eth) +
(+ factor(region),
repvote family = binomial(link = "logit"),
data = cces_biased_df,
prior = normal(0, 1, autoscale = TRUE),
prior_covariance = decov(scale = 0.50),
adapt_delta = 0.99,
seed = 1010)
<- rstanarm::posterior_epred(fit_abortion_standard, newdata = poststrat_df, draws = 500)
standard_epred_mat <- (standard_epred_mat %*% poststrat_df$n)/sum(poststrat_df$n) standard_mrp_estimates_vector
## Standard MRP estimate mean, sd: 0.379 0.008
The standard MRP with the nonrepresentative sample gives a national-level estimate of 37.9% (\(\pm\) 0.8%). As this estimate does not consider partisanship, standard MRP is not being able to adjust for the smaller statement support that results from oversampling Democrats.
2.2.3 Non-census MRP with partisanship as a predictor
In the first section we have created a poststratification table that contains partisanship. After doing this, the next step of the non-census MRP approach is to fit the same model as we did in the standard MRP, but also including party as a predictor:
\[ Pr(y_i = 1) = logit^{-1}( \alpha_{\rm s[i]}^{\rm state} + \alpha_{\rm a[i]}^{\rm age} + \alpha_{\rm r[i]}^{\rm eth} + \alpha_{\rm e[i]}^{\rm educ} + \beta^{\rm male} \cdot {\rm Male}_{\rm i} + \alpha_{\rm g[i], r[i]}^{\rm male.eth} + \alpha_{\rm e[i], a[i]}^{\rm educ.age} + \alpha_{\rm e[i], r[i]}^{\rm educ.eth} + \alpha_{\rm p[i]}^{\rm party} ) \]
\[ \begin{align*} \alpha_{\rm s}^{\rm state} &\sim {\rm Normal}(\gamma^0 + \gamma^{\rm south} \cdot {\rm South}_{\rm s} + \gamma^{\rm midwest} \cdot {\rm Midwest}_{\rm s} + \gamma^{\rm west} \cdot {\rm West}_{\rm s} + \gamma^{\rm repvote} \cdot {\rm RepVote}_{\rm s}, \sigma_{\rm state}) \textrm{ for s = 1,...,50}\\ \alpha_{\rm a}^{\rm age} & \sim {\rm Normal}(0,\sigma_{\rm age}) \textrm{ for a = 1,...,6}\\ \alpha_{\rm r}^{\rm eth} & \sim {\rm Normal}(0,\sigma_{\rm eth}) \textrm{ for r = 1,...,4}\\ \alpha_{\rm e}^{\rm educ} & \sim {\rm Normal}(0,\sigma_{\rm educ}) \textrm{ for e = 1,...,5}\\ \alpha_{\rm g,r}^{\rm male.eth} & \sim {\rm Normal}(0,\sigma_{\rm male.eth}) \textrm{ for g = 1,2 and r = 1,...,4}\\ \alpha_{\rm e,a}^{\rm educ.age} & \sim {\rm Normal}(0,\sigma_{\rm educ.age}) \textrm{ for e = 1,...,5 and a = 1,...,6}\\ \alpha_{\rm e,r}^{\rm educ.eth} & \sim {\rm Normal}(0,\sigma_{\rm educ.eth}) \textrm{ for e = 1,...,5 and r = 1,...,4}\\ \alpha_{\rm p}^{\rm party} & \sim {\rm Normal}(0,\sigma_{\rm party}) \textrm{ for p = 1,2,3}\\ \end{align*} \]
<- stan_glmer(abortion ~ (1 | state) + (1 | eth) + (1 | age) + (1 | educ) + male +
fit_abortion_noncensus 1 | male:eth) + (1 | educ:age) + (1 | educ:eth) +
(+ factor(region) + (1 | party),
repvote family = binomial(link = "logit"),
data = cces_biased_df,
prior = normal(0, 1, autoscale = TRUE),
prior_covariance = decov(scale = 0.50),
adapt_delta = 0.99,
seed = 1010)
Using posterior_epred
allows us to estimate abortion coverage support for each of the cells in the extended poststratification table. As we set draws = 500
, we obtain 500 estimates for each cell. In standard MRP, we will weight each the statement support estimates for each poststratification cell by the number of people in that cell according to the model-based estimates obtained in the previous section. However, as in this case the number of people in each cell was estimated with uncertainty, we need to propagate the uncertainty in the first (party prediction) model to the final MRP estimates. Essentially, what we can do is randomly pick one of the 500 statement support estimates for each poststratification cell (i.e. a 36,000 vector) we have just obtained and weight it by one of the 500 poststratification tables that resulted from the first model. Repeating the process for the remaining draws gives us a distribution of 500 MRP estimates for national support that correctly captures the uncertainty in the two models.
# Use posterior_epred to predict stance on abortion insurance coverage for extended poststratification table
<- rstanarm::posterior_epred(fit_abortion_noncensus, newdata = poststrat_df_threefold, draws = 500)
noncensus_epred_mat
# Calculate national MRP estimates propagating uncertainty from the two models
<- colSums(t(noncensus_epred_mat)*K) / sum(K[,1]) noncensus_mrp_estimates_vector
## Noncensus MRP estimate mean, sd: 0.417 0.008
Our national-level estimate for the right to exclude abortion coverage from employer-sponsored insurance resulting from this non-census variable MRP is 41.7% (0.8%). Unsurprisingly, this is much closer to the full (unbiased) 60,000 participant survey (43.4 \(\pm\) 0.2%) than the standard MRP estimate seen above (37.9 \(\pm\) 0.8%). Using an extended poststratification table that contained partisanship allowed us to adjust for differential partisan nonresponse.
Of course, we can also obtain state-level estimates and compare standard MRP with non-census MRP.
# Create empty dataframe
<- data.frame(
state_estimates_df state = state_abb,
standard_mrp_state_estimate = NA,
standard_mrp_state_estimate_se = NA,
noncensus_mrp_state_estimate = NA,
noncensus_mrp_state_estimate_se = NA,
full_cces_state_estimate = NA,
full_cces_state_estimate_se = NA,
n_full = NA
)
# Loop to populate the dataframe
for(i in 1:nrow(state_estimates_df)) {
# Filtering condition for standard_epred_mat (12,000 rows)
<- which(poststrat_df$state == state_estimates_df$state[i])
filtering_condition # Filtering condition for noncensus_epred_mat (36,000 rows)
<- which(poststrat_df_threefold$state == state_estimates_df$state[i])
filtering_condition_threefold
# Standard MRP estimate
<- standard_epred_mat[ ,filtering_condition]
state_standard_epred_mat <- poststrat_df[filtering_condition, ]$n
k_filtered <- state_standard_epred_mat %*% k_filtered / sum(k_filtered)
standard_mrp_state_estimates_vector $standard_mrp_state_estimate[i] <- mean(standard_mrp_state_estimates_vector)
state_estimates_df$standard_mrp_state_estimate_se[i] <- sd(standard_mrp_state_estimates_vector)
state_estimates_df
# Noncensus MRP estimate
<- noncensus_epred_mat[ ,filtering_condition_threefold]
state_noncensus_epred_mat <- K[filtering_condition_threefold, ]
K_filtered <- colSums(t(state_noncensus_epred_mat)*K_filtered) / colSums(K_filtered)
noncensus_mrp_state_estimates_vector $noncensus_mrp_state_estimate[i] <- mean(noncensus_mrp_state_estimates_vector)
state_estimates_df$noncensus_mrp_state_estimate_se[i] <- sd(noncensus_mrp_state_estimates_vector)
state_estimates_df
# Full survey estimate
$full_cces_state_estimate[i] <- mean(filter(cces_all_df, state==state_estimates_df$state[i])$abortion)
state_estimates_df$n_full[i] <- nrow(filter(cces_all_df, state==state_estimates_df$state[i]))
state_estimates_df$full_cces_state_estimate_se[i] <- get_se_bernoulli(state_estimates_df$full_cces_state_estimate[i], state_estimates_df$n_full[i])
state_estimates_df }
In general, we see that the estimates from the standard MRP are upwardly biased with respect to the 60,000 survey estimates. Conversely, the MRP with non-census variables is able to adjust for the differential partisan nonresponse.
2.3 Obtaining Estimates for Non-census Variable Subgroups
Even if we do not suspect that our survey population is different from our target population with respect to a non-census variable, using non-census MRP can allow us to obtain different estimates for the levels of the non-census variable. Here, we obtain and plot support for declining coverage of abortions by state and party within state.
<- cces_biased_df %>% expand(state, party) %>%
subgroup_estimates_df mutate(noncensus_mrp_subgroup_estimate = NA,
noncensus_mrp_subgroup_estimate_se = NA)
for(i in 1:nrow(subgroup_estimates_df)) {
<- which(poststrat_df_threefold$state == subgroup_estimates_df$state[i] &
filtering_condition_threefold $party == subgroup_estimates_df$party[i])
poststrat_df_threefold<- noncensus_epred_mat[ ,filtering_condition_threefold]
subgroup_noncensus_epred_mat <- K[filtering_condition_threefold, ]
K_filtered <- colSums(t(subgroup_noncensus_epred_mat)*K_filtered) / colSums(K_filtered)
noncensus_mrp_subgroup_estimates_vector
$noncensus_mrp_subgroup_estimate[i] <- mean(noncensus_mrp_subgroup_estimates_vector)
subgroup_estimates_df$noncensus_mrp_subgroup_estimate_se[i] <- sd(noncensus_mrp_subgroup_estimates_vector)
subgroup_estimates_df }