news 2026/9/11 6:40:21

从NumPy到Pandas再到量化项目:一条完整的数据分析学习路径

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从NumPy到Pandas再到量化项目:一条完整的数据分析学习路径

学数据分析时,Pandas 是绕不开的库,也是最容易让人半途而废的库。第一个原因很实际:不少教程把 NumPy、Pandas、Matplotlib 拆成独立章节,每个章节又单独讲 API,读者学完 NumPy 的 ndarray 后,并不知道它和 DataFrame 是什么关系;等进入 Pandas,又要消化 Series、Index、groupby、merge 一堆概念,于是很容易卡在“每个函数都见过,组合起来不会用”的状态。第二个原因是学习目标太分散,缺少一个能让知识串起来的项目。

这篇文章按“NumPy 基础 -> Pandas 数据操作 -> 数据清洗 -> 小型量化项目”这条线,把 Pandas 的学习过程拆开讲。目标不是罗列所有 API,而是搭出一条从数组到二维表,再到指标计算和项目验证的完整链路。“从 numpy 到量化项目”并不是一句口号,而是数据分析学习中很实用的一条路径:先有数组思维,再理解表格,再用表格完成一个可复盘的项目。文章后面会给出代码、排查方法和学习清单,适合刚开始学 Pandas,或者学了一半总觉得缺根的读者。

1. 先理解 NumPy 数组,Pandas 才不是堆 API

1.1 为什么先学 NumPy:数据结构决定思维方式

Pandas 的 DataFrame 底层依赖 NumPy,很多操作也沿用了“按整行整列批量计算”的向量化思路。如果跳过 NumPy,直接背df.groupby().agg(),遇到报错时很难判断是数据问题还是 API 用法问题。

NumPy 的核心不是“比 Python 列表快一点”,而是提供了多维数组ndarray。多维数组有三个关键特点:

  1. 数组中的元素类型通常一致,便于底层连续存储。
  2. 支持按轴切片和布尔索引,不需要写多层 for 循环。
  3. 支持广播机制,可以自动补全形状,让向量化计算自然发生。

Pandas 的 Series 可以理解成“带标签的一维 ndarray”,DataFrame 可以理解成“多个 Series 在列方向上拼起来”,再额外增加行索引和列索引。因此 NumPy 里的切片、索引、形状、广播规则,到 Pandas 里大多仍然成立,只是多了一层标签处理。

1.2 NumPy 最小入门:创建、切片、布尔索引

先演示最常见的创建方式:

import numpy as np arr = np.array([1, 2, 3, 4, 5]) print(arr.shape) # (5,) print(arr.dtype) # int64 arr2 = np.arange(12).reshape(3, 4) print(arr2)

输出结果是:

[[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]]

reshape(3, 4)把长度为 12 的一维数据改成 3 行 4 列。这个“形状”概念在整个 Pandas 里都会反复出现,例如df.shape返回的就是行数和列数。

切片和布尔索引是 pandas 中筛数据的底层基础:

print(arr2[1, 2]) # 第 1 行第 2 列,结果是 6 print(arr2[0:2, 1:3]) # 前两行、第 1 到第 3 列 mask = arr2 > 5 print(arr2[mask]) # 输出所有大于 5 的元素

布尔索引在 Pandas 里表现为df[df['price'] > 100],思路完全一致:先生成一个布尔数组,再根据布尔数组选择满足条件的行。

1.3 广播机制:避免 for 循环的关键

向量化的意思是“对整组数据做同一个操作”,而不是用循环一个元素一个元素算。例如同时计算多只股票每天的收益率:

prices = np.array([100.0, 101.5, 102.3, 101.0]) prev = np.array([99.0, 100.0, 101.5, 102.3]) ret = prices / prev - 1 print(ret)

这里没有写for i in range(len(prices)),程序照样完成整组计算。原因是 NumPy 会对两个形状一致的数组做逐元素运算,这就是向量化。

广播机制更进一步:当两个数组形状不一致但兼容时,NumPy 会自动扩展维度。

matrix = np.arange(12).reshape(3, 4) row = np.array([1, 2, 3, 4]) print(matrix + row)

matrix是 3 行 4 列,row是长度为 4 的一维数组,NumPy 会把row横向复制成 3 行,再做加法。这个机制在 Pandas 里也常用,例如“对每一列都减去平均值”,本质就是按轴广播。

1.4 常见坑:别把数组形状和标量混在一起

