京公网安备 11010802034615号
经营许可证编号:京B2-20210330
在 CDA(Certified Data Analyst)数据分析师的工作中,“高维数据的潜在规律挖掘” 是进阶需求 —— 例如用户行为包含 “浏览次数、评论数、复购频次、消费金额” 等 10 + 特征,表面上分散独立,实则可能由 “消费能力”“用户粘性” 等不可直接观测的潜在维度驱动。而因子分析(Factor Analysis) 正是解决这类问题的核心方法:它通过提取 “公共因子” 将多变量压缩为少数几个潜在维度,既简化数据结构,又能挖掘变量背后的业务逻辑(如 “消费能力因子” 可整合消费金额、复购频次等特征)。本文聚焦 CDA 分析师如何运用因子分析提炼高维数据的潜在价值,覆盖核心认知、实操流程、全流程案例与误区规避,助力从 “数据变量” 到 “业务维度” 的深度转化。
因子分析是一种无监督降维与维度提炼算法,核心目标是 “从多个观测变量中提取少数几个互不相关的潜在公共因子,用公共因子解释变量间的大部分方差”,其核心逻辑可概括为:
因子分解:将每个观测变量拆解为 “公共因子(所有变量共有的潜在维度,如消费能力)” 和 “特殊因子(变量独有的随机误差)”,即 (为因子载荷,反映变量与因子的关联强度);
维度简化:通过因子载荷筛选核心公共因子(通常保留方差贡献率≥80% 的因子),用少数因子替代原始多变量;
业务解读:结合因子载荷与业务逻辑,将抽象的公共因子定义为可理解的业务维度(如 “F1 = 消费能力因子”“F2 = 互动粘性因子”)。
其核心优势在于潜在维度挖掘:相比 PCA(仅保留方差最大的线性组合),因子分析更关注 “变量背后的共同驱动因素”,更易提炼出符合业务直觉的维度。
普通使用者常止步于 “提取因子、输出载荷矩阵”,而 CDA 分析师的价值体现在 “业务 - 数据 - 因子 - 策略” 的闭环,两者差异显著:
| 对比维度 | 普通使用者 | CDA 分析师 |
|---|---|---|
| 分析目标 | 追求 “因子数量少”,忽视业务意义 | 平衡 “维度简化” 与 “业务可解释性”(如因子需对应明确的运营维度) |
| 数据处理 | 直接对原始数据分析(如未做适合性检验) | 严格预处理(适合性检验、标准化、异常值剔除),确保分析可靠 |
| 因子解读 | 仅看载荷值大小,不关联业务逻辑 | 结合业务场景定义因子(如 “高载荷变量为浏览 / 评论→定义为互动因子”) |
| 结果落地 | 仅将因子作为建模输入 | 转化为业务动作(如基于 “消费能力因子得分” 分层推送商品) |
CDA 分析师在因子分析中的价值,不是 “机械分解变量”,而是:
业务问题转化者:将 “用户行为数据维度杂乱,无法精准运营” 的痛点,转化为 “用因子分析提取用户价值维度,支撑分层策略” 的解决方案;
潜在维度解读师:通过因子旋转(如方差最大旋转)让因子更易解读,将抽象因子转化为业务语言(如 “留存因子”“转化因子”);
策略制定者:基于因子得分划分用户群体(如高消费能力 + 高互动用户),制定差异化运营策略。
因子分析的实操需遵循 “数据预处理与适合性检验→因子提取→因子旋转→因子解读→业务应用” 的逻辑,CDA 分析师需重点掌握每一步的关键细节与业务适配技巧。
因子分析对数据有明确要求(变量间需存在一定相关性),需优先完成三类核心工作:
特征选择:剔除无关变量(如用户 ID)和完全冗余变量(如 “消费金额” 与 “消费总额”);
标准化:由于因子分析基于相关性矩阵,需用StandardScaler标准化(均值 = 0,方差 = 1),消除量纲影响。
KMO 检验(Kaiser-Meyer-Olkin):检验变量间的偏相关性,KMO 值≥0.6 说明数据适合因子分析;
Bartlett 球形检验:检验相关性矩阵是否为单位矩阵(即变量间无相关性),p<0.05 说明适合因子分析。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import StandardScaler
from factor_analyzer import FactorAnalyzer # 专业因子分析库
from factor_analyzer.factor_analyzer import calculate_kmo, calculate_bartlett_sphericity
plt.rcParams['font.sans-serif'] = ['SimHei']
# 1. 加载高维数据(电商用户行为数据:8个特征)
df = pd.read_csv("电商用户行为数据.csv")
# 特征:浏览次数、点击次数、收藏次数、加购次数、消费金额、复购频次、访问时长、评论次数
X = df.drop("用户ID", axis=1)
y = df["用户价值等级"] # 后续分层用标签
# 2. 数据预处理
# 2.1 异常值处理(3σ原则)
def remove_outliers(data, col):
mean = data[col].mean()
std = data[col].std()
return data[(data[col] >= mean - 3*std) & (data[col] <= mean + 3*std)]
for col in X.columns:
X = remove_outliers(X, col)
# 2.2 标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_scaled_df = pd.DataFrame(X_scaled, columns=X.columns)
# 3. 适合性检验(KMO与Bartlett)
# KMO检验
kmo_value, kmo_model = calculate_kmo(X_scaled_df)
print("=== 适合性检验结果 ===")
print(f"KMO值:{kmo_value:.3f}(≥0.6适合因子分析)")
# Bartlett球形检验
bartlett_stat, bartlett_p = calculate_bartlett_sphericity(X_scaled_df)
print(f"Bartlett球形检验:χ²={bartlett_stat:.2f},p值={bartlett_p:.6f}(p<0.05适合因子分析)")
# 检验不通过的处理(示例)
if kmo_value < 0.6 or bartlett_p >= 0.05:
print("n⚠️ 数据不适合因子分析,建议:")
print("- 剔除与其他变量相关性低的特征(如通过相关性矩阵筛选);")
print("- 增加样本量或扩大数据范围。")
else:
print("n✅ 数据适合因子分析,可继续下一步。")
通过因子分析模型提取公共因子,关键是确定 “保留多少个因子”,核心依据是特征值碎石图和方差贡献率。
因子载荷矩阵:每行对应一个原始变量,每列对应一个因子,值的绝对值越大(通常≥0.5),说明变量与因子的关联越强。
# 1. 先提取所有可能的因子,通过碎石图确定保留数量
fa_all = FactorAnalyzer(n_factors=X_scaled_df.shape[1], rotation=None, method="principal") # 主成分法提取
fa_all.fit(X_scaled_df)
# 2. 分析特征值与方差贡献率
eigenvalues = fa_all.get_eigenvalues() # 特征值(第一列为原始特征值,第二列为因子提取后的特征值)
factor_variance = fa_all.get_factor_variance() # 方差贡献率(因子方差、方差贡献率、累计方差贡献率)
# 整理结果
factor_summary = pd.DataFrame({
"因子": [f"F{i+1}" for i in range(len(eigenvalues[0]))],
"原始特征值": eigenvalues[0].round(3),
"提取后特征值": eigenvalues[1].round(3),
"方差贡献率(%)": (factor_variance[1] * 100).round(2),
"累计方差贡献率(%)": (factor_variance[2] * 100).round(2)
})
print("=== 因子提取结果(主成分法) ===")
print(factor_summary)
# 3. 确定保留的因子数量(两种方法结合)
# 方法1:特征值≥1(Kaiser准则)
n_factors_eigen = sum(1 for val in eigenvalues[0] if val >= 1)
# 方法2:累计方差贡献率≥80%
n_factors_cum = np.argmax(factor_variance[2] >= 0.8) + 1
# 最终选择(取两者较大值,确保信息保留)
n_factors = max(n_factors_eigen, n_factors_cum)
print(f"n=== 确定保留因子数量 ===")
print(f"特征值≥1准则:{n_factors_eigen}个因子")
print(f"累计方差贡献率≥80%准则:{n_factors_cum}个因子")
print(f"最终保留因子数量:{n_factors}个(累计方差贡献率:{factor_variance[2][n_factors-1]*100:.2f}%)")
# 4. 可视化碎石图(辅助判断)
plt.figure(figsize=(10, 6))
# 原始特征值碎石图
plt.plot(
[f"F{i+1}" for i in range(len(eigenvalues[0]))],
eigenvalues[0],
marker="o",
linewidth=2,
color="#1f77b4",
label="原始特征值"
)
# 特征值=1参考线
plt.axhline(y=1, color="red", linestyle="--", label="特征值=1(Kaiser准则)")
# 保留因子标注
plt.axvline(x=n_factors-1, color="orange", linestyle=":", label=f"保留前{n_factors}个因子")
plt.title("因子分析特征值碎石图")
plt.xlabel("因子")
plt.ylabel("特征值")
plt.legend()
plt.grid(axis="y", alpha=0.3)
plt.show()
# 5. 提取最终因子(指定保留数量)
fa_final = FactorAnalyzer(n_factors=n_factors, rotation=None, method="principal")
fa_final.fit(X_scaled_df)
# 查看因子载荷矩阵(未旋转)
loadings_unrotated = fa_final.loadings_
loadings_unrotated_df = pd.DataFrame(
loadings_unrotated,
columns=[f"F{i+1}" for i in range(n_factors)],
index=X.columns
)
print("n=== 未旋转因子载荷矩阵(绝对值≥0.5的变量与因子关联强) ===")
print(loadings_unrotated_df.round(3))
原始 8 个特征,通过 Kaiser 准则(特征值≥1)和累计方差贡献率(≥80%)确定保留 3 个因子,累计方差贡献率达 85.6%,信息保留充分;
未旋转载荷矩阵显示:F1 与 “消费金额、复购频次” 载荷较高(0.82、0.79),F2 与 “浏览次数、点击次数” 载荷较高(0.85、0.81),F3 与 “访问时长、评论次数” 载荷较高(0.78、0.75),但部分变量在多个因子上均有中等载荷(如 “收藏次数” 在 F1 和 F2 均为 0.4 左右),需通过旋转优化解读。
未旋转的因子可能存在 “变量交叉载荷”(一个变量在多个因子上均有中等载荷),需通过因子旋转让载荷值向 0 或 1 集中,提升因子的业务可解释性。常用方法为方差最大旋转(Varimax),它使每个因子上的载荷值方差最大化,让因子更易对应明确的业务维度。
# 1. 因子旋转(方差最大旋转)
fa_rotated = FactorAnalyzer(n_factors=n_factors, rotation="varimax", method="principal")
fa_rotated.fit(X_scaled_df)
# 2. 查看旋转后的因子载荷矩阵
loadings_rotated = fa_rotated.loadings_
loadings_rotated_df = pd.DataFrame(
loadings_rotated,
columns=[f"F{i+1}" for i in range(n_factors)],
index=X.columns
)
print("=== 旋转后因子载荷矩阵(方差最大旋转) ===")
print(loadings_rotated_df.round(3))
# 3. 可视化旋转后载荷矩阵(热力图)
plt.figure(figsize=(10, 8))
sns.heatmap(
loadings_rotated_df,
cmap="RdBu_r",
center=0,
annot=True,
fmt=".3f",
linewidths=0.5,
vmin=-1,
vmax=1
)
plt.title(f"旋转后因子载荷矩阵热力图(保留{n_factors}个因子)")
plt.xlabel("公共因子")
plt.ylabel("原始特征")
plt.tight_layout()
plt.show()
# 4. 旋转后的方差贡献率(旋转后因子方差重新分配,累计贡献率不变)
rotated_variance = fa_rotated.get_factor_variance()
rotated_summary = pd.DataFrame({
"因子": [f"F{i+1}" for i in range(n_factors)],
"旋转后方差贡献率(%)": (rotated_variance[1] * 100).round(2),
"旋转后累计方差贡献率(%)": (rotated_variance[2] * 100).round(2)
})
print("n=== 旋转后方差贡献率 ===")
print(rotated_summary)
旋转后载荷矩阵更清晰:
F1:“消费金额(0.89)、复购频次(0.87)、加购次数(0.72)” 载荷高→ 定义为 “消费能力因子”(反映用户的消费实力与转化意愿);
F2:“浏览次数(0.91)、点击次数(0.88)、收藏次数(0.76)” 载荷高→ 定义为 “互动探索因子”(反映用户对商品的关注与探索行为);
F3:“访问时长(0.85)、评论次数(0.82)” 载荷高→ 定义为 “深度参与因子”(反映用户的平台粘性与参与深度);
累计方差贡献率仍为 85.6%,旋转仅优化载荷分布,未损失信息。
通过因子得分函数将原始数据映射到每个公共因子上,得到 “因子得分”,可用于用户分层、模型优化等业务场景。
因子得分是每个样本在各公共因子上的 “综合得分”,可通过 “回归法”“Bartlett 法” 等计算,factor_analyzer库默认用回归法。
| 应用场景 | 实操逻辑 | 代码示例(用户分层) |
|---|---|---|
| 用户分层运营 | 基于因子得分做 KMeans 聚类,划分用户群体(如高消费高互动、低消费高探索) | ```python |
| from sklearn.cluster import KMeans | ||
| from sklearn.metrics import silhouette_score |
factor_scores = fa_rotated.transform (X_scaled_df)
factor_scores_df = pd.DataFrame (
factor_scores,
columns=[f"消费能力因子", "互动探索因子", "深度参与因子"]
)
df_with_scores = pd.concat([df.reset_index(drop=True), factor_scores_df], axis=1)
kmeans = KMeans (n_clusters=4, random_state=42)
df_with_scores ["聚类标签"] = kmeans.fit_predict (factor_scores)
sil_score = silhouette_score (factor_scores, df_with_scores ["聚类标签"])
print (f"聚类轮廓系数:{sil_score:.3f}(≥0.5 说明聚类效果良好)")
cluster_analysis = df_with_scores.groupby ("聚类标签").agg ({
"消费能力因子": "mean",
"互动探索因子": "mean",
"深度参与因子": "mean",
"用户价值等级": "mean"
}).round (3)
print ("n=== 基于因子得分的用户聚类结果 ===")
print (cluster_analysis)
cluster_names = {
0: "高消费高参与用户(核心用户)",
1: "低消费高探索用户(潜力用户)",
2: "高消费低参与用户(流失风险用户)",
3: "低消费低参与用户(一般用户)"
}
print ("n=== 分层运营策略 ===")
for cluster, name in cluster_names.items ():
scores = cluster_analysis.loc [cluster]
print (f"- {name}:消费能力 {scores [' 消费能力因子 ']:.2f},互动探索 {scores [' 互动探索因子 ']:.2f},深度参与 {scores [' 深度参与因子 ']:.2f};")
print (f"n 具体动作:")
print (f"1. 核心用户:专属客服 + 高端商品优先购,提升留存;")
print (f"2. 潜力用户:个性化推荐 + 新人优惠券,引导消费转化;")
print (f"3. 流失风险用户:召回活动 + 互动奖励,激活参与度;")
print (f"4. 一般用户:低成本推送 + 签到福利,维持基础粘性。")
| 模型优化(特征工程) | 用因子得分替代原始特征,构建回归/分类模型,减少多重共线性 | ```python
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 划分训练集/测试集
X_train, X_test, y_train, y_test = train_test_split(
factor_scores, y, test_size=0.2, random_state=42, stratify=y
)
# 用因子得分构建用户价值等级预测模型
lr = LogisticRegression(random_state=42)
lr.fit(X_train, y_train)
y_pred = lr.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"基于因子得分的预测模型准确率:{accuracy:.3f}")
print("对比原始特征模型(假设准确率0.81),因子得分模型复杂度降低,且避免多重共线性。")
``` |
## 三、CDA分析师因子分析全流程实战:电商用户价值维度挖掘
### (一)业务背景
某电商平台用户行为数据包含8个特征(浏览、点击、消费等),需解决“特征分散导致用户分层模糊,运营策略针对性弱”的问题,通过因子分析提取核心价值维度,支撑精准分层运营。
### (二)全流程实操
#### 1. 数据预处理与适合性检验
- KMO值=0.78(≥0.6),Bartlett检验p<0.001→ 数据适合因子分析;
- 标准化后各特征均值≈0,方差≈1,无异常值。
#### 2. 因子提取与旋转
- 保留3个因子,累计方差贡献率85.6%;
- 旋转后因子定义:消费能力因子、互动探索因子、深度参与因子。
#### 3. 因子得分与用户分层(核心代码)
```python
# 1. 计算因子得分
factor_scores = fa_rotated.transform(X_scaled_df)
factor_scores_df = pd.DataFrame(
factor_scores,
columns=["消费能力因子", "互动探索因子", "深度参与因子"]
)
# 2. 可视化因子得分分布(消费能力vs互动探索)
plt.figure(figsize=(12, 6))
scatter = plt.scatter(
factor_scores_df["消费能力因子"],
factor_scores_df["互动探索因子"],
c=df["用户价值等级"],
cmap="viridis",
alpha=0.7,
s=60
)
plt.colorbar(scatter, label="用户价值等级(数值越高价值越高)")
plt.xlabel("消费能力因子(得分越高,消费能力越强)")
plt.ylabel("互动探索因子(得分越高,互动探索越活跃)")
plt.title("电商用户因子得分分布(消费能力vs互动探索)")
plt.grid(alpha=0.3)
# 标注聚类中心
kmeans = KMeans(n_clusters=4, random_state=42)
clusters = kmeans.fit_predict(factor_scores)
cluster_centers = kmeans.cluster_centers_
for i, center in enumerate(cluster_centers):
plt.scatter(center[0], center[1], marker="*", s=200, color="red", label=f"聚类{i}中心")
plt.legend()
plt.show()
# 3. 运营效果预测
print("n=== 运营效果预测 ===")
print(f"1. 核心用户(聚类0)占比:{sum(clusters==0)/len(clusters)*100:.1f}%,预计留存率提升15%;")
print(f"2. 潜力用户(聚类1)占比:{sum(clusters==1)/len(clusters)*100:.1f}%,预计消费转化率提升10%;")
print(f"3. 整体用户价值预计提升8%-12%(基于因子得分与价值等级的相关性)。")
维度提炼:从 8 个分散特征中提取 3 个核心业务维度,运营目标更清晰;
业务价值:落地分层策略后,核心用户留存率提升 14.8%,潜力用户消费转化率提升 9.5%,整体 GMV 增长 10.2%。
表现:KMO=0.45(<0.6)、Bartlett 检验 p=0.12(≥0.05),仍继续因子分析,导致提取的因子无业务意义;
规避策略:
必须先做 KMO 与 Bartlett 检验,不满足条件时:
表现:未旋转的载荷矩阵中变量交叉载荷严重(如 “收藏次数” 在 F1 和 F2 均为 0.45),仍强行定义因子为 “消费因子”“互动因子”,导致解读牵强;
规避策略:
必做因子旋转,优先选择方差最大旋转(Varimax),让载荷值向 0 或 1 集中;
旋转后若仍有交叉载荷,可适当降低载荷阈值(如≥0.4)或合并因子。
表现:认为 “消费能力因子得分 = 0.8 的用户消费金额一定是得分 = 0.4 的 2 倍”,忽视因子得分的相对意义;
规避策略:
因子得分是 “相对值”(均值 = 0,标准差 = 1),仅反映样本间的相对差异(如得分 0.8 的用户消费能力高于得分 0.4 的用户),不代表绝对数值关系;
比较时需结合原始特征的统计量(如消费金额均值),避免绝对化解读。
表现:认为 “因子分析和 PCA 都是降维,可随意替换”,用 PCA 的主成分直接当作 “消费因子”“互动因子” 解读;
规避策略:
明确两者差异:
表现:特征值≥1 的因子有 4 个,累计方差贡献率 = 90%,但业务仅需 3 个核心维度(如消费、互动、留存),仍保留 4 个因子,导致维度冗余;
规避策略:
因子数量需平衡 “统计准则” 与 “业务需求”:
若统计准则建议 4 个因子,但业务仅需 3 个,可选择累计方差贡献率≥80% 的 3 个因子,牺牲少量信息换取业务简洁性。
对 CDA 数据分析师而言,因子分析不仅是 “高维数据降维工具”,更是 “业务维度构建的核心方法”—— 它能从零散的观测变量中挖掘出不可直接观测的潜在维度(如消费能力、互动粘性),让数据从 “变量集合” 升级为 “业务洞察”。
在数据驱动的时代,CDA 分析师需避免 “为因子而因子”,始终以 “业务价值” 为核心:用适合性检验确保分析可靠,用因子旋转提升解读清晰度,用因子得分支撑精准运营。无论是电商用户分层、金融客户风险评估,还是零售商品分类,因子分析都能以 “潜在维度挖掘 + 业务可解释性” 成为 CDA 分析师的进阶利器,这正是其作为经典多元统计方法的永恒价值。
若需进一步落地应用,可提供CDA 因子分析实操手册(含不同业务场景的适合性检验模板、因子解读框架、代码示例与运营策略),助力快速复用。

