Chapter 3 data exploration

Author

R. Luttinen

Step 1: Read in individual-level data and GPS cluster data and merge together

library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.2.1     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   4.0.3     ✔ tibble    3.2.1
✔ lubridate 1.9.4     ✔ tidyr     1.3.2
✔ purrr     1.2.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(janitor)

Attaching package: 'janitor'

The following objects are masked from 'package:stats':

    chisq.test, fisher.test
library(ipumsr)

#person-period dataset


pmauglong <- read_ipums_micro(
  ddi = "C:/Users/Rebecca/Downloads/pma_00029.xml",
  data = "C:/Users/Rebecca/Downloads/pma_00029.dat.gz")
Use of data from IPUMS PMA is subject to conditions including that users should cite the data appropriately. Use command `ipums_conditions()` for more details.
#read in spatial data

library(sf)
Linking to GEOS 3.13.1, GDAL 3.10.2, PROJ 9.5.1; sf_use_s2() is TRUE
UG_PMA_GPS<-read_sf("C:/Users/Rebecca/Downloads/PMA_UG_GPS_v2_06July2022/UGANDA/PMA_UG_GPS_v2_06July2022.csv")


#read in boundary data

UG_boundary<-read_sf("C:/Users/Rebecca/Downloads/geoug/geoug.shp")


UG_boundary_4<-read_sf("C:/Users/Rebecca/Downloads/geouggen/geouggen.shp")

UG_sub_boundary<-read_sf("C:/Users/Rebecca/Downloads/uganda-adm-boundaries/UGANDA BOUNDARIES SHAPEFILES AS OF 17 08 2018/COUNTIES_2018_UTM_36N.shp")


pmaug2022<-pmauglong
#%>%
  #filter(YEAR==2021)

UG_PMA_GPS_2022<-UG_PMA_GPS
#%>%
#filter(PMAYEAR==2021)

UG_PMA_GPS_2022<-UG_PMA_GPS%>%
  rename('EAID'='EA_ID')


pmaug2022wgps<-merge(pmaug2022, UG_PMA_GPS_2022, by=c("EAID"))

Step 2. Recoding/ building summary scale, data cleaning

#marital status 

pmaug2022wgps<-pmaug2022wgps%>%
  mutate(MARSTAT=as.factor(MARSTAT))%>%
  mutate(maritalstatus= recode(MARSTAT, '10' = "never married" ,'20' = "married or living together", '21'= "currently married", '22'= "currently living with partner", '31'="formerly in union",'32'='widow or widower',.default = NA_character_))

tabyl(pmaug2022wgps, maritalstatus)
                 maritalstatus    n      percent valid_percent
                 never married 3506 0.2472845253    0.24735431
             currently married 3416 0.2409366624    0.24100466
 currently living with partner 4980 0.3512484130    0.35134754
             formerly in union 1947 0.1373254338    0.13736419
              widow or widower  325 0.0229228382    0.02292931
                          <NA>    4 0.0002821272            NA
#age-group

pmaug2022wgps<-pmaug2022wgps %>%
  mutate(agegroup = case_when(
    AGE >= 15 & AGE <= 19 ~ "15-19",
    AGE >= 20 & AGE <= 24 ~ "20-24",
    AGE >= 25 & AGE <= 29 ~ "25-29",
    AGE >= 30 & AGE <= 34 ~ "30-34",
    AGE >= 35 & AGE <= 39 ~ "35-39",
    AGE >= 40 & AGE <= 44 ~ "40-44",
    AGE >= 45 & AGE <= 49 ~ "45-49"
  ))


pmaug2022wgps$age_z_score <- scale(pmaug2022wgps$AGE)



pmaug2022wgps<-pmaug2022wgps%>%
  filter(BIRTHEVENT<90)

pmaug2022wgps<-pmaug2022wgps %>%
  mutate(AGE=as.numeric(AGE))


pmaug2022wgps<-pmaug2022wgps %>%
  mutate(URBAN=as.factor(URBAN))

#contraceptive use

pmaug2022wgps<-pmaug2022wgps%>%
  mutate(MCP=as.factor(MCP))%>%
  mutate(usingmoderncon= recode(MCP, '1' = "yes" ,'0' = "no",.default = NA_character_))

tabyl(pmaug2022wgps, usingmoderncon)
 usingmoderncon    n    percent valid_percent
             no 9304 0.65645947     0.6662847
            yes 4660 0.32879419     0.3337153
           <NA>  209 0.01474635            NA
#education

pmaug2022wgps<-pmaug2022wgps%>%
  mutate(EDUCATTGEN=as.factor(EDUCATTGEN))%>%
  mutate(educationlevel= recode(EDUCATTGEN, '1' = "none" ,'2' = "primary/middle school", '3'= "secondary/post-primary", '4'= 'tertiary/ post-secondary',.default = NA_character_))

tabyl(pmaug2022wgps, educationlevel)
           educationlevel    n      percent valid_percent
                     none  842 0.0594087349    0.05941712
    primary/middle school 7902 0.5575389826    0.55761767
   secondary/post-primary 4268 0.3011359627    0.30117846
 tertiary/ post-secondary 1159 0.0817752064    0.08178675
                     <NA>    2 0.0001411134            NA
#collapse marital status variable

pmaug2022wgps<-pmaug2022wgps%>%
  mutate(maritalcombined= recode(maritalstatus, 'formerly in union'= 'not in a union' ,'never married' = "not in a union", 'widow or widower'= "not in a union", 'currently married'= "in a union", 'currently living with partner'="in a union"))


