Series 与 DataFrame
Series 是一列数据。DataFrame 由多个 Series 并排组成,这些列共用同一套行号,于是成为一张表。行情里的日期、收盘价、成交量各是一列;放进同一张表之后,同一行就是同一天。
从这张表里取出一列,得到的仍是 Series。本章用它计算简单收益率,并按行号和标签取得其中某一个数。
安装
python -m pip install pandas
Series:带名字的一列
import pandas as pd
close = pd.Series([10.0, 10.5, 10.2], name="close")
print(close)
print(close.mean())
0 10.0
1 10.5
2 10.2
Name: close, dtype: float64
10.233333333333333
左边的 0、1、2 是索引,右边是值。name 是列名。均值约 10.2333,和 (10 + 10.5 + 10.2) / 3 相同。
DataFrame:多列并排
把日期、收盘价、成交量各做成一列,再并排放进一张表:
date = pd.Series(["2024-01-02", "2024-01-03", "2024-01-04"], name="date")
volume = pd.Series([1000, 1200, 800], name="volume")
df = pd.DataFrame({"date": date, "close": close, "volume": volume})
print(df)
print(type(df["close"]).__name__)
date close volume
0 2024-01-02 10.0 1000
1 2024-01-03 10.5 1200
2 2024-01-04 10.2 800
Series
三列长度相同,第 0 行就是 2024-01-02、价格 10.0、成交量 1000。df["close"] 把收盘价这一列取回来,类型仍是 Series,数值也仍是 10.0、10.5、10.2。
每一列保持自己的类型:日期是字符串,收盘价是小数,成交量是整数。日期和价格不要塞进同一个 Series,否则这一列无法再按数字计算。
建表的其他写法
上面是先有 Series,再拼成表。同一张表还可以直接写出来。下面三种写法的打印结果,和上一节那张表相同。
按列写。字典的键是列名,值是这一列的数据。手写一小段行情时,这样最直接。
df = pd.DataFrame(
{
"date": ["2024-01-02", "2024-01-03", "2024-01-04"],
"close": [10.0, 10.5, 10.2],
"volume": [1000, 1200, 800],
}
)
按行写。列表里的每一项是一天,键仍然是列名。记录是一条一条到来时,适合这种写法。
df = pd.DataFrame(
[
{"date": "2024-01-02", "close": 10.0, "volume": 1000},
{"date": "2024-01-03", "close": 10.5, "volume": 1200},
{"date": "2024-01-04", "close": 10.2, "volume": 800},
]
)
先写行,再另给列名。每一行是一个列表,顺序必须和 columns 一致。
rows = [
["2024-01-02", 10.0, 1000],
["2024-01-03", 10.5, 1200],
["2024-01-04", 10.2, 800],
]
df = pd.DataFrame(rows, columns=["date", "close", "volume"])
已经有 NumPy 数组时,也可以直接交给 DataFrame。数组里的数会变成同一种类型,所以这里的成交量也成了小数:
import numpy as np
arr = np.array([[10.0, 1000], [10.5, 1200], [10.2, 800]])
df = pd.DataFrame(arr, columns=["close", "volume"])
print(df)
close volume
0 10.0 1000.0
1 10.5 1200.0
2 10.2 800.0
日期和价格类型不同,不要放进同一个数组再转成表。那种行情用前面的字典即可。从 CSV 读入一整张表,放到下一章。
取一列和取若干列
取出一列,得到 Series。取出若干列,得到的仍是由这些列组成的 DataFrame。
close = df["close"]
print(type(close).__name__)
print(close)
part = df[["date", "close"]]
print(type(part).__name__)
print(part.shape)
only = df[["close"]]
print(type(only).__name__)
print(only.shape)
Series
0 10.0
1 10.5
2 10.2
Name: close, dtype: float64
DataFrame
(3, 2)
DataFrame
(3, 1)
df["close"] 是一对中括号,取出这一列,结果是 Series。后面的 pct_change()、mean() 都写在这一列上。
df[["date", "close"]] 是中括号里再放一个列名列表,留下两列,结果仍是 DataFrame,形状是 3 行 2 列。
df[["close"]] 看起来也只取了收盘价,但因为外面还有一层列表,结果是 3 行 1 列的 DataFrame。需要把这一列继续和其他表拼接、或保持表的写法时,用这种形式。只想对价格做计算时,用 df["close"]。
列名写错会得到 KeyError。先打印 df.columns,核对拼写。
计算落在一列上,还是落在整张表上
print(close.mean())
print(df[["close", "volume"]].mean())
10.233333333333333
close 10.233333
volume 1000.000000
dtype: float64
Series.mean() 把这一列收成一个数。DataFrame.mean() 对每个数值列各算一个均值,返回的是一个新的 Series:索引是列名,值是该列的均值。成交量的均值是 1000,因为 (1000 + 1200 + 800) / 3 = 1000。
因此,收益率、波动率这类针对价格的计算,先取出 Series 再算。想同时看收盘价和成交量时,先选出这两列,再对这张小表调用 mean()。日期列是字符串或时间,直接对整张表求均值会失败,所以不要把它放进这次计算。
增加一列
df["ret"] = df["close"].pct_change()
print(df)
date close volume ret
0 2024-01-02 10.0 1000 NaN
1 2024-01-03 10.5 1200 0.050000
2 2024-01-04 10.2 800 -0.028571
pct_change() 就是上一章的简单收益率。第一行没有前一天,结果是 NaN,表示缺失。后面做均值、画图之前要决定怎么处理它,通常是删掉这一行,而不是把它当成 0。0 表示“没涨没跌”,和“没有前一天”不是同一件事。
取行:iloc 和 loc
print(df.iloc[0])
print(df.loc[1, "close"])
iloc 按下标,从 0 数。iloc[0] 取出表里的第一行。一行横过来也是一个 Series,索引变成了列名:
date 2024-01-02
close 10.0
volume 1000
ret NaN
Name: 0, dtype: object
loc 按索引标签和列名。这里行的标签正好也是 0、1、2,所以 loc[1, "close"] 是 10.5。等日期被设成索引之后,loc 里就要写日期,不能再把行号当成日期。
把日期列变成时间
df["date"] = pd.to_datetime(df["date"])
print(df.dtypes)
date datetime64[ns]
close float64
volume int64
ret float64
dtype: object
dtypes 列出每一列的类型。日期变成 datetime64 之后,才能按月份、按星期取出部分字段。这一步放在筛选和分组之前做。
课堂练习
- 用两个
Series做成一张只有date和close的表,至少四行。再用「按列的字典」写一张同样的表,并确认两张表的收盘价一致。 - 对
close这一列和整张表的数值列分别求均值,说明两个结果的类型。 - 打印
pct_change(),用iloc取出最后一行,用loc取出某一行的收盘价。 - 打印
dtypes,确认日期列已经是时间类型。
例子见 examples/fintech/pandas_frame.py。