3、数据探索性分析(EDA):描述性统计、时间序列可视化、分布分析、相关性分析、缺失值模式识别

拿到金融时间序列数据,别急着建模。我个人的习惯是——先跟数据「聊聊天」。

EDA 就是这场对话。说白了,就是通过统计和图表,摸清数据的脾气。你想想看,如果连数据长什么样都不知道,后面做的模型再花哨,也是空中楼阁。

核心目标: 理解数据分布、识别异常值、发现变量关系、定位缺失模式。

3.1 描述性统计:先看数字怎么说

描述性统计是 EDA 的第一步。我一般会先跑一个 df.describe(),看看均值、标准差、分位数这些基础指标。

import pandas as pd
import numpy as np

# 加载数据
df = pd.read_csv('stock_data.csv', parse_dates=['date'], index_col='date')

# 描述性统计
desc = df.describe()
print(desc)

输出大概长这样:

指标 close volume returns
count 2520 2520 2519
mean 45.67 1.23e7 0.0003
std 8.91 5.67e6 0.021
min 28.34 2.10e6 -0.098
25% 38.12 7.89e6 -0.012
50% 44.56 1.10e7 0.001
75% 52.34 1.56e7 0.014
max 68.90 3.45e7 0.112
我的经验: 注意 count 列。如果某个字段的 count 明显少于其他字段,说明有缺失值。另外,看 min 和 max 是否在合理范围内。比如股票收益率出现 -0.5,那大概率是数据录入错误。

嗯,这里要注意——金融数据经常有「尖峰厚尾」特征。正态分布那一套,在金融数据上往往不适用。我见过不少新手直接用 3σ 原则剔除异常值,结果把真实的极端行情给剔掉了。

3.2 时间序列可视化:一图胜千言

描述性统计只能告诉你「数字是什么」,但时间序列图能告诉你「故事是什么」。

import matplotlib.pyplot as plt
import seaborn as sns

plt.style.use('seaborn-v0_8-darkgrid')
fig, axes = plt.subplots(3, 1, figsize=(12, 10))

# 价格走势
axes[0].plot(df.index, df['close'], color='#2E86AB', linewidth=1.5)
axes[0].set_title('收盘价走势', fontsize=14)
axes[0].set_ylabel('价格')

# 收益率
axes[1].plot(df.index, df['returns'], color='#A23B72', linewidth=0.8, alpha=0.7)
axes[1].set_title('日收益率', fontsize=14)
axes[1].set_ylabel('收益率')
axes[1].axhline(y=0, color='gray', linestyle='--', linewidth=0.5)

# 成交量
axes[2].bar(df.index, df['volume'], color='#F18F01', width=1, alpha=0.6)
axes[2].set_title('成交量', fontsize=14)
axes[2].set_ylabel('成交量')

plt.tight_layout()
plt.show()

看这三张图,你能发现什么?

  • 价格走势: 有没有明显的趋势?有没有突然的跳空缺口?
  • 收益率: 波动率是否随时间变化?有没有极端值聚集的现象?
  • 成交量: 有没有异常放量或缩量的日期?
避坑指南: 我曾经在分析某只股票时,发现价格走势图上有几个突兀的「尖峰」。查了半天,原来是除权除息日的数据没做复权处理。所以,画图之前,先确认数据是否已经过复权处理。

3.3 分布分析:数据长什么样?

分布分析能告诉你数据的「身材」——是胖是瘦,是左偏还是右偏。

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# 直方图 + 核密度估计
axes[0].hist(df['returns'], bins=50, density=True, alpha=0.6, color='#2E86AB')
df['returns'].plot.kde(ax=axes[0], color='#A23B72', linewidth=2)
axes[0].set_title('收益率分布', fontsize=14)
axes[0].set_xlabel('收益率')

# Q-Q图
from scipy import stats
stats.probplot(df['returns'].dropna(), dist='norm', plot=axes[1])
axes[1].set_title('Q-Q图(正态性检验)', fontsize=14)

plt.tight_layout()
plt.show()

直方图配合核密度曲线,能直观看出分布形态。Q-Q 图则用来检验数据是否服从正态分布。

