Link Search Menu Expand Document

Easy Model Fit Overview & Table

meval() function interprets the modelfit from a lavaan object. Typical interpretation cutoffs are used (Hu and Bentler, 1999). Regular fit estimates and scaled fit estimates (Satorra & Bentler, 2001; 2010) are reported.
meval.table(models, names) function outputs a table with model fits, ready for publication

Basic R function to call:

library(devtools)
source_url("https://lucasmaunz.org/download/meval.R")

Alternatively: Download this script.

See here how to use meval()
See here how to use meval.table()

#function to evaluate model fit from lavaan object with easy interpretation
#fit is a lavaan fit object
#scaled requires MLM or MLR scaled estimators
#if standard estimators are used, define scaled=FALSE
meval <- function(fit, scaled=FALSE) {
#packages
if (!require("lavaan")) install.packages("lavaan")
if (!require("dplyr")) install.packages("dplyr")
require(lavaan)
require(dplyr)
is.logical(scaled)
  
if (scaled) {
#column regular fit estimate
r1 <- rbind(
data.frame(estimate = lavaan::fitMeasures(fit)) %>%
  subset(., rownames(.) %in% c("chisq","df", "pvalue")) %>%
  rbind(., "cmin/df" = .["chisq",]/.["df",]), 
data.frame(estimate = lavaan::fitMeasures(fit)) %>%
  subset(., rownames(.) %in% c("cfi","tli", "rmsea", "srmr"))
      )
#round
r1[,"estimate"] <- round(r1[,"estimate"], 2)
#add second column with interpretation
r1 <- cbind(r1,interpretation =
  c("/","/","/",
    if (r1["cmin/df",] <= 3){"excellent"
       } else if(r1["cmin/df",] > 3 & r1["cmin/df",] <= 5){"acceptable"
       } else if(r1["cmin/df",] > 5){"terrible"},
    if (r1["cfi",] >= .95){"excellent"
       } else if(r1["cfi",] < .95 & r1["cfi",] >= .90){"acceptable"
       } else if(r1["cfi",] < .90){"terrible"},
    if (r1["tli",] >= .95){"excellent"
       } else if(r1["tli",] < .95 & r1["tli",] >= .90){"acceptable"
       } else if(r1["tli",] < .90){"terrible"},
    if (r1["rmsea",] <= .06){"excellent"
       } else if(r1["rmsea",] > .06 & r1["rmsea",] <= .08){"acceptable"
       } else if(r1["rmsea",] > .08){"terrible"}, 
    if (r1["srmr",] <= .08){"excellent"
       } else if(r1["srmr",] > .08 & r1["srmr",] <= .1){"acceptable"
       } else if(r1["srmr",] > .1){"terrible"}))


#second table with Satorra & Bentler, 2010 scaled fit estimates
r2 <- rbind(
  data.frame(estimate = lavaan::fitMeasures(fit)) %>%
    subset(., rownames(.) %in% c("chisq.scaled","df.scaled", "pvalue.scaled")) %>%
    rbind(., "cmin/df" = .["chisq.scaled",]/.["df.scaled",]), 
  data.frame(estimate = lavaan::fitMeasures(fit)) %>%
    subset(., rownames(.) %in% c("cfi.scaled","tli.scaled", "rmsea.scaled", "srmr_bentler"))
)
#round
r2[,"estimate"] <- round(r2[,"estimate"], 2)
#add second column with interpretation
r2 <- cbind(r2,interpretation =
              c("/","/","/",
                if (r2["cmin/df",] <= 3){"excellent"
                } else if(r2["cmin/df",] > 3 & r2["cmin/df",] <= 5){"acceptable"
                } else if(r2["cmin/df",] > 5){"terrible"},
                if (r2["cfi.scaled",] >= .95){"excellent"
                } else if(r2["cfi.scaled",] < .95 & r2["cfi.scaled",] >= .90){"acceptable"
                } else if(r2["cfi.scaled",] < .90){"terrible"},
                if (r2["tli.scaled",] >= .95){"excellent"
                } else if(r2["tli.scaled",] < .95 & r2["tli.scaled",] >= .90){"acceptable"
                } else if(r2["tli.scaled",] < .90){"terrible"},
                if (r2["rmsea.scaled",] <= .06){"excellent"
                } else if(r2["rmsea.scaled",] > .06 & r2["rmsea.scaled",] <= .08){"acceptable"
                } else if(r2["rmsea.scaled",] > .08){"terrible"}, 
                if (r2["srmr_bentler",] <= .08){"excellent"
                } else if(r2["srmr_bentler",] > .08 & r2["srmr_bentler",] <= .1){"acceptable"
                } else if(r2["srmr_bentler",] > .1){"terrible"}))

#output
return(list("regular" = r1, "scaled" = r2))}

  else{
    r1 <- rbind(
      data.frame(estimate = lavaan::fitMeasures(fit)) %>%
        subset(., rownames(.) %in% c("chisq","df", "pvalue")) %>%
        rbind(., "cmin/df" = .["chisq",]/.["df",]), 
      data.frame(estimate = lavaan::fitMeasures(fit)) %>%
        subset(., rownames(.) %in% c("cfi","tli", "rmsea", "srmr"))
    )
    #round
    r1[,"estimate"] <- round(r1[,"estimate"], 2)
    #add second column with interpretation
    r1 <- cbind(r1,interpretation =
                  c("/","/","/",
                    if (r1["cmin/df",] <= 3){"excellent"
                    } else if(r1["cmin/df",] > 3 & r1["cmin/df",] <= 5){"acceptable"
                    } else if(r1["cmin/df",] > 5){"terrible"},
                    if (r1["cfi",] >= .95){"excellent"
                    } else if(r1["cfi",] < .95 & r1["cfi",] >= .90){"acceptable"
                    } else if(r1["cfi",] < .90){"terrible"},
                    if (r1["tli",] >= .95){"excellent"
                    } else if(r1["tli",] < .95 & r1["tli",] >= .90){"acceptable"
                    } else if(r1["tli",] < .90){"terrible"},
                    if (r1["rmsea",] <= .06){"excellent"
                    } else if(r1["rmsea",] > .06 & r1["rmsea",] <= .08){"acceptable"
                    } else if(r1["rmsea",] > .08){"terrible"}, 
                    if (r1["srmr",] <= .08){"excellent"
                    } else if(r1["srmr",] > .08 & r1["srmr",] <= .1){"acceptable"
                    } else if(r1["srmr",] > .1){"terrible"}))
    
    return(list("regular" = r1))}}
   