#make index: exercise of choice

library(janitor)


pmaug2022wgps <- pmaug2022wgps %>%
  mutate(startstop = case_when(
    STARTKIDDEC == 1 | PTRDISCKIDSTOPWILL== 1 ~ '1',
    STARTKIDDEC == 2 | PTRDISCKIDSTOPWILL== 2 ~ '2',
    STARTKIDDEC == 3 | PTRDISCKIDSTOPWILL== 3 ~ '3',
    STARTKIDDEC == 4 | PTRDISCKIDSTOPWILL== 4~ '4',
    STARTKIDDEC == 5 | PTRDISCKIDSTOPWILL== 5 ~ '5'
  ))

tabyl(pmaug2022wgps$startstop)
 pmaug2022wgps$startstop    n    percent valid_percent
                       1  598 0.04219290    0.04291045
                       2 1951 0.13765611    0.13999713
                       3  559 0.03944119    0.04011194
                       4 7395 0.52176674    0.53064007
                       5 3433 0.24222112    0.24634041
                    <NA>  237 0.01672194            NA
pmaug2022wgps <- pmaug2022wgps %>%
  mutate(havekid = case_when(
    STARTKIDDECWILL == 1 | NEXTKIDDEC== 1 ~ '1',
    STARTKIDDECWILL == 2 | NEXTKIDDEC== 2 ~ '2',
    STARTKIDDECWILL == 3 | NEXTKIDDEC== 3 ~ '3',
    STARTKIDDECWILL == 4 | NEXTKIDDEC== 4~ '4',
    STARTKIDDECWILL == 5 | NEXTKIDDEC== 5 ~ '5'
  ))


tabyl(pmaug2022wgps$havekid)
 pmaug2022wgps$havekid    n    percent valid_percent
                     1  343 0.02420095    0.02441976
                     2 1267 0.08939533    0.09020362
                     3  446 0.03146828    0.03175281
                     4 7311 0.51583998    0.52050406
                     5 4679 0.33013476    0.33311975
                  <NA>  127 0.00896070            NA
pmaug2022wgps <- pmaug2022wgps %>%
  mutate(negotiate = case_when(
    PTRDISCKIDSTOP == 1 | PTRDISCKIDSTART== 1 ~ '1',
    PTRDISCKIDSTOP == 2 | PTRDISCKIDSTART== 2 ~ '2',
    PTRDISCKIDSTOP == 3 | PTRDISCKIDSTART== 3 ~ '3',
    PTRDISCKIDSTOP == 4 | PTRDISCKIDSTART== 4~ '4',
    PTRDISCKIDSTOP == 5 | PTRDISCKIDSTART== 5 ~ '5'
  ))

tabyl(pmaug2022wgps$negotiate)
 pmaug2022wgps$negotiate    n    percent valid_percent
                       1  575 0.04057010    0.04133420
                       2 1341 0.09461652    0.09639853
                       3  591 0.04169901    0.04248436
                       4 7393 0.52162563    0.53144993
                       5 4011 0.28300289    0.28833297
                    <NA>  262 0.01848585            NA
#make index existence of choice

pmaug2022wgps <- pmaug2022wgps %>%
  mutate(restbtw = case_when(
    PREGREST == 1 ~ '1',
    PREGREST == 2 ~ '2',
    PREGREST == 3 ~ '3',
    PREGREST == 4 ~ '4',
    PREGREST == 5~ '5'
  ))

tabyl(pmaug2022wgps$restbtw)
 pmaug2022wgps$restbtw    n     percent valid_percent
                     1  127 0.008960700   0.009021809
                     2  221 0.015593029   0.015699368
                     3  150 0.010583504   0.010655679
                     4 6855 0.483666126   0.486964552
                     5 6724 0.474423199   0.477658592
                  <NA>   96 0.006773442            NA
pmaug2022wgps <- pmaug2022wgps %>%
  mutate(edufirst = case_when(
    EDUCPREKID == 1 | EDUCPREKIDWILL== 1 ~ '1',
    EDUCPREKID == 2 | EDUCPREKIDWILL== 2 ~ '2',
    EDUCPREKID == 3 | EDUCPREKIDWILL== 3 ~ '3',
    EDUCPREKID == 4 | EDUCPREKIDWILL== 4~ '4',
    EDUCPREKID == 5 | EDUCPREKIDWILL== 5 ~ '5'
  ))

tabyl(pmaug2022wgps$edufirst)
 pmaug2022wgps$edufirst    n     percent valid_percent
                      1  202 0.014252452    0.03886858
                      2  396 0.027940450    0.07619781
                      3  122 0.008607916    0.02347508
                      4 1684 0.118817470    0.32403310
                      5 2793 0.197064842    0.53742544
                   <NA> 8976 0.633316870            NA
#don't use because it isn't validated



#drop na's

pmauglongfilter2 <- pmaug2022wgps %>%
  filter(!is.na(startstop),
         !is.na(havekid),
         !is.na(negotiate))



pmauglongfilter2$startstopnum<-as.numeric(pmauglongfilter2$startstop)
pmauglongfilter2$havekidnum<-as.numeric(pmauglongfilter2$havekid)
pmauglongfilter2$negotiatenum<-as.numeric(pmauglongfilter2$negotiate)

#create summary variables: sum

pmauglongfilter2 <- pmauglongfilter2 %>%
  mutate(summary_score = (startstopnum + havekidnum + negotiatenum) )


