Day 5: 探索性数据分析(EDA)
学习目标
完成今天的学习后,你将能够:
- 理解EDA的概念和流程
- 进行单变量分析
- 进行多变量分析
- 掌握相关性分析方法
技术原理
什么是探索性数据分析?
探索性数据分析(Exploratory Data Analysis, EDA)是一种数据分析方法,通过总结数据的主要特征、发现数据中的模式、异常和关系,为后续的建模和分析提供基础。
EDA的目标
- 了解数据:数据的形状、类型、质量
- 发现模式:趋势、周期、异常
- 验证假设:检验初步假设
- 指导后续分析:为特征工程和建模提供方向
EDA流程
- 数据概览:查看数据基本信息
- 单变量分析:分析单个变量的分布
- 多变量分析:分析变量之间的关系
- 异常检测:识别异常值
- 特征工程:基于EDA结果创建新特征
分析方法
单变量分析
- 数值变量:均值、中位数、标准差、分布
- 类别变量:频数、占比、众数
多变量分析
- 数值-数值:相关性、散点图
- 类别-类别:交叉表、卡方检验
- 数值-类别:分组统计、箱线图
案例:鸢尾花数据集EDA
我们将对经典的鸢尾花数据集进行探索性数据分析,包括:
- 数据概览
- 单变量分析
- 多变量分析
- 相关性分析
- 结论总结
应用场景
- 数据理解:在建模前理解数据
- 数据质量检查:发现数据问题
- 特征发现:发现有用的特征
- 假设生成:生成可检验的假设
- 报告生成:生成数据报告
代码案例
数据概览
python
# 数据概览示例
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import load_iris
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 1. 加载鸢尾花数据集
iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['species'] = pd.Categorical.from_codes(iris.target, iris.target_names)
print("数据概览:")
print(f"数据形状: {df.shape}")
print(f"\n前5行数据:")
print(df.head())
# 2. 数据基本信息
print("\n数据基本信息:")
print(df.info())
# 3. 数据类型
print("\n数据类型:")
print(df.dtypes)
# 4. 缺失值检查
print("\n缺失值检查:")
print(df.isnull().sum())
# 5. 数值列统计信息
print("\n数值列统计信息:")
print(df.describe())
# 6. 类别列统计信息
print("\n类别列统计信息:")
print(df['species'].value_counts())
# 7. 数据分布可视化
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 各特征的直方图
for i, col in enumerate(iris.feature_names):
row = i // 2
col_idx = i % 2
axes[row, col_idx].hist(df[col], bins=20, edgecolor='black', alpha=0.7)
axes[row, col_idx].set_title(f'{col}分布')
axes[row, col_idx].set_xlabel(col)
axes[row, col_idx].set_ylabel('频数')
plt.suptitle('鸢尾花数据集特征分布', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()单变量分析
python
# 单变量分析示例
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import load_iris
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 加载数据
iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['species'] = pd.Categorical.from_codes(iris.target, iris.target_names)
# 1. 数值变量分析
print("数值变量分析:")
print("=" * 50)
for col in iris.feature_names:
print(f"\n{col}:")
print(f" 均值: {df[col].mean():.2f}")
print(f" 中位数: {df[col].median():.2f}")
print(f" 标准差: {df[col].std():.2f}")
print(f" 最小值: {df[col].min():.2f}")
print(f" 最大值: {df[col].max():.2f}")
print(f" 偏度: {df[col].skew():.2f}")
print(f" 峰度: {df[col].kurtosis():.2f}")
# 2. 数值变量可视化
fig, axes = plt.subplots(2, 4, figsize=(16, 8))
for i, col in enumerate(iris.feature_names):
# 直方图
axes[0, i].hist(df[col], bins=20, edgecolor='black', alpha=0.7, color='skyblue')
axes[0, i].axvline(df[col].mean(), color='red', linestyle='--', label=f'均值: {df[col].mean():.2f}')
axes[0, i].axvline(df[col].median(), color='green', linestyle='--', label=f'中位数: {df[col].median():.2f}')
axes[0, i].set_title(f'{col}分布')
axes[0, i].legend()
# 箱线图
axes[1, i].boxplot(df[col], vert=False)
axes[1, i].set_title(f'{col}箱线图')
plt.suptitle('数值变量单变量分析', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()
# 3. 类别变量分析
print("\n类别变量分析:")
print("=" * 50)
species_counts = df['species'].value_counts()
print(f"\n各类别数量:")
print(species_counts)
print(f"\n各类别占比:")
print(species_counts / len(df) * 100)
# 类别变量可视化
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# 柱状图
species_counts.plot(kind='bar', ax=axes[0], color=['#FF9999', '#66B2FF', '#99FF99'])
axes[0].set_title('鸢尾花种类分布')
axes[0].set_xlabel('种类')
axes[0].set_ylabel('数量')
axes[0].tick_params(axis='x', rotation=0)
# 饼图
axes[1].pie(species_counts, labels=species_counts.index, autopct='%1.1f%%',
colors=['#FF9999', '#66B2FF', '#99FF99'], startangle=90)
axes[1].set_title('鸢尾花种类占比')
axes[1].axis('equal')
plt.tight_layout()
plt.show()
# 4. 分布检验
from scipy import stats
print("\n正态性检验 (Shapiro-Wilk):")
print("=" * 50)
for col in iris.feature_names:
stat, p_value = stats.shapiro(df[col])
print(f"{col}: 统计量={stat:.4f}, p值={p_value:.4f}")
if p_value > 0.05:
print(f" -> 不能拒绝正态分布假设")
else:
print(f" -> 拒绝正态分布假设")多变量分析
python
# 多变量分析示例
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import load_iris
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 加载数据
iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['species'] = pd.Categorical.from_codes(iris.target, iris.target_names)
# 1. 数值-数值关系分析
print("数值-数值关系分析:")
print("=" * 50)
# 相关性矩阵
correlation_matrix = df[iris.feature_names].corr()
print("\n相关性矩阵:")
print(correlation_matrix)
# 热力图可视化
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', center=0,
square=True, linewidths=0.5)
plt.title('特征相关性热力图', fontsize=16, fontweight='bold')
plt.show()
# 2. 散点图矩阵
print("\n散点图矩阵:")
sns.pairplot(df, hue='species', markers=['o', 's', 'D'])
plt.suptitle('鸢尾花数据集散点图矩阵', y=1.02, fontsize=16, fontweight='bold')
plt.show()
# 3. 数值-类别关系分析
print("\n数值-类别关系分析:")
print("=" * 50)
# 按类别分组统计
grouped_stats = df.groupby('species')[iris.feature_names].agg(['mean', 'std', 'min', 'max'])
print("\n按类别分组统计:")
print(grouped_stats)
# 箱线图比较
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
for i, col in enumerate(iris.feature_names):
row = i // 2
col_idx = i % 2
sns.boxplot(x='species', y=col, data=df, ax=axes[row, col_idx])
axes[row, col_idx].set_title(f'{col}按种类分布')
plt.suptitle('各类别特征分布比较', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()
# 4. 小提琴图
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
for i, col in enumerate(iris.feature_names):
row = i // 2
col_idx = i % 2
sns.violinplot(x='species', y=col, data=df, ax=axes[row, col_idx])
axes[row, col_idx].set_title(f'{col}按种类分布')
plt.suptitle('各类别特征分布(小提琴图)', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()
# 5. 类别-类别关系分析
print("\n类别-类别关系分析:")
print("=" * 50)
# 创建一个新特征:花瓣大小类别
df['petal_size'] = pd.cut(df['petal length (cm)'],
bins=[0, 2, 4, 7],
labels=['小', '中', '大'])
# 交叉表
cross_tab = pd.crosstab(df['species'], df['petal_size'])
print("\n物种与花瓣大小交叉表:")
print(cross_tab)
# 卡方检验
from scipy.stats import chi2_contingency
chi2, p_value, dof, expected = chi2_contingency(cross_tab)
print(f"\n卡方检验结果:")
print(f"卡方统计量: {chi2:.4f}")
print(f"p值: {p_value:.4f}")
print(f"自由度: {dof}")
# 可视化交叉表
cross_tab.plot(kind='bar', stacked=True, figsize=(10, 6))
plt.title('物种与花瓣大小关系', fontsize=16, fontweight='bold')
plt.xlabel('物种')
plt.ylabel('数量')
plt.legend(title='花瓣大小')
plt.xticks(rotation=0)
plt.show()高级EDA技巧
python
# 高级EDA技巧示例
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import load_iris
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 加载数据
iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['species'] = pd.Categorical.from_codes(iris.target, iris.target_names)
# 1. 特征工程
df['sepal_ratio'] = df['sepal length (cm)'] / df['sepal width (cm)']
df['petal_ratio'] = df['petal length (cm)'] / df['petal width (cm)']
df['sepal_area'] = df['sepal length (cm)'] * df['sepal width (cm)']
df['petal_area'] = df['petal length (cm)'] * df['petal width (cm)']
print("特征工程后的数据:")
print(df.head())
# 2. 新特征分析
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 萼片比例
sns.boxplot(x='species', y='sepal_ratio', data=df, ax=axes[0, 0])
axes[0, 0].set_title('萼片比例按种类分布')
# 花瓣比例
sns.boxplot(x='species', y='petal_ratio', data=df, ax=axes[0, 1])
axes[0, 1].set_title('花瓣比例按种类分布')
# 萼片面积
sns.boxplot(x='species', y='sepal_area', data=df, ax=axes[1, 0])
axes[1, 0].set_title('萼片面积按种类分布')
# 花瓣面积
sns.boxplot(x='species', y='petal_area', data=df, ax=axes[1, 1])
axes[1, 1].set_title('花瓣面积按种类分布')
plt.suptitle('新特征分析', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()
# 3. 聚类分析可视化
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
# 标准化数据
scaler = StandardScaler()
X_scaled = scaler.fit_transform(df[iris.feature_names])
# K-means聚类
kmeans = KMeans(n_clusters=3, random_state=42)
df['cluster'] = kmeans.fit_predict(X_scaled)
# 聚类结果可视化
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# 实际类别
scatter1 = axes[0].scatter(df['petal length (cm)'], df['petal width (cm)'],
c=df['species'].cat.codes, cmap='viridis', alpha=0.6)
axes[0].set_title('实际类别')
axes[0].set_xlabel('花瓣长度')
axes[0].set_ylabel('花瓣宽度')
legend1 = axes[0].legend(*scatter1.legend_elements(), title="类别")
# 聚类结果
scatter2 = axes[1].scatter(df['petal length (cm)'], df['petal width (cm)'],
c=df['cluster'], cmap='viridis', alpha=0.6)
axes[1].set_title('聚类结果')
axes[1].set_xlabel('花瓣长度')
axes[1].set_ylabel('花瓣宽度')
legend2 = axes[1].legend(*scatter2.legend_elements(), title="聚类")
plt.suptitle('聚类分析可视化', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()
# 4. 聚类评估
from sklearn.metrics import silhouette_score, adjusted_rand_score
# 轮廓系数
silhouette_avg = silhouette_score(X_scaled, df['cluster'])
print(f"\n轮廓系数: {silhouette_avg:.4f}")
# 调整兰德指数
ari = adjusted_rand_score(df['species'].cat.codes, df['cluster'])
print(f"调整兰德指数: {ari:.4f}")
# 5. EDA报告生成
print("\n" + "=" * 60)
print("鸢尾花数据集EDA报告")
print("=" * 60)
print("\n1. 数据集概述:")
print(f" - 样本数量: {len(df)}")
print(f" - 特征数量: {len(iris.feature_names)}")
print(f" - 类别数量: {len(iris.target_names)}")
print(f" - 类别分布: {dict(df['species'].value_counts())}")
print("\n2. 主要发现:")
print(" - 花瓣特征比萼片特征更能区分不同种类")
print(" - setosa种类与其他两种有明显区别")
print(" - versicolor和virginica有一定重叠")
print("\n3. 特征重要性:")
print(" - 花瓣长度和花瓣宽度是最具区分性的特征")
print(" - 萼片长度和萼片宽度区分性相对较弱")
print("\n4. 建议:")
print(" - 后续建模可以重点关注花瓣特征")
print(" - 可以考虑创建花瓣比例等新特征")
print(" - 对于versicolor和virginica的区分需要更复杂的模型")课后练习
练习1:数据概览
- 选择一个数据集(如泰坦尼克号数据集)
- 查看数据的基本信息
- 检查缺失值
- 查看数据类型
练习2:单变量分析
- 分析数值变量的分布
- 计算统计量(均值、中位数、标准差)
- 绘制直方图和箱线图
- 进行正态性检验
练习3:多变量分析
- 计算相关性矩阵
- 绘制热力图
- 分析类别变量之间的关系
- 进行卡方检验
练习4:综合EDA
- 选择一个完整数据集
- 进行完整的EDA流程
- 特征工程
- 生成EDA报告
常见问题
Q1: EDA需要多长时间?
A: 取决于数据集大小和复杂度,通常需要1-3小时。大型数据集可能需要更长时间。
Q2: 如何选择分析方法?
A: 根据数据类型选择:
- 数值变量:直方图、箱线图、散点图
- 类别变量:柱状图、饼图、交叉表
- 混合分析:分组统计、小提琴图
Q3: 如何处理高维数据?
A: 可以使用:
- 降维技术(PCA、t-SNE)
- 特征选择
- 成对关系图(选择部分特征)
Q4: EDA和统计检验有什么区别?
A: EDA是探索性的,目的是发现模式;统计检验是验证性的,目的是检验假设。
Q5: 如何记录EDA过程?
A: 可以使用:
- Jupyter Notebook
- 数据报告
- 可视化仪表板
下一步学习
完成今天的学习后,建议你:
- 练习对不同数据集进行EDA
- 掌握各种分析方法
- 学习特征工程技巧
- 准备进入第二阶段的学习:数据分析进阶
恭喜你完成了数据分析基础阶段的学习!明天我们将进入数据分析进阶阶段,学习统计分析基础。