量化中Pandas库的常用函数技巧

本文最后更新于:2021年12月22日 晚上

本文借鉴了刺猬偷腥的部分内容,在此表示感谢分享。

量化中Pandas库的常用函数技巧

1
import pandas as pd

一、 量化中的常用函数

1、读取csv文件,用pd.read_csv()即可,参数值有:

  • filepath_or_buffer=‘文件的路径’
  • sep=’,’,文件中列与列之间的分隔符,一般是逗号或者’\t’
  • skiprows=1,跳过第一行描述性语句
  • nrows=5,只读取前5行数据,若不指定,则读取全部数据。调试程序的时候常用,先读一部分,把代码写完再说。
  • parse_dates=['交易日期'],将交易日期这一列的内容转化为日期格式。如果不写这个参数,则导入的该列将是string的格式。
  • index_col=['交易日期'],将交易日期这一列指定为index
  • usecols=['交易日期','标的名称'],只读取某些列的数据
  • error_bad_lines=False, 当遇到低质量的数据,程序会报错,加上这个参数后,程序就会跳过报错的数据行,然后继续读取后面的数据,使程序能够正常运行下去。
  • na_values = null,将数据中的null全部识别为空值。

2、看df的形状,用df.shape,返回有多少行多少列。查看有多少行,用df.shape[0],查看有多少列,用df.shape[1]

3、显示每一行或每一列的名字,用df.indexdf.columns。在for循环中常用。

4、查询每一列数据的类型,用df.dtypes

5、随机抽几行数据来看看,用df.sample(n=10)。如果想随机抽10%的数据来看看,则可用df.sample(frac=0.1)