第一个坑是误以为ndarray和 Python 列表完全等价。列表支持+做拼接,数组的+是逐元素相加,结果容易让人意外。

list1 = [1, 2, 3] arr = np.array(list1) print(list1 + list1) # [1, 2, 3, 1, 2, 3] print(arr + arr) # [2 4 6]

第二个坑是广播失效时没有认真看形状。比如一个形状为(3, 4)的数组,和一个长度为 3 的数组相加,会抛ValueError,而不是自动转置。解决办法是先用reshape(3, 1)np.newaxis调整维度。

第三个坑是np.random使用全局随机状态。学习时为了结果可复现,推荐显式创建随机数生成器:

rng = np.random.default_rng(42) data = rng.normal(0, 1, 100)

这在量化项目中将直接决定你能否稳定复现一份回测结果。

2. Pandas 两大核心:Series 与 DataFrame,先建立数据结构直觉

2.1 Series 是带索引的一维数组

Series 由两个部分组成:数据值和索引。索引不一定是 0,1,2,3,也可以是日期、字符串或任意标签。

import pandas as pd s = pd.Series([10, 20, 30], index=['a', 'b', 'c']) print(s) print(s['b']) print(s[s > 15])

这里的s[s > 15]和 NumPy 布尔索引是同一个套路。Series 对数据分析的意义在于:当你对 Series 做计算时,索引会自动对齐。这是 Pandas 和纯 NumPy 数组最大的区别之一,也是后续处理不同来源数据时的关键能力。

2.2 DataFrame 是由多个 Series 在列方向拼成的二维表

DataFrame 可以理解成 Excel 表格:行是样本,列是字段。每一列本质都是一个 Series,并且共享同一个行索引。

最直接的习惯是先用字典创建 DataFrame:

data = { 'date': ['2024-01-01', '2024-01-02', '2024-01-03'], 'symbol': ['HS300', 'HS300', 'HS300'], 'close': [3500.1, 3520.2, 3480.0], 'volume': [1200000, 1350000, 1150000] } df = pd.DataFrame(data) print(df) print(df.dtypes)

注意,上面的date列在创建后是字符串类型。如果后续要按日期排序、按月份聚合,必须先转成datetime64,否则会出现“看起来是日期,但排序结果完全不对”的问题。

创建 DataFrame 的常见方式有很多,除了字典之外,还有从 NumPy 数组创建、从列表创建、从外部文件读取等。从外部文件读取时经常用到这几类:

df = pd.read_csv('data.csv') df = pd.read_excel('data.xlsx') df = pd.read_parquet('data.parquet')

如果要处理很大的数据,Parquet 和 Feather 格式比 CSV 更节省磁盘空间,读取速度也更快。它们依赖 PyArrow,安装pyarrow后即可使用。

2.3 索引方式:loc、iloc、布尔条件

很多 Pandas 初学者会在df['close']df.loc[:, 'close']df.iloc[:, 2]之间犹豫。这三者本质不同:

  • df['close']:按列名取一列,返回 Series。如果列名正好和 DataFrame 的方法名冲突,会引发歧义。
  • df.loc[行标签, 列标签]:基于标签索引,取值时会包含结束位置。
  • df.iloc[行位置, 列位置]:基于整数位置索引,类似 NumPy 切片。
print(df.loc[1, 'close']) # 取出行索引为 1 的 close 值 print(df.iloc[1, 2]) # 取出第 1 行第 2 列的值 print(df.loc[df['volume'] > 1000000]) # 布尔条件筛选

布尔筛选和loc组合,是数据分析中最常用的筛选方式。不要把df[df['volume'] > 1000000]df['volume'] > 1000000搞混:后者返回的是布尔 Series,前者才返回筛选后的 DataFrame。

2.4 数据类型转换:astype 与 to_datetime

热搜词里经常看到“pandas 数据类型转换”,因为数据清洗中类型不匹配是最常见的坑之一。

df['close'] = df['close'].astype(float) df['date'] = pd.to_datetime(df['date']) df['volume'] = pd.to_numeric(df['volume'], errors='coerce')

errors='coerce'表示转换失败时置为NaN,而不是直接抛错。这在处理脏数据时很有用,但也要注意:置为NaN后需要额外处理缺失值,否则后续聚合结果会受影响。

字符串列中如果混杂数字和符号,优先用pd.to_numeric而不是astype(float),因为前者能提供更优雅的容错方式。日期列统一用pd.to_datetime,它还能解析2024/01/0120240101等不同格式。

