R中factanal()处理高维小样本因子分析遇计算奇异错误的解决咨询
碰到这个问题太正常了——150个变量却只有80个观测,变量数远多于样本量,协方差矩阵必然是奇异的(秩最多只有80),factanal()里的solve.default()要对这个矩阵求逆,自然会触发报错。我给你几个实用的解决思路,附虚拟数据集演示:
当变量数(p)> 观测数(n)时,样本协方差矩阵的秩最大为n-1,远小于p,矩阵不可逆,这就是solve.default()报错的核心原因。常规的因子分析依赖协方差矩阵的逆,所以直接用factanal()走不通,得换鲁棒性更强的方法。
1. 优先用psych包的正则化因子分析
psych包的fa()函数专门针对高维小样本场景做了优化,支持ridge正则化、主轴因子法等不需要矩阵求逆的方法,完全能避开奇异性问题。
先生成和你场景匹配的虚拟数据集:
set.seed(123) # 固定随机种子,结果可复现 # 生成150个变量×3个潜在因子的载荷矩阵 loadings <- matrix(rnorm(150*3, 0, 0.6), nrow = 150) # 生成80个观测的潜在因子得分 latent_factors <- matrix(rnorm(80*3, 0, 1), nrow = 80) # 生成观测数据:潜在因子×载荷 + 随机噪声 X <- latent_factors %*% t(loadings) + matrix(rnorm(80*150, 0, 0.3), nrow = 80) df <- as.data.frame(X) # 转成数据框
然后用psych::fa()做分析:
library(psych) # 用最小残差法+ridge正则化,指定3个因子 fa_result <- fa(df, nfactors = 3, fm = "minres", ridge = 0.01) # 查看简化后的结果 print(fa_result, digits = 2)
fm="minres":最小残差法,不需要协方差矩阵的逆,对奇异数据友好;ridge=0.01:添加微小的正则化项,进一步稳定矩阵计算;- 你也可以试试
fm="pa"(主轴因子法),同样不需要求逆,是高维数据的常用选择。
2. 先做PCA降维,再用factanal()
如果一定要用factanal(),可以先通过PCA把高维变量压缩成少数主成分,让主成分数<观测数,再做因子分析:
# 标准化数据后做PCA,提取前20个主成分(解释大部分方差) pca_result <- prcomp(df, scale. = TRUE) # 提取主成分得分 pca_scores <- pca_result$x[, 1:20] # 对主成分做因子分析(此时20<80,矩阵可逆) fa_pca <- factanal(pca_scores, factors = 3) print(fa_pca)
这个思路适合你需要保留factanal()输出格式的场景,缺点是会损失部分原始变量的信息。
3. 正则化修正协方差矩阵,再喂给factanal()
不用修改factanal()源码,你可以先手动给协方差矩阵加微小的对角项(ridge修正),让矩阵可逆:
# 计算原始协方差矩阵 cov_mat <- cov(df) # 添加ridge修正:给对角元素加0.01,避免奇异 cov_reg <- cov_mat + diag(ncol(df)) * 0.01 # 用修正后的协方差矩阵做因子分析,指定观测数 fa_reg <- factanal(covmat = cov_reg, factors = 3, n.obs = 80) print(fa_reg)
这个方法最贴近你原来的思路,但正则化参数(0.01)需要根据数据调整,避免引入过多偏差。
4. 用IRT模型(适合问卷类数据)
如果你的变量是Likert评分这类分类数据,mirt包的多维IRT模型本质也是提取潜在因子,且对高维小样本的兼容性很好:
library(mirt) # 把虚拟连续数据转成5级Likert评分 df_likert <- apply(df, 2, function(x) cut(x, breaks = 5, labels = 1:5)) # 拟合3因子IRT模型 irt_result <- mirt(df_likert, model = 3, itemtype = "graded") summary(irt_result)
最推荐的是psych包的fa()函数,它不需要修改源码,直接支持高维小样本的因子分析,输出结果也和factanal()类似,方便你对比。
内容的提问来源于stack exchange,提问作者Chenying Gao

