5 Satisfaction Within Multiracial Neighborhoods

5.1 Distribution of respondents across neighborhoods

5.1.1 Median satisfaction level in tracts

To prevent large values on intercepts, I changed the reference value of the sample_tract variable to the tract with the median level of satisfaction among neighborhoods. When calculating the median, include only those tracts with at least two respondents.

mutrtall <- dcas16 %>%
    filter(neighborhood=="Global Neighborhood") %>%
    group_by(sample_tract) %>%
    summarize(
        N = n(), 
        tractsat = mean(satisfied)
        ) %>%
    ungroup()

mutrt <- mutrtall %>%
    filter(N>1) %>%
    arrange(tractsat)

medtrt <- mutrt$sample_tract[round(nrow(mutrt)/2)] %>% as.character()
medtrtval <- mutrt$tractsat[mutrt$sample_tract==medtrt]*100
dcas16svy <- update(
    dcas16svy, sample_tract = relevel(sample_tract, ref=medtrt)
)
## Add following to allow MIpredict to predict values at intercept
dcas16$sample_tract <- relevel(dcas16$sample_tract, ref=medtrt)

onlyone <- nrow(mutrtall) - nrow(mutrt)
onlyonelist <- mutrtall$sample_tract[mutrtall$N==1]
dcas16svy <- subset(dcas16svy, !(sample_tract %in% onlyonelist))
dcas16svy <- update(
    dcas16svy, sample_tract = relevel(factor(sample_tract), ref=medtrt))

The reference level for sample_tract was set to tract 24031700817, in which 75% of residents reported being satisfied.

5.1.2 Count of respondents across neighborhoods

Plot the distribution of respondents per tract. There are 9 tracts in the sample with a single respondent. There are 6.5 respondents per tract on average (median=5) with a maximum of 20 respondents per tract. The sample size for the multiracial neighborhood resident sample used for analysis is 632.

5.1.3 Distribution of respondents by race

The analyses below consider the distribution of respondents by race across neighborhoods. Since the data include respondents, I do not need imputed values. I pull the data from the first imputed dataset. From that dataset, I calculate the percentage and sum of respondents for each race across neighborhoods.

## Summarize respondents across neighborhoods
wndta <- dcas16svy$designs[[1]]$variables
wncnt <- wndta %>%
    group_by(sample_tract, raceeth) %>%
    count() %>%
    group_by(sample_tract) %>%
    mutate(
        pct = n / sum(n),
        N = sum(n)
    ) %>%
    ungroup() %>%
    pivot_wider(
        id_cols = c(sample_tract, N), names_from = raceeth, values_from = pct
    ) %>%
    mutate(across(-1, ~replace_na(., 0))) %>%
    select(sample_tract, N, asian, black, latino, white) %>%
    filter(N > 1)

## Count number of neighborhoods with respondents of all four racial groups
all_pres <- wncnt %>%
    mutate(allpres = if_all(-1:-2, ~ . > 0)) %>%
    summarise(allpres = sum(allpres)) %>%
    pull()

There of the 103 neighborhoods with two or more respondents, 26 neighborhoods (25.2 percent) contain respondents who identify across all four racial groups. Table 5.1 contains descriptive statistics of the distribution of the racial identities of respondents across neighborhoods and Table 5.2 contains the number of respondents who live in neighborhoods where all other respondents share the same racial identity.

## Respondent distribution across neighborhoods
bind_cols(
    tibble(Statistic = 
               c("Minimum", "First Quart.", "Median", "Mean", "Third Quart.",
                 "Maximum",
                 "All of race",
                 "None of race")
    ),
    bind_rows(
        sapply(wncnt[, -1:-2], summary)  %>% as_tibble() %>% setNames(racelabs),
    
        ## Number of neighborhoods with all of each race
        sapply(wncnt[, -1:-2], function(x) sum(x == 1)) %>% setNames(racelabs),
        
        ## Number of neighborhoods with none of each race
        sapply(wncnt[, -1:-2], function(x) sum(x == 0)) %>% setNames(racelabs)
    )
) %>%
    as_huxtable() %>%
    set_bold(1, everywhere, TRUE) %>%
    set_number_format(2:7, -1, "%.2f") %>%
    set_align(everywhere, -1, "right") %>%
    set_caption(paste("Descriptive statistics of distribution of respondents",
                      "across neighborhoods by race")) %>%
    set_label("tab:nhood-desc")