2.5 数据分析学习中的一个必要检查点

拿到任何 DataFrame,第一件事不是直接调用业务方法,而是先回答四个问题:

  1. 有多少行、多少列?
  2. 每列是什么数据类型?
  3. 有没有缺失值?
  4. 索引是否唯一、是否有序?

对应代码是:

print(df.shape) print(df.dtypes) print(df.isna().sum()) print(df.index.is_unique)

把这些基础检查写成固定习惯,后面做量化项目、商业数据分析、金融风控数据,都会少踩很多坑。很多报错的根因并不是代码写错,而是数据在入口处已经不符合预期。

3. 数据清洗与聚合分析:从 DataFrame 到分析结果

3.1 数据清洗的第一轮:去重、排序、补缺

实际项目里拿到数据,很少是已经整理好的“干净数据”。常见的清洗顺序是先看整体,再处理重复值,再处理缺失值,最后按业务需求做排序和分析。

重复值处理常遇到一个需求:如果两列的值均相同,则取第一条数据即可。这个需求在 Pandas 里是drop_duplicates的典型用法:

df = pd.DataFrame({ 'date': ['2024-01-01', '2024-01-01', '2024-01-02'], 'symbol': ['A', 'A', 'B'], 'close': [10.0, 10.0, 12.0], 'volume': [100, 100, 200] }) df_clean = df.drop_duplicates( subset=['date', 'symbol'], keep='first' ).copy() print(df_clean)

subset指定用来判断重复的列。如果不写,默认是整行全部相同才去重;如果只希望“date 和 symbol 相同就算重复”,必须把subset传进去。keep='first'表示保留排序后的第一条,keep='last'则保留最后一条。

这里有一个容易被忽略的细节:drop_duplicates默认不会修改原 DataFrame,必须赋值给新变量或者使用inplace=True。推荐直接赋值,因为inplace在链式操作下容易出现隐式副作用。

3.2 缺失值处理:isna、dropna、fillna

判断缺失值最直接的方法是:

print(df.isna().sum())

每列缺失数量出来后,再决定是删除还是填充。删除通常用于缺失比例很高、或缺失列与业务无关的情况;填充则更适合时间序列,因为删除会让时间不连续。

时间序列类数据,比如股票价格,最常使用前向填充:

df['close'] = df['close'].fillna(method='ffill')

但这里要注意:Pandas 2.x 已经不建议直接写method='ffill',推荐使用df['close'].ffill()。版本升级后很多老教程代码会抛FutureWarning,这是正常的,不要 panic。

另一个常见做法是按分组填充均值或中位数:

df['volume'] = df.groupby('symbol')['volume'].transform(lambda x: x.fillna(x.median()))

使用transform可以保持返回结果的索引与原始 DataFrame 一致,便于后续直接赋值。

3.3 分组聚合 groupby 与透视表

分组聚合是 Pandas 最强大的能力之一。它的运行逻辑是三步:拆分、应用、合并。

daily = pd.DataFrame({ 'date': ['2024-01-01', '2024-01-01', '2024-01-02', '2024-01-02'], 'symbol': ['A', 'B', 'A', 'B'], 'close': [10.0, 20.0, 10.5, 19.8], 'volume': [1000, 2000, 1100, 1900] }) summary = daily.groupby('date').agg({ 'close': 'mean', 'volume': 'sum' }).reset_index() print(summary)

groupby('date')表示按日期分组,agg可以同时对不同列使用不同聚合函数。最后用reset_index()把分组键从索引变成普通列,方便后续合并或展示。

透视表pivot_table解决的问题和groupby类似,但更适合生成“行、列、值”的交叉表结构:

pivot = pd.pivot_table( daily, index='date', columns='symbol', values='close', aggfunc='mean' ) print(pivot)

pivot 输出结果中,A、B 两列会并排,形成一张宽表。宽表适合看对比,长表适合做统计聚合。很多入门教程没有强调这个区别,导致读者在pivot_tablegroupby之间反复切换却不知道何时用哪个。

更简单的判断方法:如果需要“每一行是一个日期、每一列是一个品种”的分类对比,用pivot_table;如果需要“按某个字段求汇总指标、并继续参与后续计算”,用groupby().agg()

3.4 条件筛选、apply 与 transform 的边界

条件筛选用来生成业务标记变量,比如“成交量是否放大”:

