數據!數據!數據!沒有黏土我無法造出磚來!pandas數據分析

數據!數據!數據!沒有黏土我無法造出磚來!——夏洛克•福爾摩斯

本章介紹pandas,這是一個聚焦於表格數據的數據分析程序庫。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對象:

<code>In [1]: import pandas as pd ❶In [2]: df = pd.DataFrame([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/<code>

❶ 導入pandas。

❷ 定義列表對象形式的數據。

❸ 指定列標籤。

❹ 指定索引值/標籤。

❺ 顯示DataFrame對象的數據以及列和索引標籤。

這個簡單的例子已經說明了DataFrame在存儲數據上的主要特性,具體如下。

  • 數據本身可以用不同組成及類型(列表、元組、ndarray和字典對象都是候選者)展現。
  • 數據以列的方式被組織,可以自定義列名(標籤)。
  • 索引可以採用不同的格式(如數值、字符串、時間信息)。

使用這種DataFrame對象總體上相當方便和高效,例如,當您想要進行擴大現有對象等工作時,我推薦你使用DataFrame對象。相比之下,常規的ndarray對象更專門化,也更受限制。與此同時,DataFrame對象往往在計算上和ndarray對象一樣高效。下面是簡單的例子,說明了DataFrame對象的典型操作:

<code>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/<code>

❶ index屬性和Index對象。

❷ columns屬性和Index對象。

❸ 選擇對應於索引c的值。

❹ 選擇對應於索引a和b的兩個值。

❺ 通過索引位置選擇第二行和第三行。

❻ 計算單列總和。

❼ 使用apply方法,以向量化的方式計算平方值。

❽ 和ndarray對象一樣,直接應用向量化。

與NumPy的ndarray對象不同,可以在兩個維度上同時擴增DataFrame對象:

<code>In [12]: df['floats'] = (1.5, 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/<code>

❶ 添加一個新列,該包含以元組形式提供的浮點數對象。

❷ 選擇該列並顯示其數據和索引標籤。

也可以使用整個DataFrame對象來定義一個新列。在這種情況下,索引自動對齊:

<code>In [15]: df['names'] = pd.DataFrame(['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/<code>

❶ 根據一個DataFrame對象創建另一個新列。

附加數據的方法也類似。但是,在下面的例子中,我們會看到在操作過程中必須避免的一個副作用——索引被簡單的編號索引代替:

<code>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(pd.DataFrame({'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(pd.DataFrame({'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/<code>

❶ 通過一個字典對象附加新行;這是一個臨時操作,操作期間索引信息丟失。

❷ 根據帶索引信息的DataFrame對象附加行;原始索引信息保留。

❸ 向DataFrame對象附加不完整的數據行,生成NaN值。

❹ 返回各列的不同dtype,這與結構化ndarray對象類似。

儘管此時有遺漏的值,但大部的分方法調用仍然可以正常工作:

<code>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/<code>

❶ 計算指定兩列的均值(忽略帶NaN值的行)。

❷ 計算指定兩列的標準差(忽略帶NaN值的行)。

5.1.2 使用DataFrame類的第二步

本節的例子基於一個包含標準正態分佈隨機數的ndarray對象。該例探索管理時間序列數據的更多功能,如DatetimeIndex:

<code>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]])/<code>

您可以更直接地構造DataFrame對象(正如前面所見),但是使用ndarray對象通常是一個更好的選擇,因為pandas將保留基本結構,“只”添加元信息(例如索引值)。這也代表了金融應用和科學研究的一種典型用例。例如:

<code>In [29]: df = pd.DataFrame(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/<code>

❶ 由ndarray對象創建一個DataFrame對象。

表5-1列出了DataFrame函數使用的參數。表中,“類似數組”意味著和ndarray對象類似的數據結構——如列表對象。“索引”是pandas的Index類的一個實例。

表5-1 DataFrame函數參數


數據!數據!數據!沒有黏土我無法造出磚來!pandas數據分析

和結構數組一樣,我們已經瞭解,DataFrame對象可以通過指定一個具有合適數量元素的列表來直接定義列名。下面的例子說明,我們可以隨時定義/更改DataFrame對象的屬性:

<code>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/<code>

❶ 通過列表對象指定列標籤。

❷ 選擇一列很容易。

為了高效處理金融事件序列數據,我們還必須很好地處理時間索引。這也可以視為pandas的一個重要優勢。例如,假定分為4列的9個數據項對應於2019年1月開始的月底數據。然後,用date_range生成一個DatetimeIndex對象:

<code>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')/<code>

❶ 創建一個DatetimeIndex對象。

表5-2列出了date_range函數的參數。

表5-2 date_range函數參數

數據!數據!數據!沒有黏土我無法造出磚來!pandas數據分析

下列代碼將剛剛創建的DatetimeIndex對象定義為相關的索引對象,並建立原始數據集的一個時間序列:

<code>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/<code>

至於在date_range函數幫助下生成的DatetimeIndex對象,頻率參數freq有多種選擇。表6-3列出了其所有選項。

表5-3 date_range函數的頻率參數值(略)

在某些情況下,以ndarray對象的形式訪問原始數據是有好處的。values屬性提供直接訪問的方式:

<code>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]])/<code>

數組和DataFrame

通常可以從一個ndarray對象生成DataFrame對象。也可以通過NumPy的np.array函數或DataFrame類的values屬性,來從DataFrame對象生成ndarray對象。

5.2 基本分析

和NumPy的ndarray類一樣,pandas的DataFrame類有多個便利的內建方法。首先考慮info和describe方法:

<code>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/<class>/<code>

❶ 提供關於數據、列和索引的元信息。

❷ 提供有用的每列彙總統計(對於數值數據)信息。

此外,你可以輕鬆地求得按列/行計算的總和、平均值和累計總和:

<code>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/<code>

❶ 列總和。

❷ 列均值。

❸ 行均值。

❹ 列累計總和(從第一個索引位置起)。

DataFrame對象也能理解為NumPy通用函數,這與預期相符:

<code>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/<code>

❶ 列均值。

❷ 每個元素的自然對數;顯示警告信息,但計算繼續進行,得到多個NaN值。

❸ 每個元素絕對值的平方根。

❹ 按列均值。

❺ 數值數據的線性變換。

NumPy通用函數

一般來說,在NumPy通用函數適用於ndarray對象的所有情況下,都可以將這些函數應用到包含相同數據的pandas的DataFrame對象。

pandas有相當強的容錯能力,它可以捕捉錯誤,在對應數學運算失敗時放入NaN值。不僅如此,正如前面已經展示的,在許多情況下,它還可以將這些不完整的數據集當成完整數據集來使用。這種做法很方便,因為現實中不完整數據集往往比人們預想的要多。

5.3 基本可視化

如果數據存儲在DataFrame對象中,那麼數據圖表的繪製通常只需要一行代碼(見圖5-1)。

數據!數據!數據!沒有黏土我無法造出磚來!pandas數據分析

圖5-1 DataFrame對象的折線圖

<code>In [53]: from pylab import plt, mpl ❶         plt.style.use('seaborn') ❶         mpl.rcParams['font.family'] = 'serif' ❶         %matplotlib inlineIn [54]: df.cumsum().plot(lw=2.0, figsize=(10, 6)); ❷/<code>

❶ 定製繪圖樣式。

❷ 繪製4列累計總和的折線圖。

基本上,pandas提供了專為DataFrame對象設計的matplotplib(參見第7章)包裝器。plot()方法的參數如表5-4所示。

表5-4 plot方法參數(略)

再舉個例子,考慮相同數據的柱狀圖(見圖5-2):

數據!數據!數據!沒有黏土我無法造出磚來!pandas數據分析

圖5-2 DataFrame對象的柱狀圖

<code>In [55]: df.plot.bar(figsize=(10, 6), rot=15); ❶# df.plot(kind='bar', figsize=(10, 6)) ❷/<code>

❶ plot.bar()繪製柱狀圖。

❷ 替代語法:使用kind參數改變繪圖類型。

5.4 Series類

迄今為止,本章主要介紹了pandas的DataFrame類。Series是pandas自帶的另一個重要的類。它的特點是隻有一列數據。從這個意義上講,它是DataFrame類的特例,兩者之間有許多共同特性和功能,但不完全相同。從多列的DataFrame對象上選取一列,可以得到Series對象:

<code>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.Series/<code>

DataFrame的主要方法也可用於Series對象,我們以mean和plot方法為例(見圖5-3):

<code>In [63]: s.mean()Out[63]: -0.15021177307319458In [64]: s.plot(lw=2.0, figsize=(10, 6));/<code>
數據!數據!數據!沒有黏土我無法造出磚來!pandas數據分析

圖5-3 Series對象的折線圖

5.5 GroupBy操作

pandas具備強大而靈活的分組功能,工作方式類似於SQL中的分組和Microsoft Excel中的透視表。為了進行分組,我們添加一列,表示對應索引數據所屬的季度:

<code>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/<code>

現在,我們可以根據Quarter列分組,並輸出單獨組的統計量:

<code>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]:               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]: groups.max() ❹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]: groups.aggregate([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/<code>

❶ 根據Quarter列分組。

❷ 給出每個分組的行數。

❸ 給出每的列均值。

❹ 給出每列的最大值。

❺ 給出每列的最小值和最大值。

分組也可以在多列上進行。為此,我們添加另一列,表示索引日期的月份是奇數還是偶數:

<code>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]:                        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/<code>

對pandas和DataFrame對象使用的介紹到此結束,下面將這組工具應用到現實世界的金融數據中。

5.6 複雜選擇

數據選擇往往是通過列值上的條件公式實現的,也可以通過符合邏輯的方式來組合多個條件。考慮如下數據集。

<code>In [75]: data = np.random.standard_normal((10, 2)) ❶In [76]: df = pd.DataFrame(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/<class>/<code>

❶ 包含標準正態分佈隨機數的ndarray對象。

❷ 包含相同隨機數的DataFrame對象。

❸ 通過head方法取得前5行。

❹ 通過tail方法取得最後5行。

下面的代碼說明了Python比較運算符和邏輯運算符在兩列值上的應用:

<code>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/<code>

❶ 檢查x列的值是否大於0.5。

❷ 檢查是否x列的值為正且y列的值為負。

❸ 檢查是否x列的值為正或y列的值為負。

利用得到的布爾型Series對象,就可以很容易地實現複雜數據(行)的選擇。另外,也可以使用query方法並以字符串對象的形式來傳遞條件:

<code>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[(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/<code> 

❶ 所有第x列的值大於0.5的行。

❷ 所有第x列的值為正且y的列值為負的行。

❸ 所有第x列的值為正或y的列值為負的行(各列通過對應屬性訪問)。

比較運算符也可以一次性應用到整個DataFrame對象上:

<code>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/<code>

❶ DataFrame對象中的哪些值為正?

❷ 選擇所有滿足要求的值,並將其他值設為NaN。

5.7 聯接、連接和合並

本節簡單介紹連接DataFrame對象形式的兩個簡單數據集的不同方法。這兩個簡單數據集如下所示:

<code>In [90]: df1 = pd.DataFrame(['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 = pd.DataFrame(['200', '150', '50'],                             index=['f', 'b', 'd'],                             columns=['B',])In [93]: df2Out[93]:       B         f 200         b 150         d 50/<code>

5.7.1 聯接

聯接(Concatenation)或者附加(Appending)本質上指的是將一個DataFrame對象中的行添加到另一個DataFrame對象上,這可通過append方法或者pd.concat函數完成。需要認真考慮的是索引值的處理方法:

<code>In [94]: df1.append(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]: df1.append(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]: pd.concat((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]: pd.concat((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/<code>

❶ 將df2的數據附加到df1中作為新行。

❷ 完成同樣的工作,但忽略索引。

❸ 與第一個附加操作效果相同。

❹ 與第二個附加操作效果相同。

5.7.2 連接

連接兩個數據集時,DataFrame對象的順序很重要。只有第一個DataFrame對象的索引值被使用,這種默認行為稱為左連接(left join):

<code>In [98]: df1.join(df2) ❶Out[98]:      A    B         a  100  NaN         b  200  150         c  300  NaN         d  400   50In [99]: df2.join(df1) ❷Out[99]:      B    A         f  200  NaN         b  150  200         d   50  400/<code>

❶ df1的索引值有意義。

❷ df2的索引值有意義。

共有4種不同的連接方法,每種方法都導致索引值和對應數據行的不同處理行為:

<code>In [100]: df1.join(df2, how='left') ❶Out[100]:      A    B          a  100  NaN          b  200  150          c  300  NaN          d  400   50In [101]: df1.join(df2, how='right') ❷Out[101]:      A    B          f  NaN  200          b  200  150          d  400   50In [102]: df1.join(df2, how='inner') ❸Out[102]:      A    B          b  200  150          d  400   50In [103]: df1.join(df2, how='outer') ❹Out[103]:      A    B          a  100  NaN          b  200  150          c  300  NaN          d  400   50          f  NaN  200/<code>

❶ 左連接是默認操作。

❷ 右連接相當於顛倒了DataFrame對象的順序。

❸ 內連接只保留在兩個索引中都存在的索引值。

❹ 外連接保留兩個索引中的所有索引值。

連接也可以基於空的DataFrame對象。在這種情況下,列按順序被創建,這與左連接類似:

<code>In [104]: df = pd.DataFrame()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/<code>

❶ df1為第一列A。

❷ df2為第二列B。

使用字典來組合數據集可以得到類似於外連接的結果,因為列是同時創建的:

<code>In [109]: df = pd.DataFrame({'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/<code>

❶ DataFrame對象的各列用作字典對象的值

5.7.3 合併

連接操作根據待連接的DataFrame對象的索引進行,而合併操作通常在兩個數據集共享的某列上進行。為此,在兩個原始的DataFrame對象上添加一個新列C:

<code>In [111]: c = pd.Series([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/<code>

默認情況下,這種合併操作根據單一共享列C進行。但也存在其他可能,例如外合併:

<code>In [114]: pd.merge(df1, df2) ❶Out[114]:      A      C    B          0  100    NaN  200          1  200  250.0  150          2  400  150.0   50In [115]: pd.merge(df1, df2, on='C') ❶Out[115]:      A      C    B          0  100    NaN  200          1  200  250.0  150          2  400  150.0   50In [116]: pd.merge(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/<code>

❶ 默認合併根據C列進行。

❷ 也可以進行外合併,保留所有數據行。

可用的合併操作類型還有很多,下面的代碼演示了其中的幾種:

<code>In [117]: pd.merge(df1, df2, left_on='A', right_on='B')Out[117]:      A    C_x    B  C_y          0  200  250.0  200  NaNIn [118]: pd.merge(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]: pd.merge(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]: pd.merge(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]: pd.merge(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]: pd.merge(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/<code>

5.8 性能特徵

本章的許多例子說明,用pandas實現同一個目標往往有多種選擇。本節以按元素加總兩列數值為例來這些選擇進行對比。首先用NumPy生成數據集:

<code>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() ❷          <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/<class>/<code>

❶ 包含隨機數的ndarray對象。

❷ 包含隨機數的DataFrame對象。

其次,實現手邊任務的某選擇性能還不錯:

<code>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(df.values, 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])/<code>

❶ 直接使用列(Series對象)是最快的方法。

❷ 調用DataFrame對象的sum方法來計算總和。

❸ 調用ndarray對象的sum方法來計算總和。

❹ 調用DataFrame對象的np.sum方法來計算總和。

❺ 調用ndarray對象的np.sum方法來計算總和。

最後,還有兩種選擇可以計算元素和,它們分別基於eval和apply方法[1]:

<code>The application of the eval() method requires the numexpr package to   be installed.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/<code>

❶ 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) 著,姚軍 譯

數據!數據!數據!沒有黏土我無法造出磚來!pandas數據分析

  • 金融科技算法交易量化金融教程書籍
  • 詳細講解使用Python分析處理金融大數據的專業圖書
  • 將人工智能應用於金融開發的實戰指南,金融應用開發領域從業人員的常備讀物

Python已成為數據驅動AI、金融優先選擇的編程語言。現在,一些大型的投資銀行和對沖資金均使用Python及其生態系統來構建核心交易與風險管理系統。在本書中,作者向開發人員和量化分析人員介紹了使用Python程序庫與工具,完成金融數據科學、算法交易和計算金融任務的方法。

Python與金融:Python交互式金融分析與程序開發入門。基本知識:學習Python數據類型與結構、NumPy、pandas及其DataFrame類、面向對象編程。金融數據科學:探索用於金融時間序列數據、I/O操作、推斷統計學和機器學習的Python技術與程序庫。算法交易:使用Python來驗證和部署自動算法交易策略。

衍生品分析:開發靈活、強大的Python期權、衍生品定價和風險管理程序庫。


分享到:


相關文章: