File: interpret_cfa_fit.R

package info (click to toggle)
r-cran-effectsize 0.8.3%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 1,404 kB
  • sloc: sh: 17; makefile: 2
file content (274 lines) | stat: -rw-r--r-- 8,314 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
#' Interpret of CFA / SEM Indices of Goodness of Fit
#'
#' Interpretation of indices of fit found in confirmatory analysis or structural
#' equation modelling, such as RMSEA, CFI, NFI, IFI, etc.
#'
#' @param x vector of values, or an object of class `lavaan`.
#' @param rules Can be `"default"` or custom set of [rules()].
#' @inheritParams interpret
#'
#' @inherit performance::model_performance.lavaan details
#' @inherit performance::model_performance.lavaan references
#'
#' @details
#' ## Indices of fit
#' - **Chisq**: The model Chi-squared assesses overall fit and the discrepancy
#' between the sample and fitted covariance matrices. Its p-value should be >
#' .05 (i.e., the hypothesis of a perfect fit cannot be rejected). However, it
#' is quite sensitive to sample size.
#'
#' - **GFI/AGFI**: The (Adjusted) Goodness of Fit is the proportion of variance
#' accounted for by the estimated population covariance. Analogous to R2. The
#' GFI and the AGFI should be > .95 and > .90, respectively.
#'
#' - **NFI/NNFI/TLI**: The (Non) Normed Fit Index. An NFI of 0.95, indicates the
#' model of interest improves the fit by 95\% relative to the null model. The
#' NNFI (also called the Tucker Lewis index; TLI) is preferable for smaller
#' samples. They should be > .90 (Byrne, 1994) or > .95 (Schumacker & Lomax,
#' 2004).
#'
#' - **CFI**: The Comparative Fit Index is a revised form of NFI. Not very
#' sensitive to sample size (Fan, Thompson, & Wang, 1999). Compares the fit of a
#' target model to the fit of an independent, or null, model. It should be >
#' .90.
#'
#' - **RMSEA**: The Root Mean Square Error of Approximation is a
#' parsimony-adjusted index. Values closer to 0 represent a good fit. It should
#' be < .08 or < .05. The p-value printed with it tests the hypothesis that
#' RMSEA is less than or equal to .05 (a cutoff sometimes used for good fit),
#' and thus should be not significant.
#'
#' - **RMR/SRMR**: the (Standardized) Root Mean Square Residual represents the
#' square-root of the difference between the residuals of the sample covariance
#' matrix and the hypothesized model. As the RMR can be sometimes hard to
#' interpret, better to use SRMR. Should be < .08.
#'
#' - **RFI**: the Relative Fit Index, also known as RHO1, is not guaranteed to
#' vary from 0 to 1. However, RFI close to 1 indicates a good fit.
#'
#' - **IFI**: the Incremental Fit Index (IFI) adjusts the Normed Fit Index (NFI)
#' for sample size and degrees of freedom (Bollen's, 1989). Over 0.90 is a good
#' fit, but the index can exceed 1.
#'
#' - **PNFI**: the Parsimony-Adjusted Measures Index. There is no commonly
#' agreed-upon cutoff value for an acceptable model for this index. Should be >
#' 0.50.
#'
#' See the documentation for \code{\link[lavaan:fitmeasures]{fitmeasures()}}.
#'
#'
#' ## What to report
#' For structural equation models (SEM), Kline (2015) suggests that at a minimum
#' the following indices should be reported: The model **chi-square**, the
#' **RMSEA**, the **CFI** and the **SRMR**.
#'
#' @note When possible, it is recommended to report dynamic cutoffs of fit
#'   indices. See https://dynamicfit.app/cfa/.
#'
#'
#' @examples
#' interpret_gfi(c(.5, .99))
#' interpret_agfi(c(.5, .99))
#' interpret_nfi(c(.5, .99))
#' interpret_nnfi(c(.5, .99))
#' interpret_cfi(c(.5, .99))
#' interpret_rmsea(c(.07, .04))
#' interpret_srmr(c(.5, .99))
#' interpret_rfi(c(.5, .99))
#' interpret_ifi(c(.5, .99))
#' interpret_pnfi(c(.5, .99))
#'
#' @examplesIf require("lavaan") && interactive()
#' # Structural Equation Models (SEM)
#' structure <- " ind60 =~ x1 + x2 + x3
#'                dem60 =~ y1 + y2 + y3
#'                dem60 ~ ind60 "
#'
#' model <- lavaan::sem(structure, data = lavaan::PoliticalDemocracy)
#'
#' interpret(model)
#'
#' @references
#' - Awang, Z. (2012). A handbook on SEM. Structural equation modeling.
#'
#' - Byrne, B. M. (1994). Structural equation modeling with EQS and EQS/Windows.
#' Thousand Oaks, CA: Sage Publications.
#'
#' - Tucker, L. R., and Lewis, C. (1973). The reliability coefficient for maximum
#' likelihood factor analysis. Psychometrika, 38, 1-10.
#'
#' - Schumacker, R. E., and Lomax, R. G. (2004). A beginner's guide to structural
#' equation modeling, Second edition. Mahwah, NJ: Lawrence Erlbaum Associates.
#'
#' - Fan, X., B. Thompson, and L. Wang (1999). Effects of sample size, estimation
#' method, and model specification on structural equation modeling fit indexes.
#' Structural Equation Modeling, 6, 56-83.
#'
#' - Kline, R. B. (2015). Principles and practice of structural equation
#' modeling. Guilford publications.
#'
#'
#' @keywords interpreters
#' @export
interpret_gfi <- function(x, rules = "default") {
  rules <- .match.rules(
    rules,
    list(
      default = rules(c(0.95), c("poor", "satisfactory"), name = "default", right = FALSE)
    )
  )

  interpret(x, rules)
}


#' @rdname interpret_gfi
#' @export
interpret_agfi <- function(x, rules = "default") {
  rules <- .match.rules(
    rules,
    list(
      default = rules(c(0.90), c("poor", "satisfactory"), name = "default", right = FALSE)
    )
  )

  interpret(x, rules)
}


#' @rdname interpret_gfi
#' @export
interpret_nfi <- function(x, rules = "byrne1994") {
  rules <- .match.rules(
    rules,
    list(
      byrne1994 = rules(c(0.90), c("poor", "satisfactory"), name = "byrne1994", right = FALSE),
      schumacker2004 = rules(c(0.95), c("poor", "satisfactory"), name = "schumacker2004", right = FALSE)
    )
  )

  interpret(x, rules)
}

#' @rdname interpret_gfi
#' @export
interpret_nnfi <- interpret_nfi


#' @rdname interpret_gfi
#' @export
interpret_cfi <- function(x, rules = "default") {
  rules <- .match.rules(
    rules,
    list(
      default = rules(c(0.90), c("poor", "satisfactory"), name = "default", right = FALSE)
    )
  )

  interpret(x, rules)
}




#' @rdname interpret_gfi
#' @export
interpret_rmsea <- function(x, rules = "default") {
  rules <- .match.rules(
    rules,
    list(
      default = rules(c(0.05), c("satisfactory", "poor"), name = "default"),
      awang2012 = rules(c(0.05, 0.08), c("good", "satisfactory", "poor"), name = "awang2012")
    )
  )

  interpret(x, rules)
}


#' @rdname interpret_gfi
#' @export
interpret_srmr <- function(x, rules = "default") {
  rules <- .match.rules(
    rules,
    list(
      default = rules(c(0.08), c("satisfactory", "poor"), name = "default")
    )
  )

  interpret(x, rules)
}

#' @rdname interpret_gfi
#' @export
interpret_rfi <- function(x, rules = "default") {
  rules <- .match.rules(
    rules,
    list(
      default = rules(c(0.90), c("poor", "satisfactory"), name = "default", right = FALSE)
    )
  )

  interpret(x, rules)
}

#' @rdname interpret_gfi
#' @export
interpret_ifi <- function(x, rules = "default") {
  rules <- .match.rules(
    rules,
    list(
      default = rules(c(0.90), c("poor", "satisfactory"), name = "default", right = FALSE)
    )
  )

  interpret(x, rules)
}

#' @rdname interpret_gfi
#' @export
interpret_pnfi <- function(x, rules = "default") {
  rules <- .match.rules(
    rules,
    list(
      default = rules(c(0.50), c("poor", "satisfactory"), name = "default")
    )
  )

  interpret(x, rules)
}


# lavaan ------------------------------------------------------------------

#' @rdname interpret_gfi
#' @export
interpret.lavaan <- function(x, ...) {
  interpret(performance::model_performance(x, ...), ...)
}

#' @rdname interpret_gfi
#' @export
interpret.performance_lavaan <- function(x, ...) {
  mfits <- c(
    "GFI", "AGFI", "NFI", "NNFI",
    "CFI", "RMSEA", "SRMR", "RFI",
    "IFI", "PNFI"
  )
  mfits <- intersect(names(x), mfits)

  table <- lapply(mfits, function(ind_name) {
    .interpret_ind <- eval(parse(text = paste0("interpret_", tolower(ind_name))))
    interp <- .interpret_ind(x[[ind_name]])
    rules <- attr(interp, "rules")
    data.frame(
      Name = ind_name,
      Value = x[[ind_name]],
      Threshold = rules$values,
      Interpretation = interp,
      stringsAsFactors = FALSE
    )
  })

  do.call(rbind, table)
}