6、取消自动换行,取消数据修正,可用pd.set_option(‘extend_frame_repr', False)

7、设定列宽,可用 pd.set_option('max_colwidth', 10)。若要撤销指定列宽,可用pd.reset_option('max_colwidth')

8、用label读取行列数据的时候,一般用loc或者iloc,但读取单个元素的时候,建议用at,因为效率更高。

9、如果不用label来读取,也可以用iloc()函数,根据索引来读取。同样,对单一元素,可以用iat()来指定。

10、常用的统计函数包括:max()min()、标准偏差std()count()、中位数median()、分位数quantile(0.25)等。

11、位移可用shift()函数。df.shift(1)表示读取上一行的数据,df.shift(-1)表示读取下一行的数据。

12、删除列,用del df.列,也可用df.drop(列,axis=1,inplace=true)

13、求一阶差分可用diff(), diff(-1)表示该行数据与上一行数据相减。

14、求涨跌幅可用df.pct_change(-1),表示该行与上一行的变动比例。

15、计算累加值,可用cum类函数,包括:cumsum()累加值、cumprod()累乘值。前者可用于成交量累加,后者可用于计算资金曲线的复利结果。

16、对列排序,输出排名,可用df.列.rank(ascending=true, pct=False), 该函数输出的是排名值,若pct=true则输出排名的百分比。

17、计算每个元素出现的次数,可用value_counts()

18、筛选的方式有多种,例如:

1
2
3
4
df[df[ticker]=='002466']
df[df[ticker].isin(['002466','002460'])]
df[df[price]>10.0] #输出价格大于10的行
df[df.index<='01/01/2018' & df.index>='01/01/2017'] # &表示并且,|表示或者

19、缺失值的处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 判断空值:

df.notnull()
df.isnull()
df[df.列.notnull()] #输出某列非空值的行

#删除空值:
df.dropna(how='any') #只要有空值的行都删除
df.dropna(how='all') #全是空值的行才删除
df.dropna(subset=['price','ticker'],how='all') # subset表示只看某几列,这两列都为空才将行删掉

#填充空值:
df.fillna(value='填充内容')
df.fillna(method='ffill') # 向上寻找最近一个非空值进行填充
df.fillna(method='bfill') # 向下寻找最近一个非空值进行填充

20、在使用append()合并两个df时,若两个数据的index有重复,则可使用参数ignore_index=true,这时程序就会忽略两个dfindex,并重新建立[0,1,2,3……]的index

21、去重可用df.drop_duplicates(), 其参数包含:

  • 加上subset=['列1','列2'],即判断某一行的两列数据相同才算重复。如果不加,则需要所有列数据都相同,才认为是重复的行。
  • keep='last' or 'first’,前者保留最下面一行数据,后者保留最上面一行数据。
  • keep=false,只要有重复的,全部删掉
  • inplace=true,是指是否直接在原数据上进行修改,默认为False

22、改列名可用df.rename(columns={}), 在大括号中,需要用字面进行改名,key表示原来的名称,value表示改成什么内容。

23、处理列中的字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 取每列元素的前2个字符
df.ticker.str[:2]
# str的功能还有
df.ticker.str.upper() # 小写字母转大写字母
df.ticker.str.lower()
df.ticker.str.len()
df.ticker.str.strip() # 移除字符串头尾指定的字符序列,可用lstrip()和rstrip()
df.ticker.str.contains('sh') # 是否包含sh
df.ticker.str.replace('sh','sz') # 将sh改为sz、

#对字符串进行分割
df.概念.str.split(';')[:3] #对概念字符串以;进行分割,变成一个列表,然后显示前三项内容
df.概念.str.split(';',expand=true) # 分割后将每个元素变为单独的列

24、处理时间变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#将字符串变成时间变量
df.交易日期 = pd.to_datetime(df.交易日期)

#处理时间数据
用dt库,例如df.交易日期.dt.month # 显示对应的月份,day是天数,还可以用hour
dt.week显示一年当中的第几周
dt.dayofyear,显示一年当中的第几天
dt.dayofweek,显示一周当中的第几天,0代表星期一,6代表星期天,也可以从dt.weekday表示
dt.weekday,直接显示星期几
dt.days_in_month,显示该月当中有几天
dt.is_month_start,判断是否为月初第一天
dt.is_month_end,判断是否为月末最后一天

#时间差函数
len(datetime_series) # 计算交易日天数,比如2021-01-01到2021-01-02是2天。【注意】非日期差
df.交易日期+pd.Timedelta(days=1) # 加1天,也可能 hours=1,加一个小时

25、滚动切片可用rolling,例如计算最近5天的价格的平均数。

1
2
3
4
5
6
7
8
df.移动平均线5 = df.价格.rolling(5).mean()  # 过去5天的平均值
df.价格.rolling(5).max() # 求过去5天的最大值
df.价格.rolling(5).std() # 求过去5天的标准差
df.价格.rolling(5).apply() # 使用自定义函数

# 如果要计算从一开始到现在的数据,则用expanding(),expanding是扩大的意思
df.迄今为止的均值 = df.价格.expanding().mean()
df.迄今为止的最大值 = df.价格.expanding().max()

26、处理完数据,可用to.csv('文件名',index=false, encoding='gbk')另存为导出数据。输出时默认会加上从0开始的index,可以取消这个功能。

二、 重要函数的实际应用

1、 查看文件路径

当我们需要导入文件时,如果直接输入当前的绝对路径,那么在更换运行环境时,就需要手动设置新的路径。利用os模块的自带函数,我们可以轻松解决这一问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 当我们需要获取文件的当前地址,可以用 __file__
current_file = __file__
# 输出结果为: c:\python\code\program.py

# 要获取根目录的地址,可以输入
root_path = os.path.abspath(os.path.join(current_file, os.pardir, os.pardir))
# 其中 os.path.abspath(path) 可返回绝对路径

os.path.abspath(os.path.join(current_file, os.pardir, os.pardir))
# current_file是c:\python\code\program.py,那么os.path.abspath(os.path.join(current_file, os.pardir, os.pardir))是c:\
# 获得根目录的变量
root_path = os.path.abspath(os.path.join(current_file, os.pardir, os.pardir))

# 然后可以按需指定目录的地址,例如
input_data_path = os.path.abspath(os.path.join(root_path, 'data', 'input_data'))
# input_data_path 的路径是根目录下的\data\input_data
# 绝对地址就是c:\python\\data\input_data

2、 调用简单函数的方法

假设我们写了一个自定义函数:

1
2
def addone(x):
return x+1

要调用这个函数时,可使用apply函数,也可以使用lambda函数进行调用。

1
2
3
4
5
#apply的方式
print df[['涨跌幅']].apply(addone)

#lambda的方式
print df[['涨跌幅']].apply(lambda x: x+1)

3、 补全数据

有时候获得的行情数据,会出现非交易日缺失的情况,这时就需要基于指数交易日的数据,用merge函数对其进行补全。

img

还有个参数是indecator,等于True时增加merge列,表明该行数据的出处,源自哪一张表,有left、right、both的区分。

如果是堆砌,可用concat函数,相关参数如下:

img

1
2
3
4
5
6
import pandas as pd
s1 = pd.Series([0,1,2],index = ['a','b','c'])
s2 = pd.Series([2,3,4],index = ['c','f','e'])
s3 = pd.Series([4,5,6],index = ['c','f','g'])
series = pd.concat([s1,s2,s3]) # 默认并集、纵向连接
series

img

1
2
series = pd.concat([s1,s2,s3],ignore_index = True)  # 生成纵轴上的并集,索引会自动生成新的一列
series

img

1
2
3
series = pd.concat([s1,s2,s3],axis = 1,join = 'inner')
series
# 纵向取交集,注意该方法对对象表中有重复索引时失效

img

1
2
3
series = pd.concat([s1,s2,s3],axis = 1,join = 'outer')
series
# 横向索引取并集,纵向索引取交集,注意该方法对对象表中有重复索引时失效

img

4、 resample函数

通常我们获得的数据是日线数据,需要转化成周数据或月数据时,可用resample函数获得。

当索引为时间格式时,可用resample函数将时间序列数据自动分割为周、月、季、年等块,然后再进行相应的处理。这里需要用到参数rule,参数rule=’w’代表转化为周,’m’代表月,’q’代表季度,’y’代表年份。’5min’代表5分钟,’1min’代表1分钟。

1
2
3
4
5
6
7
8
week_df = df.resample(rule='w').last() # 意思是展现每周最后一个交易日的数据
week_df['开盘价'] = df['开盘价'].resample(rule='w').first() # 获得开盘价数据
week_df['成交量'] = df['成交量'].resample(rule='w').sum() # 获得成交总量数据
week_df['最高价'] = df['最高价'].resample(rule='w').max() # 获得最高价数据
week_df['最低价'] = df['最低价'].resample(rule='w').min() # 获得最低价数据

#若要获得周涨幅,一般可用公式【(最后一天的收盘价 - 第一天的开盘价) / 第一天的开盘价 】进行计算,也可以用lambda函数直接获得
week_df['涨跌幅'] = df['涨跌幅'].resample(rule='w').apply(lambda x: (x+1.0).prod() - 1.0 )

5、 用os.walk导入数据

当输入os.walk(data_path)时,会返回root、dirsfiles的数据,root是文件的路径,dir是路径下有什么文件夹(返回列表),files是路径下有什么文件(返回列表)。

然后程序会到第一个文件夹里面,继续返回相应的root、dirs和files……直至全部遍历。

有了这个系统自带函数,后面就好办了。

首先,我们要获得想要导入的标的的代码的列表。

1
2
3
4
5
6
7
8
9
list = []
data_path = config.data_path + '/data'
for root, dirs, files in os.walk(data_path):
if files: # 当files不为空
for i in files: #对files的每一个文件
if i.endswith('.csv'): # 选取以csv为后缀的文件
list.append(i[:8]) #将该文件的前8个字符加入到列表中

print list

第二步,开始根据列表来导入数据

1
2
3
4
5
6
alldata = pd.DataFrame()
for stock in list:
print stock
df = importcode.import(stock)
alldata = alldata.append(df, ignore_index = True)
print alldata

6、 CSV的替代——HDF

一般储存数据是用csv格式,但pandas提供了一种更高效率的方式,那就是hdf格式。

想象一张Excel表,sheetname这里称作KEY,参数mode可以是w新建,也可以是a,append。更多参数如下:

img

保存的命令为:

1
2
3
4
5
6
7
8
9
10
#alldata.to_hdf(path, key='',mode='')
alldata.to_hdf(config.output_data_path + '/alldata.h5', key='all_stock_data', mode='w'))