tabyl(pmauglongfilter2$summary_score)
 pmauglongfilter2$summary_score    n     percent
                              3   67 0.004862472
                              4   68 0.004935046
                              5   98 0.007112272
                              6  342 0.024820379
                              7  273 0.019812759
                              8  632 0.045866899
                              9  514 0.037303142
                             10 1357 0.098483199
                             11  959 0.069598665
                             12 4526 0.328470861
                             13 1734 0.125843675
                             14 1330 0.096523695
                             15 1879 0.136366935
hist(pmauglongfilter2$summary_score)

#average

pmauglongfilter2 <- pmauglongfilter2 %>%
  mutate(summary_score_av = (startstopnum + havekidnum + negotiatenum)/3 )

tabyl(pmauglongfilter2$summary_score_av)
 pmauglongfilter2$summary_score_av    n     percent
                          1.000000   67 0.004862472
                          1.333333   68 0.004935046
                          1.666667   98 0.007112272
                          2.000000  342 0.024820379
                          2.333333  273 0.019812759
                          2.666667  632 0.045866899
                          3.000000  514 0.037303142
                          3.333333 1357 0.098483199
                          3.666667  959 0.069598665
                          4.000000 4526 0.328470861
                          4.333333 1734 0.125843675
                          4.666667 1330 0.096523695
                          5.000000 1879 0.136366935
hist(pmauglongfilter2$summary_score_av)

#convert to a spatial frame

pmaug2022wgps_sf<- st_as_sf(pmauglongfilter2, coords = c("GPSLONG", "GPSLAT"), crs=4326)

Step 3: Visualize the average summary score by PMA cluster

#map out the scale by EAID

UG<-st_transform(UG_boundary_4, crs=4326)

UG <- st_make_valid(UG)

joined<-st_join(UG, pmaug2022wgps_sf, join=st_intersects)

# Set the global option
options(survey.lonely.psu = "adjust")

library(dplyr)


joined<-joined%>%
  filter(summary_score_av!='NA')

joined$fertautonomyscale <- as.numeric(joined$summary_score_av)

library(dplyr)
library(srvyr)

Attaching package: 'srvyr'
The following object is masked from 'package:stats':

    filter
pmaug2022groupedweighted2 <- joined  %>%
  as_survey_design(ids=EAID, strata = STRATA, weights = FQWEIGHT, nest=TRUE) %>%
  group_by(EAID, YEAR) %>%
  summarise(
    summary_score_av_mean = mean(summary_score_av, na.rm = TRUE)
  )



#make map of cluster level pregnancy decision-making

UG_PMA_GPS_2022 <- UG_PMA_GPS_2022 %>%
  mutate(EAID = as.numeric(EAID))

mapdatareg<-left_join(pmaug2022groupedweighted2, UG_PMA_GPS_2022)
Joining with `by = join_by(EAID)`
#convert to a spatial frame

mapdatareg<- st_as_sf(mapdatareg, coords = c("GPSLONG", "GPSLAT"), crs=4326)




#all three years

library(ggplot2)
ggplot(data = mapdatareg) +
  geom_sf(data = UG_boundary_4, fill = NA, color = "lightgray", linewidth = 0.2, inherit.aes = FALSE) +
  geom_sf(data = UG_sub_boundary, fill = NA, color = "lightgray", linewidth = 0.2, inherit.aes = FALSE) +
  geom_sf(data = mapdatareg, aes(color = summary_score_av_mean), size = 1.2, alpha = 0.8) +
  scale_color_viridis_c(option = "magma", direction = -1, name = "Pregnancy exercise of choice\nsub-scale mean") +
  facet_wrap(~ YEAR) +
  theme_minimal(base_size = 12) +
  theme(
    legend.title = element_text(size = 10),
    legend.text  = element_text(size = 9),
    strip.text   = element_text(size = 12, face = "bold")
  )

Step 4: check for spatial clustering

#check for spatial clustering

library(spdep)
Loading required package: spData
To access larger datasets in this package, install the spDataLarge
package with: `install.packages('spDataLarge',
repos='https://nowosad.github.io/drat/', type='source')`
library(sf)


# 2020
mapdata_2020 <- mapdatareg %>% filter(YEAR == 2020)
mapdata_proj <- st_transform(mapdata_2020, crs = 32636)
coords_proj <- st_coordinates(st_centroid(mapdata_proj))
nb <- dnearneigh(coords_proj, 0, 50000)
Warning in dnearneigh(coords_proj, 0, 50000): neighbour object has 15
sub-graphs
lw <- nb2listw(nb, style = "W", zero.policy = TRUE)
moran_result_2020 <- moran.test(mapdata_proj$summary_score_av_mean, lw, zero.policy = TRUE)
print(moran_result_2020)

    Moran I test under randomisation

data:  mapdata_proj$summary_score_av_mean  
weights: lw  
n reduced by no-neighbour observations  

Moran I statistic standard deviate = 3.3426, p-value = 0.0004149
alternative hypothesis: greater
sample estimates:
Moran I statistic       Expectation          Variance 
      0.225683686      -0.008403361       0.004904301 
# 2021
mapdata_2021 <- mapdatareg %>% filter(YEAR == 2021)
mapdata_proj <- st_transform(mapdata_2021, crs = 32636)
coords_proj <- st_coordinates(st_centroid(mapdata_proj))
nb <- dnearneigh(coords_proj, 0, 50000)
Warning in dnearneigh(coords_proj, 0, 50000): neighbour object has 13
sub-graphs
lw <- nb2listw(nb, style = "W", zero.policy = TRUE)
moran_result_2021 <- moran.test(mapdata_proj$summary_score_av_mean, lw, zero.policy = TRUE)
print(moran_result_2021)

    Moran I test under randomisation

