数据!数据!数据!没有黏土,我就做不了砖头!——福尔摩斯
本章介绍了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]: import pandas as pd ❶In [2]: df = ([10, 20, 30, 40], ❷ columns=['numbers'], ❸ index=['a', 'b', 'c', 'd']) ❹In [3]: df ❺Out[3]: numbers A 10 b 20 c 30 d 40
❶ 导入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]: numbers 30 Name: c, dtype: int64In [7]: df.loc[['a', 'd']] ❹Out[7]: numbers a 10 d 40In [8]: df.iloc[1:3] ❺Out[8]: numbers b 20 c 30In [9]: df.sum() ❻Out[9]: numbers 100 dtype: int64In [10]: df.apply(lambda x: x ** 2) ❼Out[10]: numbers a 100 b 400 c 900 d 1600In [11]: df ** 2 ❽Out[11]: numbers a 100 b 400 c 900 d 1600
❶ index属性和Index对象。
❷ columns属性和Index对象。
❸ 选择对应于索引c的值。
❹ 选择对应于索引a和b的两个值。
❺ 通过索引位置选择第二行和第三行。
❻ 计算单列总和。
❼ 使用apply方法,以向量化的方式计算平方值。
❽ 和ndarray对象一样,直接应用向量化。
与NumPy的ndarray对象不同,可以在两个维度上同时扩增DataFrame对象:
In [12]: df['floats'] = , 2.5, 3.5, 4.5) ❶In [13]: dfOut[13]: numbers floats a 10 1.5 b 20 2.5 c 30 3.5 d 40 4.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'] = (['Yves', 'Sandra', 'Lilli', 'Henry'], index=['d', 'a', 'b', 'c']) ❶In [16]: dfOut[16]: numbers floats names A 10 1.5 Sandra b 20 2.5 Lilli c 30 3.5 Henry d 40 4.5 Yves
❶ 根据一个DataFrame对象创建另一个新列。
附加数据的方法也类似。但是,在下面的例子中,我们会看到在操作过程中必须避免的一个副作用——索引被简单的编号索引代替:
In [17]: df.append({'numbers': 100, 'floats': 5.75, 'names': 'Jil'}, ignore_index=True) ❶Out[17]: numbers floats names 0 10 1.50 Sandra 1 20 2.50 Lilli 2 30 3.50 Henry 3 40 4.50 Yves 4 100 5.75 JilIn [18]: df = df.append(({'numbers': 100, 'floats': 5.75, 'names': 'Jil'}, index=['y',])) ❷In [19]: dfOut[19]: numbers floats names a 10 1.50 Sandra b 20 2.50 Lilli c 30 3.50 Henry d 40 4.50 Yves y 100 5.75 JilIn [20]: df = df.append(({'names': 'Liz'}, index=['z',]), sort=False) ❸In [21]: dfOut[21]: numbers floats names A 10.0 1.50 Sandra b 20.0 2.50 Lilli c 30.0 3.50 Henry d 40.0 4.50 Yves y 100.0 5.75 Jil z NaN NaN LizIn [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]: numbers 40.00 Floats 3.55 dtype: float64In [24]: df[['numbers', 'floats']].std() ❷Out[24]: numbers 35.355339 Floats 1.662077 dtype: float64
❶ 计算指定两列的均值(忽略带NaN值的行)。
❷ 计算指定两列的标准差(忽略带NaN值的行)。
5.1.2 使用DataFrame类的第二步
本节的例子基于一个包含标准正态分布随机数的ndarray对象。该例探索管理时间序列数据的更多功能,如DatetimeIndex:
In [25]: import numpy as npIn [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 = (a) ❶In [30]: dfOut[30]: 0 1 2 3 0 -1.749765 0.342680 1.153036 -0.252436 1 0.981321 0.514219 0.221180 -1.070043 2 -0.189496 0.255001 -0.458027 0.435163 3 -0.583595 0.816847 0.672721 -0.104411 4 -0.531280 1.029733 -0.438136 -1.118318 5 1.618982 1.541605 -0.251879 -0.842436 6 0.184519 0.937082 0.731000 1.361556 7 -0.326238 0.055676 0.222400 -1.443217 8 -0.756352 0.816454 0.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]: No1 No2 No3 No4 0 -1.749765 0.342680 1.153036 -0.252436 1 0.981321 0.514219 0.221180 -1.070043 2 -0.189496 0.255001 -0.458027 0.435163 3 -0.583595 0.816847 0.672721 -0.104411 4 -0.531280 1.029733 -0.438136 -1.118318 5 1.618982 1.541605 -0.251879 -0.842436 6 0.184519 0.937082 0.731000 1.361556 7 -0.326238 0.055676 0.222400 -1.443217 8 -0.756352 0.816454 0.750445 -0.455947In [33]: df['No2'].mean() ❷Out[33]: 0.7010330941456459
❶ 通过列表对象指定列标签。
❷ 选择一列很容易。
为了高效处理金融事件序列数据,我们还必须很好地处理时间索引。这也可以视为pandas的一个重要优势。例如,假定分为4列的9个数据项对应于2019年1月开始的月底数据。然后,用date_range生成一个DatetimeIndex对象:
In [34]: dates = ('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]: No1 No2 No3 No4 2019-01-31 -1.749765 0.342680 1.153036 -0.252436 2019-02-28 0.981321 0.514219 0.221180 -1.070043 2019-03-31 -0.189496 0.255001 -0.458027 0.435163 2019-04-30 -0.583595 0.816847 0.672721 -0.104411 2019-05-31 -0.531280 1.029733 -0.438136 -1.118318 2019-06-30 1.618982 1.541605 -0.251879 -0.842436 2019-07-31 0.184519 0.937082 0.731000 1.361556 2019-08-31 -0.326238 0.055676 0.222400 -1.443217 2019-09-30 -0.756352 0.816454 0.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() ❶ <class ';> DatetimeIndex: 9 entries, 2019-01-31 to 2019-09-30 Freq: M Data columns (total 4 columns): No1 9 non-null float64 No2 9 non-null float64 No3 9 non-null float64 No4 9 non-null float64 dtypes: float64(4) memory usage: 360.0 bytesIn [41]: df.describe() ❷Out[41]: No1 No2 No3 No4 Count 9.000000 9.000000 9.000000 9.000000 Mean -0.150212 0.701033 0.289193 -0.387788 Std 0.988306 0.457685 0.579920 0.877532 Min -1.749765 0.055676 -0.458027 -1.443217 25% -0.583595 0.342680 -0.251879 -1.070043 50% -0.326238 0.816454 0.222400 -0.455947 75% 0.184519 0.937082 0.731000 -0.104411 max 1.618982 1.541605 1.153036 1.361556
❶ 提供关于数据、列和索引的元信息。
❷ 提供有用的每列汇总统计(对于数值数据)信息。
此外,你可以轻松地求得按列/行计算的总和、平均值和累计总和:
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]: No1 No2 No3 No4 2019-01-31 -1.749765 0.342680 1.153036 -0.252436 2019-02-28 -0.768445 0.856899 1.374215 -1.322479 2019-03-31 -0.957941 1.111901 0.916188 -0.887316 2019-04-30 -1.541536 1.928748 1.588909 -0.991727 2019-05-31 -2.072816 2.958480 1.150774 -2.110045 2019-06-30 -0.453834 4.500086 0.898895 -2.952481 2019-07-31 -0.269316 5.437168 1.629895 -1.590925 2019-08-31 -0.595554 5.492844 1.852294 -3.034142 2019-09-30 -1.351906 6.309298 2.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]: No1 No2 No3 No4 2019-01-31 NaN -1.070957 0.142398 NaN 2019-02-28 -0.018856 -0.665106 -1.508780 NaN 2019-03-31 NaN -1.366486 NaN -0.832033 2019-04-30 NaN -0.202303 -0.396425 NaN 2019-05-31 NaN 0.029299 NaN NaN 2019-06-30 0.481797 0.432824 NaN NaN 2019-07-31 -1.690005 -0.064984 -0.313341 0.308628 2019-08-31 NaN -2.888206 -1.503279 NaN 2019-09-30 NaN -0.202785 -0.287089 NaNIn [50]: np.sqrt(abs(df)) ❸Out[50]: No1 No2 No3 No4 2019-01-31 1.322787 0.585389 1.073795 0.502430 2019-02-28 0.990616 0.717091 0.470297 1.034429 2019-03-31 0.435311 0.504977 0.676777 0.659669 2019-04-30 0.763934 0.903796 0.820196 0.323127 2019-05-31 0.728890 1.014757 0.661918 1.057506 2019-06-30 1.272392 1.241614 0.501876 0.917843 2019-07-31 0.429556 0.968030 0.854986 1.166857 2019-08-31 0.571173 0.235958 0.471593 1.201340 2019-09-30 0.869685 0.903578 0.866282 0.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]: No1 No2 No3 No4 2019-01-31 -74.976547 134.268040 215.303580 74.756396 2019-02-28 198.132079 151.421884 122.117967 -7.004333 2019-03-31 81.050417 125.500144 54.197301 143.516349 2019-04-30 41.640495 181.684707 167.272081 89.558886 2019-05-31 46.871962 202.973269 56.186438 -11.831825 2019-06-30 261.898166 254.160517 74.812086 15.756426 2019-07-31 118.451869 193.708220 173.100034 236.155613 2019-08-31 67.376194 105.567601 122.239961 -44.321700 2019-09-30 24.364769 181.645401 175.044476 54.405307
❶ 列均值。
❷ 每个元素的自然对数;显示警告信息,但计算继续进行,得到多个NaN值。
❸ 每个元素绝对值的平方根。
❹ 按列均值。
❺ 数值数据的线性变换。
NumPy通用函数
一般来说,在NumPy通用函数适用于ndarray对象的所有情况下,都可以将这些函数应用到包含相同数据的pandas的DataFrame对象。
pandas有相当强的容错能力,它可以捕捉错误,在对应数学运算失败时放入NaN值。不仅如此,正如前面已经展示的,在许多情况下,它还可以将这些不完整的数据集当成完整数据集来使用。这种做法很方便,因为现实中不完整数据集往往比人们预想的要多。
5.3 基本可视化
如果数据存储在DataFrame对象中,那么数据图表的绘制通常只需要一行代码(见图5-1)。
图5-1 DataFrame对象的折线图
In [53]: from pylab import plt, mpl ❶ ('seaborn') ❶ m[';] = '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.(figsize=(10, 6), rot=15); ❶# df.plot(kind='bar', figsize=(10, 6)) ❷
❶ ()绘制柱状图。
❷ 替代语法:使用kind参数改变绘图类型。
5.4 Series类
迄今为止,本章主要介绍了pandas的DataFrame类。Series是pandas自带的另一个重要的类。它的特点是只有一列数据。从这个意义上讲,它是DataFrame类的特例,两者之间有许多共同特性和功能,但不完全相同。从多列的DataFrame对象上选取一列,可以得到Series对象:
In [56]: type(df)Out[56]: [57]: S = (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]: [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]:
DataFrame的主要方法也可用于Series对象,我们以mean和plot方法为例(见图5-3):
In [63]: s.mean()Out[63]: -0.319458In [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]: No1 No2 No3 No4 Quarter 2019-01-31 -1.749765 0.342680 1.153036 -0.252436 Q1 2019-02-28 0.981321 0.514219 0.221180 -1.070043 Q1 2019-03-31 -0.189496 0.255001 -0.458027 0.435163 Q1 2019-04-30 -0.583595 0.816847 0.672721 -0.104411 Q2 2019-05-31 -0.531280 1.029733 -0.438136 -1.118318 Q2 2019-06-30 1.618982 1.541605 -0.251879 -0.842436 Q2 2019-07-31 0.184519 0.937082 0.731000 1.361556 Q3 2019-08-31 -0.326238 0.055676 0.222400 -1.443217 Q3 2019-09-30 -0.756352 0.816454 0.750445 -0.455947 Q3
现在,我们可以根据Quarter列分组,并输出单独组的统计量:
In [66]: groups = df.groupby('Quarter') ❶In [67]: grou() ❷Out[67]: Quarter Q1 3 Q2 3 Q3 3 dtype: int64In [68]: grou() ❸Out[68]: No1 No2 No3 No4Quarter Q1 -0.319314 0.370634 0.305396 -0.295772 Q2 0.168035 1.129395 -0.005765 -0.688388 Q3 -0.299357 0.603071 0.567948 -0.179203In [69]: grou() ❹Out[69]: No1 No2 No3 No4 Quarter Q1 0.981321 0.514219 1.153036 0.435163 Q2 1.618982 1.541605 0.672721 -0.104411 Q3 0.184519 0.937082 0.750445 1.361556In [70]: grou([min, max]).round(2) ❺Out[70]: No1 No2 No3 No4 Min max min max min max min max Quarter Q1 -1.75 0.98 0.26 0.51 -0.46 1.15 -1.07 0.44 Q2 -0.58 1.62 0.82 1.54 -0.44 0.67 -1.12 -0.10 Q3 -0.76 0.18 0.06 0.94 0.22 0.75 -1.44 1.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]: grou()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]: No1 No4 Sum mean sum mean Quarter Odd_Even Q1 Even 0.981321 0.981321 -1.070043 -1.070043 Odd -1.939261 -0.969631 0.182727 0.091364 Q2 Even 1.035387 0.517693 -0.946847 -0.473423 Odd -0.531280 -0.531280 -1.118318 -1.118318 Q3 Even -0.326238 -0.326238 -1.443217 -1.443217 Odd -0.571834 -0.285917 0.905609 0.452805
对pandas和DataFrame对象使用的介绍到此结束,下面将这组工具应用到现实世界的金融数据中。
5.6 复杂选择
数据选择往往是通过列值上的条件公式实现的,也可以通过符合逻辑的方式来组合多个条件。考虑如下数据集。
In [75]: data = np.random.standard_normal((10, 2)) ❶In [76]: df = (data, columns=['x', 'y']) ❷In [77]: df.info() ❷ <class ';> RangeIndex: 10 entries, 0 to 9 Data columns (total 2 columns): X 10 non-null float64 Y 10 non-null float64 dtypes: float64(2) memory usage: 240.0 bytesIn [78]: df.head() ❸Out[78]: 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.733096In [79]: df.tail() ❹Out[79]: x y 5 -0.983310 0.357508 6 -1.613579 1.470714 7 -1.188018 -0.549746 8 -0.940046 -0.827932 9 0.108863 0.507810
❶ 包含标准正态分布随机数的ndarray对象。
❷ 包含相同随机数的DataFrame对象。
❸ 通过head方法取得前5行。
❹ 通过tail方法取得最后5行。
下面的代码说明了Python比较运算符和逻辑运算符在两列值上的应用:
In [80]: df['x'] > 0.5 ❶Out[80]: 0 True 1 False 2 False 3 False 4 True 5 False 6 False 7 False 8 False 9 False Name: x, dtype: boolIn [81]: (df['x'] > 0) & (df['y'] < 0) ❷Out[81]: 0 True 1 False 2 False 3 True 4 True 5 False 6 False 7 False 8 False 9 False dtype: boolIn [82]: (df['x'] > 0) | (df['y'] < 0) ❸Out[82]: 0 True 1 True 2 True 3 True 4 True 5 False 6 False 7 True 8 True 9 True dtype: bool
❶ 检查x列的值是否大于0.5。
❷ 检查是否x列的值为正且y列的值为负。
❸ 检查是否x列的值为正或y列的值为负。
利用得到的布尔型Series对象,就可以很容易地实现复杂数据(行)的选择。另外,也可以使用query方法并以字符串对象的形式来传递条件:
In [83]: df[df['x'] > 0] ❶Out[83]: x y 0 1.189622 -1.690617 3 0.007315 -0.612939 4 1.299748 -1.733096 9 0.108863 0.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]: x y 0 1.189622 -1.690617 3 0.007315 -0.612939 4 1.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[ > 0) | < 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]: x y 0 True False 1 False False 2 False False 3 True False 4 True False 5 False True 6 False True 7 False False 8 False False 9 True TrueIn [89]: df[df > 0] ❷Out[89]: x y 0 1.189622 NaN 1 NaN NaN 2 NaN NaN 3 0.007315 NaN 4 1.299748 NaN 5 NaN 0.357508 6 NaN 1.470714 7 NaN NaN 8 NaN NaN 9 0.108863 0.507810
❶ DataFrame对象中的哪些值为正?
❷ 选择所有满足要求的值,并将其他值设为NaN。
5.7 联接、连接和合并
本节简单介绍连接DataFrame对象形式的两个简单数据集的不同方法。这两个简单数据集如下所示:
In [90]: df1 = (['100', '200', '300', '400'], index=['a', 'b', 'c', 'd'], columns=['A',])In [91]: df1Out[91]: A a 100 b 200 c 300 d 400In [92]: df2 = (['200', '150', '50'], index=['f', 'b', 'd'], columns=['B',])In [93]: df2Out[93]: B f 200 b 150 d 50
5.7.1 联接
联接(Concatenation)或者附加(Appending)本质上指的是将一个DataFrame对象中的行添加到另一个DataFrame对象上,这可通过append方法或者函数完成。需要认真考虑的是索引值的处理方法:
In [94]: d(df2, sort=False) ❶Out[94]: A B a 100 NaN b 200 NaN c 300 NaN d 400 NaN f NaN 200 b NaN 150 d NaN 50In [95]: d(df2, ignore_index=True, sort=False) ❷Out[95]: A B 0 100 NaN 1 200 NaN 2 300 NaN 3 400 NaN 4 NaN 200 5 NaN 150 6 NaN 50In [96]: ((df1, df2), sort=False) ❸Out[96]: A B a 100 NaN b 200 NaN c 300 NaN d 400 NaN f NaN 200 b NaN 150 d NaN 50In [97]: ((df1, df2), ignore_index=True, sort=False) ❹Out[97]: A B 0 100 NaN 1 200 NaN 2 300 NaN 3 400 NaN 4 NaN 200 5 NaN 150 6 NaN 50
❶ 将df2的数据附加到df1中作为新行。
❷ 完成同样的工作,但忽略索引。
❸ 与第一个附加操作效果相同。
❹ 与第二个附加操作效果相同。
5.7.2 连接
连接两个数据集时,DataFrame对象的顺序很重要。只有第一个DataFrame对象的索引值被使用,这种默认行为称为左连接(left join):
In [98]: d(df2) ❶Out[98]: A B a 100 NaN b 200 150 c 300 NaN d 400 50In [99]: d(df1) ❷Out[99]: B A f 200 NaN b 150 200 d 50 400
❶ df1的索引值有意义。
❷ df2的索引值有意义。
共有4种不同的连接方法,每种方法都导致索引值和对应数据行的不同处理行为:
In [100]: d(df2, how='left') ❶Out[100]: A B a 100 NaN b 200 150 c 300 NaN d 400 50In [101]: d(df2, how='right') ❷Out[101]: A B f NaN 200 b 200 150 d 400 50In [102]: d(df2, how='inner') ❸Out[102]: A B b 200 150 d 400 50In [103]: d(df2, how='outer') ❹Out[103]: A B a 100 NaN b 200 150 c 300 NaN d 400 50 f NaN 200
❶ 左连接是默认操作。
❷ 右连接相当于颠倒了DataFrame对象的顺序。
❸ 内连接只保留在两个索引中都存在的索引值。
❹ 外连接保留两个索引中的所有索引值。
连接也可以基于空的DataFrame对象。在这种情况下,列按顺序被创建,这与左连接类似:
In [104]: df = ()In [105]: df['A'] = df1['A'] ❶In [106]: dfOut[106]: A a 100 b 200 c 300 d 400In [107]: df['B'] = df2 ❷In [108]: dfOut[108]: A B a 100 NaN b 200 150 c 300 NaN d 400 50
❶ df1为第一列A。
❷ df2为第二列B。
使用字典来组合数据集可以得到类似于外连接的结果,因为列是同时创建的:
In [109]: df = ({'A': df1['A'], 'B': df2['B']})In [110]: dfOut[110]: A B a 100 NaN b 200 150 c 300 NaN d 400 50 f NaN 200
❶ DataFrame对象的各列用作字典对象的值
5.7.3 合并
连接操作根据待连接的DataFrame对象的索引进行,而合并操作通常在两个数据集共享的某列上进行。为此,在两个原始的DataFrame对象上添加一个新列C:
In [111]: c = ([250, 150, 50], index=['b', 'd', 'c']) df1['C'] = c df2['C'] = cIn [112]: df1Out[112]: A C a 100 NaN b 200 250.0 c 300 50.0 d 400 150.0In [113]: df2Out[113]: B C f 200 NaN b 150 250.0 d 50 150.0
默认情况下,这种合并操作根据单一共享列C进行。但也存在其他可能,例如外合并:
In [114]: (df1, df2) ❶Out[114]: A C B 0 100 NaN 200 1 200 250.0 150 2 400 150.0 50In [115]: (df1, df2, on='C') ❶Out[115]: A C B 0 100 NaN 200 1 200 250.0 150 2 400 150.0 50In [116]: (df1, df2, how='outer')Out[116]: A C B 0 100 NaN 200 1 200 250.0 150 2 300 50.0 NaN 3 400 150.0 50
❶ 默认合并根据C列进行。
❷ 也可以进行外合并,保留所有数据行。
可用的合并操作类型还有很多,下面的代码演示了其中的几种:
In [117]: (df1, df2, left_on='A', right_on='B')Out[117]: A C_x B C_y 0 200 250.0 200 NaNIn [118]: (df1, df2, left_on='A', right_on='B', how='outer')Out[118]: A C_x B C_y 0 100 NaN NaN NaN 1 200 250.0 200 NaN 2 300 50.0 NaN NaN 3 400 150.0 NaN NaN 4 NaN NaN 150 250.0 5 NaN NaN 50 150.0In [119]: (df1, df2, left_index=True, right_index=True)Out[119]: A C_x B C_y b 200 250.0 150 250.0 d 400 150.0 50 150.0In [120]: (df1, df2, on='C', left_index=True)Out[120]: A C B f 100 NaN 200 b 200 250.0 150 d 400 150.0 50In [121]: (df1, df2, on='C', right_index=True)Out[121]: A C B a 100 NaN 200 b 200 250.0 150 d 400 150.0 50In [122]: (df1, df2, on='C', left_index=True, right_index=True)Out[122]: A C B b 200 250.0 150 d 400 150.0 50
5.8 性能特征
本章的许多例子说明,用pandas实现同一个目标往往有多种选择。本节以按元素加总两列数值为例来这些选择进行对比。首先用NumPy生成数据集:
In [123]: data = np.random.standard_normal((1000000, 2)) ❶In [124]: da ❶Out[124]: 16000000In [125]: df = (data, columns=['x', 'y']) ❷In [126]: df.info() ❷ <class ';> RangeIndex: 1000000 entries, 0 to 999999 Data columns (total 2 columns): X 1000000 non-null float64 Y 1000000 non-null float64 dtypes: float64(2) memory usage: 15.3 MB
❶ 包含随机数的ndarray对象。
❷ 包含随机数的DataFrame对象。
其次,实现手边任务的某选择性能还不错:
In [127]: %time res = df['x'] + df['y'] ❶ CPU times: user 7.35 ms, sys: 7.43 ms, total: 14.8 ms Wall time: 7.48 msIn [128]: res[:3]Out[128]: 0 0.387242 1 -0.969343 2 -0.863159 dtype: float64In [129]: %time res = df.sum(axis=1) ❷ CPU times: user 130 ms, sys: 30.6 ms, total: 161 ms Wall time: 101 msIn [130]: res[:3]Out[130]: 0 0.387242 1 -0.969343 2 -0.863159 dtype: float64In [131]: %time res = df.values.sum(axis=1) ❸ CPU times: user 50.3 ms, sys: 2.75 ms, total: 53.1 ms Wall time: 27.9 msIn [132]: res[:3]Out[132]: array([ 0.3872424 , -0.96934273, -0.86315944])In [133]: %time res = np.sum(df, axis=1) ❹ CPU times: user 127 ms, sys: 15.1 ms, total: 142 ms Wall time: 73.7 msIn [134]: res[:3]Out[134]: 0 0.387242 1 -0.969343 2 -0.863159 dtype: float64In [135]: %time res = np.sum, axis=1) ❺ CPU times: user 49.3 ms, sys: 2.36 ms, total: 51.7 ms Wall time: 26.9 msIn [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]:
The application of the eval() method requires the numexpr package to be in [137]: %time res = df.eval('x + y') ❶ CPU times: user 25.5 ms, sys: 17.7 ms, total: 43.2 ms Wall time: 22.5 msIn [138]: res[:3]Out[138]: 0 0.387242 1 -0.969343 2 -0.863159 dtype: float64In [139]: %time res = df.apply(lambda row: row['x'] + row['y'], axis=1) CPU times: user 19.6 s, sys: 83.3 ms, total: 19.7 s Wall time: 19.9 sIn [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期权、衍生品定价和风险管理程序库。
1.《100034错误代码专题之数据!数据!数据!没有黏土我无法造出砖来!pandas数据分析》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《100034错误代码专题之数据!数据!数据!没有黏土我无法造出砖来!pandas数据分析》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/gl/2081487.html