Code
timestamp()##------ Wed Apr 23 12:35:20 2025 ------##
timestamp()##------ Wed Apr 23 12:35:20 2025 ------##
This script is used to run the counterfactual marriage models for the analysis. I move the running of these models to a dedicated script because they can take a long time to run.
All models will include the following control variables:
I need to actually create the variables that the models will use. Because these will be applied to a list of market data, I am going to do this variable creation in a function.
add_vars <- function(market) {
#age difference
market$agediff <- market$ageh-market$agew
#birthplace endogamy, preferred specification is:
# 1.25: Birthplace
# 1.5: Both
# 1.75: Both
# first the strict birthplace endogamy
birth_endog <- market$bplh==market$bplw
# now create switches to the USA for 1.5 and 1.75 generation group
bplh_flex <- ifelse(market$immig_genh=="1.75" | market$immig_genh=="1.5",
1, market$bplh)
bplw_flex <- ifelse(market$immig_genw=="1.75" | market$immig_genw=="1.5",
1, market$bplw)
market$birth_endog <- birth_endog | (bplh_flex==bplw_flex)
#language endogamy
# The -1 cases are NEC languages, so we assume non-endogamous
market$language_endog <- ifelse(market$langh<0 | market$langw<0,
FALSE, market$langh==market$langw)
## Education terms ##
#educational hypergamy/hypogamy
market$hypergamy <- market$educh > market$educw
market$hypogamy <- market$educh < market$educw
#educational crossing
market$edcross_hs <- (market$educh>="HS" & market$educw<"HS") |
(market$educw>="HS" & market$educh<"HS")
market$edcross_sc <- (market$educh>="SC" & market$educw<"SC") |
(market$educw>="SC" & market$educh<"SC")
market$edcross_c <- (market$educh>="C" & market$educw<"C") |
(market$educw>="C" & market$educh<"C")
## Racial Exogamy Terms ##
# I need to also control for the race of the alternate partner
market$race_alt <- ifelse(market$sampled=="h", as.character(market$racew),
as.character(market$raceh))
market$race_alt <- factor(market$race_alt, levels=levels(market$racew))
# full racial exogamy blocks, symmetric
market$race_exog_full_sym <- createExogamyTerms(market$raceh, market$racew,
symmetric=TRUE)
# now collapse multiracial/multiracial cases to two cases:
# - multi/multi, no shared ancestry
# - multi/multi, shared ancestry
market$race_exog_short_sym <- ifelse(str_count(market$race_exog_full_sym, "-")==2,
"Multi/Multi, no shared ancestry",
as.character(market$race_exog_full_sym))
# partial shared ancestry coding for multiracial-to-multiracial intermarriage
groups <- str_split(market$race_exog_full_sym, "\\.")
constituent1 <- sapply(groups, function(x) {return(x[1])})
constituent2 <- sapply(groups, function(x) {return(x[2])})
temp <- str_split(constituent1, "-")
constituent1.1 <- sapply(temp, function(x) {return(x[1])})
constituent1.2 <- sapply(temp, function(x) {return(x[2])})
market$race_exog_short_sym <- ifelse(market$race_exog_short_sym=="Multi/Multi, no shared ancestry" &
(str_detect(constituent2, constituent1.1) |
str_detect(constituent2, constituent1.2)),
"Multi/Multi, shared ancestry",
market$race_exog_short_sym)
market$race_exog_short_sym <- relevel(factor(market$race_exog_short_sym),
"Endog")
# now do short coding, but gender asymmetric
market$race_exog_short_asym <- createExogamyTerms(market$raceh, market$racew,
symmetric=FALSE)
# replace with multi/multi coding where needed
market$race_exog_short_asym <- ifelse(str_count(market$race_exog_short_asym, "-")==2,
as.character(market$race_exog_short_sym),
as.character(market$race_exog_short_asym))
market$race_exog_short_asym <- relevel(factor(market$race_exog_short_asym),
"Endog")
return(market)
}mar_markets <- lapply(mar_markets, add_vars)
mar_markets_multi <- lapply(mar_markets_multi, add_vars)Lets start by checking the age difference coding.
summary(mar_markets[[1]]$agediff) Min. 1st Qu. Median Mean 3rd Qu. Max.
-77.000 -15.000 0.000 -1.941 11.000 78.000
summary(subset(mar_markets[[1]], choice)$agediff) Min. 1st Qu. Median Mean 3rd Qu. Max.
-76.000 0.000 2.000 2.248 5.000 73.000
This all looks good. The differences in age are of course much smaller when we look at actual unions rather than all possible unions because age is a very important factor. A few extreme age differences even among actual unions, but who are we to judge?
Next lets look at the birth endogamy variable.
table(mar_markets[[1]]$birth_endog,
mar_markets[[1]]$bplh==mar_markets[[1]]$bplw)
FALSE TRUE
FALSE 860392 0
TRUE 423741 5351747
No cases of birth endogamy coded as zero when actual birthplaces are the same. The cases of birth endogamy zero when actual birthplaces are different should be isolated to the 1.5 and 1.75 generation cases. Lets check that.
mar_markets[[1]] |>
filter(birth_endog & (bplh!=bplw)) |>
tabyl(immig_genh, immig_genw) immig_genh 1st 1.25 1.5 1.75 2nd+
1st 0 0 0 0 0
1.25 0 0 0 0 0
1.5 0 0 3067 3056 91057
1.75 0 0 2955 3647 114517
2nd+ 0 0 89737 115705 0
These results makes sense. The only cases where you can get birth endogamy without actual share birthplace is intermarriage between 1.5, 1.75, and 2nd+ generations. 2nd+/2nd+ has zero because by definition these cases always equal on actual birthplace (i.e. USA).
Now lets check the educational coding.
mar_markets[[1]] |>
group_by(educh, educw) |>
summarize(mean_hypergamy=mean(hypergamy),
mean_hypogamy=mean(hypogamy),
mean_edcross_hs=mean(edcross_hs),
mean_edcross_sc=mean(edcross_sc),
mean_edcross_c=mean(edcross_c)) |>
ungroup() |>
gt()`summarise()` has grouped output by 'educh'. You can override using the
`.groups` argument.
| educh | educw | mean_hypergamy | mean_hypogamy | mean_edcross_hs | mean_edcross_sc | mean_edcross_c |
|---|---|---|---|---|---|---|
| LHS | LHS | 0 | 0 | 0 | 0 | 0 |
| LHS | HS | 0 | 1 | 1 | 0 | 0 |
| LHS | SC | 0 | 1 | 1 | 1 | 0 |
| LHS | C | 0 | 1 | 1 | 1 | 1 |
| HS | LHS | 1 | 0 | 1 | 0 | 0 |
| HS | HS | 0 | 0 | 0 | 0 | 0 |
| HS | SC | 0 | 1 | 0 | 1 | 0 |
| HS | C | 0 | 1 | 0 | 1 | 1 |
| SC | LHS | 1 | 0 | 1 | 1 | 0 |
| SC | HS | 1 | 0 | 0 | 1 | 0 |
| SC | SC | 0 | 0 | 0 | 0 | 0 |
| SC | C | 0 | 1 | 0 | 0 | 1 |
| C | LHS | 1 | 0 | 1 | 1 | 1 |
| C | HS | 1 | 0 | 0 | 1 | 1 |
| C | SC | 1 | 0 | 0 | 0 | 1 |
| C | C | 0 | 0 | 0 | 0 | 0 |
These all look correct.
Finally lets check the shared ancestry coding.
# for non multiracial-to-multiracial marriage it should always be zero
mar_markets[[1]] |>
filter(!(str_detect(raceh, "-") & str_detect(racew, "-"))) |>
summarize(prop_noshared=mean(race_exog_short_sym=="Multi/Multi, no shared ancestry"),
prop_shared=mean(race_exog_short_sym=="Multi/Multi, shared ancestry"))# A tibble: 1 × 2
prop_noshared prop_shared
<dbl> <dbl>
1 0 0
# for multiracial-to-multiracial marriage, lets check each combo
mar_markets[[1]] |>
filter(str_detect(raceh, "-") & str_detect(racew, "-")) |>
group_by(raceh, racew) |>
summarize(prop_noshared=mean(race_exog_short_sym=="Multi/Multi, no shared ancestry"),
prop_shared=mean(race_exog_short_sym=="Multi/Multi, shared ancestry")) |>
ungroup() |>
gt()`summarise()` has grouped output by 'raceh'. You can override using the
`.groups` argument.
| raceh | racew | prop_noshared | prop_shared |
|---|---|---|---|
| White-Black | White-Black | 0 | 0 |
| White-Black | White-Indigenous | 0 | 1 |
| White-Black | White-Asian | 0 | 1 |
| White-Black | Black-Indigenous | 0 | 1 |
| White-Black | Black-Asian | 0 | 1 |
| White-Black | Asian-Indigenous | 1 | 0 |
| White-Indigenous | White-Black | 0 | 1 |
| White-Indigenous | White-Indigenous | 0 | 0 |
| White-Indigenous | White-Asian | 0 | 1 |
| White-Indigenous | Black-Indigenous | 0 | 1 |
| White-Indigenous | Black-Asian | 1 | 0 |
| White-Indigenous | Asian-Indigenous | 0 | 1 |
| White-Asian | White-Black | 0 | 1 |
| White-Asian | White-Indigenous | 0 | 1 |
| White-Asian | White-Asian | 0 | 0 |
| White-Asian | Black-Indigenous | 1 | 0 |
| White-Asian | Black-Asian | 0 | 1 |
| White-Asian | Asian-Indigenous | 0 | 1 |
| Black-Indigenous | White-Black | 0 | 1 |
| Black-Indigenous | White-Indigenous | 0 | 1 |
| Black-Indigenous | White-Asian | 1 | 0 |
| Black-Indigenous | Black-Indigenous | 0 | 0 |
| Black-Indigenous | Black-Asian | 0 | 1 |
| Black-Indigenous | Asian-Indigenous | 0 | 1 |
| Black-Asian | White-Black | 0 | 1 |
| Black-Asian | White-Indigenous | 1 | 0 |
| Black-Asian | White-Asian | 0 | 1 |
| Black-Asian | Black-Indigenous | 0 | 1 |
| Black-Asian | Black-Asian | 0 | 0 |
| Black-Asian | Asian-Indigenous | 0 | 1 |
| Asian-Indigenous | White-Black | 1 | 0 |
| Asian-Indigenous | White-Indigenous | 0 | 1 |
| Asian-Indigenous | White-Asian | 0 | 1 |
| Asian-Indigenous | Black-Indigenous | 0 | 1 |
| Asian-Indigenous | Asian-Indigenous | 0 | 0 |
The variable is behaving as expected.
I am setting up three model formulae here:
short_sym but I allow for gender asymmetry in the monoracial-to-monoracial and multiracial-to-monoracial cases.
# formula base with control variables
formula_base <- formula(choice~agediff+I(agediff^2)+
hypergamy+hypogamy+edcross_hs+edcross_sc+edcross_c+
+birth_endog+language_endog+
strata(group))
# full racial exogamy
formula_full_sym <- update(formula_base, .~.+race_alt+race_exog_full_sym)
# constrained racial exogamy
formula_short_sym <- update(formula_base, .~.+race_alt+race_exog_short_sym)
# constrained but asymmetric racial exogamy
formula_short_asym <- update(formula_base, .~.+race_alt+race_exog_short_asym)First, I run the short models on the full dataset.
model_short_sym <- poolChoiceModel(formula_short_sym, data=mar_markets,
method="efron", parallel=FALSE)Loading required package: parallel
model_short_asym <- poolChoiceModel(formula_short_asym, data=mar_markets,
method="efron", parallel=FALSE)I found that running the full models on the full dataset leads to model convergence problems, so I estimate them only on the multiracial-to-multiracial subset, since that is what they are designed to estimate. I estimate bot the full and short to see how they do.
model_full_sym_multi <- poolChoiceModel(formula_full_sym, data=mar_markets_multi,
method="efron", parallel=FALSE)
model_short_sym_multi <- poolChoiceModel(formula_short_sym, data=mar_markets_multi,
method="efron", parallel=FALSE)save(model_full_sym_multi, model_short_sym_multi,
model_short_sym, model_short_asym,
file=here("data","data_constructed","models.RData"))timestamp()##------ Wed Apr 23 14:08:14 2025 ------##