Table 5.1: Descriptive statistics of distribution of respondents across neighborhoods by race
StatisticAsianBlackLatinoWhite
Minimum0.000.000.000.00
First Quart.0.050.000.000.25
Median0.290.000.100.39
Mean0.290.140.160.41
Third Quart.0.460.250.250.56
Maximum1.000.750.751.00
All of race3005
None of race26524214
## Count of respondents living in neighborhood with only same-race neighbors
wndta %>%
    left_join(wncnt, by = "sample_tract") %>%
    filter(if_any(c(asian, black, latino, white), ~ . == 1)) %>%
    summarize(across(c(asian, black, latino, white), sum)) %>%
    rename_with(~sub("^(\\w)", "\\U\\1", ., perl = TRUE)) %>%
    as_huxtable() %>%
    set_caption(paste(
        "Number of respondents living in neighborhood with same-race", 
        "neighbors, by race"
    )) %>%
    set_label("tab:nhood-only")
Table 5.2: Number of respondents living in neighborhood with same-race neighbors, by race
AsianBlackLatinoWhite
80016

5.2 Descriptive analysis of neighborhood satisfaction

Calculate the unconditional percentage of respondents who are satisfied in their neighborhood by race.

racedfs <- list(
    dcas16svy,
    subset(dcas16svy, raceeth=="asian"),
    subset(dcas16svy, raceeth=="black"),
    subset(dcas16svy, raceeth=="latino"),
    subset(dcas16svy, raceeth=="white")
)
descwn <- lapply(racedfs, function(df){
    res <- with(df, svymean(~satisfied)) %>% MIcombine()
    n <- nrow(df$designs$imp1)
    list(mean16=coef(res)*100, se16=sqrt(vcov(res)[1,1])*100, n16 = n)
}) %>%
    bind_rows() %>%
    mutate(race = racelevs)

Create a 4x4 matrix where each cell represents the difference in satisfaction percentages between the racial group listed in the column and the racial group listed in the row. No values are entered for the bottom triangle or the diagonal.

## Create matrix of differences
diffmat <- matrix(rep(NA_real_, 16), nrow=4)
musat <- descwn$mean16[1]
muraces <- descwn$mean16[-1]
for(i in 1:3) {
    start <- i+1
    for(j in start:4) {
        diffmat[i, j] <- muraces[j] - muraces[i]
    }
}
colnames(diffmat) <- racelevs[-1]

Add an additional row to the top of the matrix that contains the percent of each group that reports being satisfied in their neighborhoods. Add an additional column to the left that contains a) the overall percent of residents who are satisfied living in multiracial neighborhoods and b) the difference between the percent of the racial group listed in the row and the overall mean. This table is saved to tables/within_descriptives.tex.

descwn_tbl <- bind_rows(
    descwn$mean16[-1] %>% set_names(racelevs[-1]), ## Percent satisfied in group
    as_tibble(diffmat)                             ## Diff. matrix
) %>%
    add_column(all = c(musat, muraces - musat), .before = 1)
names(descwn_tbl) <- c("All", "Asian", "Black", "Latino", "White")
descwn_tbl<- descwn_tbl %>%
    mutate_all(round, 1) %>%
    huxtable() %>%
    # insert_row(c("All", "Asian", "Black", "Latino", "White")) %>%
    insert_row(rep(NA, 5), after=2) %>%
    insert_column(c("Group", "Percent satisfied", "Difference", 
                    "Asian", "Black", "Latino", "White")) %>%
    set_number_format(everywhere, everywhere, "%5.1f") 
descwn_tbl
Table 5.3:
GroupAllAsianBlackLatinoWhite
Percent satisfied 71.3 71.7 68.8 75.0 70.0
Difference          
Asian 0.4   -3.0 3.2 -1.8
Black -2.5     6.2 1.2
Latino 3.7       -5.0
White -1.3        

5.3 Regression anaysis of neighborhood satisfaction

Follow a similar process of model development as in the separate multiracial neighborhood and complete DC-area datasets. Add a fourth model that removes race from the model to examine whether removing the race variables improves the model fit. Include a neighborhood fixed effect in all models so that the coefficients represent the difference for residents living in the same neighborhood.

  1. Race and neighborhood fixed effects
  2. Race, individual demographic characteristics (age, foreign-born, gender, children present, marital status, and education), and neighborhood fixed effects
  3. Race, individual demographic characteristics, neighborhood experience (home ownership, years in the neighborhood, and perceived neighborhood size), and neighborhood fixed effects
  4. No race, only individual demographic characteristics, neighborhood experience, and neighborhood fixed effects