df['volume_ratio'] = df['volume'] / df['volume'].rolling(20).mean() df['is_active'] = np.where(df['volume_ratio'] > 1.5, 1, 0)

np.where是向量化的条件分支,比apply(lambda ...)快得多。apply适合无法用向量化表达的逻辑,但也要小心滥用,因为它本质上是逐行调用 Python 函数,数据量一大就会变慢。

一个常见误区是“能用循环就不用 Pandas API”。实际上,能写成df['a'] * 2的操作就不应该写成df.apply(lambda x: x['a'] * 2, axis=1)。前者性能高一个数量级,而且代码更短。

3.5 总结一条可复用的清洗链路

在实际项目中,可以把数据清洗整理成固定流程,每次拿到新数据后按顺序执行:

  1. 读取数据,统一列名格式:df.columns = [c.lower() for c in df.columns]
  2. 查看shapedtypesisna().sum()
  3. 将日期列转为datetime,数值列转为合适类型。
  4. 按业务主键去重:df.drop_duplicates(subset=[...], keep='first')
  5. 排序:df.sort_values(['date', 'symbol']).reset_index(drop=True)
  6. 处理缺失值,时间序列优先ffill
  7. 创建派生列,比如收益率、移动平均、涨跌幅。
  8. 在进入分析前,保存一份清洗后的副本,避免反复改原始数据。

这条链路不只在量化项目里有效,做 Excel 数据分析、Spark 数据分析、金融风控数据分析时,思路也一样。区别只是数据规模变大后,需要把 Pandas 换成 Spark 或 PyArrow 等工具,但“先检查再清洗再聚合”的流程不会变。

4. 用 Pandas 完成一个小型量化项目:数据准备与指标计算

4.1 项目需求拆解:只做技术复盘,不预测涨跌

很多初学者看到“量化项目”就想到自动交易、赚钱、财务自由,这是错误的学习心态。在教学项目里,量化更合适的定位是“用数据和规则验证一个策略逻辑是否成立”。

本项目只做四件事:

  1. 准备一份模拟的 OHLCV 数据。
  2. 计算移动平均线、收益率、年化波动率。
  3. 用双均线生成交易信号。
  4. 比较策略净值与买入持有净值。

需要说明:这里的策略只是教学样例,不构成投资建议。真实量化环境还要考虑交易成本、滑点、停牌、涨跌停、资金管理等大量因素。

4.2 生成或读取 OHLCV 数据

没有真实行情时,可以用 NumPy 生成模拟数据。为了让结果稳定可复现,显式指定随机种子:

import numpy as np import pandas as pd rng = np.random.default_rng(42) n = 500 dates = pd.date_range('2023-01-01', periods=n, freq='D') returns = rng.normal(0.0005, 0.01, n) close = 100 * np.cumprod(1 + returns) df = pd.DataFrame({ 'date': dates, 'close': close }) print(df.head())

np.cumprod(1 + returns)模拟了净值曲线的累计效果。这里的核心是理解:价格序列可以看成“每日收益率连乘”的结果,后续策略净值也会用同样的思路计算。

如果已有 CSV 数据,可以直接用pd.read_csv读取。要注意把日期列解析为时间类型,并排序。

df = pd.read_csv('market_data.csv', parse_dates=['date']) df.sort_values('date', inplace=True)

4.3 计算收益率、移动平均和波动率

量化分析中大部分指标都依赖三个基础算子:差值、滚动窗口和分组聚合。

df['ret'] = df['close'].pct_change() df['ma5'] = df['close'].rolling(5).mean() df['ma20'] = df['close'].rolling(20).mean() df['volatility'] = df['ret'].rolling(20).std() * np.sqrt(252)

pct_change()计算当前值相对上一日的变化比例,结果是每日收益率。rolling(5).mean()是最近 5 日收盘价的移动平均。rolling(20).std()是 20 日收益率标准差,乘以sqrt(252)是为了换算成年化波动率,因为 A 股一年通常按 252 个交易日估算。

这几行代码充分体现了 Pandas 和 NumPy 的关系:ret是 NumPy 数组批量计算的延伸,rolling是 Pandas 新增的窗口能力。

4.4 用向量化方式生成交易信号

双均线策略的思路是:当短期均线ma5从下方穿过长期均线ma20时,产生买入信号;当ma5从上方穿过ma20时,产生卖出信号。

先定义一个signal列,1 表示持仓,0 表示空仓:

