Analyses

Author

Miroslav Rajter and Milani Medvidović

1 Introduction

This document presents all analyses conducted in the creation of the article. All analyses were performed using R 4.1.2 (R Core Team, 2021) with tidyverse package (Wickham et al., 2019). The confirmatory factor analyses were performed using lavaan package (Rosseel, 2012).

2 Data preparation

The data was loaded with sjlabelled package (Lüdecke, 2018).

Code
library(tidyverse)
-- Attaching packages --------------------------------------- tidyverse 1.3.1 --
v ggplot2 3.3.5     v purrr   0.3.4
v tibble  3.1.6     v dplyr   1.0.8
v tidyr   1.2.0     v stringr 1.4.0
v readr   2.1.2     v forcats 0.5.1
-- Conflicts ------------------------------------------ tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
Code
library(lavaan)
Warning: package 'lavaan' was built under R version 4.1.3
This is lavaan 0.6-11
lavaan is FREE software! Please report any bugs.
Code
df=sjlabelled::read_spss("data/data_eng.sav")

2.1 Variable definition

The variables in the dataset are coded as follows:

  • rbi - participant code

  • part_gen - participant’s gender (F>M)

  • par_gen - parent’s gender (vignette variant) (F>M)

  • ch_gen - child’s gender (vignette variant) (F>M)

  • age - age of the participant

  • cp_exp - experience with being corporally punished as a child

  • acp1 - acp9 - items for the Attitudes towards Corporal Punishment scale

2.2 Data balancing

Since the data was severely unbalanced by participants sex the female participants were sampled to be equal by number with male participants. The resulting data frame is called df_s.

Code
#create sample for F
set.seed(123456) #for reproducibility
df_s=dplyr::sample_n(df[df$part_gen==1,], size=291)
df_s=rbind(df[df$part_gen==0,], df_s)


#create table with N
or_part=as.data.frame(table(df$part_gen))
colnames(or_part)=c("Gender", "Before")
or_part$Gender=c("Male", "Female")

sh_part=as.data.frame(table(df_s$part_gen))
colnames(sh_part)=c("Gender", "After")

or_part$After=sh_part$After


flextable::flextable(or_part) %>% 
  flextable::add_header(Gender="Gender", Before="N", After="N") %>%
  flextable::merge_h(i=1, part="header") %>%
  flextable::merge_v(j=1, part="header") %>%
  flextable::align(align="center", part="header") %>%
  flextable::set_caption("Participant distribution by gender before and after sampling of female participants")

Participant distribution by gender before and after sampling of female participants

Gender

N

Before

After

Male

291

291

Female

571

291

3 CFA

3.1 Basic CFA

The presumed structure for the Attitudes towards Coproral Punishment - Short Situational Scale was single factor solution. The model was assessed using lavaan package using diagonally weighted least squares estimator that is more appropriate to the data(Li, 2016). The model showed an excellent theoretical fit and the standardized estimates with model fit indices are shown below.

Code
#function for model fit assessment
cp.fm=function(fit){
  a=fitmeasures(fit,fit.measures=c("chisq", "df", "pvalue", "cfi", "rmsea", "rmsea.ci.lower", "rmsea.ci.upper", "srmr"))
  b=list()
  b$chisq=format(round(a[1],2), nsmall=2)
  b$df=format(round(a[2],0), nsmall=0)
  b$p=format(round(a[3],3), nsmall=3)
  b$cfi=format(round(a[4],3), nsmall=3)
  b$rmsea=paste0(format(round(a[5],3), nsmall=3), "[", format(round(a[6],3), nsmall=3), "-", format(round(a[7],3), nsmall=3), "]")
  b$srmr=format(round(a[8],3), nsmall=3)
  return(b)
  }



model='
att_cp =~ acp1 + acp2 + acp3 + acp4 + acp5 + acp6 + acp7 + acp8 + acp9
'
fit=cfa(model= model, data=df_s, estimator="DWLS")
#not shown for brevity. For a detailed view remove comments below
#cp.fm(fit)
#summary(fit, fit.measures=T,standardized=TRUE)

3.2 Measurement invariance

The measurement invariance was assessed for three variables related to the participant gender, parent gender and child gender. The primary method of the invariance testing was to create models for each group (group parameter in cfa function) and then to use constraints for loadings and intercepts. The invariance was assessed by the difference in 𝛘² provided by anova function and changes in cfi, rmsea and srmr parameters. The function for creating the table with the fit parameters is in the code below.