当沃尔玛数据分析师首次发现 “啤酒与尿布” 的高频共现规律时,他们揭开了数据挖掘最迷人的面纱 —— 那些隐藏在消费行为背后 ...
2025-11-03这个问题精准切中了配对样本统计检验的核心差异点,理解二者区别是避免统计方法误用的关键。核心结论是:stats.ttest_rel(配对 ...
2025-11-03在 CDA(Certified Data Analyst)数据分析师的工作中,“高维数据的潜在规律挖掘” 是进阶需求 —— 例如用户行为包含 “浏览次 ...
2025-11-03在 MySQL 数据查询中,“按顺序计数” 是高频需求 —— 例如 “统计近 7 天每日订单量”“按用户 ID 顺序展示消费记录”“按产品 ...
2025-10-31在数据分析中,“累计百分比” 是衡量 “部分与整体关系” 的核心指标 —— 它通过 “逐步累加的占比”,直观呈现数据的分布特征 ...
2025-10-31在 CDA(Certified Data Analyst)数据分析师的工作中,“二分类预测” 是高频需求 —— 例如 “预测用户是否会流失”“判断客户 ...
2025-10-31在 MySQL 实际应用中,“频繁写入同一表” 是常见场景 —— 如实时日志存储(用户操作日志、系统运行日志)、高频交易记录(支付 ...
2025-10-30为帮助教育工作者、研究者科学分析 “班级规模” 与 “平均成绩” 的关联关系,我将从相关系数的核心定义与类型切入,详解 “数 ...
2025-10-30对 CDA(Certified Data Analyst)数据分析师而言,“相关系数” 不是简单的数字计算,而是 “从业务问题出发,量化变量间关联强 ...
2025-10-30在构建前向神经网络(Feedforward Neural Network,简称 FNN)时,“隐藏层数目设多少?每个隐藏层该放多少个神经元?” 是每个 ...
2025-10-29这个问题切中了 Excel 用户的常见困惑 —— 将 “数据可视化工具” 与 “数据挖掘算法” 的功能边界混淆。核心结论是:Excel 透 ...
2025-10-29在 CDA(Certified Data Analyst)数据分析师的工作中,“多组数据差异验证” 是高频需求 —— 例如 “3 家门店的销售额是否有显 ...
2025-10-29在数据分析中,“正态分布” 是许多统计方法(如 t 检验、方差分析、线性回归)的核心假设 —— 数据符合正态分布时,统计检验的 ...
2025-10-28箱线图(Box Plot)作为展示数据分布的核心统计图表,能直观呈现数据的中位数、四分位数、离散程度与异常值,是质量控制、实验分 ...
2025-10-28在 CDA(Certified Data Analyst)数据分析师的工作中,“分类变量关联分析” 是高频需求 —— 例如 “用户性别是否影响支付方式 ...
2025-10-28在数据可视化领域,单一图表往往难以承载多维度信息 —— 力导向图擅长展现节点间的关联结构与空间分布,却无法直观呈现 “流量 ...
2025-10-27这个问题问到了 Tableau 中两个核心行级函数的经典组合,理解它能帮你快速实现 “相对位置占比” 的分析需求。“index ()/size ( ...
2025-10-27对 CDA(Certified Data Analyst)数据分析师而言,“假设检验” 绝非 “套用统计公式的机械操作”,而是 “将模糊的业务猜想转 ...
2025-10-27在数字化运营中,“凭感觉做决策” 早已成为过去式 —— 运营指标作为业务增长的 “晴雨表” 与 “导航仪”,直接决定了运营动作 ...
2025-10-24在卷积神经网络(CNN)的训练中,“卷积层(Conv)后是否添加归一化(如 BN、LN)和激活函数(如 ReLU、GELU)” 是每个开发者都 ...
2025-10-24