m1 <- "raceeth + sample_tract"
m2 <- paste(m1, "+ agec + forbornc + manc + kidsc + marriedc",
            " + educ1c + educ3c + educ4c + educ5c")
m3 <- paste(m2, "+ ownc + nhdyrsc + nhdsize2c + nhdsize3c")
m4 <- sub("raceeth \\+ ", "", m3)

Estimate model parameters for each of the four models. Record the parameter estimates and standard errors in a table stored in file tables/within.tex.

## Note: "non-integer #successes" warnings suppressed
m1wn <- with(dcas16svy, svyglm(
    as.formula(paste('satisfied ~', m1)), family=binomial)
    )

m2wn <- with(dcas16svy, svyglm(
    as.formula(paste('satisfied ~', m2)), family=binomial)
    )

m3wn <- with(dcas16svy, svyglm(
    as.formula(paste('satisfied ~', m3)), family=binomial)
    )

m4wn <- with(dcas16svy, svyglm(
    as.formula(paste('satisfied ~', m4)), family=binomial)
    )

wn_tbl <- report_models(
    MIcombine_aic(m1wn), MIcombine_aic(m3wn), MIcombine_aic(m4wn),
    caption = paste(
        "Logistic regression coefficients and standard errors predicted",
        "from models estimating neighborhood satisfaction among residents", 
        "of mulitracial neighborhoods"
        ),
    label = "tab:within",
    reglabels = regression_labels,
    use.headers = TRUE
)
statrow <- nrow(wn_tbl)-3
wn_tbl <- insert_row(
    wn_tbl, c("Tract fixed effects", rep("X", 3)),after=statrow) %>%
    set_top_border(statrow+1, everywhere, TRUE) %>%
    set_top_border(statrow+2, everywhere, FALSE) %>% 
    set_top_padding(0) %>%
    set_bottom_padding(0)
cat(
    sub("\\(\\\\#(tab:.+?)\\)", "\\\\label{\\1\\}", to_latex(wn_tbl)),
    file="tables/within.tex"
)
wn_tbl
Table 5.4: Logistic regression coefficients and standard errors predicted from models estimating neighborhood satisfaction among residents of mulitracial neighborhoods
(1)(2)(3)
(Intercept)0.533 0.512   0.838   
(1.051)(1.089)  (1.144)  
Race                   
Asian-0.050 0.041          
(0.384)(0.487)         
Black0.364 0.450          
(0.400)(0.450)         
Latinx0.207 0.507          
(0.424)(0.539)         
Demographics                   
Age     0.003   0.003   
     (0.012)  (0.012)  
Foreign Born     -0.106   -0.057   
     (0.446)  (0.348)  
Male     0.219   0.190   
     (0.297)  (0.298)  
Children Present     -0.659   -0.568   
     (0.376)  (0.370)  
Married     0.415   0.326   
     (0.315)  (0.308)  
Socioeconomic                   
<H.S.     -1.824 * -1.802 * 
     (0.863)  (0.881)  
Some college, no B.A.     -1.031   -1.030   
     (0.635)  (0.632)  
B.A.     -0.976   -1.050   
     (0.608)  (0.603)  
M.A.+     -1.362 * -1.434 * 
     (0.587)  (0.578)  
Neighborhood perceptions                   
Home owner     0.734   0.719   
     (0.396)  (0.398)  
Years in neighborhood     -0.019   -0.020   
     (0.017)  (0.017)  
10-50 blocks     1.030 **1.070 **
     (0.377)  (0.358)  
>50 blocks     0.320   0.457   
     (0.603)  (0.580)  
Tract fixed effectsX     X       X       
N 632      632        632       
AIC663.145 647.778   642.032   
*** p < 0.001; ** p < 0.01; * p < 0.05.

5.4 Marginal Effect of Race on Neighborhood Satisfaction

Calculate the marginal effect of race on neighborhood satisfaction using white residents as the reference group. The function MImargins() is defined in the file marginal_effects.R. The function combines the standard errors of marginal effects from the multiply imputed datasets using Rubin’s rules (following this algorithm).

