做笔试题的时候发现 pandas 没怎么学过,很久之前很潦草地看过而已,现在把金融分析里面常用的方法整理一下。
pandas 中有若干个常用的数据类型,先来看 Series,是 pandas 中的一维数组,常用于时间序列分析。
创建 Series
可以传入一个数组,也可以是字典(以键作为索引),
import pandas as pd
# 传入一个数列来创建一个 pandas.Series 对象
s = pd.Series([1, 2, 'mmm', 5, 7])
# 有属性 name,可以赋予字符串类型的值
s.name= 'number'
查看和修改索引
print(s.index)
# RangeIndex(start=0, stop=5, step=1)
# 表示默认情况下,一个 Series 的索引是从零开始的整数索引
new_index = pd.date_range('2016-01-01',periods = len(s), freq = 'D')
print(new_index)
# DatetimeIndex(['2016-01-01', '2016-01-02', '2016-01-03', '2016-01-04', '2016-01-05'], dtype='datetime64[ns]', freq='D')
# 用 date_range 函数输入三个变量,start,periods(end),freq 来产生一个日期索引
s.index = new_index
# 更改 s 的索引
print(s)
# 2016-01-01 1.0
# 2016-01-02 2.0
# 2016-01-03 NaN
# 2016-01-04 4.0
# 2016-01-05 5.0
# Freq: D, Name: Price series, dtype: float64
访问 Series 的元素
习惯下我们可以像数组或元组一样直接用从 0 的整数来按顺序查看 Series 中的元素,也还可以用 s.iloc[i] 和 s.loc[i] 来访问。区别在于 loc 中需要严格按照该 Series 的索引来访问,而 iloc 是从序号也就是从 0 开始的整数索引。
如果我们访问单个,结果是值,例如 s.iloc[0] 的结果为 1.0;而范围访问的结果仍是一个 Series。
print(type(s))
# <class 'pandas.core.series.Series'>
print(type(s.iloc[0]))
# <class 'numpy.float64'>
print(type(s.iloc[0:3]))
# <class 'pandas.core.series.Series'>
对应的索引不是整数时,例如前面我们把索引改成了日期,然后想要按日期来切片数据,就需要引入索引对应的数据类型才可以。
from datetime import datetime
data_s= Series.loc[datetime(2017,1,1):datetime(2017,1,10)]
布尔索引
比较 s 序列中的值是否符合标准,返回的是填充了 True 和 False 的 Series 对象,
print(s<3)
# 2016-01-01 True
# 2016-01-02 True
# 2016-01-03 False
# 2016-01-04 False
# 2016-01-05 False
# Freq: D, Name: Price series, dtype: bool
把我们的标准放入到 loc 中(只能是 loc 不能是 iloc),返回符合标准的 Series 对象。
print(s.loc[s<3])
# 2016-01-01 1.0
# 2016-01-02 2.0
# Freq: D, Name: Price series, dtype: float64
缺失值处理
我们在导入文件的时候时常会发生数据缺漏的情况,而视若不见必定会对我们后续的处理带来影响。例如我们定义的 Series 中有缺失值 np.nan,有两种方法可以用:第一种是替代 fillna,用一个另外的值插入,需要传入参数 method 确定以何种方法获取值,method='ffill' 是将前一行的数据拿过来用,;第二种是删除 dropna,直接将该行删去。
创建 DataFrame 对象
创建 DataFrame 对象的时候可以接受多种类型的输入。以字典输入为例,会将字典中的两个 Series 对象(同理是 ndarray 或 list 这种一维结构) 给组合成一个二维的结构,在这个二维结构如下,['a', 'b', 'c'] 仍然是索引,然后 'one' 和 'two' 可以通过 data.keys() 方法或者查看 data.columns 属性获取,得到的是一个 Index 类型仍是一种索引,不过如果想要获取该列不是加入中括号 [] 内的方法而是 data.one 来得到一个 Series 对象。
# 接受由 Series 组成的字典
col1 = pd.Series([1,2,3],index = ['a','b','c'])
col2 = pd.Series([5,6,7],index = ['a','b','c'])
dict = {'one':col1, 'two':col2}
data = pd.DataFrame(dict)
print(data)
# one two
# a 1 5
# b 2 6
# c 3 7
在创建 DataFrame 时可以传入参数 index 和 columns,应该说这两个参数负责"筛选"将会在 data 中展示的内容。如果给的不是 Series 对象而是 list 对象组成的字典,也就意味着没有自带 index,那么如果在创建的时候仍没有传入 index 参数那么会默认从 0 开始的整数来作为这个 dataframe 的 index。
col1 = pd.Series([1,2,3],index = ['a','b','c'])
col2 = pd.Series([5,6,7],index = ['a','b','c'])
dict = {'one':col1, 'two':col2}
data = pd.DataFrame(dict, index = ['b', 'a'], columns = ['two','one','three'])
print(data)
# two one three
# b 6 2 NaN
# a 5 1 NaN
DataFrame 对象支持一个方法 mean,接受一个参数 axis,取值为 0 表示将每一列的数据求平均,取值为 1 表示每一行的内容求平均。更多情况下还是取值为 0,因为若是 1 的话一行中的内容表示的是一只股票的开盘收盘等不同属性的值,平均值的意义不是很明显。
目前有个小任务就是有一个 xlsx 文件,里面有多个 sheet,我们要用 pandas 导入保存成 DataFrame 格式。然后把索引设置为 datetime(否则会默认为 Index),将所有 sheets 的名字打印出来。先把文件导入一个管理器 ExcelFile 的实例中,然后再传给 read_excel,参数 sheetname 的本意是指定打开哪个 sheet,设为 None 的时候就是所有一并打开。
import pandas as pd
import numpy as np
file = pd.ExcelFile('D:/python3.6/sz50.xlsx')
data = pd.read_excel(file, sheetname = None,index_col = 'datetime')
# 此时的 data 是 OrderedDict 有序字典类型,所以下面打印的才会显示 odict_keys,与答案中的 dict_keys 不符
print(data.keys())
得到的 OrderedDict 有着和普通 Dict 相似的操作,区别在于前者是有序的,我可以按照我存放数据的数据依次打印出来,通过如下的方法将一个 OrderedDict 的键和值给输出。
for key, value in odict.items():
print(key, value)
另一个任务就是,读取刚才我们创建的 DataFrame 对象 data 里编号为 600036 这只股票,将其收盘价转换成用 Numpy 的 Array 格式,并用talib计算10日均线值,返回ndarray的最后五个值。talib 是一个计算技术指标的库,里面封装了许多常用和不常用的技术指标的计算函数,接受的参数通常为 Series 或者 DataFrame。
import pandas as pd
import numpy as np
import talib as tb
file = pd.ExcelFile('D:/python3.6/sz50.xlsx')
data = pd.read_excel(file, sheetname = '600036.XSHG',index_col = 'datetime')
close = data.close.values
res = tb.MA(close, timeperiod = 10)
print(res[-5:])
第三个任务是将MA的ndarray数据格式转换成Series格式,并加上datetime索引,最后将价格和MA值用Matplotlib展示出来。
import pandas as pd
import numpy as np
import talib as tb
import matplotlib.pyplot as plt
file = pd.ExcelFile('D:/python3.6/sz50.xlsx')
data = pd.read_excel(file, sheetname = '600036.XSHG', index_col = 'datetime')
res = tb.MA(data.close.values, 10)
close = pd.Series(res, index = data.close.index)
plt.plot(close, color = 'orange')
plt.plot(data.close, color = 'blue')
plt.show()
第四个任务是用talib计算50只股票的周期为5的ROCR100,生成Dataframe,并将前5只股票的ROCR100(参数timeperiod=5)用一张图显示出来。出现了一个问题就是其中一张 sheet 是空的,填充为 nan 在传给 talib.ROCR100 时会报错所以选择 pass 掉。用循环去获取有序字典的键和值,把值即一个 DataFrame 对象的 close 列转成 numpy.ndarray 的形式传给 talib.ROCR 计算得到一个 ndarray,再转成 Series 保存进字典中准备再生成所有有效的股票数据得到的 ROCR100 的字典。
之后再提取该 DataFrame 的 columns 为一个列表方便我们用里面的名字,来依次提取 DataFrame 中的前五列数据。plot 中添加 label 为的是可以指示每条不同颜色的折线代表的对象,需要有plt.legend() 方法才能够在最后的图像中显示出来。
import pandas as pd
import numpy as np
import talib as tb
import matplotlib.pyplot as plt
file_path = 'D:/python3.6/sz50.xlsx'
file = pd.ExcelFile(file_path)
data = pd.read_excel(file_path, sheetname = None, index_col = 'datetime')
date_index = pd.read_excel(file_path, index_col = 'datetime').index
columns_index = pd.read_excel(file_path).columns
new_dict = {}
for key,value in data.items():
try:
index = value.index
new_dict[key] = pd.Series(tb.ROCR100(value.close.values, timeperiod = 5), index = index)
except AttributeError:
pass
df = pd.DataFrame(new_dict)
list = df.columns.values.tolist()
for i in range(5):
plt.plot(df[list[i]],label = list[i])
plt.legend()
plt.show()
最后一道题目。用 Pane l来计算 50 只股票的 MACD 并且输出 MACD 的 Panel 的 MultiIndex 格式。
Panel 是继一维的 Series,二维的 DataFrame 之后的三维结构,有三个维度 items,major_xs,minor_xs 组成。在创建时需要给出一个字典,字典中的键名对应 items,字典中的值为 DataFrame 类型,DataFrame 的 major_xs 对应 index,minor_xs 对应 columns。访问 items 数据用中括号,也就是查看一个个表;访问 major_xs 数据用方法 major_xs(索引值) ,查看例如所有股票在某一天的行情;访问 minor_xs 数据用 minor_xs 方法,例如查看所有股票在所有时间上的收盘价。与创建一个 Panel 对象反向的操作是类似于我们提取字典中得 items(),对于 Panel 是 iteritems() 来获取 items 和对应的一个 DataFrame 对象。
Panel 有个方法 transpose,顾名思义 trans+pose 改变形状,就是把我们原本 items、major_xs 和 minor_xs 中的内容交换位置。例如对一个 Panel 对象执行 transpose(2, 0, 1),结果如下。显然 2 指代 minor_xs,1 指代 major_xs 而 0 指代 items,而在 transpose 里面的位置表示改变之后原本索引(Index)新的归属。而且要注意 transpose 不是改变对象本身,而是产生一个新版本,需要把它赋给一个新变量保存起来。
before tranpose | after transpose | |
items | 600000,600001 | open,close |
major_xs | 2017-01-01 | 600000,600001 |
minor_xs | open,close | 2017-01-01 |
MultiIndex 称为 hierarchical index,分层索引,我们之前接触的 date_range 的结果 datetimeindex 和 index 都属于单层索引,分层索引就是是为了提高索引速度,将一部分索引归成一类然后所有归类的结果再组合成索引。简单的理解就是把 0-9 合成 0,把 10 到 19 合成1,以此类推,我们如果想要进入 78 这一行,首先要找到第一层 7 的索引内容然后从中再找到 78。但在 pandas 中 MultiIndex 的意义更多的是在二维情况下对数据进行更深的划分。
从 Panel 得到 MultiIndex 有方法 to_frame,根据官方文档中的注解,Panel.to_frame 方法是 Transform wide format into long (stacked) format as DataFrame whose columns are the Panel’s items and whose index is a MultiIndex formed of the Panel’s major and minor axes。我们希望最终得到的结果是 index 是 一个 MultiIndex,第一层索引是 datetime,然后第二层索引是股票编号,所以根据 toframe 方法计算的原则,我们在 Panel 中就要把 datetime 放到 major_xs,把股票编号放到 minor_xs,把 macd、macdsignal 等放到 items 上,所以 transpose(2,1,0)。
import pandas as pd
import numpy as np
import talib as tb
import matplotlib.pyplot as plt
file_path = 'D:/python3.6/sz50.xlsx'
file = pd.ExcelFile(file_path)
data = pd.read_excel(file_path, sheetname = None, index_col = 'datetime')
date_index = pd.read_excel(file_path, index_col = 'datetime').index
columns_index = pd.read_excel(file_path).columns
new_dict = {}
for key,value in data.items():
try:
new_dict[key] = tb.abstract.MACD(value,10)
except:
pass
p = pd.Panel(new_dict)
q = p.transpose(2,1,0)
qq = q.to_frame()
print(qq.head())
另外补充 Panel 的方法 resample 抽取数据,举个例子就是
PN.resample('W-MON',axis = 1)
表示抽取每周周一的数据,也就是筛选。axis 应该是表示我表示日期的是在哪一个属性中也就是是 major_xs 还是 minor_xs 等,通常在 transpose 方法之后使用,就要注意经过 transpose 方法之后表示时间的项是在哪个位置。经过 resample 得到的对象不是 Panel 而是 DatetimeIndexResampler,要 to_frame 才能得到具有 MultiIndex 的 DataFrame。
还有方法 stack,它的作用是将一个 DataFrame 对象转化成 Series 对象,这个 Series 的索引总会是 MultiIndex 的形式,其形式为 index 为外层索引而 columns 为内层索引;哪怕只有一个 columns 属性,也会固执地转化成 MultiIndex。我们转化成 Series 之后就可以添加到 MultiIndex 相同的 DataFrame 对象中去。