Code
invariance.fit=function(f1,f2,f3){

cp.fm0=cp.fm(f1)
for.table0=c( #original
  paste0(cp.fm0$chisq,"/",cp.fm0$df, "/", cp.fm0$p), 
  cp.fm0$cfi, 
  cp.fm0$rmsea, 
  cp.fm0$srmr, 
  NA)

a1=anova(f1,f2)
cp.fm1=cp.fm(f2)
#weak
for.table1=c(
  paste0(cp.fm1$chisq,"/",cp.fm1$df, "/", cp.fm1$p), 
  cp.fm1$cfi, 
  cp.fm1$rmsea, 
  cp.fm1$srmr, 
  paste0(
    format(round(a1$`Chisq diff`[2], 2), nsmall=2), 
    "/", 
    a1$`Df diff`[2],
    "/",
    format(round(a1$`Pr(>Chisq)`[2],3), nsmall=3)
    )
  ) 

a2=anova(f2,f3)
cp.fm2=cp.fm(f3)
for.table2=c( #weak
  paste0(cp.fm2$chisq,"/",cp.fm2$df, "/", cp.fm2$p), 
  cp.fm2$cfi, 
  cp.fm2$rmsea, 
  cp.fm2$srmr, 
  paste0(format(round(a2$`Chisq diff`[2], 2), nsmall=2), "/", a2$`Df diff`[2],"/",  format(round(a2$`Pr(>Chisq)`[2],3), nsmall=3)) 
)

res=data.frame(Invariance=c("Free", "Weak", "Strong"))
res=cbind(res, rbind(for.table0, for.table1, for.table2))
rownames(res)=NULL

result=list()
result$df=res

r=flextable::flextable(res) %>%
  flextable::set_header_labels(
    values=list(
      Invariance="Invariance",
      V1=paste0("\U1D6D8", "\U00B2", "/df/p"),
      cfi="cfi",
      V3="rmsea[95%CI]",
      srmr="srmr",
      V5=paste0("\U0394", "\U1D6D8", "\U00B2", "/", "\U0394", "df/p") 
  )) %>%
  flextable::align(align="center", part="all") %>%
  flextable::autofit()

result$table=r

colnames(result$df)=c("Invariance", "chi_sq", "cfi", "rmsea", "srmr", "delta_chi_sq")

return(result)
}

3.2.1 Participant gender

Code
fit_part1=cfa(
  model= model, 
  data=df_s, 
  estimator="DWLS", 
  group="part_gen")

fit_part2=cfa(
  model= model, 
  data=df_s, 
  estimator="DWLS", 
  group="part_gen", 
  group.equal=c("loadings"))

fit_part3=cfa(
  model= model, 
  data=df_s, 
  estimator="DWLS", 
  group="part_gen", 
  group.equal=c("loadings", "intercepts"))

invariance.fit(fit_part1, fit_part2, fit_part3)$table

Invariance

𝛘²/df/p

cfi

rmsea[95%CI]

srmr

Δ𝛘²/Δdf/p

Free

28.75/54/0.998

1.000

0.000[0.000-0.000]

0.030

Weak

78.16/62/0.081

0.998

0.030[0.000-0.049]

0.047

49.41/8/0.000

Strong

85.27/70/0.103

0.999

0.027[0.000-0.046]

0.050

7.12/8/0.524

The results show that all three models fit the data well. Although the difference in the models is statistically significant based on the \(\chi^2\) tests, the difference in cfi parameter is less than 0.002, which is the most strict cut-off value as recommended by Kline (2016, p. 401). Regarding the \(\chi^2\) tests it has to be noted that the model fit indices based on the \(\chi^2\) tests show adequate fit of the each model to the data.

3.2.2 Parent gender

The same method was applied to the parent gender variable.

Code
fit_par1=cfa(
  model= model, 
  data=df_s, 
  estimator="DWLS", 
  group="par_gen")

fit_par2=cfa(
  model= model, 
  data=df_s, 
  estimator="DWLS", 
  group="par_gen", 
  group.equal=c("loadings"))

fit_par3=cfa(
  model= model, 
  data=df_s, 
  estimator="DWLS", 
  group="par_gen", 
  group.equal=c("loadings", "intercepts"))