data:  mapdata_proj$summary_score_av_mean  
weights: lw  
n reduced by no-neighbour observations  

Moran I statistic standard deviate = 3.3277, p-value = 0.0004378
alternative hypothesis: greater
sample estimates:
Moran I statistic       Expectation          Variance 
      0.191137691      -0.007246377       0.003554025 
# 2022
mapdata_2022 <- mapdatareg %>% filter(YEAR == 2022)
mapdata_proj <- st_transform(mapdata_2022, crs = 32636)
coords_proj <- st_coordinates(st_centroid(mapdata_proj))
nb <- dnearneigh(coords_proj, 0, 50000)
Warning in dnearneigh(coords_proj, 0, 50000): neighbour object has 13
sub-graphs
lw <- nb2listw(nb, style = "W", zero.policy = TRUE)
moran_result_2022 <- moran.test(mapdata_proj$summary_score_av_mean, lw, zero.policy = TRUE)
print(moran_result_2022)

    Moran I test under randomisation

data:  mapdata_proj$summary_score_av_mean  
weights: lw  
n reduced by no-neighbour observations  

Moran I statistic standard deviate = 2.11, p-value = 0.01743
alternative hypothesis: greater
sample estimates:
Moran I statistic       Expectation          Variance 
      0.118176049      -0.007246377       0.003533431 
library(spdep)
library(sf)

# Transform to UTM Zone 36N
mapdata_proj <- st_transform(mapdatareg, crs = 32636)

# Use centroids for coordinates
coords_proj <- st_coordinates(st_centroid(mapdata_proj))

# Create neighbors within 100 km
nb <- dnearneigh(coords_proj, 0, 50000)
Warning in dnearneigh(coords_proj, 0, 50000): neighbour object has 13
sub-graphs
lw <- nb2listw(nb, style = "W", zero.policy = TRUE)

# Check column existence and run Moran's I (univariate example)
moran_result <- moran.mc(mapdata_proj$summary_score_av_mean, lw, nsim = 499)

print(moran_result)

    Monte-Carlo simulation of Moran I

data:  mapdata_proj$summary_score_av_mean 
weights: lw  
number of simulations + 1: 500 

statistic = 0.28762, observed rank = 500, p-value = 0.002
alternative hypothesis: greater
p_value <- mean(abs(moran_result$res) >= abs(moran_result$statistic))
p_value
[1] 0.002

Step 5: join indivdual level and cluster level information and drop missing values

individualandcluster<-left_join(joined, pmaug2022groupedweighted2)
Joining with `by = join_by(EAID, YEAR)`
#drop missing values

individualandcluster<-individualandcluster%>%
  filter(MCP!='98')


individualandcluster<-individualandcluster%>%
  filter(educationlevel!='NA')



individualandcluster<-individualandcluster%>%
  filter(maritalcombined!='NA')



individualandcluster<-individualandcluster%>%
  filter(URBAN!='98')

Step 6: Regression analysis

#bivariate relationship with urban status and summary score

modelbiv <- lm(summary_score_av ~factor(URBAN), 
              data = individualandcluster)

summary(modelbiv)

Call:
lm(formula = summary_score_av ~ factor(URBAN), data = individualandcluster)

Residuals:
     Min       1Q   Median       3Q      Max 
-2.97222 -0.30555  0.09765  0.43098  1.09765 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
(Intercept)    3.902353   0.008945 436.237  < 2e-16 ***
factor(URBAN)1 0.069863   0.014962   4.669 3.05e-06 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.7904 on 12148 degrees of freedom
Multiple R-squared:  0.001792,  Adjusted R-squared:  0.001709 
F-statistic:  21.8 on 1 and 12148 DF,  p-value: 3.055e-06
library(modelsummary)


modelsummary(modelbiv, stars=TRUE)
(1)
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
(Intercept) 3.902***
(0.009)
factor(URBAN)1 0.070***
(0.015)
Num.Obs. 12150
R2 0.002
R2 Adj. 0.002
AIC 28768.4
BIC 28790.6
Log.Lik. -14381.196
F 21.802
RMSE 0.79
modelsummary(
  modelbiv,
  slope = "b",
  stars = TRUE,
  title = "Bivariate relationship between urban/ rural status and pregnancy decision-makiong",
  gof_map = NA  
) 
Bivariate relationship between urban/ rural status and pregnancy decision-makiong
(1)
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
(Intercept) 3.902***
(0.009)
factor(URBAN)1 0.070***
(0.015)
library(lme4)
Loading required package: Matrix

Attaching package: 'Matrix'
The following objects are masked from 'package:tidyr':

    expand, pack, unpack
modelmulti <- lmer(summary_score_av ~BIRTHEVENT + educationlevel + agegroup+ maritalcombined+ factor(MCP)+ factor(URBAN)+ factor(WEALTHQ)+ factor(YEAR)+ (1|FQINSTID), 
              data = individualandcluster)

summary(modelmulti)
Linear mixed model fit by REML ['lmerMod']
Formula: 
summary_score_av ~ BIRTHEVENT + educationlevel + agegroup + maritalcombined +  
    factor(MCP) + factor(URBAN) + factor(WEALTHQ) + factor(YEAR) +  
    (1 | FQINSTID)
   Data: individualandcluster

REML criterion at convergence: 27689.3

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-4.1669 -0.4298  0.0884  0.6048  2.7229 

Random effects:
 Groups   Name        Variance Std.Dev.
 FQINSTID (Intercept) 0.1540   0.3924  
 Residual             0.4333   0.6583  