df['signal'] = 0 df.loc[df['ma5'] > df['ma20'], 'signal'] = 1 df['position'] = df['signal'].diff().fillna(0)

这里的signal是“状态”,position是“状态变化”。position == 1表示从空仓变为持仓,position == -1表示从持仓变为空仓。

之所以先计算状态再取差分,而不是直接判断“某天是否上穿”,是因为状态变化需要在连续的时间序列中检测,逐行 if 容易出错且性能差。

4.5 简单回测与结果验证

策略收益率的计算方式是:用前一天的持仓状态乘以当天的收益率。原因是在实际操作中,每天收盘后才能确认信号,次日才能执行。

df['strategy_ret'] = df['position'].shift(1) * df['ret'] df['strategy_nav'] = (1 + df['strategy_ret']).cumprod() df['buy_hold_nav'] = (1 + df['ret']).cumprod()

这里shift(1)是关键,它把持仓状态向后平移一天,避免“当天信号当天交易”的未来函数。未来函数是量化回测中最隐蔽的错误,很多策略回测曲线非常漂亮,实盘却失效,原因之一就在这里。

最后查看尾部结果:

print(df[['date', 'close', 'ma5', 'ma20', 'signal', 'strategy_nav', 'buy_hold_nav']].tail())

运行后会有 500 行数据。对比strategy_navbuy_hold_nav能看出双均线策略在模拟数据上是否优于持有不动。不过这只是单次随机样本,不能说明策略本身好坏。更严谨的方式是多次模拟,或者使用历史真实数据。

这个项目虽然小,但已经覆盖了 NumPy、Pandas、时间序列、滚动窗口、信号生成和回测验证。完整教程里通常会把这一节拆成多集,每集花时间讲一个函数,并把每一步的中间结果打印出来。建议学习时也这样做:每计算一个指标,立即print(df.head())df[['date', 'column']].tail(),确认中间结果符合预期。

5. 环境准备与版本适配:学习、测试、生产三种环境的不同选择

5.1 本地学习环境怎么装 pandas 和 numpy

最直接的方式是使用 pip 安装:

pip install numpy pandas

如果需要画图,再加上:

pip install matplotlib

如果使用 Anaconda,也可以使用 conda:

conda install numpy pandas

安装完成后,在 Python 交互环境里验证版本:

import numpy as np import pandas as pd print(np.__version__) print(pd.__version__)

这一步非常重要。后续教程中的代码如果是基于旧版本写的,某些 API 行为可能已经变化。例如 Pandas 2.x 对字符串类型默认使用str类型,和旧版的object类型存在差异,虽然多数操作兼容,但dtype输出看起来会不一样。

5.2 PyCharm 中安装 pandas 包的常见方式

在 PyCharm 中,打开File -> Settings -> Project -> Python Interpreter,点击右上角+,搜索pandasnumpy,选中后点击Install Package即可。

这里容易踩坑的是选择了错误的 Python 解释器。PyCharm 默认可能使用全局 Python,而不是当前项目创建的虚拟环境。安装包后,项目里仍然报ModuleNotFoundError: No module named 'pandas',通常就是因为解释器没对。

解决办法是先确认右下角或Settings里当前使用的解释器路径,然后打开终端执行:

pip list

检查pandas是否在当前解释器的包列表中。如果不在,就在同一解释器环境下重新pip install pandas

5.3 Python 3.10 与 pandas 版本适配怎么确认

不少学习者会遇到“Python 3.10 到底装哪个 pandas 版本”的问题。这个问题没有固定答案,因为 pandas 会随新版本持续提供对最新 Python 版本的轮子支持。

稳妥的做法是:

  1. 先确认自己的 Python 版本:python --version
  2. 安装 pandas 时不指定版本,让 pip 自动选择当前 Python 可用的最新版本:pip install pandas
  3. 如果项目需要固定版本,再根据官方文档或pip index versions pandas查询可用版本。
  4. 遇到“当前 Python 版本不支持该 pandas 版本”时,优先升级 pandas 到最新版本,而不是降级 pandas 去迁就旧环境。

从经验上看,Python 3.10 配合 pandas 1.4、1.5、2.x 这几种常见组合都能正常使用。但“能用”和“生产级稳定”是两回事。生产项目建议把 Python 版本、pandas 版本、numpy 版本都写进requirements.txtpyproject.toml,并锁定版本范围。

numpy>=1.23,<2.0 pandas>=1.5,<2.2

