第二章:Python数据处理基础

做量化投研,说白了就是跟数据打交道。你策略再牛,模型再花哨,数据一塌糊涂,全白搭。我刚开始入行那会儿,就吃过这个亏——拿到的行情数据有缺失,回测结果漂亮得不行,实盘一跑直接傻眼。所以,数据处理这块,咱们得扎扎实实打牢。

2.1 Pandas核心数据结构:Series与DataFrame

Pandas有两个核心玩意儿:SeriesDataFrame。你可以把Series想象成一列数据,带个索引标签。DataFrame呢,就是一张表格,有行有列。

核心区别:Series是一维的,DataFrame是二维的。DataFrame由多个Series组成。

我个人习惯,拿到数据第一件事,先看它是Series还是DataFrame。用type()一查就清楚。

import pandas as pd
import numpy as np

# 创建一个Series
s = pd.Series([1, 3, 5, np.nan, 6, 8])
print(s)

# 创建一个DataFrame
df = pd.DataFrame({
    '股票代码': ['000001', '000002', '000003'],
    '收盘价': [10.5, 22.3, 15.8],
    '成交量': [10000, 15000, 8000]
})
print(df)

嗯,这里要注意:np.nan就是缺失值。我在项目中遇到过,很多初学者以为它是字符串,其实不是,它是浮点数类型。后面清洗数据时会经常跟它打交道。

2.2 DataFrame操作:增删改查

DataFrame的操作,说白了就四个字:增、删、改、查。你想想看,Excel里能干的事,Pandas基本都能干,而且更灵活。

2.2.1 查看数据

拿到一个DataFrame,别急着操作。先看看它长什么样。

# 查看前5行
print(df.head())

# 查看基本信息
print(df.info())

# 查看统计摘要
print(df.describe())

我建议,每次读取数据后,先跑一遍info()。它能告诉你每列的数据类型、有多少非空值。有一次我发现某列本该是浮点数,结果显示为object,一查才知道里面有脏数据——混入了字符串。这种坑,早点发现早解决。

2.2.2 选择与过滤

选列、选行、按条件筛选,这是最常用的操作。

# 选择单列
df['收盘价']

# 选择多列
df[['股票代码', '收盘价']]

# 按行索引选择
df.iloc[0:3]  # 前3行

# 按条件筛选
df[df['收盘价'] > 20]

为什么用iloc而不是lociloc是按位置索引,loc是按标签索引。我刚开始也经常搞混,后来记住一句话:iloc是数字位置,loc是标签名字

2.2.3 新增与删除

新增一列很简单,直接赋值就行。删除列用drop

# 新增列
df['涨跌幅'] = [0.02, -0.01, 0.03]

# 删除列
df.drop('成交量', axis=1, inplace=True)

# 删除行
df.drop(0, axis=0, inplace=True)

小技巧:inplace=True表示直接修改原DataFrame。如果不加,它会返回一个新的副本。我个人习惯,除非明确要修改原数据,否则不轻易用inplace=True,避免误操作。

2.3 时间序列处理

量化投研里,时间序列是家常便饭。K线数据、因子数据、回测结果,全都跟时间挂钩。Pandas对时间序列的支持,说实话,是我见过最顺手的。

2.3.1 时间索引

把日期列设为索引,是处理时间序列的第一步。

# 创建时间序列
dates = pd.date_range('2024-01-01', periods=6, freq='D')
df_ts = pd.DataFrame(np.random.randn(6, 4), index=dates, columns=list('ABCD'))
print(df_ts)

freq='D'表示按天。你可以改成'H'(小时)、'T'(分钟)、'M'(月末)。我在项目中遇到过,用'M'时要注意,它默认是月末最后一天,不是月初。

2.3.2 时间切片与重采样

时间索引的好处,就是可以像切蛋糕一样切数据。

# 选择某个月的数据
df_ts['2024-01']

# 选择某个时间段
df_ts['2024-01-02':'2024-01-05']

# 重采样:日线转周线
df_ts.resample('W').mean()

重采样这个功能,我几乎天天用。日线数据转周线、月线,或者高频数据降频,resample一把搞定。注意,重采样后要指定聚合函数,比如mean()sum()ohlc()(生成OHLC数据)。

避坑指南:我曾经在重采样时忘记处理缺失值,结果周线数据里出现了NaN。后来我养成了习惯,重采样前先检查数据完整性,或者用fillna()填充。

2.4 数据清洗实战

数据清洗,说白了就是给数据「洗澡」。脏数据不洗掉,后面分析全是错的。我见过太多人,模型跑得飞起,结果数据源有bug,白忙活一场。

2.4.1 处理缺失值

缺失值是最常见的问题。处理方式无非三种:删除、填充、插值。

# 检查缺失值
print(df.isnull().sum())

# 删除含缺失值的行
df.dropna(inplace=True)

# 填充缺失值
df.fillna(method='ffill', inplace=True)  # 前向填充
df.fillna(method='bfill', inplace=True)  # 后向填充
df.fillna(df.mean(), inplace=True)       # 用均值填充

我个人习惯,金融数据尽量用前向填充。因为股票价格是连续的,缺失值用上一个交易日的价格填充,逻辑上说得通。用均值填充反而可能引入偏差。

2.4.2 处理重复值

重复数据也是常客。比如数据源重复推送,或者合并数据时产生了重复行。

# 检查重复行
print(df.duplicated().sum())

# 删除重复行
df.drop_duplicates(inplace=True)

# 按指定列去重
df.drop_duplicates(subset=['股票代码', '日期'], inplace=True)

嗯,这里要注意:duplicated()默认判断所有列是否完全相同。如果你只想按某几列去重,记得指定subset参数。

2.4.3 处理异常值

异常值,比如某只股票突然涨了1000%,或者成交量突然为0。这些很可能是数据错误。

# 用Z-score检测异常值
from scipy import stats
z_scores = np.abs(stats.zscore(df['收盘价']))
df_clean = df[(z_scores < 3)]

# 用IQR检测异常值
Q1 = df['收盘价'].quantile(0.25)
Q3 = df['收盘价'].quantile(0.75)
IQR = Q3 - Q1
df_clean = df[(df['收盘价'] >= Q1 - 1.5*IQR) & (df['收盘价'] <= Q3 + 1.5*IQR)]

小技巧:我建议先用describe()看数据的分布,如果最大值和最小值明显离谱,再动手清洗。别一上来就全删,有些异常值可能是真实的极端行情。

知识体系总览

下面这张图,是我自己总结的数据处理核心逻辑。你照着这个流程走,基本不会出大错。

Python数据处理核心流程 数据读取 数据预览 数据清洗 转换 缺失值处理 重复值处理 异常值处理 时间序列处理 时间索引设置 重采样与切片 清洗后的数据 → 因子计算 → 策略回测

这张图里,我特意把「数据清洗」拆成了三个子步骤。你按这个顺序来,基本能覆盖90%的脏数据问题。剩下的10%,就得靠经验积累了。

好了,数据处理这块,咱们就聊到这儿。记住一句话:数据质量决定策略上限。后面咱们会用到这些技能,去处理真实的行情数据、财务数据。到时候你会发现,今天打的基础,全都能用上。