Number of obs: 12150, groups:  FQINSTID, 7148

Fixed effects:
                                         Estimate Std. Error t value
(Intercept)                             3.7084765  0.0380575  97.444
BIRTHEVENT                             -0.0141431  0.0051273  -2.758
educationlevelprimary/middle school    -0.0071130  0.0325525  -0.219
educationlevelsecondary/post-primary    0.2102102  0.0361228   5.819
educationleveltertiary/ post-secondary  0.2632737  0.0441504   5.963
agegroup20-24                          -0.0168789  0.0242336  -0.697
agegroup25-29                           0.0053488  0.0281180   0.190
agegroup30-34                           0.0471566  0.0326761   1.443
agegroup35-39                          -0.0007062  0.0376092  -0.019
agegroup40-44                          -0.0318069  0.0425407  -0.748
agegroup45-49                          -0.0294877  0.0461900  -0.638
maritalcombinedin a union              -0.0520836  0.0181677  -2.867
factor(MCP)1                            0.1046921  0.0155415   6.736
factor(URBAN)1                         -0.0960249  0.0183780  -5.225
factor(WEALTHQ)2                        0.1355002  0.0234981   5.766
factor(WEALTHQ)3                        0.2216543  0.0240357   9.222
factor(WEALTHQ)4                        0.2421327  0.0255412   9.480
factor(WEALTHQ)5                        0.2993570  0.0277365  10.793
factor(YEAR)2021                        0.0556178  0.0155699   3.572
factor(YEAR)2022                        0.0256086  0.0160039   1.600

Correlation matrix not shown by default, as p = 20 > 12.
Use print(x, correlation=TRUE)  or
    vcov(x)        if you need it
library(modelsummary)


modelsummary(modelmulti, stars=TRUE)
(1)
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
(Intercept) 3.708***
(0.038)
BIRTHEVENT -0.014**
(0.005)
educationlevelprimary/middle school -0.007
(0.033)
educationlevelsecondary/post-primary 0.210***
(0.036)
educationleveltertiary/ post-secondary 0.263***
(0.044)
agegroup20-24 -0.017
(0.024)
agegroup25-29 0.005
(0.028)
agegroup30-34 0.047
(0.033)
agegroup35-39 -0.001
(0.038)
agegroup40-44 -0.032
(0.043)
agegroup45-49 -0.029
(0.046)
maritalcombinedin a union -0.052**
(0.018)
factor(MCP)1 0.105***
(0.016)
factor(URBAN)1 -0.096***
(0.018)
factor(WEALTHQ)2 0.136***
(0.023)
factor(WEALTHQ)3 0.222***
(0.024)
factor(WEALTHQ)4 0.242***
(0.026)
factor(WEALTHQ)5 0.299***
(0.028)
factor(YEAR)2021 0.056***
(0.016)
factor(YEAR)2022 0.026
(0.016)
SD (Intercept FQINSTID) 0.392
SD (Observations) 0.658
Num.Obs. 12150
R2 Marg. 0.064
R2 Cond. 0.309
AIC 27733.3
BIC 27896.2
ICC 0.3
RMSE 0.58
#investigate interactions between year and urban status

modelmulti_interact <- lmer(summary_score_av ~ BIRTHEVENT + educationlevel + agegroup + maritalcombined + factor(URBAN)+factor(YEAR)+factor(MCP) + factor(URBAN) * factor(YEAR) + factor(WEALTHQ) + (1|FQINSTID),
                            data = individualandcluster)

modelsummary(modelmulti_interact, stars = TRUE)
(1)
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
(Intercept) 3.683***
(0.039)
BIRTHEVENT -0.014**
(0.005)
educationlevelprimary/middle school -0.008
(0.033)
educationlevelsecondary/post-primary 0.208***
(0.036)
educationleveltertiary/ post-secondary 0.262***
(0.044)
agegroup20-24 -0.017
(0.024)
agegroup25-29 0.006
(0.028)
agegroup30-34 0.049
(0.033)
agegroup35-39 0.001
(0.038)
agegroup40-44 -0.031
(0.042)
agegroup45-49 -0.028
(0.046)
maritalcombinedin a union -0.053**
(0.018)
factor(URBAN)1 -0.020
(0.027)
factor(YEAR)2021 0.101***
(0.019)
factor(YEAR)2022 0.061**
(0.020)
factor(MCP)1 0.105***
(0.016)
factor(WEALTHQ)2 0.135***
(0.023)
factor(WEALTHQ)3 0.220***
(0.024)
factor(WEALTHQ)4 0.241***
(0.026)
factor(WEALTHQ)5 0.301***
(0.028)
factor(URBAN)1 × factor(YEAR)2021 -0.127***
(0.032)
factor(URBAN)1 × factor(YEAR)2022 -0.099**
(0.033)
SD (Intercept FQINSTID) 0.391
SD (Observations) 0.658
Num.Obs. 12150
R2 Marg. 0.065
R2 Cond. 0.309
AIC 27731.1
BIC 27908.8
ICC 0.3
RMSE 0.58
models3list <- list(
  "Model 1: Base Model" = modelmulti,
  "Model 2: Year x Urban" = modelmulti_interact
)