#------------------    
#function to output models to talbe
#models is a list of fitted lavaan objects e.g., c(fit1, fit2, ...)
#names is names of fitted lavaan objects e.g., c("name1", "name2",...)
meval.table <- function(models, names, scaled=FALSE){

#option if scaled or regular
is.logical(scaled)

if (scaled) {
  lapply(models, meval, scaled=T) %>%
    sapply(., function(x)x["scaled"])  %>%
    sapply(., function(x)x["estimate"]) %>%
    data.frame(.) %>%
    setNames(names) %>%
    t() %>%
    as.data.frame() %>%
    setNames(c("chisq.scaled", "df.scaled", "pvalue.scaled", "cmin/df" ,"cfi.scaled", "tli.scaled", "rmsea.scaled", "srmr_bentler")) %>%
    select(, -pvalue.scaled, -tli.scaled)
}else {
  lapply(models, meval) %>%
    sapply(., function(x)x["regular"])  %>%
    sapply(., function(x)x["estimate"]) %>%
    data.frame(.) %>%
    setNames(names) %>%
    t() %>%
    as.data.frame() %>%
    setNames(c("chisq", "df", "pvalue", "cmin/df" ,"cfi", "tli", "rmsea", "srmr")) %>%
    select(, -pvalue, -tli)
  }
}