数据!数据!数据!没有黏土我无法造出砖来!——夏洛克•福尔摩斯
本章介绍pandas,这是一个聚焦于表格数据的数据分析程序库。pandas是一个强大的工具,它不仅提供了许多实用类和函数,而且很好地封装了来自其他软件包的功能。该工具提供了一个用户接口,能够让用户方便且高效地实现数据分析,特别是金融分析。
本章介绍如下基本数据结构:
本章的组织如下。
DataFrame类本节首先用简单的小数据集来探索pandas中DataFrame类的基本特性和功能;然后说明如何将NumPy中的ndarray对象转换为DataFrame对象。
基本分析与基本可视化这两节介绍基本分析与可视化功能(后续的章节将深入介绍这些主题)。
Series类本节短小精悍,介绍了pandas的Series类。从某种意义上讲,它是DataFrame的一种特例,只有一列数据。
GroupBy操作DataFrame的优势之一是根据一列或者多列来分组数据。这一节探索pandas的分组能力。
复杂选择这一节介绍如何使用(复杂)条件,轻松地从DataFrame对象中选择数据。
联接、连接和合并将不同数据集合而为一是数据分析中的重要操作。pandas提供了实现这一任务的不同选项,本节将对此进行介绍。
性能特征pandas往往提供实现相同目标的多个选项,这符合Python的常规。本节简单介绍潜在的性能差异。
5.1 DataFrame类
pandas(以及本章)的核心是DataFrame,它是设计用于高效处理表格数据(也就是以列进行组织的数据)的类。为此,DataFrame提供了列标签以及数据集中各行(记录)的索引功能,这与关系数据库的一个表或者Excel电子表格类似。
本节介绍pandas中 DataFrame类的一些根本特性。该类非常复杂且强大,这里只能介绍其中的一小部分功能。后续章节将提供更多示例,揭示其不同方面的特征。
5.1.1 使用DataFrame类的第一步
从最根本的层面上看,DataFrame类设计用来管理具有索引和标签的数据,这些数据与来自SQL数据库表或者电子表格应用中工作表的数据没有太多的不同。考虑如下代码创建的DataFrame对象:
In [1]:importpandasaspd ❶In [2]: df = pd.DataFrame([10,20,30,40], ❷ columns=[numbers], ❸ index=[a,b,c,d]) ❹In [3]: df ❺Out[3]: numbers A10b20c30d40❶ 导入pandas。
❷ 定义列表对象形式的数据。
❸ 指定列标签。
❹ 指定索引值/标签。
❺ 显示DataFrame对象的数据以及列和索引标签。
这个简单的案列已经说明了DataFrame在存储数据上的主要特性,具体如下。
数据本身可以用不同组成及类型(列表、元组、ndarray和字典对象都是候选者)展现。数据以列的方式被组织,可以自定义列名(标签)。索引可以采用不同的格式(如数值、字符串、时间信息)。使用这种DataFrame对象总体上相当方便和高效,例如,当您想要进行扩大现有对象等工作时,我推荐你使用DataFrame对象。相比之下,常规的ndarray对象更专门化,也更受限制。与此同时,DataFrame对象往往在计算上和ndarray对象一样高效。下面是简单的案列,说明了DataFrame对象的典型操作:
In[4]:df.index❶Out[4]:Index([a,b,c,d], dtype=object)In[5]:df.columns❷Out[5]:Index([numbers], dtype=object)In[6]:df.loc[c]❸Out[6]:numbers30Name:c,dtype:int64In[7]:df.loc[[a, d]] ❹Out[7]:numbersa10d40In[8]:df.iloc[1:3]❺Out[8]:numbersb20c30In[9]:df.sum() ❻Out[9]:numbers100dtype:int64In[10]:df.apply(lambdax: x **2) ❼Out[10]:numbersa100b400c900d1600In[11]:df**2❽Out[11]:numbersa100b400c900d1600❶ index属性和Index对象。
❷ columns属性和Index对象。
❸ 选择对应于索引c的值。
❹ 选择对应于索引a和b的两个值。
❺ 通过索引位置选择第二行和第三行。
❻ 计算单列总和。
❼ 使用apply方法,以向量化的方式计算平方值。
❽ 和ndarray对象一样,直接应用向量化。
与NumPy的ndarray对象不同,可以在两个维度上同时扩增DataFrame对象:
In[12]:df[floats]=(1.5,2.5,3.5,4.5)❶In[13]:dfOut[13]:numbersfloatsa101.5b202.5c303.5d404.5In[14]:df[floats]❷Out[14]:a 1.5 b 2.5 c 3.5 d 4.5 Name:floats,dtype:float64❶ 添加一个新列,该包含以元组形式提供的浮点数对象。
❷ 选择该列并显示其数据和索引标签。
也可以使用整个DataFrame对象来定义一个新列。在这种情况下,索引自动对齐:
In[15]: df[names] = pd.DataFrame([Yves,Sandra,Lilli,Henry], index=[d,a,b,c]) ❶In [16]: dfOut[16]: numbers floats names A101.5Sandra b202.5Lilli c303.5Henry d404.5Yves❶ 根据一个DataFrame对象创建另一个新列。
附加数据的方法也类似。但是,在下面的案列中,我们会看到在操作过程中必须避免的一个副作用——索引被简单的编号索引代替:
In[17]:df.append({numbers:100,floats:5.75,names:Jil},ignore_index=True)❶Out[17]:numbersfloatsnames0101.50Sandra1202.50Lilli2303.50Henry3404.50Yves41005.75JilIn[18]:df=df.append(pd.DataFrame({numbers:100,floats:5.75,names:Jil},index=[y,]))❷In[19]:dfOut[19]:numbersfloatsnamesa101.50Sandrab202.50Lillic303.50Henryd404.50Yvesy1005.75JilIn[20]:df=df.append(pd.DataFrame({names:Liz},index=[z,]),sort=False)❸In[21]:dfOut[21]:numbersfloatsnamesA10.01.50Sandrab20.02.50Lillic30.03.50Henryd40.04.50Yvesy100.05.75JilzNaNNaNLizIn[22]:df.dtypes❹Out[22]:numbers float64 floats float64 names object dtype:object❶ 通过一个字典对象附加新行;这是一个临时操作,操作期间索引信息丢失。
❷ 根据带索引信息的DataFrame对象附加行;原始索引信息保留。
❸ 向DataFrame对象附加不完整的数据行,生成NaN值。
❹ 返回各列的不同dtype,这与结构化ndarray对象类似。
尽管此时有遗漏的值,但大部的分方法调用仍然可以正常工作:
In[23]:df[[numbers,floats]].mean() ❶Out[23]:numbers40.00Floats3.55dtype:float64In[24]:df[[numbers,floats]].std() ❷Out[24]:numbers35.355339Floats1.662077dtype:float64❶ 计算指定两列的均值(忽略带NaN值的行)。
❷ 计算指定两列的标准差(忽略带NaN值的行)。
5.1.2 使用DataFrame类的第二步
本节的案列基于一个包含标准正态分布随机数的ndarray对象。该例探索管理时间序列数据的更多功能,如DatetimeIndex:
In[25]:importnumpyasnpIn[26]:np.random.seed(100)In[27]:a=np.random.standard_normal((9,4))In[28]:aOut[28]:array([[-1.74976547,0.3426804,1.1530358,-0.25243604],[0.98132079,0.51421884,0.22117967,-1.07004333],[-0.18949583,0.25500144,-0.45802699,0.43516349],[-0.58359505,0.81684707,0.67272081,-0.10441114],[-0.53128038,1.02973269,-0.43813562,-1.11831825],[1.61898166,1.54160517,-0.25187914,-0.84243574],[0.18451869,0.9370822,0.73100034,1.36155613],[-0.32623806,0.05567601,0.22239961,-1.443217],[-0.75635231,0.81645401,0.75044476,-0.45594693]])您可以更直接地构造DataFrame对象(正如前面所见),但是使用ndarray对象通常是一个更好的选择,因为pandas将保留基本结构,只添加元信息(例如索引值)。这也代表了金融应用和科学研究的一种典型用例。例如:
In[29]:df=pd.DataFrame(a)❶In[30]:dfOut[30]:01230-1.7497650.3426801.153036-0.25243610.9813210.5142190.221180-1.0700432-0.1894960.255001-0.4580270.4351633-0.5835950.8168470.672721-0.1044114-0.5312801.029733-0.438136-1.11831851.6189821.541605-0.251879-0.84243660.1845190.9370820.7310001.3615567-0.3262380.0556760.222400-1.4432178-0.7563520.8164540.750445-0.455947❶ 由ndarray对象创建一个DataFrame对象。
表5-1列出了DataFrame函数使用的参数。表中,类似数组意味着和ndarray对象类似的数据结构——如列表对象。索引是pandas的Index类的一个实例。
表5-1 DataFrame函数参数
和结构数组一样,我们已经了解,DataFrame对象可以通过指定一个具有合适数量元素的列表来直接定义列名。下面的案列说明,我们可以随时定义/更改DataFrame对象的属性:
In[31]:df.columns=[No1,No2,No3,No4]❶In[32]:dfOut[32]:No1No2No3No40-1.7497650.3426801.153036-0.25243610.9813210.5142190.221180-1.0700432-0.1894960.255001-0.4580270.4351633-0.5835950.8168470.672721-0.1044114-0.5312801.029733-0.438136-1.11831851.6189821.541605-0.251879-0.84243660.1845190.9370820.7310001.3615567-0.3262380.0556760.222400-1.4432178-0.7563520.8164540.750445-0.455947In[33]:df[No2].mean()❷Out[33]:0.7010330941456459❶ 通过列表对象指定列标签。
❷ 选择一列很容易。
为了高效处理金融事件序列数据,我们还必须很好地处理时间索引。这也可以视为pandas的一个重要优势。例如,假定分为4列的9个数据项对应于2019年1月开始的月底数据。然后,用date_range生成一个DatetimeIndex对象:
In[34]: dates = pd.date_range(2019-1-1, periods=9, freq=M) ❶In [35]: datesOut[35]: DatetimeIndex([2019-01-31,2019-02-28,2019-03-31,2019-04-30,2019-05-31,2019-06-30,2019-07-31,2019-08-31,2019-09-30], dtype=datetime64[ns], freq=M)❶ 创建一个DatetimeIndex对象。
表5-2列出了date_range函数的参数。
表5-2 date_range函数参数
下列代码将刚刚创建的DatetimeIndex对象定义为相关的索引对象,并建立原始数据集的一个时间序列:
In[36]:df.index=datesIn[37]:dfOut[37]:No1No2No3No42019-01-31-1.7497650.3426801.153036-0.2524362019-02-280.9813210.5142190.221180-1.0700432019-03-31-0.1894960.255001-0.4580270.4351632019-04-30-0.5835950.8168470.672721-0.1044112019-05-31-0.5312801.029733-0.438136-1.1183182019-06-301.6189821.541605-0.251879-0.8424362019-07-310.1845190.9370820.7310001.3615562019-08-31-0.3262380.0556760.222400-1.4432172019-09-30-0.7563520.8164540.750445-0.455947至于在date_range函数帮助下生成的DatetimeIndex对象,频率参数freq有多种选择。表6-3列出了其所有选项。
表5-3 date_range函数的频率参数值(略)
在某些情况下,以ndarray对象的形式访问原始数据是有好处的。values属性提供直接访问的方式:
In[38]:df.valuesOut[38]:array([[-1.74976547,0.3426804,1.1530358,-0.25243604],[0.98132079,0.51421884,0.22117967,-1.07004333],[-0.18949583,0.25500144,-0.45802699,0.43516349],[-0.58359505,0.81684707,0.67272081,-0.10441114],[-0.53128038,1.02973269,-0.43813562,-1.11831825],[1.61898166,1.54160517,-0.25187914,-0.84243574],[0.18451869,0.9370822,0.73100034,1.36155613],[-0.32623806,0.05567601,0.22239961,-1.443217],[-0.75635231,0.81645401,0.75044476,-0.45594693]])In[39]:np.array(df)Out[39]:array([[-1.74976547,0.3426804,1.1530358,-0.25243604],[0.98132079,0.51421884,0.22117967,-1.07004333],[-0.18949583,0.25500144,-0.45802699,0.43516349],[-0.58359505,0.81684707,0.67272081,-0.10441114],[-0.53128038,1.02973269,-0.43813562,-1.11831825],[1.61898166,1.54160517,-0.25187914,-0.84243574],[0.18451869,0.9370822,0.73100034,1.36155613],[-0.32623806,0.05567601,0.22239961,-1.443217],[-0.75635231,0.81645401,0.75044476,-0.45594693]])数组和DataFrame
通常可以从一个ndarray对象生成DataFrame对象。也可以通过NumPy的np.array函数或DataFrame类的values属性,来从DataFrame对象生成ndarray对象。
5.2 基本分析
和NumPy的ndarray类一样,pandas的DataFrame类有多个便利的内建方法。首先考虑info和describe方法:
In[40]:df.info()❶❶ 提供关于数据、列和索引的元信息。
❷ 提供有用的每列汇总统计(对于数值数据)信息。
此外,你可以轻松地求得按列/行计算的总和、平均值和累计总和:
In[43]:df.sum()❶Out[43]:No1 -1.351906 No2 6.309298 No3 2.602739 No4 -3.490089 dtype:float64In[44]:df.mean()Out[44]:No1 -0.150212 No2 0.701033 No3 0.289193 No4 -0.387788 dtype:float64In[45]:df.mean(axis=0)❷Out[45]:No1 -0.150212 No2 0.701033 No3 0.289193 No4 -0.387788 dtype:float64In[46]:df.mean(axis=1)❸Out[46]:2019-01-31 -0.126621 2019-02-28 0.161669 2019-03-31 0.010661 2019-04-30 0.200390 2019-05-31 -0.264500 2019-06-30 0.516568 2019-07-31 0.803539 2019-08-31 -0.372845 2019-09-30 0.088650 Freq:M,dtype:float64In[47]:df.cumsum()❹Out[47]:No1No2No3No42019-01-31-1.7497650.3426801.153036-0.2524362019-02-28-0.7684450.8568991.374215-1.3224792019-03-31-0.9579411.1119010.916188-0.8873162019-04-30-1.5415361.9287481.588909-0.9917272019-05-31-2.0728162.9584801.150774-2.1100452019-06-30-0.4538344.5000860.898895-2.9524812019-07-31-0.2693165.4371681.629895-1.5909252019-08-31-0.5955545.4928441.852294-3.0341422019-09-30-1.3519066.3092982.602739-3.490089❶ 列总和。
❷ 列均值。
❸ 行均值。
❹ 列累计总和(从第一个索引位置起)。
DataFrame对象也能理解为NumPy通用函数,这与预期相符:
In[48]:np.mean(df)❶Out[48]:No1 -0.150212 No2 0.701033 No3 0.289193 No4 -0.387788 dtype:float64In[49]:np.log(df)❷Out[49]:No1No2No3No42019-01-31NaN-1.0709570.142398NaN2019-02-28-0.018856-0.665106-1.508780NaN2019-03-31NaN-1.366486NaN-0.8320332019-04-30NaN-0.202303-0.396425NaN2019-05-31NaN0.029299NaNNaN2019-06-300.4817970.432824NaNNaN2019-07-31-1.690005-0.064984-0.3133410.3086282019-08-31NaN-2.888206-1.503279NaN2019-09-30NaN-0.202785-0.287089NaNIn[50]:np.sqrt(abs(df))❸Out[50]:No1No2No3No42019-01-311.3227870.5853891.0737950.5024302019-02-280.9906160.7170910.4702971.0344292019-03-310.4353110.5049770.6767770.6596692019-04-300.7639340.9037960.8201960.3231272019-05-310.7288901.0147570.6619181.0575062019-06-301.2723921.2416140.5018760.9178432019-07-310.4295560.9680300.8549861.1668572019-08-310.5711730.2359580.4715931.2013402019-09-300.8696850.9035780.8662820.675238In[51]:np.sqrt(abs(df)).sum()❹Out[51]:No1 7.384345 No2 7.075190 No3 6.397719 No4 7.538440 dtype:float64In[52]:100*df+100❺Out[52]:No1No2No3No42019-01-31-74.976547134.268040215.30358074.7563962019-02-28198.132079151.421884122.117967-7.0043332019-03-3181.050417125.50014454.197301143.5163492019-04-3041.640495181.684707167.27208189.5588862019-05-3146.871962202.97326956.186438-11.8318252019-06-30261.898166254.16051774.81208615.7564262019-07-31118.451869193.708220173.100034236.1556132019-08-3167.376194105.567601122.239961-44.3217002019-09-3024.364769181.645401175.04447654.405307❶ 列均值。
❷ 每个元素的自然对数;显示警告信息,但计算继续进行,得到多个NaN值。
❸ 每个元素绝对值的平方根。
❹ 按列均值。
❺ 数值数据的线性变换。
NumPy通用函数
一般来说,在NumPy通用函数适用于ndarray对象的所有情况下,都可以将这些函数应用到包含相同数据的pandas的DataFrame对象。
pandas有相当强的容错能力,它可以捕捉错误,在对应数学运算失败时放入NaN值。不仅如此,正如前面已经展示的,在许多情况下,它还可以将这些不完整的数据集当成完整数据集来使用。这种做法很方便,因为现实中不完整数据集往往比人们预想的要多。
5.3 基本可视化
如果数据存储在DataFrame对象中,那么数据图表的绘制通常只需要一行代码(见图5-1)。
图5-1 DataFrame对象的折线图
In [53]:frompylabimportplt, mpl ❶ plt.style.use(seaborn) ❶ mpl.rcParams[font.family] =serif❶ %matplotlib inlineIn [54]: df.cumsum().plot(lw=2.0, figsize=(10,6)); ❷❶ 定制绘图样式。
❷ 绘制4列累计总和的折线图。
基本上,pandas提供了专为DataFrame对象设计的matplotplib(参见第7章)包装器。plot()方法的参数如表5-4所示。
表5-4 plot方法参数(略)
再举个案列,考虑相同数据的柱状图(见图5-2):
图5-2 DataFrame对象的柱状图
In[55]:df.plot.bar(figsize=(10,6), rot=15); ❶df.plot(kind=bar, figsize=(10,6)) ❷❶ plot.bar()绘制柱状图。
❷ 替代语法:使用kind参数改变绘图类型。
5.4 Series类
迄今为止,本章主要介绍了pandas的DataFrame类。Series是pandas自带的另一个重要的类。它的特点是只有一列数据。从这个意义上讲,它是DataFrame类的特例,两者之间有许多共同特性和功能,但不完全相同。从多列的DataFrame对象上选取一列,可以得到Series对象:
In[56]:type(df)Out[56]:pandas.core.frame.DataFrameIn[57]:S=pd.Series(np.linspace(0,15,7),name=series)In[58]:SOut[58]:0 0.0 1 2.5 2 5.0 3 7.5 4 10.0 5 12.5 6 15.0 Name:series,dtype:float64In[59]:type(S)Out[59]:pandas.core.series.SeriesIn[60]:s=df[No1]In[61]:sOut[61]:2019-01-31 -1.749765 2019-02-28 0.981321 2019-03-31 -0.189496 2019-04-30 -0.583595 2019-05-31 -0.531280 2019-06-30 1.618982 2019-07-31 0.184519 2019-08-31 -0.326238 2019-09-30 -0.756352 Freq:M,Name:No1,dtype:float64In[62]:type(s)Out[62]:pandas.core.series.SeriesDataFrame的主要方法也可用于Series对象,我们以mean和plot方法为例(见图5-3):
In[63]:s.mean()Out[63]:-0.15021177307319458In[64]:s.plot(lw=2.0, figsize=(10,6));图5-3 Series对象的折线图
5.5 GroupBy操作
pandas具备强大而灵活的分组功能,工作方式类似于SQL中的分组和Microsoft Excel中的透视表。为了进行分组,我们添加一列,表示对应索引数据所属的季度:
In[65]:df[Quarter]=[Q1,Q1,Q1,Q2,Q2,Q2,Q3,Q3,Q3]dfOut[65]:No1No2No3No4Quarter2019-01-31-1.7497650.3426801.153036-0.252436Q12019-02-280.9813210.5142190.221180-1.070043Q12019-03-31-0.1894960.255001-0.4580270.435163Q12019-04-30-0.5835950.8168470.672721-0.104411Q22019-05-31-0.5312801.029733-0.438136-1.118318Q22019-06-301.6189821.541605-0.251879-0.842436Q22019-07-310.1845190.9370820.7310001.361556Q32019-08-31-0.3262380.0556760.222400-1.443217Q32019-09-30-0.7563520.8164540.750445-0.455947Q3现在,我们可以根据Quarter列分组,并输出单独组的统计量:
In[66]:groups=df.groupby(Quarter)❶In[67]:groups.size()❷Out[67]:Quarter Q1 3 Q2 3 Q3 3 dtype:int64In[68]:groups.mean()❸Out[68]:No1No2No3No4QuarterQ1-0.3193140.3706340.305396-0.295772Q20.1680351.129395-0.005765-0.688388Q3-0.2993570.6030710.567948-0.179203In[69]:groups.max()❹Out[69]:No1No2No3No4QuarterQ10.9813210.5142191.1530360.435163Q21.6189821.5416050.672721-0.104411Q30.1845190.9370820.7504451.361556In[70]:groups.aggregate([min,max]).round(2)❺Out[70]:No1No2No3No4MinmaxminmaxminmaxminmaxQuarterQ1-1.750.980.260.51-0.461.15-1.070.44Q2-0.581.620.821.54-0.440.67-1.12-0.10Q3-0.760.180.060.940.220.75-1.441.36❶ 根据Quarter列分组。
❷ 给出每个分组的行数。
❸ 给出每的列均值。
❹ 给出每列的最大值。
❺ 给出每列的最小值和最大值。
分组也可以在多列上进行。为此,我们添加另一列,表示索引日期的月份是奇数还是偶数:
In[71]:df[Odd_Even]=[Odd,Even,Odd,Even,Odd,Even,Odd,Even,Odd]In[72]:groups=df.groupby([Quarter,Odd_Even])In[73]:groups.size()Out[73]:Quarter Odd_Even Q1 Even 1 Odd 2 Q2 Even 2 Odd 1 Q3 Even 1 Odd 2 dtype:int64In[74]:groups[[No1,No4]].aggregate([sum,np.mean])Out[74]:No1No4SummeansummeanQuarterOdd_EvenQ1Even0.9813210.981321-1.070043-1.070043Odd-1.939261-0.9696310.1827270.091364Q2Even1.0353870.517693-0.946847-0.473423Odd-0.531280-0.531280-1.118318-1.118318Q3Even-0.326238-0.326238-1.443217-1.443217Odd-0.571834-0.2859170.9056090.452805对pandas和DataFrame对象使用的介绍到此结束,下面将这组工具应用到现实世界的金融数据中。
5.6 复杂选择
数据选择往往是通过列值上的条件公式实现的,也可以通过符合逻辑的方式来组合多个条件。考虑如下数据集。
In[75]:data=np.random.standard_normal((10,2))❶In[76]:df=pd.DataFrame(data,columns=[x,y])❷In[77]:df.info()❷❶ 包含标准正态分布随机数的ndarray对象。
❷ 包含相同随机数的DataFrame对象。
❸ 通过head方法取得前5行。
❹ 通过tail方法取得最后5行。
下面的代码说明了Python比较运算符和逻辑运算符在两列值上的应用:
In [80]: df[x] >0.5❶Out[80]:0True1False2False3False4True5False6False7False8False9FalseName: x, dtype: boolIn [81]: (df[x] >0) & (df[y] <0) ❷Out[81]:0True1False2False3True4True5False6False7False8False9Falsedtype: boolIn [82]: (df[x] >0) | (df[y] <0) ❸Out[82]:0True1True2True3True4True5False6False7True8True9Truedtype: bool❶ 检查x列的值是否大于0.5。
❷ 检查是否x列的值为正且y列的值为负。
❸ 检查是否x列的值为正或y列的值为负。
利用得到的布尔型Series对象,就可以很容易地实现复杂数据(行)的选择。另外,也可以使用query方法并以字符串对象的形式来传递条件:
In[83]:df[df[x]>0]❶Out[83]:xy01.189622-1.69061730.007315-0.61293941.299748-1.73309690.1088630.507810In[84]:df.query(x>0) ❶Out[84]: x y 0 1.189622 -1.690617 3 0.007315 -0.612939 4 1.299748 -1.733096 9 0.108863 0.507810In [85]: df[(df[x]>0)&(df[y]<0)]❷Out[85]:xy01.189622-1.69061730.007315-0.61293941.299748-1.733096In[86]:df.query(x>0&y<0) ❷Out[86]: x y 0 1.189622 -1.690617 3 0.007315 -0.612939 4 1.299748 -1.733096In [87]: df[(df.x > 0) | (df.y < 0)] ❸Out[87]: x y 0 1.189622 -1.690617 1 -1.356399 -1.232435 2 -0.544439 -0.668172 3 0.007315 -0.612939 4 1.299748 -1.733096 7 -1.188018 -0.549746 8 -0.940046 -0.827932 9 0.108863 0.507810❶ 所有第x列的值大于0.5的行。
❷ 所有第x列的值为正且y的列值为负的行。
❸ 所有第x列的值为正或y的列值为负的行(各列通过对应属性访问)。
比较运算符也可以一次性应用到整个DataFrame对象上:
In[88]:df>0❶Out[88]:xy0TrueFalse1FalseFalse2FalseFalse3TrueFalse4TrueFalse5FalseTrue6FalseTrue7FalseFalse8FalseFalse9TrueTrueIn[89]:df[df>0]❷Out[89]:xy01.189622NaN1NaNNaN2NaNNaN30.007315NaN41.299748NaN5NaN0.3575086NaN1.4707147NaNNaN8NaNNaN90.1088630.507810❶ DataFrame对象中的哪些值为正?
❷ 选择所有满足要求的值,并将其他值设为NaN。
5.7 联接、连接和合并
本节简单介绍连接DataFrame对象形式的两个简单数据集的不同方法。这两个简单数据集如下所示:
In [90]: df1 = pd.DataFrame([100,200,300,400],index=[a,b,c,d], columns=[A,])In [91]: df1Out[91]: A a100b200c300d400In [92]: df2 = pd.DataFrame([200,150,50],index=[f,b,d], columns=[B,])In [93]: df2Out[93]: B f200b150d505.7.1 联接
联接(Concatenation)或者附加(Appending)本质上指的是将一个DataFrame对象中的行添加到另一个DataFrame对象上,这可通过append方法或者pd.concat函数完成。需要认真考虑的是索引值的处理方法:
In[94]:df1.append(df2,sort=False)❶Out[94]:ABa100NaNb200NaNc300NaNd400NaNfNaN200bNaN150dNaN50In[95]:df1.append(df2,ignore_index=True,sort=False)❷Out[95]:AB0100NaN1200NaN2300NaN3400NaN4NaN2005NaN1506NaN50In[96]:pd.concat((df1,df2),sort=False)❸Out[96]:ABa100NaNb200NaNc300NaNd400NaNfNaN200bNaN150dNaN50In[97]:pd.concat((df1,df2),ignore_index=True,sort=False)❹Out[97]:AB0100NaN1200NaN2300NaN3400NaN4NaN2005NaN1506NaN50❶ 将df2的数据附加到df1中作为新行。
❷ 完成同样的工作,但忽略索引。
❸ 与第一个附加操作效果相同。
❹ 与第二个附加操作效果相同。
5.7.2 连接
连接两个数据集时,DataFrame对象的顺序很重要。只有第一个DataFrame对象的索引值被使用,这种默认行为称为左连接(left join):
In[98]:df1.join(df2)❶Out[98]:ABa100NaNb200150c300NaNd40050In[99]:df2.join(df1)❷Out[99]:BAf200NaNb150200d50400❶ df1的索引值有意义。
❷ df2的索引值有意义。
共有4种不同的连接方法,每种方法都导致索引值和对应数据行的不同处理行为:
In[100]:df1.join(df2,how=left)❶Out[100]:ABa100NaNb200150c300NaNd40050In[101]:df1.join(df2,how=right)❷Out[101]:ABfNaN200b200150d40050In[102]:df1.join(df2,how=inner)❸Out[102]:ABb200150d40050In[103]:df1.join(df2,how=outer)❹Out[103]:ABa100NaNb200150c300NaNd40050fNaN200❶ 左连接是默认操作。
❷ 右连接相当于颠倒了DataFrame对象的顺序。
❸ 内连接只保留在两个索引中都存在的索引值。
❹ 外连接保留两个索引中的所有索引值。
连接也可以基于空的DataFrame对象。在这种情况下,列按顺序被创建,这与左连接类似:
In[104]:df=pd.DataFrame()In[105]:df[A]=df1[A]❶In[106]:dfOut[106]:Aa100b200c300d400In[107]:df[B]=df2❷In[108]:dfOut[108]:ABa100NaNb200150c300NaNd40050❶ df1为第一列A。
❷ df2为第二列B。
使用字典来组合数据集可以得到类似于外连接的结果,因为列是同时创建的:
In[109]:df=pd.DataFrame({A:df1[A],B:df2[B]})In[110]:dfOut[110]:ABa100NaNb200150c300NaNd40050fNaN200❶ DataFrame对象的各列用作字典对象的值
5.7.3 合并
连接操作根据待连接的DataFrame对象的索引进行,而合并操作通常在两个数据集共享的某列上进行。为此,在两个原始的DataFrame对象上添加一个新列C:
In[111]:c=pd.Series([250,150,50],index=[b,d,c])df1[C]=cdf2[C]=cIn[112]:df1Out[112]:ACa100NaNb200250.0c30050.0d400150.0In[113]:df2Out[113]:BCf200NaNb150250.0d50150.0默认情况下,这种合并操作根据单一共享列C进行。但也存在其他可能,例如外合并:
In[114]:pd.merge(df1,df2)❶Out[114]:ACB0100NaN2001200250.01502400150.050In[115]:pd.merge(df1,df2,on=C)❶Out[115]:ACB0100NaN2001200250.01502400150.050In[116]:pd.merge(df1,df2,how=outer)Out[116]:ACB0100NaN2001200250.0150230050.0NaN3400150.050❶ 默认合并根据C列进行。
❷ 也可以进行外合并,保留所有数据行。
可用的合并操作类型还有很多,下面的代码演示了其中的几种:
In[117]:pd.merge(df1,df2,left_on=A,right_on=B)Out[117]:AC_xBC_y0200250.0200NaNIn[118]:pd.merge(df1,df2,left_on=A,right_on=B,how=outer)Out[118]:AC_xBC_y0100NaNNaNNaN1200250.0200NaN230050.0NaNNaN3400150.0NaNNaN4NaNNaN150250.05NaNNaN50150.0In[119]:pd.merge(df1,df2,left_index=True,right_index=True)Out[119]:AC_xBC_yb200250.0150250.0d400150.050150.0In[120]:pd.merge(df1,df2,on=C,left_index=True)Out[120]:ACBf100NaN200b200250.0150d400150.050In[121]:pd.merge(df1,df2,on=C,right_index=True)Out[121]:ACBa100NaN200b200250.0150d400150.050In[122]:pd.merge(df1,df2,on=C,left_index=True,right_index=True)Out[122]:ACBb200250.0150d400150.0505.8 性能特征
本章的许多案列说明,用pandas实现同一个目标往往有多种选择。本节以按元素加总两列数值为例来这些选择进行对比。首先用NumPy生成数据集:
In [123]:data= np.random.standard_normal((1000000,2)) ❶In [124]:data.nbytes ❶Out[124]:16000000In [125]: df = pd.DataFrame(data, columns=[x,y]) ❷In [126]: df.info() ❷ <classpandas.core.frame.DataFrame>RangeIndex:1000000 entries,0 to 999999 Data columns(total2columns): X1000000non-nullfloat64 Y1000000non-nullfloat64 dtypes: float64(2) memory usage:15.3MB❶ 包含随机数的ndarray对象。
❷ 包含随机数的DataFrame对象。
其次,实现手边任务的某选择性能还不错:
In[127]:%timeres=df[x]+df[y]❶CPU times:user7.35ms,sys:7.43ms,total: 14.8 ms Wall time:7.48msIn[128]:res[:3]Out[128]:0 0.387242 1 -0.969343 2 -0.863159 dtype:float64In[129]:%timeres=df.sum(axis=1)❷CPU times:user130ms,sys:30.6ms,total: 161 ms Wall time:101msIn[130]:res[:3]Out[130]:0 0.387242 1 -0.969343 2 -0.863159 dtype:float64In[131]:%timeres=df.values.sum(axis=1)❸CPU times:user50.3ms,sys:2.75ms,total: 53.1 ms Wall time:27.9msIn[132]:res[:3]Out[132]:array([0.3872424,-0.96934273,-0.86315944])In[133]:%timeres=np.sum(df,axis=1)❹CPU times:user127ms,sys:15.1ms,total: 142 ms Wall time:73.7msIn[134]:res[:3]Out[134]:0 0.387242 1 -0.969343 2 -0.863159 dtype:float64In[135]:%timeres=np.sum(df.values,axis=1)❺CPU times:user49.3ms,sys:2.36ms,total: 51.7 ms Wall time:26.9msIn[136]:res[:3]Out[136]:array([0.3872424,-0.96934273,-0.86315944])❶ 直接使用列(Series对象)是最快的方法。
❷ 调用DataFrame对象的sum方法来计算总和。
❸ 调用ndarray对象的sum方法来计算总和。
❹ 调用DataFrame对象的np.sum方法来计算总和。
❺ 调用ndarray对象的np.sum方法来计算总和。
最后,还有两种选择可以计算元素和,它们分别基于eval和apply方法[1]:
Theapplicationoftheeval()methodrequiresthenumexprpackagetobeinstalled.In[137]:%timeres=df.eval(x+y)❶CPU times:user25.5ms,sys:17.7ms,total: 43.2 ms Wall time:22.5msIn[138]:res[:3]Out[138]:0 0.387242 1 -0.969343 2 -0.863159 dtype:float64In[139]:%timeres=df.apply(lambdarow:row[x]+row[y],axis=1)CPU times:user19.6s,sys:83.3ms,total: 19.7 s Wall time:19.9sIn[140]:res[:3]Out[140]:0 0.387242 1 -0.969343 2 -0.863159 dtype:float64❶ eval是专门用于计算(复杂)数值表达式的方法,可以直接处理数据列。
❷ 最慢的选择是逐行使用apply方法,这就像在Python级别上循环访问所有行。
明智的选择
pandas常常为实现统一目标提供多个选择。如果不确定使用哪一个,那么在时间是关键因素的情况下,比较各种选择,选择性能最好的一种。在上面这个简单的案列里,不同选择的执行时间相差好几个数量级。
5.9 结语
pandas是强大的数据分析工具,已经成为所谓的PyData栈的核心软件包。它的DataFrame类特别适合于处理任何类型的表格数据。这些对象上的大部分操作是向量化的,这不仅可以得到和NumPy类似的简洁代码,而且还可以得到高性能。此外,pandas能够很方便地处理不完整数据集(NumPy做不到这一点)。pandas和DataFrame类将是本书后续多个章节的核心,在必要时将使用并介绍它们的更多特性。
5.10 延伸阅读
pandas是一个开源项目,你既可以阅读在线文档,也可以下载其PDF版本[2]。官网上还提供了一些附加资源。
至于NumPy,我们建议的pandas参考书如下。
McKinney, Wes (2017). Python for Data Analysis. Sebastopol, CA: O’Reilly.VanderPlas, Jake (2016). Python Data Science Handbook. Sebastopol, CA: O’Reilly.[1] 要想使用eval方法需要安装numexpr软件包。——原注
[2] 在本书编写期间,PDF文档共有2500页。——原注
本文摘自《Python金融大数据分析 第2版》[德] 伊夫·希尔皮斯科(Yves Hilpisch) 著,姚军 译
金融科技算法交易量化金融教程书籍详细讲解使用Python分析处理金融大数据的专业图书将人工智能应用于金融开发的实战指南,金融应用开发领域从业人员的常备读物Python已成为数据驱动AI、金融优先选择的编程语言。现在,一些大型的投资银行和对冲资金均使用Python及其生态系统来构建核心交易与风险管理系统。在本书中,作者向开发人员和量化分析人员介绍了使用Python程序库与工具,完成金融数据科学、算法交易和计算金融任务的方法。
Python与金融:Python交互式金融分析与程序开发入门。基本知识:学习Python数据类型与结构、NumPy、pandas及其DataFrame类、面向对象编程。金融数据科学:探索用于金融时间序列数据、I/O操作、推断统计学和机器学习的Python技术与程序库。算法交易:使用Python来验证和部署自动算法交易策略。衍生品分析:开发灵活、强大的Python期权、衍生品定价和风险管理程序库。