---
title: "Structural Models (EFA, CFA, SEM...)"
output: 
  github_document:
    toc: true
    fig_width: 10.08
    fig_height: 6
  rmarkdown::html_vignette:
    toc: true
    fig_width: 10.08
    fig_height: 6
tags: [r, parameters, efa, cfa, factor analysis, sem, fa, pca, how many factors, n factors]
vignette: >
  %\VignetteIndexEntry{Structural Models (EFA, CFA, SEM, ...)}
  \usepackage[utf8]{inputenc}
  %\VignetteEngine{knitr::rmarkdown}
editor_options: 
  chunk_output_type: console
bibliography: bibliography.bib
---

```{r message=FALSE, warning=FALSE, include=FALSE}
library(knitr)
options(knitr.kable.NA = '')
options(digits = 2)
knitr::opts_chunk$set(comment = "#>")

if (!requireNamespace("dplyr", quietly = TRUE) ||
    !requireNamespace("see", quietly = TRUE) ||
    !requireNamespace("lavaan", quietly = TRUE) ||
    !requireNamespace("performance", quietly = TRUE) ||
    !requireNamespace("psych", quietly = TRUE)) {
  knitr::opts_chunk$set(eval = FALSE)
}

set.seed(333)
```


# How to perform a Factor Analysis (FA)

The difference between PCA and EFA can be quite hard to intuitively grasp as their output is very familiar. The idea is that PCA aims at extracting the most variance possible from all variables of the dataset, whereas EFA aims at creating consistent factors from the dataset without desperately trying to represent all the variables. 

This is why PCA is popular for feature reduction, as it will try to best represent the variance contained in the original data, minimizing the loss of information. On the other hand, EFA is usually in the context of exploring the latent dimensions that might be hidden in the observed variables, without necessary striving at representing the whole dataset.