## Calculate partial effects of race
m1wn_mfx <- MImargins(m1wn, param="margins")
m3wn_mfx <- MImargins(m3wn, param="margins")

## Record marginal effects and standard errors in table and report
mfxwn <- inner_join(m1wn_mfx, m3wn_mfx, by="raceeth", 
                    suffix=c(".Unadjusted", ".Adjusted")) 

mfxwn_tbl <- mfxwn %>%
    mutate_at(vars(starts_with("val")), ~paste0(" ", round(.,3), " ")) %>%
    mutate_at(vars(starts_with("SE")), ~paste0("(", round(.,3), ")"))
kable(mfxwn_tbl, digits=3)
raceeth val.Unadjusted SE.Unadjusted val.Adjusted SE.Adjusted
asian -0.008 (0.059) 0.006 (0.068)
black 0.053 (0.058) 0.06 (0.059)
latino 0.031 (0.063) 0.067 (0.069)
## Plot marginal effects and standard errors
mfxwn_long <- bind_rows(m1wn_mfx, m3wn_mfx) %>%
    mutate(
        model=relevel(factor(rep(c("Unadjusted", "Adjusted"), each=3)),
                         ref="Unadjusted"),
        raceeth = sub("^(\\w)", "\\U\\1", raceeth, perl = TRUE)
    )
dodge_wd <- 0.25
wn_mfx_plt <- ggplot(mfxwn_long, 
       aes(x=raceeth, y=val, ymin=val-1.96*SE, ymax=val+1.96*SE,
           group=model, color=model, shape=model)) +
    geom_point(position=position_dodge(width=dodge_wd), size=2.15) +
    geom_linerange(size=.5, position=position_dodge(dodge_wd)) +
    geom_hline(yintercept=0, color="#666666") +
    scale_color_manual(values = c("#888888", "#222222")) +
    scale_x_discrete(
        labels=gsub("^(\\w)", "\\U\\1", mfxwn_long$raceeth, perl=TRUE)
    ) +
    scale_y_continuous(limits=c(-.21, .21)) +
    # scale_shape_discrete(labels=c("No controls", "With controls")) +
    labs(
        x = NULL,
        y = "Marginal effect",
        shape = NULL, color = NULL
    ) +
    theme_minimal() +
    theme(
        legend.position = "bottom",
        panel.grid.major.x = element_blank()
    )

wn_mfx_plt_cap <- paste(
    "Marginal effects of race on being satisfied in multiracial",
    "neighborhoods compared to white residents of multiracial neighborhoods"
)
Marginal effects of race on being satisfied in multiracial neighborhoods compared to white residents of multiracial neighborhoods

Figure 5.1: Marginal effects of race on being satisfied in multiracial neighborhoods compared to white residents of multiracial neighborhoods

5.5 Partial Predicted Probabilities

m3wn_prd <- MImargins(m3wn, param="predicted")
kable(m3wn_prd)
raceeth val SE
white 0.6832765 0.0365181
asian 0.6889261 0.0477260
black 0.7434700 0.0424701
latino 0.7505796 0.0509451

5.6 Wald Statistics of Model Fit

Report the value of Wald tests measuring the change in model fit for the full model (Model 3) compared to a model without race covariates (Model 4).

racewn_wald <- lapply(m3wn, regTermTest, test.terms=~raceeth, df=NULL)
racewn_wald_tbl <- tibble(
    val = c(
        sapply(racewn_wald, function(x) x$Ftest),
        sapply(racewn_wald, function(x) x$p)
    ),
    stat = rep(c("F test", "p"), each=5),
    i = rep(1:5,2)
) %>%
    pivot_wider(id_cols = "stat", values_from="val", names_from="i", 
                names_prefix="imp")

racewn_wald_tbl$mean <- apply(racewn_wald_tbl[, 2:6], 1, mean)
racewn_wald_tbl$min <- apply(racewn_wald_tbl[, 2:6], 1, min)
racewn_wald_tbl$max <- apply(racewn_wald_tbl[, 2:6], 1, max)
kable(racewn_wald_tbl, digits=3)
stat imp1 imp2 imp3 imp4 imp5 mean min max
F test 0.633 0.622 0.563 0.631 0.588 0.607 0.563 0.633
p 0.594 0.601 0.640 0.595 0.623 0.611 0.594 0.640