因子分析適合處理這樣一類社會科學研究問題:問卷中有許多彼此相關的題項,但研究者希望將它們歸并為少數幾個具有理論含義的潛在維度。例如,多個關于學習投入、學業壓力或組織認同的題項,可能分別反映更少數量的潛在因子。使用 R 完成因子分析時,真正困難的地方通常不在于運行一條函數,而在于確定哪些變量可以進入模型、提取幾個因子、如何解釋載荷,以及如何把分析結果轉化為后續回歸或結構模型中的變量。
下面以問卷題項為例,介紹一套可以寫入論文方法部分的完整流程。示例中的變量名使用 q1、q2 等占位符,實際分析時應替換為自己的題項名稱。
一、先明確因子分析的研究目的
因子分析不是簡單的“把變量壓縮成幾個變量”。在開始運行 R 之前,應先回答兩個問題。
第一,題項是否具有共同的潛在結構?如果每個題項都在測量完全不同的內容,強行進行因子分析并不能產生有意義的因子。
第二,因子結果將如何用于后續研究?如果因子分數要進入回歸、相關分析或組間比較,就需要在提取因子時關注穩定性、解釋性和可復現性,而不能只選擇能夠最大限度解釋方差的方案。
社會科學問卷中的題項通常來自理論構念,因此因子數量不能完全交給統計軟件決定。統計指標可以幫助判斷合理范圍,但最終方案應同時考慮理論預期、題項內容和載荷結果。
二、導入數據并檢查變量編碼
假設數據保存在一個數據文件中,且每一行代表一名受訪者,每一列代表一個題項。首先導入數據,并檢查數據的基本結構。
# 讀取數據
dat <- read.csv("survey_data.csv",
stringsAsFactors = FALSE,
na.strings = c("", "NA", "N/A", "缺失"))
# 查看數據結構
str(dat)
dim(dat)
head(dat)
# 查看每個變量的缺失數量
colSums(is.na(dat))
因子分析所使用的變量應當是數值型。如果問卷答案被讀成字符或因子,需要先轉換為數值。需要特別小心的是,直接使用 as.numeric() 轉換因子變量,可能得到內部編碼,而不是題項原本的分值。
# 查看變量類型
sapply(dat, class)
# 如果原始變量是字符型,確認其內容確實為數值后再轉換
items <- c("q1", "q2", "q3", "q4", "q5", "q6",
"q7", "q8", "q9", "q10")
dat[items] <- lapply(dat[items], function(x) {
as.numeric(trimws(x))
})
# 檢查轉換后的取值
lapply(dat[items], function(x) sort(unique(x)))
如果題項采用五級或七級量表,應確認所有變量的編碼方向一致。例如,有些題目分值越高表示認同程度越高,另一些反向題可能分值越高表示認同程度越低。反向題如果未處理,會降低相關性,甚至導致因子結構被錯誤分裂。
以五點量表為例,假設 q3 和 q7 是反向題:
# 五點量表的反向計分
dat$q3_r <- 6 - dat$q3
dat$q7_r <- 6 - dat$q7
# 后續使用反向處理后的變量
items <- c("q1", "q2", "q3_r", "q4", "q5",
"q6", "q7_r", "q8", "q9", "q10")
反向計分公式必須根據量表范圍調整。對于最低分為 min_score、最高分為 max_score 的量表,反向后的分數通常可按下式計算:
dat$reverse_item <- max_score + min_score - dat$original_item
不要在沒有核對問卷編碼說明的情況下直接反向處理題項。題目措辭中的否定表達,并不一定意味著需要反向計分,最終應以量表設計和編碼規則為準。
三、篩選進入模型的題項
檢查取值范圍和異常編碼
先檢查每個題項的最小值、最大值、均值和缺失情況。
item_summary <- data.frame(
variable = items,
n = sapply(dat[items], function(x) sum(!is.na(x))),
missing = sapply(dat[items], function(x) sum(is.na(x))),
min = sapply(dat[items], function(x) min(x, na.rm = TRUE)),
max = sapply(dat[items], function(x) max(x, na.rm = TRUE)),
mean = sapply(dat[items], function(x) mean(x, na.rm = TRUE)),
sd = sapply(dat[items], function(x) sd(x, na.rm = TRUE))
)
item_summary
如果量表范圍為一到五,卻出現了九十九、負值或其他異常編碼,應先將這些編碼轉換為缺失值,再進行分析。
invalid_codes <- c(99, 999, -9)
dat[items] <- lapply(dat[items], function(x) {
x[x %in% invalid_codes] <- NA
x
})
不能把異常編碼當成普通分值參與相關矩陣計算,否則相關系數和因子載荷都可能失真。
檢查題項之間的相關性
因子分析要求題項之間存在一定程度的相關。如果所有題項之間都幾乎沒有相關性,提取出的因子通常缺少解釋意義。
cor_mat <- cor(dat[items],
use = "pairwise.complete.obs",
method = "pearson")
round(cor_mat, 2)
如果使用成對刪除處理缺失值,不同相關系數可能基于不同的樣本量。缺失較多時,應在報告中說明缺失處理方式,并檢查相關矩陣是否仍然穩定。對于題項缺失較少的情況,也可以使用完整案例進行分析:
dat_complete <- dat[complete.cases(dat[items]), ]
cor_mat_complete <- cor(dat_complete[items],
method = "pearson")
round(cor_mat_complete, 2)
不能僅憑某一個相關系數決定刪除題項。題項之間的相關性還要結合題目內容、理論歸屬和后續載荷共同判斷。
四、評估數據是否適合因子分析
計算 KMO 指標
KMO 指標用于比較題項之間的普通相關與偏相關。整體 KMO 越高,說明題項之間更可能存在共同因子結構。下面使用 R 代碼計算整體 KMO 和各題項的 KMO。
kmo_result <- function(R) {
R_inv <- solve(R)
# 由逆相關矩陣計算偏相關矩陣
P <- -R_inv / sqrt(outer(diag(R_inv), diag(R_inv)))
diag(P) <- 1
r2 <- R^2
p2 <- P^2
# 去除對角線
diag(r2) <- 0
diag(p2) <- 0
overall_kmo <- sum(r2) / (sum(r2) + sum(p2))
item_kmo <- rowSums(r2) / (rowSums(r2) + rowSums(p2))
list(
overall = overall_kmo,
item = item_kmo
)
}
kmo <- kmo_result(cor_mat_complete)
kmo$overall
kmo$item
KMO 較低時,先不要急于刪除變量。應檢查是否存在以下問題:題項內容本身不屬于同一構念、反向題沒有正確處理、樣本量過少、變量取值幾乎沒有變化,或者相關矩陣存在嚴重異常。
計算 Bartlett 球形檢驗
Bartlett 球形檢驗用于檢驗相關矩陣是否顯著不同于單位矩陣。其原假設是各題項之間不存在總體相關。
bartlett_test <- function(R, n) {
p <- ncol(R)
determinant_value <- det(R)
chi_square <- -(n - 1 - (2 * p + 5) / 6) *
log(determinant_value)
df <- p * (p - 1) / 2
p_value <- pchisq(chi_square, df = df, lower.tail = FALSE)
data.frame(
chi_square = chi_square,
df = df,
p_value = p_value
)
}
bartlett_test(
R = cor_mat_complete,
n = nrow(dat_complete)
)
如果相關矩陣接近單位矩陣,因子分析通常缺乏基礎。相反,檢驗顯著也不能單獨證明因子分析一定合理,因為樣本量較大時,很小的相關也可能達到顯著水平。因此,KMO、相關矩陣、題項內容和后續因子載荷應結合判斷。
五、確定因子數量
因子數量是因子分析中最需要研究者判斷的環節之一。可以先比較理論預期和不同因子數量下的模型表現,再觀察結果是否具有清晰解釋。
使用特征值進行初步判斷
eigen_result <- eigen(cor_mat_complete)
eigen_values <- eigen_result$values
data.frame(
factor = seq_along(eigen_values),
eigenvalue = eigen_values,
explained_percent = eigen_values / sum(eigen_values) * 100,
cumulative_percent = cumsum(eigen_values / sum(eigen_values) * 100)
)
特征值可以幫助觀察相關矩陣中主要維度的數量,但不應機械地把特征值大于一的維度全部保留。該標準容易在題項較多時提取過多因子,也可能產生難以解釋的結構。
比較不同因子數量的可解釋性
假設理論上可能存在二到四個因子,可以分別運行模型:
fa_2 <- factanal(
x = dat_complete[items],
factors = 2,
rotation = "promax",
scores = "regression"
)
fa_3 <- factanal(
x = dat_complete[items],
factors = 3,
rotation = "promax",
scores = "regression"
)
fa_4 <- factanal(
x = dat_complete[items],
factors = 4,
rotation = "promax",
scores = "regression"
)
fa_2
fa_3
fa_4
比較時重點關注以下問題:
- 每個因子是否至少有若干個具有清晰載荷的題項;
- 同一個因子中的題項是否具有共同的理論含義;
- 是否存在一個因子只有一個題項或極少題項;
- 是否有大量題項在多個因子上同時具有較高載荷;
- 增加因子后,解釋是否變得更清晰,還是只是把相近題項機械拆開。
如果多個因子之間理論上可能相關,應優先考慮斜交旋轉,例如 promax。如果研究者有充分理由認為因子彼此獨立,可以考慮正交旋轉,例如 varimax。社會科學構念往往存在相關,因此不應默認所有因子完全獨立。
六、運行因子分析并選擇旋轉方式
使用最大似然法提取因子
factanal() 可以使用最大似然方法提取因子。下面以三個因子為例:
fa_model <- factanal(
x = dat_complete[items],
factors = 3,
rotation = "promax",
scores = "regression"
)
print(fa_model,
cutoff = 0.30,
sort = TRUE)
cutoff = 0.30 只影響打印結果,不會改變模型本身。為了判斷題項在不同因子上的真實表現,也可以直接查看載荷矩陣:
loadings_matrix <- as.matrix(fa_model$loadings)
round(loadings_matrix, 3)
載荷可以理解為題項與因子的關系強度。載荷絕對值較高,通常說明題項與該因子聯系較緊密,但不能只根據某個統一數值機械刪除題項。載荷的解釋還需要結合題項內容、樣本量、因子數量和理論預期。
比較正交旋轉與斜交旋轉
fa_varimax <- factanal(
x = dat_complete[items],
factors = 3,
rotation = "varimax",
scores = "regression"
)
fa_promax <- factanal(
x = dat_complete[items],
factors = 3,
rotation = "promax",
scores = "regression"
)
round(as.matrix(fa_varimax$loadings), 3)
round(as.matrix(fa_promax$loadings), 3)
如果使用斜交旋轉,應同時查看因子之間的相關矩陣:
fa_promax$Phi
因子之間存在相關并不代表模型失敗。相反,如果理論上相關的構念在斜交旋轉后顯示出一定相關,往往比強行使用正交旋轉更符合研究對象。報告中應說明提取方法、旋轉方法和選擇該方法的理由。
七、讀取并解釋因子載荷
為了更清楚地識別每個題項主要歸屬的因子,可以整理載荷矩陣。
loading_df <- as.data.frame(unclass(fa_model$loadings))
loading_df$item <- rownames(loading_df)
loading_df <- loading_df[, c("item",
setdiff(names(loading_df), "item"))]
loading_df
還可以為每個題項標記絕對載荷最大的因子:
factor_columns <- setdiff(names(loading_df), "item")
loading_df$primary_factor <- factor_columns[
apply(abs(loading_df[factor_columns]), 1, which.max)
]
loading_df$primary_loading <- apply(
abs(loading_df[factor_columns]),
1,
max
)
loading_df
解釋載荷時,至少要關注三種情況。
第一,題項在某個因子上的主要載荷較高,且在其他因子上的載荷較低。這類題項通常有較清晰的歸屬。
第二,題項在兩個或多個因子上的載荷都較高。這說明題項可能同時反映多個構念,也可能存在題目表述過于寬泛、反向題處理錯誤或因子數量設定不合適等問題。此時不能只看統計結果,還要回到題目內容判斷是否保留。
第三,題項在所有因子上的載荷都較低。這類題項可能沒有充分反映共同構念,但也應先檢查數據編碼、缺失處理和量表方向,再考慮刪除。
因子命名應根據同一因子下題項的共同含義完成,而不是根據軟件自動生成的編號決定。因子編號本身沒有實質意義,旋轉或重新運行模型后,因子編號可能改變。
例如,如果某個因子上的題項都圍繞“主動參與課堂、投入學習時間和完成學習任務”,可以將其命名為“學習投入”。論文中應說明命名依據,并列出構成該因子的主要題項。
八、處理題項刪除與模型重估
刪除題項不能只因為它的載荷低。一個較穩妥的處理過程是:先發現問題,再核對題目內容,刪除后重新估計模型,最后比較新舊結構是否更合理。
# 假設經過理論判斷后,刪除 q5 和 q8
items_reduced <- setdiff(items, c("q5", "q8"))
dat_reduced <- dat_complete[items_reduced]
fa_reduced <- factanal(
x = dat_reduced,
factors = 3,
rotation = "promax",
scores = "regression"
)
print(fa_reduced,
cutoff = 0.30,
sort = TRUE)
刪除題項后,應重新檢查相關矩陣、KMO、因子數量和載荷結構。不能先反復刪除變量,直到輸出結果看起來“漂亮”為止。過度根據當前樣本調整題項,會增加結果對特定樣本的依賴,也可能削弱量表的內容效度。
如果刪除某個題項只是為了提高某個統計指標,卻使理論構念的重要內容缺失,通常不應刪除。論文中還應記錄刪除依據,而不是只報告最終保留下來的題項。
九、提取因子得分并構建后續變量
完成因子結構確認后,可以提取每位受訪者的因子得分。使用 scores = "regression" 后,因子得分通常保存在模型對象的 scores 中。
factor_scores <- as.data.frame(fa_model$scores)
head(factor_scores)
summary(factor_scores)
將因子得分合并回原始數據:
dat_analysis <- cbind(
dat_complete,
factor_scores
)
head(dat_analysis)
為了讓變量名更容易用于后續分析,可以重命名:
names(factor_scores) <- c(
"learning_engagement",
"academic_pressure",
"institutional_support"
)
dat_analysis <- cbind(
dat_complete,
factor_scores
)
因子得分通常是標準化形式,均值接近零。它們適合用于后續回歸或相關分析,但解釋時應明確說明:系數反映的是潛在因子得分變化與結果變量之間的關系,而不是原始問卷分值的直接變化。
除了模型估計的因子得分,也可以根據題項均值或總分構建量表得分。這種方法更容易解釋,但前提是題項已經確認屬于同一個因子,并且缺失處理規則已經確定。
engagement_items <- c("q1", "q2", "q4")
dat_analysis$engagement_mean <- rowMeans(
dat_analysis[engagement_items],
na.rm = TRUE
)
使用 rowMeans(..., na.rm = TRUE) 時要注意:如果某一行所有題項都是缺失,結果可能不符合預期。可以先設置最低有效題項數量。
row_mean_min <- function(x, min_valid = 2) {
valid_n <- rowSums(!is.na(x))
result <- rowMeans(x, na.rm = TRUE)
result[valid_n < min_valid] <- NA
result
}
dat_analysis$engagement_mean <- row_mean_min(
dat_analysis[engagement_items],
min_valid = 2
)
因子得分和題項均值不是同一個變量。前者由模型估計,考慮了題項之間的關系;后者直接反映題項答案的平均水平。研究者應根據研究目的和論文中的變量定義選擇一種,并在全文保持一致。
十、將因子結果用于后續分析
如果后續需要把因子結果用于回歸分析,可以直接使用已經生成的因子變量。下面的代碼僅展示變量進入模型的方式,具體結果應根據自己的研究設計確定。
model <- lm(
outcome ~ learning_engagement +
academic_pressure +
institutional_support +
age +
gender,
data = dat_analysis
)
summary(model)
如果因子之間存在較高相關,應在解釋回歸結果時保持謹慎。高度相關的因子可能使回歸系數不穩定,也可能導致單個因子的解釋與零階相關不一致。此時應回到因子相關矩陣和研究理論,確認這些因子是否確實代表可以區分的構念。
在構建后續變量之前,還應檢查因子得分是否存在大量缺失、極端分布或與原始題項方向不一致。
sapply(dat_analysis[c(
"learning_engagement",
"academic_pressure",
"institutional_support"
)], function(x) {
c(
missing = sum(is.na(x)),
mean = mean(x, na.rm = TRUE),
sd = sd(x, na.rm = TRUE)
)
})
十一、常見錯誤及排查方法
報錯“相關矩陣不是正定矩陣”
這通常意味著題項之間存在完全重復、近乎重復,或者某些變量之間存在嚴重線性關系。可以先檢查相關矩陣:
round(cor_mat_complete, 3)
重點查看是否有兩個題項的相關系數接近一。如果存在,應核對這兩個題項是否重復、是否被錯誤復制,或是否實際上測量了完全相同的內容。也要檢查變量方差:
sapply(dat_complete[items], var, na.rm = TRUE)
方差為零或極低的題項沒有足夠的信息參與因子分析。
factanal() 無法收斂
無法收斂可能與因子數量過多、樣本信息不足、相關矩陣異常或題項之間高度共線有關。可以先減少因子數量,檢查數據和相關矩陣,再比較不同旋轉方式。如果某些題項存在極端分布或幾乎沒有變化,也應優先處理這些變量。
不要把增加迭代次數當成唯一解決辦法。即使模型最終收斂,如果因子數量和題項結構本身不合理,結果仍然可能無法解釋。
結果中出現 Heywood 情況
如果某個題項的獨特性接近零,或者模型估計出的共同度異常偏高,可能存在 Heywood 情況。這通常提示模型設定不合適、題項高度重復、因子數量不合理或數據規模不足。
fa_model$uniquenesses
發現異常時,應檢查相關矩陣、因子數量和題項內容,必要時重新設定模型。不能簡單把異常結果當作“題項質量很好”。
反向題載荷方向相反
因子載荷為負不一定意味著模型錯誤。它可能反映題項方向與其他題項相反,也可能是反向計分未完成。先核對原始問卷編碼和反向處理過程,再決定是否需要調整。
如果只是整個因子的方向相反,而同一因子內各題項關系一致,可以在解釋時統一方向;如果同一因子內部載荷正負混雜,則應重點檢查題項編碼和因子結構。
缺失值導致樣本量變化
complete.cases() 會刪除任一分析題項缺失的受訪者,題項較多時可能損失較多樣本。pairwise.complete.obs 雖然可以保留更多相關系數,但不同相關系數的樣本量可能不同,相關矩陣有時會變得不穩定。
因此,論文中應明確報告缺失值處理方法、最終分析樣本量,以及該處理方式可能帶來的限制。不能在不同步驟中隨意切換缺失處理方式而不記錄。
十二、如何撰寫可復現的分析報告
一份可復現的因子分析報告,應讓讀者知道數據如何進入模型、模型如何設定,以及最終因子如何被解釋和使用。建議至少交代以下內容:
- 分析對象、題項數量和題項的計分范圍;
- 反向題的處理方式;
- 缺失值和異常編碼的處理方式;
- 題項進入或退出模型的依據;
- KMO 和 Bartlett 球形檢驗結果;
- 因子數量的判斷依據;
- 因子提取方法和旋轉方法;
- 主要題項在各因子上的載荷;
- 因子命名的理論依據;
- 因子得分或量表均值如何構建;
- 因子變量如何進入后續分析。
為了保留分析過程,可以將關鍵代碼和模型對象保存下來:
analysis_record <- list(
items = items,
correlation_matrix = cor_mat_complete,
eigenvalues = eigen_values,
factor_model = fa_model,
loadings = as.matrix(fa_model$loadings),
factor_scores = fa_model$scores
)
saveRDS(analysis_record, file = "factor_analysis_record.rds")
也可以保存整理后的載荷表:
write.csv(
loading_df,
file = "factor_loadings.csv",
row.names = FALSE
)
論文中不必逐行展示全部代碼,但應保留能夠復現主要結果的腳本、變量篩選記錄和模型設定。尤其要避免只報告最終因子名稱,卻不說明題項刪除和因子數量選擇過程。
因子分析的最終目標不是得到一張載荷表,而是建立一個有理論依據、統計結構清晰、能夠用于后續研究的變量體系。只要數據編碼、題項篩選、因子數量、旋轉方式和變量構建都被完整記錄,研究者就能在 R 中重復運行分析,并讓論文讀者理解每一個因子結果是如何形成的。