To illustrate EFA, let us use the [International Personality Item Pool](https://ipip.ori.org/) data available in the [`psych`](https://www.personality-project.org/r/html/bfi.html) package. It includes 25 personality self report items. The authors built these items following the **big 5** personality structure.

## Factor Structure (Sphericity and KMO)

The first step is to test the dataset for factor analysis suitability. Two existing methods are the **Bartlett's Test of Sphericity** and the **Kaiser, Meyer, Olkin (KMO) Measure of Sampling Adequacy (MSA)**. The former tests whether a matrix is significantly different from an identity matrix. This statistical test for the presence of correlations among variables, providing the statistical probability that the correlation matrix has significant correlations among at least some of variables. As for factor analysis to work, some relationships between variables are needed, thus, a significant Bartlett's test of sphericity is required, say *p* < .001. The latter was introduced by Kaiser (1970) as the Measure of Sampling Adequacy (MSA), later modified by Kaiser and Rice (1974). The Kaiser-Meyer-Olkin (KMO) statistic, which can vary from 0 to 1, indicates the degree to which each variable in a set is predicted without error by the other variables. A value of 0 indicates that the sum of partial correlations is large relative to the sum correlations, indicating factor analysis is likely to be inappropriate. A KMO value close to 1 indicates that the sum of partial correlations is not large relative to the sum of correlations and so factor analysis should yield distinct and reliable factors.

Both tests can be performed by using the `check_factorstructure()` function.

```{r message=FALSE, warning=FALSE}
library(parameters)
library(dplyr)
library(psych)

# Load the data
data <- psych::bfi[, 1:25]  # Select only the 25 first columns corresponding to the items
data <- na.omit(data)  # remove missing values

# Check factor structure
check_factorstructure(data)
```



## Exploratory Factor Analysis (EFA)

Now that we are confident that our dataset is appropriate, we will explore a factor structure made of 5 latent variables, corresponding to the items' authors theory of personality.

```{r message=FALSE, warning=FALSE}
# Fit an EFA
efa <- psych::fa(data, nfactors = 5) %>% 
  model_parameters(sort = TRUE, threshold = "max")
efa
```

As we can see, the 25 items nicely spread on the 5 latent factors, the famous **big 5**. Based on this model, we can now predict back the scores for each individual for these new variables:

```{r message=FALSE, warning=FALSE, eval=FALSE}
predict(efa, names = c("Neuroticism", "Conscientiousness", "Extraversion", "Agreeableness", "Opennness"))
```
```{r message=FALSE, warning=FALSE, echo=FALSE}
head(predict(efa, names = c("Neuroticism", "Conscientiousness", "Extraversion", "Agreeableness", "Opennness")), 5)
```

## How many factors to retain in Factor Analysis (FA)


When running a **factor analysis (FA)**, one often needs to specify **how many components** (or latent variables) to retain or to extract. This decision is often motivated or supported by some statistical indices and procedures aiming at finding the optimal number of factors. 

Interestingly, a huge amount of methods exist to statistically address this issue, giving sometimes very different results... Unfortunately, there is no consensus on **which method to use**, or which is the best.


### The Method Agreement procedure

The Method Agreement procedure, first implemented in the [`psycho`](https://neuropsychology.github.io/psycho.R/2018/05/24/n_factors.html) package [@makowski2018psycho], proposes to rely on the consensus of methods, rather than on one method in particular.


This procedure can be easily used via the `n_factors()` function, re-implemented and improved in the [**parameters**](https://github.com/easystats/parameters) package. One can provide a dataframe, and the function will run a large number of routines and return the optimal number of factors based on the higher consensus.  


```{r message=FALSE, warning=FALSE}
n <- n_factors(data)
n
```

Interestingly, the smallest nubmer of factors that most methods suggest is 6... Which is consistent whith the newer models of personality (e.g., HEXACO).

More details, as well as a summary table can be obtained as follows:

```{r message=FALSE, warning=FALSE}
as.data.frame(n)
summary(n)
```


A plot can also be obtained (the `see` package must be loaded):

```{r message=FALSE, warning=FALSE}
library(see)

plot(n) + theme_modern()
```


## Confirmatory Factor Analysis (CFA)

We've seen above that while an EFA with 5 latent variables works great on our dataset, a structure with 6 latent factors might in fact be more appropriate. How can we **statistically test** if that's actually the case? This can be done using Confirmatory Factor Analysis (CFA), that bridges factor analysis with Structural Equation Modelling (SEM).

However, in order to do that cleanly, EFA should be independent from CFA, in the sense that the factor structure should be explored on a **"training" set**, and then tested (or "confirmed") on a **test set**. In other words, the dataset used for exploration and confirmation is not the same. Note that this procedure is also standard in the field of machine learning.

### Partition the data

The data can be easily split into two sets with the `data_partition()` function, through which we will use 70\% of the sample as training and the rest as test.

```{r message=FALSE, warning=FALSE}
partitions <- data_partition(data, training_proportion = 0.7)
training <- partitions$training
test <- partitions$test
```

### Create CFA structures out of EFA models

In the next step, we will run two EFA models on the training set, specifying 5 and 6 latent factors respectively, that we will then transform into CFA structures. 

```{r message=FALSE, warning=FALSE}
structure_big5 <- psych::fa(training, nfactors = 5) %>% 
  efa_to_cfa()
structure_big6 <- psych::fa(training, nfactors = 6)  %>% 
  efa_to_cfa()

# Investigate how a model looks
structure_big5
```

As we can see, a structure is just a string encoding how the **manifest variables** (the observed variables) are integrated into latent variables.


### Fit and Compare models

We can finally with that structure to the test set using the `lavaan` package, and compare these models together:

```{r message=FALSE, warning=FALSE}
library(lavaan)
library(performance)

big5 <- lavaan::cfa(structure_big5, data = test)
big6 <- lavaan::cfa(structure_big6, data = test)

performance::compare_performance(big5, big6)
```

All in all, it seems that the big 5 structure remains quite reliable.


# References