如果原始材料没有给出明确版本,落地前先确认依赖版本,这是工程习惯,而不是多此一举。

5.4 生产项目还要补齐哪些能力

学习环境能跑通代码,生产环境还需要考虑:

  • 配置外置化:数据路径、参数、数据库连接不要写死在代码里。
  • 日志与监控:记录每次数据读取、清洗、聚合的耗时和异常。
  • 数值稳定性:大文件读取时观察内存使用,必要时分块处理。
  • 数据版本管理:清洗后的数据要保存快照,方便回溯。
  • 错误处理:捕捉读取文件、类型转换、聚合计算中的异常并记录上下文。

例如读取大文件时,可以先看文件大小,再用pd.read_csv(..., nrows=10000)做小规模探测,避免一次性加载把内存打满。

6. 常见报错与排查链路:看到 traceback 后从哪里开始查

6.1 常见报错速查表

报错信息常见原因处理建议
KeyError: 'close'列名不存在,或列名大小写不匹配先打印df.columns.tolist()核对
ValueError: cannot reindex索引不一致,合并或赋值时对齐失败检查两个 DataFrame 的索引是否唯一且类型一致
SettingWithCopyWarning对 DataFrame 切片后直接赋值使用.copy()创建副本,或改为df.loc[...]
FutureWarning: method='ffill'API 即将变更改为df.ffill()
TypeError: unsupported operand数据类型不对,字符串参与了数值计算astypepd.to_numeric
MemoryError数据量太大,一次性加载内存不足分块读取、筛选列、使用 Parquet 列式格式

6.2 KeyError:先检查列名而不是代码

很多新手看到KeyError第一反应是代码写错了,实际上更常见的原因是数据文件列名和预期不一致。比如 CSV 里列名可能是Close,而代码里写的是close

排查顺序:

print(df.columns.tolist()) print(df.dtypes)

这两个输出能解决 80% 的KeyError。如果列名带空格或 BOM 字符,可以提前做列名标准化:

df.columns = df.columns.str.strip().str.lower()

6.3 SettingWithCopyWarning:链式赋值的隐患

这是一个经典警告,很多教程只是说“忽略它”,这并不负责任。警告出现的典型场景:

sub = df[df['volume'] > 1000] sub['flag'] = 1

sub可能只是原始 DataFrame 的一个视图,也可能是一份副本。Pandas 为了避免修改不确定性,就会提示警告。最直接的解决方案是显式复制:

sub = df[df['volume'] > 1000].copy() sub['flag'] = 1

这样明确告诉 Pandas:我要独立操作这份数据。生产代码里,只要你不希望原数据被意外修改,就在筛选、切片后加.copy()

6.4 从现象倒推根因的排查顺序

一个系统性排查思路是按下面顺序走,不要一上来就怀疑框架:

  1. 输入数据是否正确:文件路径、编码、分隔符、列名。
  2. 数据类型是否正确:日期是不是datetime,数值列是不是float/int
  3. 索引是否唯一且有序:重复索引会导致loc返回多行。
  4. 中间结果是否符合预期:每步操作后打印shapeheadtail
  5. 依赖版本是否匹配:在更新 pandas 后,部分老代码行为会变化。
  6. 日志是否出现明确异常:优先看第一个 traceback,不要看最后一行。

例如回测结果全是NaN,按这个顺序排查:

print(df.isna().sum()) # 缺失是否过多 print(df['ret'].head()) # 收益率是否正常 print(df['position'].shift(1).head()) # 持仓是否延迟

6.5 用最小数据复现问题

遇到复杂报错时,最好的办法是摆脱真实数据,用 3 到 5 行构造一个最小可复现样本。比如怀疑merge导致行数膨胀,可以:

left = pd.DataFrame({'key': ['a', 'a'], 'value': [1, 2]}) right = pd.DataFrame({'key': ['a'], 'other': [10]}) merged = left.merge(right, on='key') print(merged)

接着对比实际业务数据与最小样本的结构差异。这个方法几乎适用于所有 Pandas 报错,也适用于 NumPy 数组形状不匹配的问题。完整教程中,排错能力比背 API 更重要,因为真实项目里的错误组合远多于教程示例。

7. 把 34 集教程拆成学习路径:从 NumPy 基础到量化项目

7.1 阶段一:NumPy 基础与数组思维

目标:理解数组形状、切片、广播、随机数,并养成“先确认 shape 再计算”的习惯。