金融数据有个特点——峰度通常大于 3。这意味着「肥尾」,极端值出现的概率比正态分布预测的要高。我做过一个统计,A 股日收益率超过 ±5% 的天数,比正态分布预测的多出 3 倍以上。

关键指标:
  • 偏度(Skewness): 正偏表示右尾长,负偏表示左尾长。股票收益率通常有轻微的负偏。
  • 峰度(Kurtosis): 大于 3 表示尖峰肥尾,小于 3 表示平峰瘦尾。

3.4 相关性分析:变量之间怎么勾搭?

金融数据里,变量之间往往存在复杂的相关性。比如,大盘涨的时候,个股大概率跟着涨。但有些相关性是虚假的,需要警惕。

# 计算相关系数矩阵
corr_matrix = df[['close', 'volume', 'returns', 'ma5', 'ma20', 'volatility']].corr()

# 热力图可视化
plt.figure(figsize=(10, 8))
sns.heatmap(corr_matrix, annot=True, cmap='RdBu_r', center=0, 
            square=True, linewidths=0.5, fmt='.2f')
plt.title('变量相关系数矩阵', fontsize=14)
plt.show()

看热力图时,我一般关注三点:

  1. 强相关对: 相关系数绝对值大于 0.7 的,要考虑是否有多重共线性问题。
  2. 与目标变量的关系: 哪些变量与收益率的相关性较高?这能帮你筛选特征。
  3. 意外的不相关: 理论上应该相关的变量却不相关,这可能是数据有问题。
我的经验: 别只看 Pearson 相关系数。金融数据经常有非线性关系,比如成交量和波动率之间,往往是 U 型关系——低成交量和高成交量都对应高波动。这时候,Spearman 秩相关系数更靠谱。

3.5 缺失值模式识别:数据里的「黑洞」

缺失值在金融数据里太常见了。停牌、节假日、数据源故障,都会导致缺失。关键是要搞清楚缺失的模式——是随机缺失,还是系统缺失?

# 缺失值统计
missing_count = df.isnull().sum()
missing_ratio = df.isnull().mean() * 100

missing_df = pd.DataFrame({
    '缺失数量': missing_count,
    '缺失比例(%)': missing_ratio
})
print(missing_df[missing_df['缺失数量'] > 0])

# 缺失值可视化
plt.figure(figsize=(12, 4))
sns.heatmap(df.isnull(), cbar=False, cmap='viridis', yticklabels=False)
plt.title('缺失值模式热力图', fontsize=14)
plt.xlabel('变量')
plt.ylabel('样本')
plt.show()

缺失值热力图能直观展示缺失的模式。我总结了几种常见情况:

缺失模式 特征 常见原因
完全随机缺失(MCAR) 缺失位置无规律 数据采集偶发故障
随机缺失(MAR) 缺失与其他变量相关 低成交量日缺失某些指标
非随机缺失(MNAR) 缺失与自身值相关 极端行情导致数据缺失
块状缺失 连续时间段内全部缺失 停牌、节假日
避坑指南: 我曾经处理过一份美股数据,发现某只股票连续 5 天缺失数据。一开始以为是停牌,后来查了日历才发现——那周是感恩节,美股休市。所以,处理缺失值之前,先确认是不是正常的非交易日。

识别出缺失模式后,才能选择合适的处理方法。比如,MCAR 可以直接删除,MAR 可以用插值,MNAR 则需要更复杂的模型来处理。这些内容,我们会在后面的章节详细展开。

EDA 核心流程框架 数据探索性分析 描述性统计 时间序列可视化 分布分析 均值、标准差、分位数 价格、收益率、成交量 直方图、Q-Q图、偏度峰度 相关性分析 缺失值模式识别 异常值检测 为后续数据清洗与建模奠定基础

EDA 做得好,后面数据清洗和建模就顺风顺水。做不好,那就是「垃圾进,垃圾出」。我个人觉得,EDA 花的时间至少应该占整个项目时间的 30%。别急着跑模型,先把数据摸透了再说。

蓝海数据掘金营,专注资料整理