# 在前面的例子中,读取的标的数据df,可以这样保存
# h5[stock] = df
# 或者
# df.to_hdf(path, key = code, mode='a')

#保存完数据之后,记得关闭h5文件,否则容易报错
h5.close()

要读取的时候,可以用 pd.read_hdf(path, key='')

img

1
2
3
4
5
6
 #读取某个stock的数据有两种方式
print h5.get('sz000006')
print h5['sz000007']

#查询有多少张表,可以用keys()
print h5.keys()

7、 分组统计的groupby函数

一张超大的数据表中,我们想要看某只标的的平均价格,可用groupby函数来实现。

1、基本的groupby用法

1
2
3
4
5
6
7
print stock_data.groupby('交易日期')  #按交易日期来分组
print stock_data.groupby('交易日期') .size() #显示每天交易标的的数量
print stock_data.groupby('标的代码') .size() #显示每只标的累计交易的天数

#分组后想看某只标的的数据,可用get_group()
print stock_data.groupby('标的代码').get_group('002466')
#只会输出天齐锂业的数据

除此之外,还可以

1
2
3
4
5
6
print stock_data.groupby('标的代码').describe()
print stock_data.groupby('标的代码').first()
print stock_data.groupby('标的代码').last()
print stock_data.groupby('标的代码').head()
print stock_data.groupby('标的代码').tail()
print stock_data.groupby('标的代码').nth(n) 表示该组的第n行数据

输出的时候,默认group的变量,即标的代码为index,不想这样的话,可以用as_index=False, 例如:
print stock_data.groupby('标的代码',as_index=False)

还可以

1
2
3
print stock_data.groupby('标的代码')['收盘价', '涨跌幅'].mean()     #取某列数据算均值
.max()
.sum() # 都是可以的。

还可以输出排名,用rank()

1
2
3
4
5
6
7
 print stock_data.groupby('标的代码')['成交量'].rank()  输出组内的算数排名
print stock_data.groupby('标的代码')['成交量'].rank(pct=True) 输出百分比

# 还可以
print stock_data.groupby(stock_data['交易日期'].dt.year).size() # 计算该年共有多少个交易数据
# 还可以先groupby一个,然后再在其中groupby另一个参数,例如:
stock_data.groupby(['标的代码', stock_data['交易日期'].dt.year]).size() # 按照标的代码分组,然后求该证券每年有多少个交易日

2、进阶的groupby用法

前面介绍的resample、apply、fillna等函数,都可以嵌入到groupby函数里面。

当然,也可以用笨办法,用for key, group in df.groupby('列名称'): 将所需功能遍历一遍,以达到相同的效果。key是列名,列的每一个内容,group是该列的内容。

遍历的时候,对group进行单独操作即可,例如group.apply()或group.fillna()

然后只要将每个group append起来即可。