免费基金定投助手全功能拆解:为什么你的基金定投还在亏钱?因为你的工具用错了。动态平衡仓位管理+8种智能定投策略引擎,会自己算买卖点的定投系统-优快云博客
https://download.youkuaiyun.com/download/weitingfu/93448039?spm=1001.2014.3001.5503
很多人学 pandas 卡在同一个地方:一会儿 Series,一会儿 DataFrame,一会儿 Index,一会儿 ndarray——四个词长得像四胞胎,用起来却各管一摊。今天不背概念,直接用投资场景把这 4 类数据结构一次分清:它们分别装什么、怎么互换、什么时候该用谁。
一、先上全家福:四兄弟各管一摊
graph TD
A[numpy ndarray<br/>最底层:纯数字数组] --> B[pandas Series<br/>一列数据 + 索引]
B --> C[pandas DataFrame<br/>多列数据 + 索引]
B --> D[pandas Index<br/>行/列的标签集合]
C --> D
| 数据结构 | 一句话人设 | 投资场景举例 |
|---|---|---|
ndarray | 纯数字矩阵,无标签 | 矩阵运算、模型输入 |
Series | 一列带标签的数据 | 一只基金的净值序列 |
DataFrame | 多列带标签的表 | 多只基金的净值表 |
Index | 标签的集合 | 日期序列、基金代码列表 |
记忆锚点:ndarray 是"裸数据",Series 是"一列 + 名字",DataFrame 是"多列 + 名字",Index 是"那些名字本身"。
二、ndarray:看不见的底层引擎
2.1 什么是 ndarray?
ndarray 是 numpy 的数组,没有行名列名,纯粹的数字(或同类型数据)格子:
import numpy as np
# 一维数组(像一行数字)
a = np.array([1.0, 2.0, 3.0, 4.0])
print(a, a.shape) # [1. 2. 3. 4.] (4,)
# 二维数组(像一张表,但没有表头)
b = np.array([[1, 2], [3, 4]])
print(b.shape) # (2, 2)
# 运算飞快:整组数字直接算
print(a * 2) # [2. 4. 6. 8.]
2.2 为什么 pandas 底层是它?
因为快。numpy 用 C 语言实现数组运算,比 Python 纯循环快几十到几百倍:
import time
# Python 纯循环 vs numpy 向量化
数据 = list(range(1_000_000))
t0 = time.time()
结果1 = [x * 2 for x in 数据] # 纯循环
t1 = time.time()
arr = np.array(数据)
结果2 = arr * 2 # numpy 向量化
t2 = time.time()
print(f"纯循环: {t1 - t0:.3f}s numpy: {t2 - t1:.3f}s")
纯循环: 0.087s numpy: 0.002s
💡 结论:pandas 的"快"本质上是 numpy 的"快"。你平时写的
df["价格"] * 100,底层就是在 ndarray 上做向量化运算。
2.3 什么时候直接碰 ndarray?
| 场景 | 原因 |
|---|---|
| 机器学习模型输入 | sklearn / 深度学习接口收 ndarray |
| 矩阵运算(协方差、特征值) | numpy 原生支持 |
| 性能瓶颈优化 | 绕开 pandas 开销直接算 |
| 数值类型转换 | astype(np.float64) 等 |
# DataFrame → ndarray(丢掉所有标签)
价格数组 = df["最新净值"].to_numpy()
print(type(价格数组)) # <class 'numpy.ndarray'>
print(价格数组) # [3.987 5.812 2.153]
2.4 ndarray 的维度概念(投资里最常用的三种)
投资分析中你会反复接触不同"维度"的数组,提前眼熟:
# 一维:一串数字(向量)
一维 = np.array([1, 2, 3])
print("一维 shape:", 一维.shape) # (3,)
# 二维:一张表(矩阵)
二维 = np.array([[1, 2, 3], [4, 5, 6]])
print("二维 shape:", 二维.shape) # (2, 3) → 2 行 3 列
# 三维:一批表(张量)——多策略回测结果常这么存
三维 = np.random.rand(4, 2, 3) # 4 个 2x3 的矩阵
print("三维 shape:", 三维.shape) # (4, 2, 3)
| 维度 | 叫法 | 投资场景 |
|---|---|---|
| 1 维 | 向量 | 单只基金净值序列 |
| 2 维 | 矩阵 | 多基金 × 多日期收益表 |
| 3 维 | 张量 | 多策略 × 多基金 × 多日期 |
💡 提示:
shape元组是理解 ndarray 的钥匙——(行, 列),永远先看 shape 再动手。
2.5 布尔索引:ndarray 的筛选
ndarray 也有筛选能力,虽然没有 pandas 那么好用,但性能场景必需:
价格 = np.array([3.95, 4.00, 3.88, 4.12, 3.90])
# 布尔掩码:大于 4.0 的位置
掩码 = 价格 > 4.0
print(掩码) # [False True False True False]
# 用掩码取数
print(价格[掩码]) # [4. 4.12]
# 一步到位:直接取满足条件的值
print(价格[价格 > 4.0]) # [4. 4.12]
⚠️ 注意:pandas 的
df[条件]和 ndarray 的arr[条件]语法几乎一样,因为 pandas 就是在沿用 numpy 的"布尔掩码"机制——理解了 ndarray,pandas 筛选就是白送的。
三、Series:投资数据的最小单元
3.1 Series = 数据 + 索引
一只基金的净值序列,就是最典型的 Series:
import pandas as pd
# 模拟 5 个交易日的沪深300ETF 净值
净值 = pd.Series(
[3.950, 3.962, 3.987, 3.975, 4.001],
index=["2026-09-11", "2026-09-14", "2026-09-15", "2026-09-16", "2026-09-17"],
name="沪深300ETF",
)
print(净值)
2026-09-11 3.950
2026-09-14 3.962
2026-09-15 3.987
2026-09-16 3.975
2026-09-17 4.001
Name: 沪深300ETF, dtype: float64
关键点:数据 是 [3.950, ...],索引 是日期,name 是名字。三者缺一不可。
3.2 Series 在投资里的高频玩法
# 日收益率(后一天 / 前一天 - 1)
日收益 = 净值.pct_change()
print(日收益)
# 累计收益率(净值 / 首日净值 - 1)
累计 = 净值 / 净值.iloc[0] - 1
print(累计)
# 滚动 3 日均线(第 09 篇会细讲)
均线3 = 净值.rolling(3).mean()
print(均线3)
2026-09-11 NaN
2026-09-14 0.003038
2026-09-15 0.006310
2026-09-16 -0.003009
2026-09-17 0.006541
Name: 沪深300ETF, dtype: float64
2026-09-11 0.000000
2026-09-14 0.003038
2026-09-15 0.009367
2026-09-16 0.006329
2026-09-17 0.012911
Name: 沪深300ETF, dtype: float64
2026-09-11 NaN
2026-09-14 NaN
2026-09-15 3.966333
2026-09-16 3.974667
2026-09-17 3.987667
Name: 沪深300ETF, dtype: float64
💡 注意:
pct_change()第一行必然是 NaN(没有前一天),rolling(3)前两行也是 NaN——不是 bug,是数学事实。处理方式见 03 篇的缺失值三件套。
3.3 Series 与 ndarray 的关系
# Series → ndarray(丢掉索引和名字)
数组 = 净值.to_numpy()
print(type(数组), 数组.shape) # <class 'numpy.ndarray'> (5,)
# ndarray → Series(重新戴上索引和名字)
恢复 = pd.Series(数组, index=净值.index, name=净值.name)
print(恢复.head(2))
2026-09-11 3.95
2026-09-14 3.96
Name: 沪深300ETF, dtype: float64
核心心智:Series 是 ndarray 的"戴帽子版本"——帽子是索引和名字。脱帽子用 to_numpy(),戴帽子用 pd.Series(...)。
四、DataFrame:投资组合的家
4.1 DataFrame = 多个 Series 拼在一起
DataFrame 的本质是"列对齐的 Series 集合"——每列都是一个 Series,共享同一个索引:
# 三只基金的净值,拼成一张表
df = pd.DataFrame({
"沪深300ETF": [3.950, 3.962, 3.987, 3.975, 4.001],
"中证500ETF": [5.780, 5.801, 5.812, 5.790, 5.835],
"创业板ETF": [2.110, 2.131, 2.153, 2.141, 2.160],
}, index=["2026-09-11", "2026-09-14", "2026-09-15", "2026-09-16", "2026-09-17"])
print(df)
沪深300ETF 中证500ETF 创业板ETF
2026-09-11 3.950 5.780 2.110
2026-09-14 3.962 5.801 2.131
2026-09-15 3.987 5.812 2.153
2026-09-16 3.975 5.790 2.141
2026-09-17 4.001 5.835 2.160
4.2 DataFrame 的高频操作矩阵
| 操作 | 代码 | 说明 |
|---|---|---|
| 取一列(变 Series) | df["沪深300ETF"] | 最常用 |
| 取多列(仍 DataFrame) | df[["沪深300ETF", "创业板ETF"]] | 注意双括号 |
| 取一行 | df.loc["2026-09-17"] | 返回 Series |
| 取单元格 | df.loc["2026-09-17", "创业板ETF"] | 行 + 列 |
| 全表算收益 | df.pct_change() | 每列都算 |
| 全表看统计 | df.describe() | 一表看全 |
# 多列同时算日收益率——一次搞定
收益表 = df.pct_change()
print(收益表.round(4))
沪深300ETF 中证500ETF 创业板ETF
2026-09-11 NaN NaN NaN
2026-09-14 0.0030 0.0036 0.0100
2026-09-15 0.0063 0.0019 0.0103
2026-09-16 -0.0030 -0.0038 -0.0056
2026-09-17 0.0065 0.0078 0.0089
💡 这就是 DataFrame 相对"多个 Series 各自算"的核心优势:一列一套循环都不用写,全表批量算。
4.3 增删改查:DataFrame 的日常
# 新增一列:三只基金当日平均收益率
收益表["平均"] = 收益表.mean(axis=1)
print(收益表.tail(2))
# 删除一列
收益表 = 收益表.drop(columns=["平均"])
print(收益表.columns)
# 修改一列(重命名)
收益表 = 收益表.rename(columns={"沪深300ETF": "300ETF收益"})
print(收益表.columns)
沪深300ETF 中证500ETF 创业板ETF 平均
2026-09-16 -0.0030 -0.0038 -0.0056 -0.0041
2026-09-17 0.0065 0.0078 0.0089 0.0077
Index(['沪深300ETF', '中证500ETF', '创业板ETF'], dtype='object')
Index(['300ETF收益', '中证500ETF', '创业板ETF'], dtype='object')
⚠️ 注意:
axis=1表示"沿列方向"(每行算一个结果),axis=0表示"沿行方向"(每列算一个结果)——axis 方向是 pandas 新手第二大坑。
4.4 空值处理:DataFrame 级别的 dropna / fillna
# 造一张带空值的收益表
df_na = df.pct_change()
print(df_na.head(2))
# ① 删掉含空值的行(最粗暴)
print(df_na.dropna().head(2))
# ② 填 0(适用于"没数据就当没涨跌")
print(df_na.fillna(0).head(2))
# ③ 向前填充(适用于停牌补价)
print(df_na.ffill().head(2))
沪深300ETF 中证500ETF 创业板ETF
2026-09-11 NaN NaN NaN
2026-09-14 0.0030 0.0036 0.0100
沪深300ETF 中证500ETF 创业板ETF
2026-09-14 0.0030 0.0036 0.0100
2026-09-15 0.0063 0.0019 0.0103
沪深300ETF 中证500ETF 创业板ETF
2026-09-11 0.0000 0.0000 0.0000
2026-09-14 0.0030 0.0036 0.0100
沪深300ETF 中证500ETF 创业板ETF
2026-09-11 NaN NaN NaN
2026-09-14 0.0030 0.0036 0.0100
💡 提示:没有放之四海皆准的空值策略——算收益用
dropna(),统计市场涨跌分布用fillna(0),停牌补价用ffill()。先想清楚业务语义,再选函数。
4.5 透视:DataFrame 的"分组汇总"能力
Excel 里的数据透视表,pandas 用 groupby 一行实现:
# 造一个带板块标签的持仓表
持仓 = pd.DataFrame({
"基金": ["沪深300ETF", "中证500ETF", "创业板ETF", "科创50ETF", "红利ETF"],
"板块": ["宽基", "宽基", "成长", "成长", "红利"],
"市值": [39870, 29060, 17224, 9800, 15000],
})
# 按板块分组求总市值
分组汇总 = 持仓.groupby("板块")["市值"].sum()
print(分组汇总)
# 按板块分组求平均
分组平均 = 持仓.groupby("板块")["市值"].mean()
print(分组平均.round(0))
板块
红利 15000
宽基 68930
成长 27024
Name: 市值, dtype: int64
板块
红利 15000.0
宽基 34465.0
成长 13512.0
Name: 市值, dtype: int64
💡 提示:
groupby("列名")["数值列"].聚合函数()是三段式标准写法——按什么分组、对哪列、算什么。第 11 篇讲指标时会大量使用。
五、Index:数据的"身份证"
5.1 Index 到底是什么?
Index 是行(或列)的标签集合,独立存在:
print(df.index) # 行索引(日期)
print(df.columns) # 列索引(基金名)——列也有索引!
# Index 类型
print(type(df.index)) # <class 'pandas.core.indexes.base.Index'>
print(type(df.columns)) # 同样是 Index
5.2 三类高频 Index
| 类型 | 创建方式 | 用途 |
|---|---|---|
| 普通 Index | pd.Index([...]) | 默认行号 / 标签 |
| DatetimeIndex | pd.to_datetime([...]) | 时间序列的核心 |
| MultiIndex | pd.MultiIndex.from_product([...]) | 多基金 × 多日期 |
# DatetimeIndex:把字符串日期转成真正的日期索引
df.index = pd.to_datetime(df.index)
print(df.index) # DatetimeIndex
print(df.index[0].day_name()) # Thursday
# 日期索引的好处:按时间切片
print(df.loc["2026-09-14":"2026-09-16"].shape) # (3, 3)
DatetimeIndex(['2026-09-11', '2026-09-14', '2026-09-15', '2026-09-16',
'2026-09-17'],
dtype='datetime64[ns]', freq=None)
Thursday
(3, 3)
💡 提示:字符串日期和
datetime64日期长得像,但能力天差地别——后者支持加减天数、切片、重采样,是时间序列分析的必需品(第 09 篇展开)。
5.3 为什么 Index 单独成一类?
因为对齐是 pandas 最强大的机制之一:
a = pd.Series([1, 2, 3], index=["x", "y", "z"])
b = pd.Series([10, 20, 30], index=["z", "y", "x"])
# 两个 Series 相加:pandas 按索引自动对齐,而不是按位置!
print(a + b)
x 31
y 22
z 33
dtype: int64
这就是 Index 存在的意义:x 和 x 加、y 和 y 加——按名字对齐,而不是按顺序。投资里合并不同来源的数据时,这个机制能避免"错位相加"的致命 bug。
5.4 索引对齐的边界:join 与 merge
投资里最常干的活是"合并两个数据源",pandas 的索引机制决定了合并方向:
# 两个不同来源的数据
基金净值 = pd.Series([3.95, 4.00], index=["2026-09-16", "2026-09-17"], name="沪深300ETF")
额外指标 = pd.Series([12.5, 12.8], index=["2026-09-17", "2026-09-18"], name="PE")
# ① 按索引横向拼接(类似 VLOOKUP 按行号匹配)
合并 = pd.concat([基金净值, 额外指标], axis=1)
print(合并)
# ② 用 merge 按列匹配(类似 SQL JOIN)
df_a = pd.DataFrame({"日期": ["2026-09-17"], "净值": [4.001]})
df_b = pd.DataFrame({"日期": ["2026-09-17"], "PE": [12.8]})
print(pd.merge(df_a, df_b, on="日期"))
沪深300ETF PE
2026-09-16 3.95 NaN
2026-09-17 4.00 12.8
2026-09-18 NaN 12.8
日期 净值 PE
0 2026-09-17 4.001 12.8
⚠️ 注意:
concat是按索引对齐(没匹配到的自动 NaN),merge是按列值对齐(可指定on/how)。合并前先想清楚"按什么对齐",这是投资数据合并最常出错的地方。
六、四兄弟互换路线图
graph LR
A[ndarray] -->|pd.Series| B[Series]
A -->|pd.DataFrame| C[DataFrame]
B -->|to_numpy| A
B -->|to_frame| C
C -->|to_numpy| A
C -->|取列 df[列]| B
C -->|.index| D[Index]
B -->|.index| D
D -->|pd.Series 创建时指定| B
| 转换 | 代码 |
|---|---|
| ndarray → Series | pd.Series(arr, index=..., name=...) |
| ndarray → DataFrame | pd.DataFrame(arr, columns=...) |
| Series → ndarray | series.to_numpy() |
| Series → DataFrame | series.to_frame() |
| DataFrame → ndarray | df.to_numpy() |
| DataFrame → Series | df["列名"] |
| 任意 → Index | df.index / df.columns / pd.Index([...]) |
# 全链路演练:数组 → Series → DataFrame → 数组
arr = np.array([3.9, 3.95, 4.0])
s = pd.Series(arr, index=["d1", "d2", "d3"], name="净值")
df2 = s.to_frame()
arr2 = df2.to_numpy().ravel()
print(arr2) # [3.9 3.95 4. ]
七、选型决策:什么时候用谁?
graph TD
A[数据是一列还是多列?] -->|一列| B[用 Series]
A -->|多列| C[用 DataFrame]
B --> D[需要纯数字算矩阵?]
C --> D
D -->|是| E[用 ndarray]
D -->|否| F[保持 pandas 即可]
B --> G[索引要时间对齐?]
G -->|是| H[Index 用 DatetimeIndex]
| 你的任务 | 首选类型 | 原因 |
|---|---|---|
| 算一只基金的收益率 | Series | 一列数据,轻量 |
| 对比多只基金 | DataFrame | 列对齐,批量算 |
| 给机器学习模型喂数据 | ndarray | 接口要求 |
| 按日期切片、重采样 | DatetimeIndex | 时间序列专用 |
| 合并两只基金数据 | 两个 Series + 对齐 | 索引自动对齐 |
八、实战:用四兄弟完成"组合体检"
把全篇串起来:用 Series 算单只收益 → 拼成 DataFrame → 交给 ndarray 做协方差 → 用 Index 对齐日期。
import numpy as np
import pandas as pd
# ① 两只基金净值(Series,共享 DatetimeIndex)
dates = pd.to_datetime(["2026-09-11", "2026-09-14", "2026-09-15", "2026-09-16", "2026-09-17"])
nav_a = pd.Series([3.950, 3.962, 3.987, 3.975, 4.001], index=dates, name="沪深300ETF")
nav_b = pd.Series([5.780, 5.801, 5.812, 5.790, 5.835], index=dates, name="中证500ETF")
# ② 拼成 DataFrame
df = pd.concat([nav_a, nav_b], axis=1)
print(df)
# ③ 收益率表
ret = df.pct_change().dropna()
print(ret.round(4))
# ④ 转 ndarray 算协方差(风险分散的度量)
cov = ret.to_numpy()
协方差矩阵 = np.cov(cov.T)
print("协方差矩阵:\n", 协方差矩阵.round(6))
沪深300ETF 中证500ETF
2026-09-11 3.950 5.780
2026-09-14 3.962 5.801
2026-09-15 3.987 5.812
2026-09-16 3.975 5.790
2026-09-17 4.001 5.835
沪深300ETF 中证500ETF
2026-09-14 0.0030 0.0036
2026-09-15 0.0063 0.0019
2026-09-16 -0.0030 -0.0038
2026-09-17 0.0065 0.0078
协方差矩阵:
[[0.000019 0.000017]
[0.000017 0.000016]]
这一段代码就是未来所有策略的骨架:数据进来是 Series/DataFrame,算完交给 numpy,中间全靠 Index 对齐。四兄弟一个不少,各司其职。
八点五、FAQ:四兄弟选型的 3 个高频疑问
Q1:什么时候该直接用 ndarray,而不是 DataFrame?
当你要调用 numpy/scipy 的矩阵运算、或者给 scikit-learn 喂特征矩阵时,DataFrame 反而碍事——模型只认纯数值数组,不吃列名。此时用 df.to_numpy() 丢掉标签,算完再用 pd.DataFrame(result, columns=...) 把标签戴回去。一条规则记住:要标签时用 pandas,要纯数值矩阵时用 ndarray。
Q2:两个 Series 长度不同,能直接相加吗?
能,这正是 pandas 最值钱的地方:它会按索引自动对齐,找不到的位置自动补 NaN。但注意 NaN 参与运算会"传染"——1 加 NaN 还是 NaN。所以对齐完要立刻 fillna(0) 或 dropna() 再算,别让组合收益率悄悄变成一堆空值。
Q3:为什么说 Index 是"对齐的裁判"?
因为 Series/DataFrame 的所有算术运算都默认按索引匹配,而不是按行号。哪怕两个 DataFrame 行序完全打乱,只要索引一致,相加结果依然正确。这也是投资数据清洗里最省心的一环:只要索引规范,顺序乱了你都不怕。
九、总结与思考题
9.1 一张图回顾
graph TD
A[ndarray] -->|戴上标签| B[Series]
B -->|多列拼接| C[DataFrame]
C -->|索引集合| D[Index]
D -->|时间对齐| E[DatetimeIndex]
9.2 核心口诀
- ndarray:裸数字,跑得最快
- Series:一列数据 + 名字,单基金分析主力
- DataFrame:多列 + 名字,组合分析主场
- Index:标签本身,对齐的根基
📌 总结:四个兄弟不是竞争关系,是层级关系:ndarray 在底层跑,Series 和 DataFrame 在表层用,Index 负责把一切对齐。搞懂这一层,pandas 就通了 80%。
9.3 思考题
- 用
pd.Series造你持仓基金 10 天的净值,转成 DataFrame 再转回 ndarray,观察三个类型的变化。 - 造两个顺序不同的 Series(同样的标签),相加验证"按索引对齐"而不是"按位置相加"。
- 把日期字符串转成 DatetimeIndex,试试
df.loc["某月":"某月"]的切片效果。
SEO 关键词:pandas 数据结构、Series DataFrame 区别、numpy ndarray pandas、pandas Index 教程、Python 量化数据结构
本文标签:#Python #pandas #numpy #数据结构 #量化 #投资分析 #Series #DataFrame
数据锚点:沪深300ETF、中证500ETF、创业板ETF 净值数据为教学示例(2026-09 中旬模拟行情);numpy 官方文档:向量化运算性能优势
源码获取:本文完整示例(组合体检脚本)见 GitHub - Python投资应用系列。
系列进度打卡:01 ✅ 02 ✅ 03 ✅ 04 ✅ | 下一篇:05 行情数据全链路
689



被折叠的 条评论
为什么被折叠?