modelsummary(
  models3list,
  slope = "b",
  stars = TRUE,
  title = "Multivariate relationship between urban/ rural status and pregnancy decision-making",
  gof_map = NA  
) 
Multivariate relationship between urban/ rural status and pregnancy decision-making
Model 1: Base Model Model 2: Year x Urban
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
(Intercept) 3.708*** 3.683***
(0.038) (0.039)
BIRTHEVENT -0.014** -0.014**
(0.005) (0.005)
educationlevelprimary/middle school -0.007 -0.008
(0.033) (0.033)
educationlevelsecondary/post-primary 0.210*** 0.208***
(0.036) (0.036)
educationleveltertiary/ post-secondary 0.263*** 0.262***
(0.044) (0.044)
agegroup20-24 -0.017 -0.017
(0.024) (0.024)
agegroup25-29 0.005 0.006
(0.028) (0.028)
agegroup30-34 0.047 0.049
(0.033) (0.033)
agegroup35-39 -0.001 0.001
(0.038) (0.038)
agegroup40-44 -0.032 -0.031
(0.043) (0.042)
agegroup45-49 -0.029 -0.028
(0.046) (0.046)
maritalcombinedin a union -0.052** -0.053**
(0.018) (0.018)
factor(MCP)1 0.105*** 0.105***
(0.016) (0.016)
factor(URBAN)1 -0.096*** -0.020
(0.018) (0.027)
factor(WEALTHQ)2 0.136*** 0.135***
(0.023) (0.023)
factor(WEALTHQ)3 0.222*** 0.220***
(0.024) (0.024)
factor(WEALTHQ)4 0.242*** 0.241***
(0.026) (0.026)
factor(WEALTHQ)5 0.299*** 0.301***
(0.028) (0.028)
factor(YEAR)2021 0.056*** 0.101***
(0.016) (0.019)
factor(YEAR)2022 0.026 0.061**
(0.016) (0.020)
factor(URBAN)1 × factor(YEAR)2021 -0.127***
(0.032)
factor(URBAN)1 × factor(YEAR)2022 -0.099**
(0.033)
SD (Intercept FQINSTID) 0.392 0.391
SD (Observations) 0.658 0.658

Urban status is significant and positive at the bivariate level. At the multivariate level it is also significant but it shows a negative relationship with pregnancy decision-making. When year is interacted with urban status we see that the urban effect is concentrated in 2021 and 2022 of the panel.

#see if URBAN is significant for each year on its own
library(modelsummary)

individualandcluster2020 <- individualandcluster %>% filter(YEAR == 2020)
individualandcluster2021 <- individualandcluster %>% filter(YEAR == 2021)
individualandcluster2022 <- individualandcluster %>% filter(YEAR == 2022)

modelmulti2020 <- lm(summary_score_av ~ BIRTHEVENT + educationlevel + agegroup + maritalcombined +
                         factor(MCP) + factor(URBAN) + factor(WEALTHQ),
                       data = individualandcluster2020)

modelmulti2021 <- lm(summary_score_av ~ BIRTHEVENT + educationlevel + agegroup + maritalcombined +
                         factor(MCP) + factor(URBAN) + factor(WEALTHQ),
                       data = individualandcluster2021)

modelmulti2022 <- lm(summary_score_av ~ BIRTHEVENT + educationlevel + agegroup + maritalcombined +
                         factor(MCP) + factor(URBAN) + factor(WEALTHQ),
                       data = individualandcluster2022)

models <- list(
  "2020" = modelmulti2020,
  "2021" = modelmulti2021,
  "2022" = modelmulti2022
)

modelsummary(models, stars = TRUE)
2020 2021 2022
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
(Intercept) 3.638*** 3.840*** 3.774***
(0.059) (0.058) (0.062)
BIRTHEVENT -0.011 -0.021** -0.007
(0.009) (0.008) (0.008)
educationlevelprimary/middle school 0.025 -0.022 -0.065
(0.052) (0.050) (0.055)
educationlevelsecondary/post-primary 0.251*** 0.167** 0.163**
(0.059) (0.056) (0.060)
educationleveltertiary/ post-secondary 0.330*** 0.221*** 0.183*
(0.072) (0.067) (0.074)
agegroup20-24 -0.009 -0.047 -0.016
(0.042) (0.038) (0.040)
agegroup25-29 0.033 -0.062 0.029
(0.048) (0.044) (0.045)
agegroup30-34 0.042 0.017 0.067
(0.055) (0.051) (0.051)
agegroup35-39 0.052 -0.024 -0.062
(0.064) (0.057) (0.059)
agegroup40-44 -0.056 -0.067 -0.026
(0.071) (0.065) (0.065)
agegroup45-49 -0.033 -0.010 -0.031
(0.077) (0.069) (0.072)
maritalcombinedin a union -0.068* -0.016 -0.073*
(0.032) (0.028) (0.028)
factor(MCP)1 0.043 0.147*** 0.148***
(0.029) (0.025) (0.026)
factor(URBAN)1 -0.034 -0.118*** -0.120***
(0.030) (0.028) (0.028)
factor(WEALTHQ)2 0.134** 0.091* 0.159***
(0.042) (0.038) (0.036)
factor(WEALTHQ)3 0.225*** 0.243*** 0.189***
(0.042) (0.038) (0.037)
factor(WEALTHQ)4 0.269*** 0.214*** 0.232***
(0.044) (0.039) (0.041)
factor(WEALTHQ)5 0.385*** 0.220*** 0.317***
(0.047) (0.042) (0.045)
Num.Obs. 3823 4228 4099
R2 0.080 0.057 0.065
R2 Adj. 0.076 0.054 0.061
AIC 9058.8 9567.7 9396.6
BIC 9177.5 9688.4 9516.6
Log.Lik. -4510.379 -4764.857 -4679.288
F 19.386 15.078 16.561
RMSE 0.79 0.75 0.76
modelsummary(
  models,
  slope = "b",
  stars = TRUE,
  title = "Multivariate relationship between urban/ rural status and pregnancy decision-making, stratified by year",
  gof_map = NA  
)
Multivariate relationship between urban/ rural status and pregnancy decision-making, stratified by year
2020 2021 2022
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
(Intercept) 3.638*** 3.840*** 3.774***
(0.059) (0.058) (0.062)
BIRTHEVENT -0.011 -0.021** -0.007
(0.009) (0.008) (0.008)
educationlevelprimary/middle school 0.025 -0.022 -0.065
(0.052) (0.050) (0.055)
educationlevelsecondary/post-primary 0.251*** 0.167** 0.163**
(0.059) (0.056) (0.060)
educationleveltertiary/ post-secondary 0.330*** 0.221*** 0.183*
(0.072) (0.067) (0.074)
agegroup20-24 -0.009 -0.047 -0.016
(0.042) (0.038) (0.040)
agegroup25-29 0.033 -0.062 0.029
(0.048) (0.044) (0.045)
agegroup30-34 0.042 0.017 0.067
(0.055) (0.051) (0.051)
agegroup35-39 0.052 -0.024 -0.062
(0.064) (0.057) (0.059)
agegroup40-44 -0.056 -0.067 -0.026
(0.071) (0.065) (0.065)
agegroup45-49 -0.033 -0.010 -0.031
(0.077) (0.069) (0.072)
maritalcombinedin a union -0.068* -0.016 -0.073*
(0.032) (0.028) (0.028)
factor(MCP)1 0.043 0.147*** 0.148***
(0.029) (0.025) (0.026)
factor(URBAN)1 -0.034 -0.118*** -0.120***
(0.030) (0.028) (0.028)
factor(WEALTHQ)2 0.134** 0.091* 0.159***
(0.042) (0.038) (0.036)
factor(WEALTHQ)3 0.225*** 0.243*** 0.189***
(0.042) (0.038) (0.037)
factor(WEALTHQ)4 0.269*** 0.214*** 0.232***
(0.044) (0.039) (0.041)
factor(WEALTHQ)5 0.385*** 0.220*** 0.317***
(0.047) (0.042) (0.045)

