在上一篇中我们介绍了如何创建并访问pandas的Series和DataFrame类型的数据,本篇将介绍如何对pandas数据进行操作,掌握这些操作之后,基本可以处理大多数的数据了。首先,导入本篇中使用到的模块:
1 2 3
| import numpy as np import pandas as pd from pandas import Series, DataFrame
|
为了看数据方便一些,我们设置一下输出屏幕的宽度
1
| pd.set_option('display.width', 200)
|
#一、数据创建的其他方式
数据结构的创建不止是上篇中介绍的标准形式,本篇再介绍几种。例如,我们可以创建一个以日期为元素的Series:
1 2
| dates = pd.date_range('20150101', periods=5) print dates
|
将这个日期Series作为索引赋给一个DataFrame:
1 2
| df = pd.DataFrame(np.random.randn(5, 4),index=dates,columns=list('ABCD')) print df
|
只要是能转换成Series的对象,都可以用于创建DataFrame:
1 2
| df2 = pd.DataFrame({ 'A' : 1., 'B': pd.Timestamp('20150214'), 'C': pd.Series(1.6,index=list(range(4)),dtype='float64'), 'D' : np.array([4] * 4, dtype='int64'), 'E' : 'hello pandas!' }) print df2
|
#二、数据的查看
在多数情况下,数据并不由分析数据的人员生成,而是通过数据接口、外部文件或者其他方式获取。这里我们通过量化实验室的数据接口获取一份数据作为示例:
1 2 3 4 5
| stock_list = ['000001.XSHE', '000002.XSHE', '000568.XSHE', '000625.XSHE', '000768.XSHE', '600028.XSHG', '600030.XSHG', '601111.XSHG', '601390.XSHG', '601998.XSHG'] 2 raw_data = DataAPI.MktEqudGet(secID=stock_list, beginDate='20150101', endDate='20150131', pandas='1') 3 df = raw_data[['secID', 'tradeDate', 'secShortName', 'openPrice', 'highestPrice', 'lowestPrice', 'closePrice', 'turnoverVol']]
|
以上代码获取了2015年一月份全部的交易日内十支股票的日行情信息,首先我们来看一下数据的大小:
我们可以看到有200行,表示我们获取到了200条记录,每条记录有8个字段,现在预览一下数据,dataframe.head()和dataframe.tail()可以查看数据的头五行和尾五行,若需要改变行数,可在括号内指定:
1 2 3 4
| print "Head of this DataFrame:" print df.head() print "Tail of this DataFrame:" print df.tail(3)
|
dataframe.describe()提供了DataFrame中纯数值数据的统计信息:
对数据的排序将便利我们观察数据,DataFrame提供了两种形式的排序。一种是按行列排序,即按照索引(行名)或者列名进行排序,可调用dataframe.sort_index,指定axis=0表示按索引(行名)排序,axis=1表示按列名排序,并可指定升序或者降序:
1 2
| print "Order by column names, descending:" print df.sort_index(axis=1, ascending=False).head()
|
第二种排序是按值排序,可指定列名和排序方式,默认的是升序排序:
1 2 3 4 5
| print "Order by column value, ascending:" print df.sort(columns='tradeDate').head() print "Order by multiple columns value:" df = df.sort(columns=['tradeDate', 'secID'], ascending=[False, True]) print df.head()
|
#三、数据的访问和操作
##3.1 再谈数据的访问
上篇中已经介绍了使用loc、iloc、at、iat、ix以及[]访问DataFrame数据的几种方式,这里再介绍一种方法,使用”:”来获取部行或者全部列:
我们可以扩展上篇介绍的使用布尔类型的向量获取数据的方法,可以很方便地过滤数据,例如,我们要选出收盘价在均值以上的数据:
1
| print df[df.closePrice > df.closePrice.mean()].head()
|
isin()函数可方便地过滤DataFrame中的数据:
1 2
| print df[df['secID'].isin(['601628.XSHG', '000001.XSHE', '600030.XSHG'])].head() print df.shape
|
##3.2 处理缺失数据
在访问数据的基础上,我们可以更改数据,例如,修改某些元素为缺失值:
1 2 3 4 5 6
| df['openPrice'][df['secID'] == '000001.XSHE'] = np.nan df['highestPrice'][df['secID'] == '601111.XSHG'] = np.nan df['lowestPrice'][df['secID'] == '601111.XSHG'] = np.nan df['closePrice'][df['secID'] == '000002.XSHE'] = np.nan df['turnoverVol'][df['secID'] == '601111.XSHG'] = np.nan print df.head(10)
|
原始数据的中很可能存在一些数据的缺失,就如同现在处理的这个样例数据一样,处理缺失数据有多种方式。通常使用dataframe.dropna(),dataframe.dropna()可以按行丢弃带有nan的数据;若指定how=’all’(默认是’any’),则只在整行全部是nan时丢弃数据;若指定thresh,则表示当某行数据非缺失列数超过指定数值时才保留;要指定根据某列丢弃可以通过subset完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| print "Data size before filtering:" print df.shape print "Drop all rows that have any NaN values:" print "Data size after filtering:" print df.dropna().shape print df.dropna().head(10)
print "Drop only if all columns are NaN:" print "Data size after filtering:" print df.dropna(how='all').shape print df.dropna(how='all').head(10)
print "Drop rows who do not have at least six values that are not NaN" print "Data size after filtering:" print df.dropna(thresh=6).shape print df.dropna(thresh=6).head(10)
print "Drop only if NaN in specific column:" print "Data size after filtering:" print df.dropna(subset=['closePrice']).shape print df.dropna(subset=['closePrice']).head(10)
|
有数据缺失时也未必是全部丢弃,dataframe.fillna(value=value)可以指定填补缺失值的数值
1
| print df.fillna(value=20150101).head()
|
##3.3 数据操作
Series和DataFrame的类函数提供了一些函数,如mean()、sum()等,指定0按列进行,指定1按行进行:
1 2
| df = raw_data[['secID', 'tradeDate', 'secShortName', 'openPrice', 'highestPrice', 'lowestPrice', 'closePrice', 'turnoverVol']] print df.mean(0)
|
value_counts函数可以方便地统计频数:
1
| print df['closePrice'].value_counts().head()
|
在panda中,Series可以调用map函数来对每个元素应用一个函数,DataFrame可以调用apply函数对每一列(行)应用一个函数,applymap对每个元素应用一个函数。这里面的函数可以是用户自定义的一个lambda函数,也可以是已有的其他函数。下例展示了将收盘价调整到[0, 1]区间:
1
| print df[['closePrice']].apply(lambda x: (x - x.min()) / (x.max() - x.min())).head()
|
使用append可以在Series后添加元素,以及在DataFrame尾部添加一行:
1 2 3 4 5 6 7
| dat1 = df[['secID', 'tradeDate', 'closePrice']].head() dat2 = df[['secID', 'tradeDate', 'closePrice']].iloc[2] print "Before appending:" print dat1 dat = dat1.append(dat2, ignore_index=True) print "After appending:" print dat
|
DataFrame可以像在SQL中一样进行合并,在上篇中,我们介绍了使用concat函数创建DataFrame,这就是一种合并的方式。另外一种方式使用merge函数,需要指定依照哪些列进行合并,下例展示了如何根据security ID和交易日合并数据:
1 2 3 4 5 6 7 8 9
| dat1 = df[['secID', 'tradeDate', 'closePrice']] dat2 = df[['secID', 'tradeDate', 'turnoverVol']] dat = dat1.merge(dat2, on=['secID', 'tradeDate']) print "The first DataFrame:" print dat1.head() print "The second DataFrame:" print dat2.head() print "Merged DataFrame:" print dat.head()
|
DataFrame另一个强大的函数是groupby,可以十分方便地对数据分组处理,我们对2015年一月内十支股票的开盘价,最高价,最低价,收盘价和成交量求平均值:
1 2 3
| df_grp = df.groupby('secID') grp_mean = df_grp.mean() print grp_mean
|
如果希望取每只股票的最新数据,应该怎么操作呢?drop_duplicates可以实现这个功能,首先对数据按日期排序,再按security ID去重:
1 2
| df2 = df.sort(columns=['secID', 'tradeDate'], ascending=[True, False]) print df2.drop_duplicates(subset='secID')
|
若想要保留最老的数据,可以在降序排列后取最后一个记录,通过指定take_last=True(默认值为False,取第一条记录)可以实现:
1
| print df2.drop_duplicates(subset='secID', take_last=True)
|
#四、数据可视化
pandas数据直接可以绘图查看,下例中我们采用中国石化一月的收盘价进行绘图,其中set_index(‘tradeDate’)[‘closePrice’]表示将DataFrame的’tradeDate’这一列作为索引,将’closePrice’这一列作为Series的值,返回一个Series对象,随后调用plot函数绘图,更多的参数可以在matplotlib的文档中查看。
1 2
| dat = df[df['secID'] == '600028.XSHG'].set_index('tradeDate')['closePrice'] dat.plot(title="Close Price of SINOPEC (600028) during Jan, 2015")
|
#参考资料
[1] 量化分析师的Python日记【第6天:数据处理的瑞士军刀pandas下篇
[2] pandas 文档
#进阶阅读
[1] pandas: powerful Python data analysis toolkit