Python版本 :Python 3.12+
开发工具 :PyCharm 或 VS Code
操作系统:Windows / macOS / Linux (通用)
学习目标
通过本章学习,你将能够:
- 理解"让数据说话"的EDA核心理念
- 掌握2025年最新的EDA方法论与工具链
- 运用系统化流程进行数据探索
- 使用自动化工具加速分析过程
- 从数据中提取有价值的业务洞察
- 完成完整的端到端EDA案例
1. EDA核心理念:让数据说话
1.1 什么是"让数据说话"
探索性数据分析(Exploratory Data Analysis, EDA)的本质不是证明预设结论,而是让数据自己揭示真相。
| 传统分析 | 让数据说话的EDA |
|---|---|
| 先有假设,再找证据 | 先观察数据,再形成假设 |
| 关注验证已知模式 | 发现未知模式和异常 |
| 追求统计显著性 | 追求业务可解释性 |
| 输出报表和数字 | 输出洞察和故事 |
1.2 EDA在数据科学流程中的位置
业务理解 -> 数据获取 -> 数据清洗 -> 【EDA探索性分析】 -> 特征工程 -> 建模 -> 部署
^ |
|__________ 迭代反馈 _______________|
EDA是连接数据清洗与特征工程的桥梁,也是建模失败的"预警系统"。
1.3 2025年EDA新趋势
| 趋势 | 说明 | 代表工具 |
|---|---|---|
| 自动化EDA | 一键生成完整分析报告 | ydata-profiling, Sweetviz |
| 交互式探索 | 实时筛选、钻取、联动 | D-Tale, Lux |
| AI辅助洞察 | 自动识别异常和建议 | DataRobot, H2O |
| 大规模数据处理 | 亿级数据秒级分析 | Polars, DuckDB |
| 可解释性增强 | 自动生成数据故事 | Narrative Science |
2. EDA系统化方法论
2.1 三步走策略
第一步:数据体检(Health Check)
- 数据规模:行数、列数、内存占用
- 数据质量:缺失值、重复值、异常值
- 数据类型:数值型、分类型、时间型、文本型
第二步:单变量探索(Univariate Analysis)
- 分布形态:正态、偏态、多峰
- 集中趋势:均值、中位数、众数
- 离散程度:标准差、四分位距
第三步:多变量探索(Multivariate Analysis)
- 相关性:线性、非线性、条件相关
- 分组差异:类别间的数值差异
- 时序模式:趋势、周期、异常点
2.2 EDA问题清单
markdown
## 数据质量检查清单
### 完整性
- [ ] 缺失值比例是否可接受(<5%)?
- [ ] 缺失模式是随机还是系统性?
- [ ] 关键字段是否有缺失?
### 准确性
- [ ] 是否存在逻辑错误(如年龄为负)?
- [ ] 异常值是否合理?
- [ ] 数据范围是否符合业务常识?
### 一致性
- [ ] 日期格式是否统一?
- [ ] 分类变量编码是否一致?
- [ ] 单位是否统一?
### 时效性
- [ ] 数据是否反映当前业务状态?
- [ ] 时间跨度是否足够?
3. 描述性统计:数据的基础画像
3.1 核心统计量速查表
| 统计量 | 用途 | 适用场景 | 注意事项 |
|---|---|---|---|
| 均值 | 数据中心位置 | 对称分布 | 受异常值影响大 |
| 中位数 | 数据中心位置 | 偏态分布 | 稳健性强 |
| 众数 | 最频繁值 | 分类数据 | 可能有多个 |
| 标准差 | 数据离散程度 | 正态分布 | 与均值配合使用 |
| 四分位距 | 中间50%数据范围 | 存在异常值 | 不受极端值影响 |
| 偏度 | 分布不对称性 | 判断偏态方向 | >0右偏,<0左偏 |
| 峰度 | 分布尖锐程度 | 判断尾部厚度 | >3厚尾,<3薄尾 |
3.2 Python实现
python
import pandas as pd
import numpy as np
# 创建示例数据
np.random.seed(42)
df = pd.DataFrame({
'年龄': np.random.normal(35, 10, 1000).clip(18, 65).astype(int),
'收入': np.random.lognormal(10, 0.5, 1000).round(2),
'评分': np.random.beta(2, 5, 1000).round(3) * 10,
'类别': np.random.choice(['A', 'B', 'C'], 1000)
})
# 基础描述性统计
print("=" * 50)
print("基础统计量")
print("=" * 50)
print(df.describe())
# 完整统计量
print("\n" + "=" * 50)
print("扩展统计量")
print("=" * 50)
stats_df = pd.DataFrame({
'均值': df.select_dtypes(include=[np.number]).mean(),
'中位数': df.select_dtypes(include=[np.number]).median(),
'标准差': df.select_dtypes(include=[np.number]).std(),
'偏度': df.select_dtypes(include=[np.number]).skew(),
'峰度': df.select_dtypes(include=[np.number]).kurtosis(),
'变异系数': df.select_dtypes(include=[np.number]).std() / df.select_dtypes(include=[np.number]).mean()
})
print(stats_df.round(3))
3.3 分组统计洞察
python
# 按类别分组统计
print("\n" + "=" * 50)
print("分组统计:不同类别的收入对比")
print("=" * 50)
group_stats = df.groupby('类别')['收入'].agg([
('样本量', 'count'),
('均值', 'mean'),
('中位数', 'median'),
('标准差', 'std'),
('最小值', 'min'),
('最大值', 'max')
]).round(2)
print(group_stats)
# 洞察:均值与中位数的差异
print("\n" + "=" * 50)
print("洞察:均值 vs 中位数")
print("=" * 50)
for cat in df['类别'].unique():
subset = df[df['类别'] == cat]['收入']
mean_val = subset.mean()
median_val = subset.median()
diff_pct = (mean_val - median_val) / median_val * 100
print(f"类别 {cat}: 均值={mean_val:.0f}, 中位数={median_val:.0f}, 差异={diff_pct:.1f}%")
if diff_pct > 10:
print(f" -> 右偏分布,存在高收入群体拉高均值")
elif diff_pct < -10:
print(f" -> 左偏分布,存在低收入群体拉低均值")
else:
print(f" -> 近似对称分布")
4. 数据分布可视化:看见数据的形状
4.1 可视化工具选择指南
| 场景 | 推荐图表 | 优势 | 劣势 |
|---|---|---|---|
| 单变量分布 | 直方图+KDE | 直观展示分布形状 | 区间选择影响观感 |
| 异常值检测 | 箱线图 | 清晰标识异常值 | 丢失分布细节 |
| 多组对比 | 小提琴图 | 展示分布差异 | 大数据量时拥挤 |
| 正态性检验 | Q-Q图 | 精确检验分布 | 需要统计知识 |
| 原始数据展示 | 蜂群图 | 保留所有数据点 | 大数据量时性能差 |
4.2 完整可视化代码
python
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
# 创建综合可视化
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
# 1. 直方图 + KDE
ax1 = axes[0, 0]
sns.histplot(df['年龄'], kde=True, ax=ax1, color='steelblue')
ax1.axvline(df['年龄'].mean(), color='red', linestyle='--', label=f'均值={df["年龄"].mean():.1f}')
ax1.axvline(df['年龄'].median(), color='green', linestyle='--', label=f'中位数={df["年龄"].median():.1f}')
ax1.set_title('年龄分布:直方图 + KDE', fontsize=12, fontweight='bold')
ax1.legend()
# 2. 箱线图
ax2 = axes[0, 1]
sns.boxplot(data=df, y='收入', ax=ax2, color='lightcoral')
ax2.set_title('收入分布:箱线图', fontsize=12, fontweight='bold')
# 添加统计注释
q1, q2, q3 = df['收入'].quantile([0.25, 0.5, 0.75])
iqr = q3 - q1
ax2.annotate(f'中位数: {q2:.0f}\nIQR: {iqr:.0f}',
xy=(0.1, q3), fontsize=9,
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))
# 3. 小提琴图(分组)
ax3 = axes[0, 2]
sns.violinplot(data=df, x='类别', y='评分', ax=ax3, palette='Set2')
ax3.set_title('评分分布:按类别分组小提琴图', fontsize=12, fontweight='bold')
# 4. Q-Q图(正态性检验)
ax4 = axes[1, 0]
stats.probplot(df['年龄'], dist="norm", plot=ax4)
ax4.set_title('年龄 Q-Q图(正态性检验)', fontsize=12, fontweight='bold')
# 添加解读
ax4.annotate('近似直线 = 正态分布', xy=(0.05, 0.95), xycoords='axes fraction',
fontsize=9, bbox=dict(boxstyle='round', facecolor='lightyellow'))
# 5. 核密度估计对比
ax5 = axes[1, 1]
for cat in df['类别'].unique():
subset = df[df['类别'] == cat]['收入']
sns.kdeplot(subset, ax=ax5, label=f'类别 {cat}', fill=True, alpha=0.3)
ax5.set_title('收入密度:按类别分组KDE', fontsize=12, fontweight='bold')
ax5.legend()
# 6. 累积分布函数
ax6 = axes[1, 2]
for cat in df['类别'].unique():
subset = df[df['类别'] == cat]['评分'].sort_values()
y = np.arange(1, len(subset) + 1) / len(subset)
ax6.plot(subset, y, label=f'类别 {cat}', linewidth=2)
ax6.set_xlabel('评分')
ax6.set_ylabel('累积概率')
ax6.set_title('评分累积分布函数(CDF)', fontsize=12, fontweight='bold')
ax6.legend()
ax6.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('eda_distribution_analysis.png', dpi=150, bbox_inches='tight')
plt.show()
5. 相关性分析:发现变量间的秘密
5.1 相关系数选择指南
| 相关系数 | 变量类型 | 关系类型 | 对异常值敏感度 |
|---|---|---|---|
| 皮尔逊 | 连续型 | 线性 | 高 |
| 斯皮尔曼 | 有序型/连续型 | 单调 | 中 |
| 肯德尔 | 有序型/连续型 | 单调 | 低 |
| 点二列 | 连续型 + 二分类 | 线性 | 高 |
| Cramer's V | 分类型 | 关联性 | 低 |
5.2 相关性分析代码
python
# 计算多种相关系数
print("=" * 60)
print("相关性分析")
print("=" * 60)
# 皮尔逊相关系数
pearson_corr = df[['年龄', '收入', '评分']].corr(method='pearson')
print("\n皮尔逊相关系数(线性关系):")
print(pearson_corr.round(3))
# 斯皮尔曼相关系数
spearman_corr = df[['年龄', '收入', '评分']].corr(method='spearman')
print("\n斯皮尔曼相关系数(单调关系):")
print(spearman_corr.round(3))
# 相关性热力图
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# 皮尔逊热力图
sns.heatmap(pearson_corr, annot=True, cmap='RdBu_r', center=0,
square=True, linewidths=0.5, ax=axes[0], fmt='.2f')
axes[0].set_title('皮尔逊相关性热力图\n(线性关系)', fontsize=12, fontweight='bold')
# 斯皮尔曼热力图
sns.heatmap(spearman_corr, annot=True, cmap='RdBu_r', center=0,
square=True, linewidths=0.5, ax=axes[1], fmt='.2f')
axes[1].set_title('斯皮尔曼相关性热力图\n(单调关系)', fontsize=12, fontweight='bold')
plt.tight_layout()
plt.show()
# 相关性解读
print("\n" + "=" * 60)
print("相关性洞察")
print("=" * 60)
def interpret_correlation(corr_val):
abs_corr = abs(corr_val)
if abs_corr < 0.1:
return "极弱相关"
elif abs_corr < 0.3:
return "弱相关"
elif abs_corr < 0.5:
return "中等相关"
elif abs_corr < 0.7:
return "强相关"
else:
return "极强相关"
for i in pearson_corr.columns:
for j in pearson_corr.columns:
if i < j:
corr_val = pearson_corr.loc[i, j]
interpretation = interpret_correlation(corr_val)
print(f"{i} vs {j}: r={corr_val:.3f} ({interpretation})")
5.3 散点图矩阵
python
# 散点图矩阵
import seaborn as sns
# 添加类别颜色
g = sns.pairplot(df, hue='类别', diag_kind='kde',
plot_kws={'alpha': 0.6, 's': 30},
diag_kws={'fill': True})
g.fig.suptitle('散点图矩阵:多变量关系一览', y=1.02, fontsize=14, fontweight='bold')
plt.show()
6. 自动化EDA工具实战
6.1 工具对比
| 工具 | 核心优势 | 适用场景 | 报告格式 |
|---|---|---|---|
| ydata-profiling | 最全面的统计分析 | 深度数据探索 | HTML/JSON |
| Sweetviz | 美观的可视化界面 | 快速数据对比 | HTML |
| D-Tale | 交互式数据探索 | 实时数据查看 | Web界面 |
| Lux | 智能可视化推荐 | 快速发现模式 | Jupyter插件 |
| AutoViz | 一键生成多图表 | 快速数据概览 | 图片/HTML |
6.2 ydata-profiling深度使用
python
# 安装:pip install ydata-profiling
from ydata_profiling import ProfileReport
# 基础报告生成
profile = ProfileReport(
df,
title="数据集探索分析报告",
explorative=True, # 启用深度分析
minimal=False # 完整模式
)
# 保存报告
profile.to_file("ydata_profile_report.html")
# 在Jupyter中显示
# profile.to_notebook_iframe()
ydata-profiling报告包含:
| 模块 | 内容 | 价值 |
|---|---|---|
| Overview | 数据集概览、变量类型、内存占用 | 快速了解数据规模 |
| Variables | 每个变量的详细统计和可视化 | 深入理解单变量特征 |
| Interactions | 变量间交互散点图 | 发现变量关系 |
| Correlations | 多种相关系数矩阵 | 量化变量关联 |
| Missing values | 缺失值模式和可视化 | 指导缺失值处理 |
| Sample | 数据样本展示 | 验证数据内容 |
| Alerts | 数据质量警告 | 发现潜在问题 |
6.3 Sweetviz对比分析
python
# 安装:pip install sweetviz
import sweetviz as sv
# 单数据集分析
report = sv.analyze(df)
report.show_html("sweetviz_report.html")
# 训练集/测试集对比(检测数据漂移)
train_df = df.sample(frac=0.8, random_state=42)
test_df = df.drop(train_df.index)
comparison = sv.compare(train_df, test_df, "训练集 vs 测试集")
comparison.show_html("data_drift_report.html")
# 带目标变量的分析
df_with_target = df.copy()
df_with_target['是否高评分'] = (df_with_target['评分'] > 5).astype(int)
report_with_target = sv.analyze(df_with_target, target_feat='是否高评分')
report_with_target.show_html("target_analysis.html")
6.4 D-Tale交互式探索
python
# 安装:pip install dtale
import dtale
# 启动交互式界面
d = dtale.show(df)
print(f"D-Tale界面地址: {d._url}")
# 在浏览器中打开进行交互式探索
# 支持功能:
# - 实时筛选和排序
# - 列统计信息
# - 可视化图表生成
# - 相关性分析
# - 代码导出
6.5 自动化工具选择决策树
需要交互式探索?
├── 是 -> 使用 D-Tale
└── 否 -> 需要对比数据集?
├── 是 -> 使用 Sweetviz
└── 否 -> 需要最全面的统计?
├── 是 -> 使用 ydata-profiling
└── 否 -> 使用 AutoViz(快速生成)
7. 完整EDA流程案例:电商用户行为分析
7.1 案例背景
某电商平台希望了解用户行为特征,优化推荐策略。数据集包含用户ID、浏览时长、购买金额、访问次数、注册天数、用户等级等字段。
7.2 完整分析代码
python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from ydata_profiling import ProfileReport
import warnings
warnings.filterwarnings('ignore')
# 设置样式
plt.style.use('seaborn-v0_8-whitegrid')
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# ==================== 步骤1:生成模拟数据 ====================
np.random.seed(2025)
n_users = 5000
data = {
'用户ID': range(1, n_users + 1),
'浏览时长': np.random.gamma(2, 15, n_users).clip(1, 120), # 分钟
'购买金额': np.random.exponential(200, n_users).clip(0, 2000), # 元
'访问次数': np.random.poisson(5, n_users).clip(1, 50),
'注册天数': np.random.randint(1, 1000, n_users),
'用户等级': np.random.choice(['青铜', '白银', '黄金', '铂金', '钻石'],
n_users, p=[0.4, 0.3, 0.2, 0.08, 0.02])
}
df = pd.DataFrame(data)
# 添加一些业务逻辑:高等级用户购买金额更高
df.loc[df['用户等级'] == '钻石', '购买金额'] *= 3
df.loc[df['用户等级'] == '铂金', '购买金额'] *= 2
df.loc[df['用户等级'] == '黄金', '购买金额'] *= 1.5
# 添加缺失值(模拟真实场景)
missing_idx = np.random.choice(df.index, size=int(n_users * 0.05), replace=False)
df.loc[missing_idx, '浏览时长'] = np.nan
print("=" * 60)
print("数据集基本信息")
print("=" * 60)
print(f"样本量: {len(df)} 行")
print(f"特征数: {len(df.columns)} 列")
print(f"内存占用: {df.memory_usage(deep=True).sum() / 1024**2:.2f} MB")
print(f"\n数据预览:")
print(df.head(10))
# ==================== 步骤2:数据质量检查 ====================
print("\n" + "=" * 60)
print("数据质量报告")
print("=" * 60)
quality_report = pd.DataFrame({
'列名': df.columns,
'数据类型': df.dtypes.values,
'非空数量': df.count().values,
'缺失数量': df.isnull().sum().values,
'缺失比例': (df.isnull().sum() / len(df)).values,
'唯一值数量': df.nunique().values
})
print(quality_report.to_string(index=False))
# 缺失值处理建议
print("\n缺失值处理建议:")
if df['浏览时长'].isnull().sum() > 0:
missing_pct = df['浏览时长'].isnull().mean() * 100
print(f"- 浏览时长缺失率: {missing_pct:.1f}%")
print(f" 建议: 使用 median={df['浏览时长'].median():.1f} 填充")
# ==================== 步骤3:单变量分析 ====================
print("\n" + "=" * 60)
print("单变量分析")
print("=" * 60)
# 数值型变量统计
numeric_cols = ['浏览时长', '购买金额', '访问次数', '注册天数']
print("\n数值型变量描述统计:")
print(df[numeric_cols].describe().round(2))
# 分类型变量统计
print("\n用户等级分布:")
level_dist = df['用户等级'].value_counts()
level_pct = df['用户等级'].value_counts(normalize=True) * 100
level_summary = pd.DataFrame({
'用户数': level_dist,
'占比(%)': level_pct.round(1)
})
print(level_summary)
# ==================== 步骤4:可视化分析 ====================
fig, axes = plt.subplots(2, 3, figsize=(16, 10))
# 1. 购买金额分布
ax1 = axes[0, 0]
sns.histplot(df['购买金额'], kde=True, ax=ax1, color='steelblue')
ax1.axvline(df['购买金额'].mean(), color='red', linestyle='--', label=f'均值={df["购买金额"].mean():.0f}')
ax1.axvline(df['购买金额'].median(), color='green', linestyle='--', label=f'中位数={df["购买金额"].median():.0f}')
ax1.set_title('购买金额分布', fontweight='bold')
ax1.legend()
# 2. 用户等级饼图
ax2 = axes[0, 1]
colors = ['#CD7F32', '#C0C0C0', '#FFD700', '#E5E4E2', '#B9F2FF']
level_dist.plot(kind='pie', ax=ax2, autopct='%1.1f%%', colors=colors, startangle=90)
ax2.set_title('用户等级分布', fontweight='bold')
ax2.set_ylabel('')
# 3. 浏览时长 vs 购买金额 散点图
ax3 = axes[0, 2]
sample_df = df.dropna().sample(min(1000, len(df)))
scatter = ax3.scatter(sample_df['浏览时长'], sample_df['购买金额'],
c=sample_df['访问次数'], cmap='viridis', alpha=0.6)
ax3.set_xlabel('浏览时长(分钟)')
ax3.set_ylabel('购买金额(元)')
ax3.set_title('浏览时长 vs 购买金额', fontweight='bold')
plt.colorbar(scatter, ax=ax3, label='访问次数')
# 4. 各等级用户购买金额箱线图
ax4 = axes[1, 0]
level_order = ['青铜', '白银', '黄金', '铂金', '钻石']
sns.boxplot(data=df, x='用户等级', y='购买金额', order=level_order, ax=ax4, palette='Set2')
ax4.set_title('各等级用户购买金额分布', fontweight='bold')
# 5. 注册天数 vs 购买金额
ax5 = axes[1, 1]
# 分箱统计
df['注册时长分组'] = pd.cut(df['注册天数'], bins=[0, 30, 90, 180, 365, 1000],
labels=['新用户(<1月)', '近期(1-3月)', '中期(3-6月)', '老用户(6-12月)', '资深(>1年)'])
grouped_purchase = df.groupby('注册时长分组')['购买金额'].mean()
grouped_purchase.plot(kind='bar', ax=ax5, color='coral')
ax5.set_title('不同注册时长用户的平均购买金额', fontweight='bold')
ax5.set_ylabel('平均购买金额(元)')
ax5.tick_params(axis='x', rotation=45)
# 6. 相关性热力图
ax6 = axes[1, 2]
corr_cols = ['浏览时长', '购买金额', '访问次数', '注册天数']
corr_matrix = df[corr_cols].corr()
sns.heatmap(corr_matrix, annot=True, cmap='RdBu_r', center=0, ax=ax6, fmt='.2f')
ax6.set_title('数值变量相关性热力图', fontweight='bold')
plt.tight_layout()
plt.savefig('ecommerce_eda_analysis.png', dpi=150, bbox_inches='tight')
plt.show()
# ==================== 步骤5:业务洞察提取 ====================
print("\n" + "=" * 60)
print("业务洞察")
print("=" * 60)
insights = []
# 洞察1:用户等级与购买金额
level_purchase = df.groupby('用户等级')['购买金额'].agg(['mean', 'median', 'count'])
level_purchase = level_purchase.reindex(level_order)
print("\n1. 用户等级价值分析:")
print(level_purchase.round(2))
diamond_avg = level_purchase.loc['钻石', 'mean']
bronze_avg = level_purchase.loc['青铜', 'mean']
print(f" 钻石用户平均消费是青铜用户的 {diamond_avg/bronze_avg:.1f} 倍")
insights.append(f"钻石用户价值是青铜用户的 {diamond_avg/bronze_avg:.1f} 倍,应重点维护")
# 洞察2:注册时长与购买行为
print("\n2. 用户生命周期价值:")
ltv_analysis = df.groupby('注册时长分组').agg({
'购买金额': ['mean', 'sum'],
'用户ID': 'count'
}).round(2)
ltv_analysis.columns = ['平均消费', '总消费', '用户数']
print(ltv_analysis)
insights.append("老用户(6-12月)贡献最高平均消费,应设计留存策略")
# 洞察3:高价值用户特征
high_value_threshold = df['购买金额'].quantile(0.8)
high_value_users = df[df['购买金额'] >= high_value_threshold]
print(f"\n3. 高价值用户特征(消费前20%,金额>={high_value_threshold:.0f}元):")
print(f" 占比: {len(high_value_users)/len(df)*100:.1f}%")
print(f" 平均浏览时长: {high_value_users['浏览时长'].mean():.1f}分钟")
print(f" 平均访问次数: {high_value_users['访问次数'].mean():.1f}次")
print(f" 高等级用户占比: {(high_value_users['用户等级'].isin(['铂金', '钻石'])).mean()*100:.1f}%")
insights.append(f"高价值用户浏览时长更长,应优化内容推荐")
# 洞察4:相关性发现
print("\n4. 关键相关性发现:")
browse_purchase_corr = df[['浏览时长', '购买金额']].corr().iloc[0, 1]
visit_purchase_corr = df[['访问次数', '购买金额']].corr().iloc[0, 1]
print(f" 浏览时长与购买金额相关性: {browse_purchase_corr:.3f}")
print(f" 访问次数与购买金额相关性: {visit_purchase_corr:.3f}")
if browse_purchase_corr > 0.3:
insights.append("浏览时长与购买金额正相关,可增加内容吸引力")
print("\n" + "=" * 60)
print("核心洞察总结")
print("=" * 60)
for i, insight in enumerate(insights, 1):
print(f"{i}. {insight}")
# ==================== 步骤6:生成自动化报告 ====================
print("\n" + "=" * 60)
print("生成自动化EDA报告...")
print("=" * 60)
profile = ProfileReport(df, title="电商用户行为EDA报告", explorative=True)
profile.to_file("ecommerce_eda_report.html")
print("报告已保存至: ecommerce_eda_report.html")
7.3 案例输出示例
============================================================
核心洞察总结
============================================================
1. 钻石用户价值是青铜用户的 3.2 倍,应重点维护
2. 老用户(6-12月)贡献最高平均消费,应设计留存策略
3. 高价值用户浏览时长更长,应优化内容推荐
4. 浏览时长与购买金额正相关,可增加内容吸引力
8. 避坑小贴士
避坑小贴士1:数据类型陷阱
python
# 错误示例:日期被识别为字符串
df['日期'] = '2025-01-01' # 字符串类型
# 正确做法
df['日期'] = pd.to_datetime(df['日期']) # 转换为日期类型
# 检查数据类型
print(df.dtypes)
避坑小贴士2:缺失值处理误区
| 处理方式 | 适用场景 | 风险 |
|---|---|---|
| 直接删除 | 缺失率<5%且随机缺失 | 损失样本,可能引入偏差 |
| 均值/中位数填充 | 数值型,缺失率中等 | 降低方差,扭曲分布 |
| 前向/后向填充 | 时间序列数据 | 可能传播错误 |
| 模型预测填充 | 缺失率较高 | 引入估计误差 |
| 标记为单独类别 | 分类变量 | 保留缺失信息 |
避坑小贴士3:相关性不等于因果
python
# 警示案例:冰淇淋销量与溺水事件高度相关
# 原因:两者都受气温影响(混杂变量)
# 正确做法:控制混杂变量后重新计算
from scipy.stats import pearsonr
# 偏相关分析(简化版)
def partial_correlation(x, y, z):
"""计算控制z后x和y的偏相关"""
from sklearn.linear_model import LinearRegression
# 回归去除z的影响
model_x = LinearRegression().fit(z.reshape(-1, 1), x)
model_y = LinearRegression().fit(z.reshape(-1, 1), y)
residuals_x = x - model_x.predict(z.reshape(-1, 1))
residuals_y = y - model_y.predict(z.reshape(-1, 1))
return pearsonr(residuals_x, residuals_y)[0]
避坑小贴士4:可视化误导
| 误导手法 | 正确做法 |
|---|---|
| 截断Y轴夸大差异 | Y轴从0开始 |
| 不恰当的坐标轴比例 | 根据数据范围选择合适比例 |
| 3D效果扭曲感知 | 使用2D图表 |
| 颜色选择不当 | 考虑色盲友好配色 |
| 过度平滑KDE | 根据样本量选择合适带宽 |
避坑小贴士5:样本量不足
python
# 检查统计功效
def check_sample_size(effect_size, alpha=0.05, power=0.8):
"""估算所需样本量"""
from scipy.stats import norm
z_alpha = norm.ppf(1 - alpha/2)
z_beta = norm.ppf(power)
n = 2 * ((z_alpha + z_beta) / effect_size) ** 2
return int(np.ceil(n))
# 示例:检测0.2个标准差的差异,每组至少需要394个样本
required_n = check_sample_size(effect_size=0.2)
print(f"每组至少需要 {required_n} 个样本")
9. EDA最佳实践总结
9.1 分析前准备
markdown
## EDA启动检查清单
- [ ] 明确业务问题和分析目标
- [ ] 了解数据来源和采集方式
- [ ] 确认数据字典和字段含义
- [ ] 检查数据更新频率和时效性
- [ ] 准备分析环境(Python库、内存)
9.2 分析过程原则
| 原则 | 说明 |
|---|---|
| 系统性 | 覆盖所有变量,不遗漏潜在信息 |
| 可视化优先 | 用图表代替表格,让数据说话 |
| 假设检验结合 | 不仅描述,还要验证假设 |
| 迭代深入 | 初步发现引导深入探索 |
| 文档记录 | 所有发现都应有记录和解释 |
9.3 分析后交付
markdown
## EDA交付物清单
1. 数据质量报告
- 缺失值分析
- 异常值清单
- 数据一致性检查结果
2. 数据画像报告
- 单变量分布特征
- 变量间关系分析
- 业务规则符合性检查
3. 可视化图表集
- 关键分布图
- 相关性热力图
- 业务洞察图
4. 洞察与建议
- 核心发现(3-5条)
- 数据质量问题及修复建议
- 后续分析方向
本章小结
本章从"让数据说话"的角度,系统讲解了2025年最新的EDA方法论:
核心理念:EDA不是证明假设,而是让数据揭示真相
方法论:三步走策略(数据体检 -> 单变量 -> 多变量)
工具链:
- 基础工具:Pandas + Matplotlib + Seaborn
- 自动化工具:ydata-profiling(全面分析)、Sweetviz(对比分析)、D-Tale(交互探索)
实战能力:通过电商用户行为案例,展示了从数据质量检查到业务洞察提取的完整流程
避坑指南:数据类型、缺失值、相关性解读、可视化误导、样本量
掌握EDA不仅是技术能力的体现,更是数据思维的培养。优秀的数据科学家能够通过EDA快速识别数据异常、预判分析方向、提出有价值的假设。
文章标签:python、数据科学、EDA、探索性数据分析、数据可视化、自动化分析
版权声明:本文为《Python数据科学实战之路》系列教程,转载请注明出处。