Urban status is significant in 2021 and 2022 but not 2020

#add random effect for cluster using all three years

modelmulti_cluster <- lmer(summary_score_av ~ BIRTHEVENT + educationlevel + agegroup + maritalcombined +
                              factor(MCP) + factor(URBAN) + factor(YEAR) + factor(WEALTHQ) + (1|FQINSTID)+ (1| EAID),
                            data = individualandcluster)




modelmulti_cluster2020 <- lmer(summary_score_av ~ BIRTHEVENT + educationlevel + agegroup + maritalcombined +
                              factor(MCP) + factor(URBAN) + factor(WEALTHQ) + (1| EAID),
                            data = individualandcluster2020)




modelmulti_cluster2021 <- lmer(summary_score_av ~ BIRTHEVENT + educationlevel + agegroup + maritalcombined +
                              factor(MCP) + factor(URBAN) + factor(WEALTHQ) + (1| EAID),
                            data = individualandcluster2021)



modelmulti_cluster2022 <- lmer(summary_score_av ~ BIRTHEVENT + educationlevel + agegroup + maritalcombined +
                              factor(MCP) + factor(URBAN) + factor(WEALTHQ) + (1| EAID),
                            data = individualandcluster2022)






modelscluster <- list(
  "full panel"= modelmulti_cluster,
  "2020" = modelmulti_cluster2020,
  "2021" = modelmulti_cluster2021,
  "2022" = modelmulti_cluster2022
)