练习清单:

  • 创建长度为 10 的随机数组,输出奇数位置的元素。
  • 创建 5 行 4 列矩阵,计算每列平均值。
  • 把两个形状不同的数组通过广播相加,观察结果。
  • np.cumprod模拟净值曲线。

验收标准:不看文档,能写出从收盘价序列计算每日收益率的代码。

7.2 阶段二:Pandas 数据操作

目标:掌握 Series、DataFrame 的创建、索引、切片、类型转换和文件读写。

练习清单:

  • 从字典创建 DataFrame,并把日期列转为datetime
  • lociloc分别取出某个具体值。
  • 把一个长表通过pivot_table转成宽表。
  • drop_duplicates对指定列去重,只保留首次出现的数据。
  • 读取一个 CSV 文件,输出缺失值统计。

验收标准:能快速说出locilocdrop_duplicates的关键参数。

7.3 阶段三:数据清洗与分析实战

目标:把零散操作整合成一条完整流水线,为后续项目打底。

练习清单:

  • 对一份含缺失值和重复值的表格执行完整清洗流程。
  • groupby().agg()计算分组统计量。
  • transform对分组内的缺失值填充。
  • np.where创建条件标记列。
  • 对比apply和向量化操作的运行时长。

验收标准:拿到一份脏数据后,能按固定流程完成清洗,并解释每一步为什么这样做。

7.4 阶段四:量化项目落地

目标:把 NumPy、Pandas 的知识串成一个可运行的小项目,并理解回测中的基本概念。

练习清单:

  • 生成或读取模拟行情数据。
  • 计算移动平均、收益率、波动率。
  • 生成双均线信号,并用shift(1)避免未来函数。
  • 计算策略净值和买入持有净值。
  • 对比结果,并解释潜在偏差。

验收标准:能独立写出一个小型回测脚本,并能通过打印中间结果解释策略逻辑。

7.5 学习里程碑与自检清单

阶段关键能力达标标准
NumPy数组创建、切片、广播能写出无显式循环的收益率计算
Pandas 基础Series/DataFrame 索引与类型转换能准确处理日期和列名
数据清洗去重、缺失值、分组聚合能整理一份完整清洗流程
量化项目指标计算、信号、回测验证能解释shift(1)的作用

自检时,每次只问三个问题:数据从哪里来?中间结果长什么样?结果怎么验证?如果三个问题都能回答,说明这个知识点已经进入工程实践层面,而不是只停留在 API 名称上。

8. 最佳实践与扩展方向

8.1 日常编码中的 Pandas 规范

  1. 列名统一小写,避免空格和特殊符号。
  2. 日期列尽早转为datetime,数值列尽早转为float/int
  3. 所有清洗步骤都生成新变量,不要反复修改原始 DataFrame。
  4. 使用df.copy()保护视图。
  5. 每条分析链路最后都输出一份中间结果快照,便于回溯。
  6. 写自定义函数时,优先使用向量化操作,禁止在超大 DataFrame 上写 for 循环。

针对第 6 条,常见的例外是复杂字符串清洗或无法用现有 Pandas API 表达的业务逻辑,这个时候可以用apply,但也要限制在数据规模可控的范围内。

8.2 性能建议:向量化、分块、减少复制

数据分析的机器学习场景下,Pandas 性能瓶颈往往不在计算速度,而在内存复制和循环遍历。

推荐做法:

  • 筛选列时只保留需要的字段,减少内存占用。
  • 读取 CSV 时使用usecols指定列,分块读取用chunksize
  • 数据量达到数 GB 时,优先使用 Parquet 格式替代 CSV。
  • 合并多个 DataFrame 前,先确认连接键是否唯一,避免笛卡尔积膨胀。
  • 能使用agg完成的分组聚合,不要写groupby().apply()

一个典型的读取优化:

df = pd.read_csv( 'large_data.csv', usecols=['date', 'symbol', 'close', 'volume'], parse_dates=['date'] )

只用需要的列,读取时间会显著下降。这也符合数据分析项目“低成本验证、再上全量数据”的思路。

8.3 大数据量场景的扩展:Parquet、Feather、Spark

Pandas 适合单机内存中的数据分析。当数据量超过单机内存,或者需要分布式计算时,常见方向是:

  • 使用 PyArrow 读写 Parquet 和 Feather,格式上比 CSV 更高效。
  • 使用 Dask 或 Polars 处理单机超大数据。
  • 使用 Spark 的 DataFrame API 处理分布式数据。