invariance.fit(fit_par1, fit_par2, fit_par3)$table

Invariance

𝛘²/df/p

cfi

rmsea[95%CI]

srmr

Δ𝛘²/Δdf/p

Free

30.79/54/0.995

1.000

0.000[0.000-0.000]

0.030

Weak

50.15/62/0.860

1.000

0.000[0.000-0.020]

0.038

19.37/8/0.013

Strong

53.71/70/0.925

1.000

0.000[0.000-0.011]

0.039

3.56/8/0.894

The results show that the parameter changes for the approximate fit indexes are not supporting the rejection of invariance.

3.2.3 Child gender

The final invariance test was conducted for the child gender.

Code
fit_ch1=cfa(
  model= model, 
  data=df_s, 
  estimator="DWLS", 
  group="ch_gen")

fit_ch2=cfa(
  model= model, 
  data=df_s, 
  estimator="DWLS", 
  group="ch_gen", 
  group.equal=c("loadings"))

fit_ch3=cfa(
  model= model, 
  data=df_s, 
  estimator="DWLS", 
  group="ch_gen", 
  group.equal=c("loadings", "intercepts"))

invariance.fit(fit_ch1, fit_ch2, fit_ch3)$table

Invariance

𝛘²/df/p

cfi

rmsea[95%CI]

srmr

Δ𝛘²/Δdf/p

Free

32.87/54/0.990

1.000

0.000[0.000-0.000]

0.031

Weak

46.29/62/0.932

1.000

0.000[0.000-0.010]

0.037

13.42/8/0.098

Strong

49.73/70/0.968

1.000

0.000[0.000-0.000]

0.038

3.44/8/0.904

The results show no significant changes between models, even when using the \(\chi^2\) tests.

We can conclude that the Attitudes towards Coproral Punishment - Short Situational Scale is functionally invariant regarding the participant gender and the parent’s and child’s gender described in the vignette.

3.3 Scale score calculation

The final step was to assess the method for the creation of the total scale score. The preferred method was to use average result (mean score) on the scale items. This includes the reversal of items 5, 6, 7 and 9. This score was compared to the factor scores as predicted with CFA.

Code
#factor scores
df_s=cbind(df_s, lavaan::predict(fit))

# calculation of total score as items mean

# function for item reversal
rev.code=function(item){
  item=(item-6)*-1
  return(item)
}


df_s$acp5_r=rev.code(df_s$acp5)
df_s$acp6_r=rev.code(df_s$acp6)
df_s$acp7_r=rev.code(df_s$acp7)
df_s$acp9_r=rev.code(df_s$acp9)

#calculate total score as row mean
df_s$acp_mean=rowMeans(df_s%>%select(acp1, acp2, acp3, acp4, acp5_r, acp6_r, acp7_r, acp8, acp9_r))

#correlation of item means with factor scores
score_cor=cor.test(df_s$att_cp, df_s$acp_mean)

The results show that the correlation of scores calculated as items mean and the factor scores is 0.997, thus indicating that the items mean as the scale score calculation method is functionally equal to factor scores and because of practical use the preferred method for the total score calculation.

4 References

References

Kline, R. B. (2016). Principles and practice of structural equation modeling (Fourth edition). The Guilford Press.
Li, C.-H. (2016). The performance of ML, DWLS, and ULS estimation with robust corrections in structural equation models with ordinal variables. Psychological Methods, 21(3), 369–387. https://doi.org/10.1037/met0000093
Lüdecke, D. (2018). Sjlabelled: Labelled Data Utility Functions. Zenodo. https://doi.org/10.5281/ZENODO.1249215
R Core Team. (2021). R: A language and environment for statistical computing. R Foundation for Statistical Computing.
Rosseel, Y. (2012). Lavaan: An R Package for Structural Equation Modeling. Journal of Statistical Software, 48(2). https://doi.org/10.18637/jss.v048.i02
Wickham, H., Averick, M., Bryan, J., Chang, W., McGowan, L., François, R., Grolemund, G., Hayes, A., Henry, L., Hester, J., Kuhn, M., Pedersen, T., Miller, E., Bache, S., Müller, K., Ooms, J., Robinson, D., Seidel, D., Spinu, V., … Yutani, H. (2019). Welcome to the Tidyverse. Journal of Open Source Software, 4(43), 1686. https://doi.org/10.21105/joss.01686