modelsummary(modelscluster, stars = TRUE)
full panel 2020 2021 2022
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
(Intercept) 3.733*** 3.640*** 3.796*** 3.809***
(0.051) (0.074) (0.070) (0.075)
BIRTHEVENT -0.018*** -0.015+ -0.025*** -0.011+
(0.005) (0.008) (0.007) (0.007)
educationlevelprimary/middle school 0.078* 0.118* 0.077 0.004
(0.033) (0.053) (0.050) (0.055)
educationlevelsecondary/post-primary 0.269*** 0.328*** 0.263*** 0.186**
(0.036) (0.058) (0.055) (0.060)
educationleveltertiary/ post-secondary 0.338*** 0.387*** 0.354*** 0.216**
(0.043) (0.070) (0.065) (0.072)
agegroup20-24 -0.016 -0.020 -0.029 -0.014
(0.022) (0.039) (0.035) (0.036)
agegroup25-29 0.011 0.033 -0.040 0.024
(0.026) (0.045) (0.040) (0.041)
agegroup30-34 0.057+ 0.040 0.037 0.075
(0.030) (0.051) (0.047) (0.046)
agegroup35-39 0.026 0.068 0.024 -0.033
(0.034) (0.059) (0.053) (0.053)
agegroup40-44 -0.010 -0.062 -0.012 -0.006
(0.039) (0.066) (0.060) (0.059)
agegroup45-49 -0.004 0.007 0.023 -0.046
(0.042) (0.072) (0.064) (0.064)
maritalcombinedin a union -0.037* -0.052+ -0.014 -0.059*
(0.017) (0.029) (0.026) (0.025)
factor(MCP)1 0.109*** 0.054* 0.142*** 0.153***
(0.015) (0.027) (0.024) (0.023)
factor(URBAN)1 -0.042 0.033 -0.082 -0.054
(0.056) (0.070) (0.065) (0.070)
factor(YEAR)2021 0.037*
(0.015)
factor(YEAR)2022 0.004
(0.016)
factor(WEALTHQ)2 0.051* 0.064 0.031 0.045
(0.026) (0.046) (0.041) (0.039)
factor(WEALTHQ)3 0.098*** 0.106* 0.103* 0.092*
(0.027) (0.047) (0.043) (0.040)
factor(WEALTHQ)4 0.114*** 0.116* 0.114** 0.112*
(0.029) (0.051) (0.044) (0.045)
factor(WEALTHQ)5 0.145*** 0.200*** 0.134** 0.165***
(0.032) (0.057) (0.050) (0.050)
SD (Intercept EAID) 0.304 0.340 0.338 0.369
SD (Observations) 0.658 0.714 0.672 0.661
SD (Intercept FQINSTID) 0.254
Num.Obs. 12150 3823 4228 4099
R2 Marg. 0.045 0.058 0.049 0.043
R2 Cond. 0.299 0.232 0.241 0.271
AIC 26426.2 8639.0 9052.0 8669.5
BIC 26596.5 8764.0 9179.0 8795.8
ICC 0.3 0.2 0.2 0.2
RMSE 0.62 0.70 0.66 0.65
modelsummary(
  modelscluster,
  slope = "b",
  stars = TRUE,
  title = "Mixed Effects Regression of Pregnancy Decision-Making",
  gof_map = NA  
) 
Mixed Effects Regression of Pregnancy Decision-Making
full panel 2020 2021 2022
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
(Intercept) 3.733*** 3.640*** 3.796*** 3.809***
(0.051) (0.074) (0.070) (0.075)
BIRTHEVENT -0.018*** -0.015+ -0.025*** -0.011+
(0.005) (0.008) (0.007) (0.007)
educationlevelprimary/middle school 0.078* 0.118* 0.077 0.004
(0.033) (0.053) (0.050) (0.055)
educationlevelsecondary/post-primary 0.269*** 0.328*** 0.263*** 0.186**
(0.036) (0.058) (0.055) (0.060)
educationleveltertiary/ post-secondary 0.338*** 0.387*** 0.354*** 0.216**
(0.043) (0.070) (0.065) (0.072)
agegroup20-24 -0.016 -0.020 -0.029 -0.014
(0.022) (0.039) (0.035) (0.036)
agegroup25-29 0.011 0.033 -0.040 0.024
(0.026) (0.045) (0.040) (0.041)
agegroup30-34 0.057+ 0.040 0.037 0.075
(0.030) (0.051) (0.047) (0.046)
agegroup35-39 0.026 0.068 0.024 -0.033
(0.034) (0.059) (0.053) (0.053)
agegroup40-44 -0.010 -0.062 -0.012 -0.006
(0.039) (0.066) (0.060) (0.059)
agegroup45-49 -0.004 0.007 0.023 -0.046
(0.042) (0.072) (0.064) (0.064)
maritalcombinedin a union -0.037* -0.052+ -0.014 -0.059*
(0.017) (0.029) (0.026) (0.025)
factor(MCP)1 0.109*** 0.054* 0.142*** 0.153***
(0.015) (0.027) (0.024) (0.023)
factor(URBAN)1 -0.042 0.033 -0.082 -0.054
(0.056) (0.070) (0.065) (0.070)
factor(YEAR)2021 0.037*
(0.015)
factor(YEAR)2022 0.004
(0.016)
factor(WEALTHQ)2 0.051* 0.064 0.031 0.045
(0.026) (0.046) (0.041) (0.039)
factor(WEALTHQ)3 0.098*** 0.106* 0.103* 0.092*
(0.027) (0.047) (0.043) (0.040)
factor(WEALTHQ)4 0.114*** 0.116* 0.114** 0.112*
(0.029) (0.051) (0.044) (0.045)
factor(WEALTHQ)5 0.145*** 0.200*** 0.134** 0.165***
(0.032) (0.057) (0.050) (0.050)
SD (Intercept EAID) 0.304 0.340 0.338 0.369
SD (Observations) 0.658 0.714 0.672 0.661
SD (Intercept FQINSTID) 0.254

Note urban status is not significant for any year once PMA clusters are accounted for within the model

Step 7: Did urban status change over time?

library(dplyr)

# cleanest way — keeps it a data.frame/tibble, drops the geometry column
individualandcluster_df <- st_drop_geometry(individualandcluster)


urban_check <- individualandcluster_df %>%
  distinct(FQINSTID, YEAR, URBAN) %>%
  group_by(FQINSTID) %>%
  summarise(n_distinct_urban = n_distinct(URBAN),
            urban_values = paste(sort(unique(URBAN)), collapse = ","),
            years_seen = paste(sort(unique(YEAR)), collapse = ","))

# individuals that flip classification
urban_check %>% filter(n_distinct_urban > 1)
# A tibble: 0 × 4
# ℹ 4 variables: FQINSTID <chr>, n_distinct_urban <int>, urban_values <chr>,
#   years_seen <chr>
# are distributions stable across years
table(individualandcluster_df$YEAR, individualandcluster_df$URBAN)
      
          0    1   98
  2020 2416 1407    0
  2021 2717 1511    0
  2022 2674 1425    0
prop.table(table(individualandcluster_df$YEAR, individualandcluster_df$URBAN), margin = 1)
      
               0         1        98
  2020 0.6319644 0.3680356 0.0000000
  2021 0.6426206 0.3573794 0.0000000
  2022 0.6523542 0.3476458 0.0000000

No individuals changed urban/ rural status across years and the urban/ rural distribution was stable across years