热搜词中出现的“pandas 与 numpy 实现 spark 在格式 parquet 及语言 feather 等上的案例操作”,本质就是在说:数据格式是通用中间层,Pandas 和 Spark 都能读写 Parquet,因此跨工具协作时,先统一数据格式比统一代码更重要。

df.to_parquet('output.parquet', index=False) df2 = pd.read_parquet('output.parquet')

这段代码可以成为 Pandas 项目接入 Spark 的前置步骤。Spark 读入同一个 Parquet 文件后,后续聚合逻辑可以逐步改写。

8.4 继续往前走的三条路线

完成从 NumPy 到 Pandas 再到量化项目这条线之后,下一步可以根据兴趣选择方向。

第一条是数据可视化:用 Matplotlib、Seaborn、Plotly 把清洗后的结果画成图。很多分析结论只靠print看不出来,画出净值曲线和均线交叉后,理解会比文字深刻得多。

第二条是统计分析:用scipystatsmodels做假设检验、回归分析,把“看起来有变化”变成“统计上是否显著”。

第三条是机器学习与商业数据分析:把 Pandas 清洗后的结果输入到scikit-learn、XGBoost 或深度学习框架中,做分类、回归、风控评分等任务。金融风控数据分析里的评分卡、违约预测,都离不开 Pandas 阶段的数据准备能力。

对于刚开始看教程、学不明白 Pandas 的读者,最值得做的练习,不是把 34 集都看一遍,而是从第 1 集开始,每一段代码都运行一遍,并试图修改参数观察结果变化。学完一个阶段就对应做一次“输入是什么、输出是什么、为什么这样写”的复盘。把 API 当成工具而不是知识来记,把项目当成验证工具而不是收藏夹来对待,才能真正把从 NumPy 到量化项目这条链路串起来。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/4 1:02:09

Vue 3 + Vite:2026年主流前端技术栈完全指南

前言截至2026年&#xff0c;Vue 3市场占有率已超71%&#xff0c;搭配Vite构建工具的组合成为国内前端开发绝对主流方案&#xff0c;尤其适合与FastAPI等Python后端搭配使用。Vue 3自动生成的OpenAPI文档可直接导出TypeScript类型定义&#xff0c;与FastAPI的Pydantic模型形成完…

作者头像 李华
网站建设 2026/9/4 8:52:22

生日悖论与哈希碰撞:从概率原理到工程防碰撞实践

生日悖论听起来像一道概率脑筋急转弯&#xff0c;但它真正决定的是实际工程问题&#xff1a;随机 ID 什么时候会重复、验证码什么时候会撞车、自定义哈希什么时候会出现碰撞。别只盯着“单个值出现的概率很小”&#xff0c;更危险的是“两个值落在同一个集合里还恰好相同”。这…

作者头像 李华
网站建设 2026/9/10 3:04:10

雌激素的“雄性化”作用:从神经内分泌到性别二态性行为

最近在梳理神经内分泌相关研究资料时&#xff0c;遇到一个非常有意思且容易被初学者绕晕的问题&#xff1a;在大鼠和小鼠的大脑中&#xff0c;那些被认为带有“雄性特征”的性别二态脑区和行为&#xff0c;很多情况下是由雌激素塑造出来的&#xff0c;而不是大家直觉上以为的雄…

作者头像 李华
网站建设 2026/9/3 2:01:05

拼多多笔试真题-多多的GPU批处理调度(C++/Py/Java /Js/Go)

多多的GPU批处理调度 拼多多技术岗 7月19号笔试 第二题 拼多多真题目录点击查看: 拼多多 春招&秋招 笔试真题题库目录|笔试题库 + 算法考点详解 题目内容 多多是一个大模型架构师,在部署大语言模型时为了提高 G P U GPU GPU 的利用率,推理引擎通常会将多个用户的请求…

作者头像 李华
网站建设 2026/9/3 3:09:13

AI网络防御实战:基于Isolation Forest的日志异常检测系统

在讨论“AI 网络防御”时&#xff0c;很多人关注的是某个国家、某个机构是否已经大规模应用了这类技术。网络上确实也有不少类似“中国是否参与 AI 网络防御”的讨论&#xff0c;但从工程师视角来看&#xff0c;比“是否参与”更有价值的&#xff0c;是“AI 到底能在网络防御中…

作者头像 李华