1. 程式人生 > >pandas 資料聚合與分組運算

pandas 資料聚合與分組運算

1. GroupBy技術
pandas物件(無論是Series、DataFrame還是其他的)中的資料會根據你所提供的一個或多個鍵被拆分(split)為多組。
拆分操作是在物件的特定軸上執行的。

例如:DataFrame可以在其行(axis=0)或列(axis=1)上進行分組,然後將一個函式應用(apply)到各個分組併產生一個新值。
最後,所有這些函式的執行結果會被合併(combine)到最終的結果物件中。

下圖大致說明了分組聚合的演示:

分組鍵可以有多種形式,且型別不必相同:
(1)列表或陣列,其長度與待分組的軸一樣。
(2)表示DataFrame某個列名的值
(3)字典或Series,給出待分組軸上的值與分組名之間的對應關係
(4)函式,用於處理軸索引或索引中的各個標籤
注意,後三種都只是快捷方式而己,其最終目的仍然是產生一組用於拆分物件的值。

import numpy as np
from pandas import DataFrame,Series

#(1)執行numpy的groupby方法分組
df=DataFrame({'key1':['a','a','b','b','a'],
               'key2':['one','two','one','two','one'],
               'data1':np.random.randn(5),
               'data2':np.random.randn(5)})
print df
#輸出結果如下:
#       data1     data2 key1 key2
# 0  1.605758  1.477281    a  one
# 1  0.726355  0.724383    a  two
# 2  2.007510  1.778955    b  one
# 3  0.543327 -0.057515    b  two
# 4 -0.351209 -0.518018    a  one

#假設想要按kye1進行分組,並計算data1列的平均值。訪問data1,並根據key1呼叫groupby:
grouped=df['data1'].groupby(df['key1']) #data1資料按key1進行分組
print grouped
#輸出結果如下:<pandas.core.groupby.SeriesGroupBy object at 0x1048d8490>
#grouped是一個GroupBy物件,它實際上還沒有進行任何計算,只是含有一些有關分組鍵df['key1']
#的中間資料而己。換句話說,該物件已經有了接下來對各分組執行運算所需的一切資訊。
#我們可以呼叫GroupBy的mean方法來計算分組平均值
print grouped.mean()
#輸出結果如下:
# key1
# a   -0.434085
# b   -0.375503
# Name: data1, dtype: float64
#注意:資料(Series)根據分組鍵進行了聚合,產生了一個新的Series,其索引為key1列中的唯一值。
#之所以結果中索引的名稱為key1,是因為原始DataFrame的列df['key1']就叫這個名字


#若我們一次傳入多個數組,就會得到不同的結果
means=df['data1'].groupby([df['key1'],df['key2']]).mean()
print means
#輸出結果如下:
# key1  key2
# a     one    -0.365097
#       two    -0.381603
# b     one    -1.403614
#       two    -2.742555
# Name: data1, dtype: float64
#注意:通過兩個鍵對資料進行了分組,得到的Series具有一個層次化索引(由唯一的鍵對組成)

print means.unstack()  #unstack和stack行列的轉化
#輸出結果如下:
# key2       one       two
# key1
# a    -0.350190  0.480907
# b    -0.857794  0.949170

#在上面這些示例中,分組鍵均為Series.實際上,分組鍵可以是任何長度適當的陣列:
states=np.array(['Ohio','California','California','Ohio','Ohio'])
years=np.array([2005,2005,2006,2005,2006])
print df['data1'].groupby([states,years]).mean()
#輸出結果如下:
# California  2005    0.449663
#             2006    0.425458
# Ohio        2005    0.537592
#             2006   -0.279219
# Name: data1, dtype: float64

#還可以將列名(可以是字串、數字或其他Python物件)用作分組鍵:
print df.groupby('key1').mean()
#輸出結果如下:
#          data1     data2
# key1
# a     0.046327  0.561451
# b    -0.085989  0.215877
print df.groupby(['key1','key2']).mean()
#輸出結果如下:
# key1 key2
# a    one  -0.540068 -1.080045
#      two  -0.905713 -0.996828
# b    one   2.111136  0.546535
#      two   0.648188 -0.498834

#GroupBy的size方法,它可以返回一個含有分組大小的Series:
print df.groupby(['key1','key2']).size()
#輸出結果如下:
# key1  key2
# a     one     2  表示a one的有2個數
#       two     1
# b     one     1
#       two     1

對分組進行迭代:

import numpy as np
from pandas import DataFrame,Series

(1)執行numpy的groupby方法分組
df=DataFrame({'key1':['a','a','b','b','a'],
               'key2':['one','two','one','two','one'],
               'data1':np.random.randn(5),
               'data2':np.random.randn(5)})
print df
#輸出結果如下:
#       data1     data2 key1 key2
# 0  1.605758  1.477281    a  one
# 1  0.726355  0.724383    a  two
# 2  2.007510  1.778955    b  one
# 3  0.543327 -0.057515    b  two
# 4 -0.351209 -0.518018    a  one

(2)GroupBy物件支援迭代,可以產生一組二元元組(由分組名和資料塊組成)
for name,group in df.groupby('key1'):
    print name
    print group
#輸出結果如下:
#name:
# a
#       data1     data2 key1 key2
# 0  1.891706 -0.492166    a  one
# 1  1.809804 -0.292517    a  two
# 4 -1.116779  1.870921    a  one
#group:
# b
#       data1     data2 key1 key2
# 2  0.737782 -0.521142    b  one
# 3 -0.854384  1.356374    b  two

(3)對於多重鍵情況,元組的第一個元素將會是由鍵值組成的元組:
 for (k1,k2),group in df.groupby(['key1','key2']):
     print k1,k2
     print group
#輸出結果如下:
#k1,k2
# a one
#       data1     data2 key1 key2
# 0 -0.767050 -2.596870    a  one
# 4  0.641077  0.480521    a  one
# a two
#       data1     data2 key1 key2
# 1  2.083827 -0.680062    a  two
#group
# b one
#       data1     data2 key1 key2
# 2 -0.070502 -0.213549    b  one
# b two
#       data1     data2 key1 key2
# 3 -0.614806 -0.016378    b  two


(4)當然,你可以對這些資料片段做任何操作。有一個有用的運算:將這些資料片段做成一個字典:dict(list(df.groupby('')))
pieces=dict(list(df.groupby('key1')))
print pieces
#輸出結果如下:
# {'a':       data1     data2 key1 key2
# 0 -2.860537 -0.139119    a  one
# 1  1.832103 -0.073315    a  two
# 4 -0.524176  0.052688    a  one,
#  'b':       data1     data2 key1 key2
# 2 -0.213096  1.076033    b  one
# 3  0.654225 -0.786246    b  two}
# print pieces['b']
#輸出結果如下:
#       data1     data2 key1 key2
# 2 -0.213096  1.076033    b  one
# 3  0.654225 -0.786246    b  two

(5)groupby預設是在axis=0(行)上進行分組,通過設定也可以在其他任何軸上進行分組:axis=1(列)
#拿上面的例子中的df來說,我們可以根據dtype對列進行分組:
print df.dtypes
#輸出結果如下:
# data1    float64
# data2    float64
# key1      object
# key2      object
# dtype: object
grouped=df.groupby(df.dtypes,axis=1)
print dict(list(grouped)) #將其轉化為字典
#輸出結果如下:
# {dtype('O'):   key1 key2
# 0    a  one
# 1    a  two
# 2    b  one
# 3    b  two
# 4    a  one,
#  dtype('float64'):       data1     data2
# 0 -1.586465 -0.380714
# 1 -1.786134 -0.663670
# 2  0.320359  0.624725
# 3 -0.620432  0.924842
# 4 -0.663463  1.023058}

選取一個或一組列:

對於由DataFrame產生的GroupBy物件,如果一個(單個字串)或一組(字串陣列)列名對其進行索引,
就能實選取部分列進行整合的目的。也就是說:

print df.groupby('key1')['data1']
是下面程式碼的語法糖:
print df['data1'].groupby(df['key1'])

尤其對於大資料集,很可能只需要對部分列進行聚合。

import numpy as np
from pandas import DataFrame,Series

# (1)執行numpy的groupby方法分組
df=DataFrame({'key1':['a','a','b','b','a'],
               'key2':['one','two','one','two','one'],
               'data1':np.random.randn(5),
               'data2':np.random.randn(5)})
# print df
#輸出結果如下:
#       data1     data2 key1 key2
# 0  1.605758  1.477281    a  one
# 1  0.726355  0.724383    a  two
# 2  2.007510  1.778955    b  one
# 3  0.543327 -0.057515    b  two
# 4 -0.351209 -0.518018    a  one

#例如,在前面那個資料集中,如果只需計算data2列的平均值並以DataFrame形式得到結果,我們可以編寫:
print df.groupby(['key1','key2'])[['data2']].mean()
#輸出結果如下:
#               data2
# key1 key2
# a    one  -1.329059
#      two  -0.486441
# b    one   0.400199
#      two  -1.215200
#這種索引操作所返回的物件是一個已分組的DataFrame
s_grouped=df.groupby(['key1','key2'])['data2']
print s_grouped
#輸出結果如下:<pandas.core.groupby.SeriesGroupBy object at 0x103fa0590> 它是一個groupby物件
print s_grouped.mean()

通過字典或Series進行分組:

import numpy as np
from pandas import DataFrame,Series

#(1)除陣列以外,分組資訊還可以其他形式存在。
# people=DataFrame(np.random.randn(5,5),
#                  columns=['a','b','c','d','e'],
#                  index=['Joe','Steve','Wes','Jim','Travis'])
# # print people
people=DataFrame([[1,0,1,2,1],
                  [0,2,3,1,0],
                  [1,2,3,0,1],
                  [-1,0,-1,2,0],
                  [2,3,1,-2,0]],
                 columns=['a','b','c','d','e'],
                 index=['Joe','Steve','Wes','Jim','Travis'])
people.ix[2:3,['b','c']]=np.nan  #ix[2:3]行索引為[2:3],索引切片不包括3,'b','c'列的值為Nan
print people
#輸出結果如下:
#         a    b    c  d  e
# Joe     1  0.0  1.0  2  1
# Steve   0  2.0  3.0  1  0
# Wes     1  NaN  NaN  0  1
# Jim    -1  0.0 -1.0  2  0
# Travis  2  3.0  1.0 -2  0

#(2)假設己知列的分組關係,並希望根據分組計算列的總計:
mapping={'a':'red','b':'red','c':'blue',
         'd':'blue','e':'red','f':'orange'}
#現在只需將這個字典傳給groupby即可:
by_column=people.groupby(mapping,axis=1)
print by_column.sum()
#輸出結果如下:'c','d'相加存入blue中,其它的相加放入red中
#         blue  red
# Joe      3.0  2.0
# Steve    4.0  2.0
# Wes      0.0  2.0
# Jim      1.0 -1.0
# Travis  -1.0  5.0

#(3) Series也有同樣的功能,它可以被看做一個固定大小的對映。
#對於上面的那個例子,如果用Series作為分組鍵,則pandas會檢查Series以確保其索引跟分組軸是對齊的:
map_series=Series(mapping)
print map_series
#輸出結果如下:
# a       red
# b       red
# c      blue
# d      blue
# e       red
# f    orange
print people.groupby(map_series,axis=1).count()
#輸出結果如下:
#         blue  red
# Joe        2    3
# Steve      2    3
# Wes        1    2
# Jim        2    3
# Travis     2    3

通過函式進行分組:

import numpy as np
from pandas import DataFrame,Series

#前面一小節的示例DataFrame為例,其索引值為人的名字。假設你希望根據人名的長度進行分組,
#雖然可以求取一個字串長度陣列,但其實僅僅傳入len函式就可以了:
people=DataFrame([[1,0,1,2,1],
                  [0,2,3,1,0],
                  [1,2,3,0,1],
                  [-1,0,-1,2,0],
                  [2,3,1,-2,0]],
                 columns=['a','b','c','d','e'],
                 index=['Joe','Steve','Wes','Jim','Travis'])
print people
#輸出結果如下:
#         a  b  c  d  e
# Joe     1  0  1  2  1
# Steve   0  2  3  1  0
# Wes     1  2  3  0  1
# Jim    -1  0 -1  2  0
# Travis  2  3  1 -2  0
print people.groupby(len).sum()
#輸出結果如下:
#    a  b  c  d  e
# 3  1  2  3  4  2   #3是Joe,wes,Jim 在a,b,c,d,e列相加
# 5  0  2  3  1  0   #5只有steve一個人,故steve的資料抄下來即可
# 6  2  3  1 -2  0

#將函式跟陣列、列表、字典、Series混合使用,因為任何東西最終都會被轉換為陣列:
key_list=['one','one','one','two','two']
print people.groupby([len,key_list]).min()
#輸出結果如下:
#        a  b  c  d  e
# 3 one  1  0  1  0  1
#   two -1  0 -1  2  0
# 5 one  0  2  3  1  0
# 6 two  2  3  1 -2  0

根據索引級別分組:

層次化索引資料集就在於它能夠根據索引級別進行聚合。要實現該目的,通過Level關鍵字傳入級別編號或名稱即可:

import numpy as np
from pandas import DataFrame,Series
import pandas as pd

columns=pd.MultiIndex.from_arrays([['US','US','US','JP','JP'],
                                   [1,3,5,1,3]],names=['cty','tenor'])
hier_df=DataFrame(np.random.randn(4,5),columns=columns)
print hier_df
#輸出結果如下:
# cty          US                            JP
# tenor         1         3         5         1         3
# 0      1.189705  2.334732  0.602338  0.650734  1.933684
# 1     -0.011591  1.247894 -1.701154 -0.052255  0.835968
# 2     -0.936263 -0.313018  1.577031  0.108007  0.116402
# 3     -0.032721  0.688506  0.001428 -0.550061  0.525248

print hier_df.groupby(level='cty',axis=1).count()  #axis=1列分組,count是取總計的個數
#輸出結果如下:
# cty  JP  US
# 0     2   3   #US的0的有3個
# 1     2   3
# 2     2   3
# 3     2   3

2.資料聚合
對於聚合,指的是任何能夠從陣列產生標量值的資料轉換過程。例如:mean,count,min以及sum等。
許多常見的聚合運算都有就地計算資料集統計資訊的優化實現,然而,並不是只能使用這些方法。
可以自己定義聚合運算,還可以呼叫分組物件上已經定義好的任何方法。例如,quantile可以計算
Series或DataFrame列的樣本分位數。
 

import numpy as np
from pandas import DataFrame,Series
import pandas as pd

df=DataFrame({'key1':['a','a','b','b','a'],
               'key2':['one','two','one','two','one'],
               'data1':np.random.randn(5),
               'data2':np.random.randn(5)})
# print df
#輸出結果如下:
#       data1     data2 key1 key2
# 0 -0.065696  0.404059    a  one
# 1 -1.202053 -0.303539    a  two
# 2  1.963036  0.989266    b  one
# 3  0.461733 -0.061501    b  two
# 4  1.198864  2.111709    a  one
grouped=df.groupby('key1')
print grouped  #返回groupby物件
print grouped['data1'].quantile(0.9) #算出按key1分組後的data1的分位數(0.9)的值 
#輸出結果如下:
# key1
# a    0.143167
# b    1.407836
#注意:雖然quantile並沒有明確地實現於GroupBy,但它是一個Series方法,所以這裡是能用的。
#實際上,GroupBy會高效地對Series進行切片,然後對各片呼叫piece.quantile(0.9),最後將
#這些結果組裝成最終結果。

(1)作用自定義的聚合函式:只需將其傳入aggregate或agg方法即可

import numpy as np
from pandas import DataFrame,Series
import pandas as pd

df=DataFrame({'key1':['a','a','b','b','a'],
               'key2':['one','two','one','two','one'],
               'data1':np.random.randn(5),
               'data2':np.random.randn(5)})
# print df
#輸出結果如下:
#       data1     data2 key1 key2
# 0 -0.065696  0.404059    a  one
# 1 -1.202053 -0.303539    a  two
# 2  1.963036  0.989266    b  one
# 3  0.461733 -0.061501    b  two
# 4  1.198864  2.111709    a  one
grouped=df.groupby('key1')
print grouped  #返回groupby物件

def peak_to_peak(arr):
    return arr.max()-arr.min()
print grouped.agg(peak_to_peak)
#輸出結果如下:
#         data1     data2
# key1
# a     1.672300  1.298854
# b     0.315454  2.404649
#注意:自定義函式要比那些經過優化的函式慢得多,這是因為在構造中間分組資料塊時存在非常大的開銷(函式呼叫/資料重排等)

#有些方法(如describe)也是可以用在這裡的。即使嚴格來講,它並非聚合運算:
print grouped.describe()

經過優化的groupby的方法:
函式名               說明
count                分組中非NA值的數量
sum                  非NA值的和
mean                非NA值的平均值
median             非NA值的算術中位數
std、var            無偏(分母為n-1)標準差和方差
min、max          非NA值的最小值、最大值
prod                   非NA值的積
first、last           第一個和最後一個非NA值

import numpy as np
from pandas import DataFrame,Series
import pandas as pd

#以餐館小費的資料集為例
#(1)通過read_csv載入之後,添加了一個表示小費比例的列tip_pct
tips=pd.read_csv('ch08/tips.csv')
tips['tip_pct']=tips['tip']/tips['total_bill']
print tips[:6]

面向列的多函式應用:

我們已經看到,對Series或DataFrame列的聚合運算其實就是使用aggregate(自定義函式)或呼叫mean,std之類的方法。
你可能希望對不同的列使用不同的聚合函式,或一次應用多個函式。
 

import numpy as np
from pandas import DataFrame,Series
import pandas as pd

#根據sex和smoker對tips進行分組:
grouped=tips.groupby(['sex','smoker'])
#可以將函式名以字串的形式傳入:
grouped_pct=grouped['tip_pct']
print grouped_pct.agg('mean') 
#如果傳入一組函式或函式名,得到的DataFrame的列就會以相應的函式命名
print grouped_pct.agg(['mean','std',peak_to_peak])  #mean,std是優化後的聚合函式,peak_to_peak是前面自定義的函式

import numpy as np
from pandas import DataFrame,Series
import pandas as pd

#(1)如果傳入的是一個由(name,function)元組組成的列表,則各元組的第一個元素就會被用作DataFrame的列名
print grouped_pct.agg([('foo','mean'),('bar',np.std)])
#輸出結果如下:
#                   foo          bar
# sex     smoker
# Female   False     0.15        0.03
#          True      0.18        0.07
# Male     False     0.16        0.04
#          True      0.15        0.09

#(2)對於DataFrame,還可以定義一組應用於全部列的函式,或不同的列應用不同的函式。
#我們想要對tip_pct和total_bill列計算三個統計資訊
functions=['count','mean','max']
result=grouped['tip_pct','total_bill'].agg(functions)
print result
#輸出結果如下:
#                     tip_pct                     total_bill
#                     count   mean    max         count     mean      max
# sex      smoker
# Female   False      54      0.15    0.25        54        18.1      35.8
#          True       33      0.18    0.41        33        17.9      44.3
# Male     False      97      0.16    0.29        97        19.7      48.3
#          True       60      0.15    0.71        60        22.2      50.8
#如上所見,結果DataFrame擁有層次化的列,這相當於分別對各列進行聚合,然後用concat將結果組裝到一起(列名用作keys引數)。
print result['tip_pct'] #取出tip_pct的資料

#(3)跟前面一樣,這裡也可以傳入帶有自定義名稱的元組列表:
ftuples=[('Durchschnitt','mean'),('Abweichung',np.var)] #Durchschnitt自定義名,mean該列要用到的方法
print grouped['tip_pct','total_bill'].agg(ftuples)
#輸出結果如下:
#                     tip_pct                       total_bill
# #                  Durchschnitt    Abweichung    Durchschnitt    Abweichung
# # sex     smoker
# # Female   False     0.15           0.03          18.1              53.0
# #          True      0.18           0.07          17.9              84.4
# # Male     False     0.16           0.04          19.7              76.1
# #          True      0.15           0.09          22                98

#(4)假設你想要對不同的列應用不同的函式。具體的辦法是向agg傳入一個從列名對映到函式的字典:
grouped.agg({'tip':np.max,'size':'sum'}) #tip是列名,np.max是方法
#輸出結果如下:
#                   size          tip
# sex     smoker
# Female   False     140        5.2
#          True      74        0.07
# Male     False     263        0.04
#          True      150        0.09
grouped.agg({'tip_pct':['min','max','mean','std'],
             'size':'sum'}) #tip_pct有min,max,mean,std方法,size有sum方法
#輸出結果如下:
#                     tip_pct                     size
#                     min   mean    max         std     sum
# sex      smoker
# Female   False      0.05     0.15    0.25     0.03   140
#          True       0.06      0.18    0.41    0.07    74
# Male     False      0.07     0.16    0.29     0.04    263
#          True       0.03      0.15    0.71    0.08    150
#只有將多個函式應用到至少一列時,DataFrame才會擁有層次化的列

以“無索引”的形式返回聚合資料:

到目前為止,所有示例中的聚合資料都有由唯一的分組鍵組成的索引(可能還是層次化的)。由於並不總是需要如此,
所以你可以向groupby傳入as_index=False以禁用該功能:

print tips.groupby(['sex','smoker'],as_index=False).mean()
#輸出結果如下:                    
#                    
# sex      smoker   total_bill  tip     size    tip_pct
# Female   False      18.1     2.77    2.58    0.15   
#          True       17.1     2.93    2.41    0.18   
# Male     False      19.07    3.16    2.29    0.16    
#          True       22.03    3.15    2.51    0.15    
#當然,對結果呼叫reset_index也能得到這種形式的結果。

3.分組級運算和轉換

聚合只不過是分組運算的其中一種而己。它是資料轉換的一個特例。下面將介紹transform和apply方法,它們能
夠執行更多其他的分組運算。

(1)transform:會將一個函式應用到各個分組,然後將結果放置到各個分組的適當的位置上。

假設我們想要為一個DataFrame新增一個用於存放各索引分組平均值的例。一個辦法是先聚合再合併:
df=DataFrame({'key1':['a','a','b','b','a'],
               'key2':['one','two','one','two','one'],
               'data1':np.random.randn(5),
               'data2':np.random.randn(5)})
print df
#輸出結果如下:
#       data1     data2 key1 key2
# 0  0.999322  0.005281    a  one
# 1  1.057139 -0.322877    a  two
# 2  1.130281 -0.272296    b  one
# 3  2.328475 -0.497984    b  two
# 4 -0.364299 -0.701872    a  one

#(1)按key1分組後取mean計算。因為k1可分為a,b兩組,a,b兩組中各有data1,data2
k1_means=df.groupby('key1').mean().add_prefix('mean_') #因為按key1分組,新增mean_
print k1_means
#輸出結果如下:
#       mean_data1  mean_data2
# key1
# a       1.685305    0.720963
# b      -0.671712   -0.444973

#(2)聚合
print pd.merge(df,k1_means,left_on='key1',right_index=True) #df,k1_means左連線
#輸出結果如下:
#       data1     data2 key1 key2  mean_data1  mean_data2
# 0  1.300802  1.159461    a  one    0.851929    0.649493
# 1  1.542113  0.605346    a  two    0.851929    0.649493
# 4 -0.287129  0.183673    a  one    0.851929    0.649493
# 2 -0.923089 -0.248955    b  one   -0.507969   -0.464783
# 3 -0.092848 -0.680611    b  two   -0.507969   -0.464783
#注意:雖然這樣也行,但是不太靈活,你可以將該過程看做利用np.mean函式對兩個資料列進行轉換。

#(3)這次我們在GroupBy上使用transform方法。
# 不難看出,transform會將一個函式應用到各個分組,然後將結果放置到適當的位置上。
# 如果各分組產生的是一個標量值,則該值就會被廣播出去。
key=['one','two','one','two','one']
print df.groupby(key).mean()
#輸出結果如下:
#         data1     data2
# one -0.860574 -0.130721
# two -0.563933  0.581333
print df.groupby(key).transform(np.mean)
#輸出結果如下:
#       data1     data2
# 0  0.174402 -0.123673
# 1  0.704685  0.599223
# 2  0.174402 -0.123673
# 3  0.704685  0.599223
# 4  0.174402 -0.123673

def demean(arr):
    return arr-arr.mean()
demeaned=df.groupby(key).transform(demean)
print demeaned
#輸出結果如下:
#       data1     data2
# 0 -0.600930 -0.755259
# 1 -0.960131  0.548909
# 2  0.067299  0.460494
# 3  0.960131 -0.548909
# 4  0.533631  0.294765
#檢查一下demeaned現在的分組平均值是否是0:
print demeaned.groupby(key).mean()
#輸出結果如下:
#             data1         data2
# one -3.700743e-17 -1.850372e-17
# two  0.000000e+00 -1.387779e-17

(2)apply:一般性的“拆分-應用-合併”

跟aggregate一樣,transform也是一個有著嚴格條件的特殊函式:傳入的函式只能產生兩種結果,要麼產生
一個可以廣播的標量值(np.mean),要麼產生一個相同大小的結果陣列。apply會將待處理的物件拆分成多個片段,
然後對各片段呼叫傳入的函式,最後將各片段組合到一起。

import numpy as np
from pandas import DataFrame,Series
import pandas as pd

#原先那個小費資料集,假設你想要根據分組選出最高的5個tip_pct值。
#(1)編寫一個選取指定列具有最大值的行的函式
def top(df,n=5,column='tip_pct'):
    #在批定列找出最大值,然後把這個值所在的行選取出來
    return df.sort_index(by=column)[-n:] #按列排序,取[-n:]倒數第n個取出後面n個數
print top(tips,n=6)
#輸出結果如下:
#     total_bill   tip     sex     smoker    day     time    size    tip_pct
# 109     14       4      Female   True      Sat    Dinner    2      0.27
# 183     23      6.5     Male     True      Sun    Dinner    4      0.28
# 232     11      3.3     Male     False     Sat    Dinner    2      0.29
# 67      3       1       Female   True      Sat    Dinner    1      0.32
# 178     9.6     4       Female   True      Sun    Dinner    2      0.41
# 172     7.25    5.15    Male     True      Sun    Dinner    2      0.71

(2)現在,如果對smoker分組並用該函式呼叫apply,就會得到:
tips.groupby('smoker').apply(top)
#返回結果如下:
#        total_bill  tip    sex     smoker    day    time    size    tip_pct
# smoker
# No  88   24.71     5.85   Male    False     Thur    Lunch   2   0.23
#     185  20.69     5      Male    False     Sun     Dinner  5   0.24
#     185  20.69     5      Male    False     Sun     Dinner  5   0.24
#     185  20.69     5      Male    False     Sun     Dinner  5   0.24
# Yes 109  14.3      4     FeMale   True      Sat     Dinner  4   0.28
#     109  14.3      4     FeMale   True      Sat     Dinner  4   0.28
#     109  14.3      4     FeMale   True      Sat     Dinner  4   0.28
#     109  14.3      4     FeMale   True      Sat     Dinner  4   0.28

#(3)top函式在DataFrame的各個片段上呼叫,然後結果由pandas.concat組裝到一起,並以分組名稱進行了標記。
#於是,最終結果就有了一個層次化索引,其內層索引值來自DataFrame.
print tips.groupby(['smoker','day']).apply(top,n=1,column='total_bill')
#輸出結果如下:
#             total_bill  tip    sex   smoker    day    time    size    tip_pct
# smoker day
#   No  Fri   24.71     5.85   Male    False     Thur    Lunch   2   0.23
#       Sat  20.69     5      Male    False     Sun     Dinner  5   0.24
#  Yes  Fri  14.3      4     FeMale   True      Sat     Dinner  4   0.28
#       Sat  14.3      4     FeMale   True      Sat     Dinner  4   0.28
#       Sun  14.3      4     FeMale   True      Sat     Dinner  4   0.28
result=tips.groupby('smoker')['tip_pct'].describe()
print result
#輸出結果如下:
 # smoker
 # No  count   151
 #       mean  0.159
 #        std
 #        min
 #        25%
 #        50%
 #        75%
 #        max
 #  Yes  count
 #       mean
 #       std
 #        min
 #        25%
 #        50%
 #        75%
 #        max
 print result.unstack('smoker') #行,列互換
 #輸出結果如下:
#  smoker     No      Yes
# count     151      93
# mean      0.15     0.16
# std       0.03     0.08
# min       0.05     0.03
# 25%       0.13     0.10
# 50%       0.15     0.15
# 75%       0.18     0.18
# max       0.28     0.71

#(4)GroupBy中,當你呼叫諸如describe之類的方法時,實際上只應用了下面兩條程式碼的快捷方式而己:
f=lambda x:x.describe()
print grouped.apply(f)

禁止分組鍵:group_keys=False

分組鍵會跟原始物件的索引共同構成結果物件中的層次化索引。將group_keys=False傳入groupby即可禁止該效果:
#分組鍵會跟原始物件的索引共同構成結果物件中的層次化索引。將group_keys=False傳入groupby即可禁止該效果:
def top(df,n=5,column='tip_pct'):
    return df.sort_index(by=column)[-n:]
print tip.groupby('smoker',group_keys=False).apply(top)
#輸出結果如下:
#     total_bill   tip     sex     smoker    day     time    size    tip_pct
# 109     14       4      Female   True      Sat    Dinner    2      0.27
# 183     23      6.5     Male     True      Sun    Dinner    4      0.28
# 232     11      3.3     Male     False     Sat    Dinner    2      0.29
# 67      3       1       Female   True      Sat    Dinner    1      0.32
# 178     9.6     4       Female   True      Sun    Dinner    2      0.41
# 172     7.25    5.15    Male     True      Sun    Dinner    2      0.71

分位數和桶分析:

pandas有一些能根據指定面元或樣本分位數將資料拆分成多塊的工具(比如cut和qcut).將這些函式跟groupby結合起來,
就能非常輕鬆地實現對資料集的桶(bucket)或分位數(quantile)分析了。

下面的例子是以隨機資料集為例,我們利用cut將其裝入長度相等的桶中:每個等矩離區間就是一個桶

import numpy as np
from pandas import DataFrame,Series
import pandas as pd

#(1)cut:bins為1表示整數--將X劃分為多個個等間矩的區間
frame=DataFrame({'data1':np.random.randn(1000),
                 'data2':np.random.randn(1000)})
factor=pd.cut(frame.data1,4) #將frame.data1劃分為等間矩的區間,4是precision=4精確度
print factor[:10]
#輸出結果如下:
# 0      (1.465, 3.234]
# 1    (-2.074, -0.304]
# 2     (-0.304, 1.465]
# 3    (-2.074, -0.304]
# 4    (-2.074, -0.304]
# 5    (-2.074, -0.304]
# 6    (-2.074, -0.304]
# 7     (-0.304, 1.465]
# 8     (-0.304, 1.465]
# 9    (-2.074, -0.304]
# Name: data1, dtype: category
# Categories (4, interval[float64]): [(-3.85, -2.074] < (-2.074, -0.304] < (-0.304, 1.465] <
#                                     (1.465, 3.234]]

#(2)由於cut返回的Factor物件可直接用於groupby.
#因此,我們可以像下面這樣對data2做一些統計計算:
def get_stats(group):
    return {'min':group.min(),'max':group.max(),
            'count':group.count(),'mean':group.mean()}
#data2按factor(等矩離區間)分組,factor的等矩離區間:但是每次執行一次就是隨機資料,所以每次執行會不一樣。
# [(-3.85, -2.074] < (-2.074, -0.304] < (-0.304, 1.465] <(1.465, 3.234]]
grouped=frame.data2.groupby(factor)
#分組後用apply呼叫方法,unstack再將行列轉換
print grouped.apply(get_stats).unstack()
#輸出結果如下:
#                   count       max      mean       min
# data1
# (-3.85, -2.074]    24.0  1.771732 -0.037332 -1.769997
# (-2.074, -0.304]  356.0  3.781041  0.060009 -3.552757
# (-0.304, 1.465]   549.0  3.146836 -0.057476 -3.123641
# (1.465, 3.234]     71.0  2.765544 -0.056112 -1.968819

#(3)上面的都是長度相等的桶。要根據樣本分位數得到大小相等的桶,使用qcut.傳入labels=False即可只獲取分位數的編號。
#返回分位數編號
grouping=pd.qcut(frame.data1,10,labels=False) #qcut與cut區別是qcuat是根據這些值的頻率來選擇的,而cut是根據值來選擇的
# print grouping
grouped=frame.data2.groupby(grouping)
print grouped.apply(get_stats).unstack()
#輸出結果如下:
#        count       max      mean       min
# data1
# 0      100.0  2.835962  0.031973 -1.992897
# 1      100.0  3.049567  0.071329 -2.239653
# 2      100.0  3.032595  0.123676 -2.090986
# 3      100.0  1.662204 -0.103337 -3.066419
# 4      100.0  2.660674  0.032828 -2.237807
# 5      100.0  2.834181  0.201375 -1.970967
# 6      100.0  2.444546 -0.129342 -2.436978
# 7      100.0  2.260455  0.066945 -1.728250
# 8      100.0  2.339373  0.035168 -2.050225
# 9      100.0  2.102498  0.012707 -3.043281

“長度相等的桶”指的是“區間大小相等”,“大小相等的桶”指的是“資料點數量相等”

 

示例:用特定於分組的值填充缺失值

對於缺失資料的清理工作,有時你會用dropna將其濾除,而有時則可能會希望用一個固定值或由資料集本身所衍生出來的
值去填充NA值。這時就得使用fillna這個工具了。在下面的例子中,用平均值去填充NA值。

import numpy as np
from pandas import DataFrame,Series
import pandas as pd

s=Series(np.random.randn(6))
s[::2]=np.nan  #s[::2]從頭到尾,步長為2
print s
#輸出結果如下:
# 0         NaN
# 1   -2.199721
# 2         NaN
# 3   -0.286466
# 4         NaN
# 5   -0.660635
# dtype: float64
print s.fillna(s.mean())
#輸出結果如下:
# 0   -0.570229
# 1   -0.423539
# 2   -0.570229
# 3    0.055381
# 4   -0.570229
# 5   -1.342529
# dtype: float64

#假設需要對不同的分組填充不同的值。只需將資料分組,並使用apply和一個能夠對各資料塊呼叫fillna的函式即可。
#下面是一些有關美國幾個州的示例資料,這些州又被分為東部和西部:
states=['Ohio','New York','Vermont','Florida',
        'Oregon','Nevada','California','Idaho']
group_key=['East']*4+['West']*4
print group_key
#輸出結果:
# ['East', 'East', 'East', 'East', 'West', 'West', 'West', 'West']
data=Series(np.random.randn(8),index=states)
data[['Vermont','Nevada','Idaho']]=np.nan
print data
#輸出結果如下:
# Ohio          0.640267
# New York     -0.313729
# Vermont            NaN
# Florida       2.214834
# Oregon        1.363352
# Nevada             NaN
# California    0.265563
# Idaho              NaN
# dtype: float64
print data.groupby(group_key).mean()
#輸出結果如下:
# Ohio         -0.657227
# New York      0.502025
# Vermont            NaN
# Florida       0.045405
# Oregon        0.960563
# Nevada             NaN
# California   -0.366368
# Idaho              NaN
# dtype: float64
# East   -0.036599
# West    0.297098
# dtype: float64

#可以用分組平均值去填充NA值,如下所示:
fill_mean=lambda g:g.fillna(g.mean()) #每個空值用g.fillna(g.mean)來填充
print data.groupby(group_key).apply(fill_mean) #按group_key分組,分完組呼叫fill_mean的lambda方法
#即用mean填充Nan值
# Ohio          0.980489
# New York      1.151041
# Vermont       0.786433
# Florida       0.227769
# Oregon       -1.550078
# Nevada       -1.025264
# California   -0.500450
# Idaho        -1.025264
# dtype: float64

#此外,也可以在程式碼中預定義各組的填充值。由於分組具有一個name屬性,所以我們可以拿來用一下。
fill_values={'East':0.5,'West':-1}
fill_func=lambda g:g.fillna(fill_values[g.name]) #每個空值用fill_values的name來填充
print data.groupby(group_key).apply(fill_func) #group_key分組,分完後呼叫函式fill_func來填充
#輸出結果如下:
# Ohio          0.095284
# New York     -0.232429
# Vermont       0.500000
# Florida       0.039151
# Oregon        1.393373
# Nevada       -1.000000
# California    0.756425
# Idaho        -1.000000
# dtype: float64

示例:隨機取樣和排列

假設你想要從一個大資料集中隨機抽取樣本以進行蒙特卡羅模擬或其他分析工作。“抽取”的方式有很多,
其中一些的效率會比其他的高很多。一個辦法是:選取np.random.permutation(N)的前K個元素,
其中N為完整資料的大小,K為期望的樣本大小。

下面是構造一副英語型撲克牌的一個方式:隨機抽取take,排列np.random.permutation

import numpy as np
from pandas import DataFrame,Series
import pandas as pd

#(1)紅桃(Hearts)、黑桃(Spades)、梅花(Clubs)、方片(Diamonds)
suits=['H','S','C','D']
card_val=(range(1,11)+[10]*3)*4 #1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 4組這個數
base_names=['A']+range(2,11)+['J','K','Q']
print base_names
#輸出結果如下:['A', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'K', 'Q']
cards=[]
for suit in ['H','S','C','D']:
    cards.extend(str(num)+suit for num in base_names)#num為base_name的值,str(num)+suit值
print cards
#輸出結果如下:'AH', '2H', '3H', '4H', '5H', '6H', '7H', '8H', '9H', '10H', 'JH', 'KH', 'QH',H/S/C/D4組與base_name組合
deck=Series(card_val,index=cards)
print deck
#輸出結果如下:index為H組合的如下,還有其它3組,AS..,AC..,AD...
# AH      1  #AH中A是牌名,H是花色   1是牌面的點數
# 2H      2
# 3H      3
# 4H      4
# 5H      5
# 6H      6
# 7H      7
# 8H      8
# 9H      9
# 10H    10
# JH     10
# KH     10
# QH     10

#(2)現在我有了一個長度為52的Series,其索引為牌名,值則是21點或其它遊戲中用於計分的點數,A的點數為1
print deck[:13] #取前13個數,即結果是上面展現的值

#(3)take從列表或者是Series中抽取,permutation(int)返回一個一維從0-9的序列的隨機排列
#現在從整副牌中抽出5張:
def draw(deck,n=5):
    return deck.take(np.random.permutation(len(deck))[:n]) #permutation隨機排列的抽取5個數據
print draw(deck)
#輸出結果如下:
# 4H      4
# JH     10
# 7D      7
# 10H    10
# 9H      9
# dtype: int64

#(4)假設想要從每種花色中隨機抽取兩張牌。由於花色是牌名的最後一個字元。所以我們可以據此進行分組
get_suit=lambda card:card[-1]  #card是牌名如:AH,card[-1]取牌名的最後一個字元是花色
print deck.groupby(get_suit).apply(draw,n=2) #按card[-1]花色分組,分好後抽取2張
#輸出結果如下:
# C  QC     10
#    7C      7
# D  QD     10
#    2D      2
# H  3H      3
#    2H      2
# S  4S      4
#    10S    10
# dtype: int64

#(5)另一種辦法:
print deck.groupby(get_suit,group_keys=False).apply(draw,n=2)
#輸出結果如下:
# 10C    10
# 5C      5
# 8D      8
# 5D      5
# 2H      2
# 4H      4
# 6S      6
# 10S    10
# dtype: int64

示例:分組加權平均數和相關係數
根據groupby的“拆分(groupBy)-應用(apply)-合併(merge,concate)”,DataFrame的列與列之間或
兩個Series之間的運算(比如分組加權平均)成為一種標準作業。以下面這個資料集為例,它含有分組鍵、
值以及一些權重值:

import pandas as pd
import numpy as np
from pandas import DataFrame,Series

df=DataFrame({'category':['a','a','a','a','b','b','b','b'],
              'data':np.random.randn(8),
              'weight':np.random.rand(8)})
print df
#輸出結果如下:
#   category      data    weight
# 0        a  0.613657  0.723545
# 1        a  0.973732  0.845376
# 2        a  0.201082  0.634316
# 3        a  0.597991  0.833267
# 4        b -1.017848  0.355760
# 5        b -0.017974  0.963866
# 6        b -0.352486  0.076372
# 7        b -0.975237  0.147921

#然後可以利用category計算分組加權平均數:
grouped=df.groupby('category') #按category分組
get_wavg=lambda g: np.average(g['data'],weights=g['weights']) #g['data']的平均值,weights加上權重的平均值
print grouped.apply(get_wavg)
#下面舉一個股票的收盤
close_px=pd.read_csv('cho9/stock_px.csv',parse_dates=True,index_col=0)
print close_px[-4:]
#計算收益率(通過百分比變化計算):
rets=close_px.pct_change().dropna()
spx_corr=lambda x:x.corrwith(x['SPX'])
by_year=rets.groupby(lambda x:x.year)
print by_year.apply(spx_corr)

#還可以計算列與列之間的相關係數:
by_year.apply(lambda g:g['AAPL'].corr(g['MSFT']))

示例:面向分組的線性迴歸

順著上一例子繼續,你可以用groupby執行更為複雜的分組統計分析,只要函式返回的是pandas物件或標量值即可。
例:我可以定義下面這個regress函式(利用statsmodels)庫對各資料塊執行普通最小二乘法迴歸(OLS)。

import statesmodels.api as sm 利用sm.OLS(Y,X).fit()最小二乘法
#利用OLS二乘法線性迴歸
import statesmodels.api as sm
def regress(data,yvar,xvars):
    Y=data[yvar]
    X=data[xvars]
    X['intercept']=1.
    result=sm.OLS(Y,X).fit()
    return result.params

#現在,為了按年計算AAPL對SPX收益率的線性迴歸:
print by_year.apply(regress,'AAPL',['SPX'])

透視表和交叉表:透視表pivot_table用於分組求和(例求每個月買出的帽子總和)

透視表是各種電子表格程式和其它分析軟體中一種常見的資料彙總工具。它根據一個或多個鍵對資料進行聚合,並根據
行和列上的分組鍵將資料分配到各個矩形區域中。
在python和pandas中,可以用groupby功能以及(能夠利用層次化索引的)重塑運算製作透視表。
DataFrame有一個pivot_table方法,還有一個pand.pivot_table函式。pivot_table還可以新增分項小計(也叫margins)

import pandas as pd
import numpy as np
from pandas import DataFrame,Series


#(1)回到小費資料集,假設我想要根據sex和smoker計算分組平均數(pivot_table的預設聚合型別),並將sex和smoker放到行上
print tips.pivot_table(rows=['sex','smoker'])
#輸出結果如下:
#                 size    tip     tip_pct     total_bill
# sex    smoker
# Female  No      2.59    2.77    0.15    18.10
#         Yes     2.24    2.93    0.18    17.97
# Male    No      2.71    3.11    0.16    19.79
#         Yes     2.50    3.05    0.15    22.97
#上面的對於groupby來說也是很簡單就可以做到的事情。

#(2)現在我們只想聚合tip_pct和size,而且想根據day進行分組。我將smoker放到列上,把day放到行上。
tips.pivot_table(['tip_pct','size'],rows=['sex','day'])
#輸出結果如下:
                 # tip_pct      size
# smoker           No     Yes     No      Yes
# sex     day
# Female  Fri     0.16    0.2    2.5      2.0
#         SAT     0.14    0.16   2.3      2.2
# Male    Fri     0.13    0.14   2.0      2.1
#         SAT     0.16    0.13   2.65    2.62

#(3)還可以對這個表作進一步處理,傳入margins=True新增分項小計。這將會新增標籤為All的行和列。
#其值對應於單個等級中所有資料的分組統計。
# 在下面的這個例子中,All值為平均數:不單獨考慮菸民與非菸民(All列),不單獨考慮行分組兩個級別
#中的任何單項(All)行。
tips.pivot_table(['tip_pct','size'],rows=['sex','day'],
                 cols='smoker',margins=True)
#輸出結果如下:
                 # tip_pct      size
# smoker           No     Yes     No      Yes       All
# sex     day
# Female  Fri     2.16    2.2    2.5      2.0      0.199
#         SAT     2.14    2.16   2.3      2.2      0.156
# Male    Fri     2.13    2.14   2.0      2.1      0.180
#         SAT     2.16    2.13   2.65    2.62      0.165
# All             2.66    2.40   2.56    2.22      0.160

#(4)要使用其它的聚合函式,將其傳給aggfunc即可
#例:使用count或len可以得到有關分組大小的交叉表:
tips.pivot_table('tip_pct',rows=['sex','smoker'],cols='day',
                 aggfunc=len,margins=True) #行:'sex','smoker';列:'day';margins=True分項小計All;len方法
#輸出結果如下:
# day          Fri   Sat   Sun   Thur   All
# sex  smoker
# Female  No    2    13     14     25    54
#         Yes   7    15     4      7     33
# Male    No    2    32     43     20    97
#         Yes   8    27     15     10    60
# All          19    87     76     62    244

#(5)如果存在空的組合(也就是NA),可能會希望設定一個fill_value:
tips.pivot_table('size',rows=['time','sex','smoker'],
                 cols='day',aggfunc='sum',fill_value=0)
#輸出結果如下:
# day                Fri   Sat   Sun   Thur
# time   sex    smoker
# Dinner Female  No    2   30    43     2
#               Yes   8    33    10     0
#        Male    No    2   85    143    0
#               Yes   8    27    15    10
# Lunch Female  No    3    0     0     60
#               Yes   6    0     0     17
#        Male    No   0    0     0     50
#               Yes   5    0     0     23

pivot_table的引數
引數名       說明
values      待聚合的列的名稱。預設聚合所有數值列
rows        用於分組的列名或其他分組鍵,出現在結果透視表的行
cols        用於分組的列名或其他分組鍵,出現在結果透視表的列
aggfunc     聚合函式或函式列表,預設為'mean'.可以是任何對groupby有效的函式
fill_value  用於替換結果表中的缺失值
margins     新增行,列小計和總計,預設為False

交叉表:crosstable(兩個變數)

import pandas as pd
import numpy as np
from pandas import DataFrame,Series

#交叉表:crosstab
#交叉表是一種用於計算分組頻率的特殊透透表。
#這個範例資料很典型,取自交駐表的Wikipedia頁:
data=DataFrame({'Sample':[1,2,3,4,5,6,7,8,9,10],
                'Gender':['Female','Male','Female','Male','Male','Male',
                          'Female','Female','Male','Female'],
                'Handedness':['Right-handed','Left-handed','Right-handed','Right-handed',
                              'Left-handed','Right-handed','Right-handed','Left-handed',
                              'Right-handed','Right-handed']})
# print data
#輸出結果如下:
#    Gender    Handedness  Sample
# 0  Female  Right-handed       1
# 1    Male   Left-handed       2
# 2  Female  Right-handed       3
# 3    Male  Right-handed       4
# 4    Male   Left-handed       5
# 5    Male  Right-handed       6
# 6  Female  Right-handed       7
# 7  Female   Left-handed       8
# 8    Male  Right-handed       9
# 9  Female  Right-handed      10

#(1)假設我們想要根據性別和用手習慣對這段資料進行統計彙總。
#雖然可以用pivot_table實現該功能,但是用pandas.crosstab函式更方便
print pd.crosstab(data.Gender,data.Handedness,margins=True) #index,columns,margins
#輸出結果如下:
# Handedness  Left-handed  Right-handed  All
# Gender
# Female                1             4    5
# Male                  2             3    5
# All                   3             7   10

#(2)crosstab的前兩個引數可以是陣列、Series或陣列列表。再比如對小費資料集:
print pd.crosstab([tips.time,tips.day],tips.smoker,margins=True)
#輸出結果如下:
# smoker         No    Yes     All
# time   day
# Dinner Fri    3       9       12
#        Sat    45      42      87
#        Sun    57      19      76
#        Thur   1       0       1
# Lunch  Fri    1       6       7
#        Thur   44      17      61
# All           151     93      244 

示例:2012聯邦選舉委員會資料庫

用到了:mapping,get,pivot_table
 

import pandas as pd
import numpy as np
from pandas import DataFrame,Series
import os
import matplotlib.pyplot as plt

#獲取檔案的路徑
path1=os.path.abspath('..') #獲取當前指令碼所在路徑的上一級路徑
print path1

#(1)先將資料載入進來:
fec=pd.read_csv(path1+'/pydata-book-2nd-edition/datasets/fec/P00000001-ALL.csv')
print fec.ix[123456] #ix是取行索引ix[2]按下標取,下標從0開始算
#輸出結果如下:
# cmte_id                             C00431445
# cand_id                             P80003338
# cand_nm                         Obama, Barack
# contbr_nm                         ELLMAN, IRA
# contbr_city                             TEMPE
# contbr_st                                  AZ
# contbr_zip                          852816719
# contbr_employer      ARIZONA STATE UNIVERSITY
# contbr_occupation                   PROFESSOR
# contb_receipt_amt                          50
# contb_receipt_dt                    01-DEC-11
# receipt_desc                              NaN
# memo_cd                                   NaN
# memo_text                                 NaN
# form_tp                                 SA17A
# file_num                               772372
# Name: 123456, dtype: object

#(2)上面的資料中沒有黨派資訊,因此最好把它加進去,通過unique,可以獲取全部的候選人名單。
unique_cands=fec.cand_nm.unique() #取fec(Series)中的cand_nm欄位的值
print unique_cands
#輸出結果如下:
# ['Bachmann, Michelle' 'Romney, Mitt' 'Obama, Barack'
#  "Roemer, Charles E. 'Buddy' III" 'Pawlenty, Timothy' 'Johnson, Gary Earl'
#  'Paul, Ron' 'Santorum, Rick' 'Cain, Herman' 'Gingrich, Newt'
#  'McCotter, Thaddeus G' 'Huntsman, Jon' 'Perry, Rick']
print unique_cands[2]
#輸出結果如下:
# Obama, Barack

#(3)最簡單的辦法是利用字典說明黨派關係:
parties={'Bachmann, Michelle':'Republican',
         'Romney, Mitt':'Republican',
         'Obama, Barack':'Democrat',
         "Roemer, Charles E. 'Buddy' III":'Republican',
         'Pawlenty, Timothy':'Republican',
         'Johnson, Gary Earl':'Republican',
         'Paul, Ron':'Republican',
         'Santorum, Rick':'Republican',
         'Cain, Herman':'Republican',
         'Gingrich, Newt':'Republican',
         'McCotter, Thaddeus G':'Republican',
         'Huntsman, Jon':'Republican',
         'Perry, Rick':'Republican'}
#現在通過這個對映以及Series物件的map方法,你可以根據候選人姓名得到一組黨派資訊
print fec.cand_nm[123456:123461]
#輸出結果如下:
# Obama, Barack
# 123456    Obama, Barack
# 123457    Obama, Barack
# 123458    Obama, Barack
# 123459    Obama, Barack
# 123460    Obama, Barack
# Name: cand_nm, dtype: object

#(4)將其新增為一新列,使用map對映,有幾個欄位都對映為同一個,則values值相加
fec['party']=fec.cand_nm.map(parties) #fec.cand_nm有兩個值Obama, Barack,map(parties)對映後就是找出Obama, Barack對應parties的黨派
print fec['party'].value_counts()
#輸出結果如下:
# Democrat      593746
# Republican    407985
# Name: party, dtype: int64

#(5)這裡有兩個需要注意的地方。第一,該資料既包括贊助也包括退款(負的出資額)
#value_counts對值進行統計
print (fec.contb_receipt_amt>0).value_counts()
#輸出結果如下:
# True     991475
# False     10256
# Name: contb_receipt_amt, dtype: int64

#(6)為了簡化分析過程,限定該資料集只能有正的出資額:
fec=fec[fec.contb_receipt_amt>0]
#由於Barack Obama和Mitt Romney是最主要的兩名候選人,所以專門準備了一個子集,只包含針對他們兩人的競選活動的贊助商用:
fec_mrbo=fec[fec.cand_nm.isin(['Obama,Barack','Romney,Mitt'])]
# print fec_mrbo[-2:]

#(7)根據職業和僱主統計贊助資訊:
#例如律師們更傾向於民主黨
#首先,根據職業計算出資總額
print fec.contbr_occupation.value_counts()[:10] #統計出不同職業的總計
#輸出結果如下:
# RETIRED                                   233990
# INFORMATION REQUESTED                      35107
# ATTORNEY                                   34286
# HOMEMAKER                                  29931
# PHYSICIAN                                  23432
# INFORMATION REQUESTED PER BEST EFFORTS     21138
# ENGINEER                                   14334
# TEACHER                                    13990
# CONSULTANT                                 13273
# PROFESSOR                                  12555
# Name: contbr_occupation, dtype: int64

#(8)不難看出,許多職業都涉及相同的基本工作型別,或者同一樣東西有多種變體。
#下面程式碼可以清理一些這樣的資料(將一個職業資訊對映到另一個)。注意:這裡巧妙地利用了dict.get,它允許沒有對映
#關係的職業也能"通過":
occ_mapping={'INFORMATION REQUESTED':'NOT PROVIDED',
             'INFORMATION REQUESTED PER BEST EFFORTS':'NOT PROVIDED',
             'INFORMATION REQUESTED(BEST EFFORTS)':'NOT PROVIDED',
             'C.E.O':'CEO'}
#如果沒有提供相關對映,則返回x
f=lambda x:occ_mapping.get(x,x) #occ_mapping,get如有則返回x的mapping的值,如果沒有則返回原值
fec.contbr_occupation=fec.contbr_occupation.map(f)
print fec.contbr_occupation.value_counts()[:10] #將前面的3個REQUESTED都對映為'NOT PROVIDED'
#輸出結果如下:
# RETIRED         233990
# NOT PROVIDED     56245
# ATTORNEY         34286
# HOMEMAKER        29931
# PHYSICIAN        23432
# ENGINEER         14334
# TEACHER          13990
# CONSULTANT       13273
# PROFESSOR        12555
# NOT EMPLOYED      9828
# Name: contbr_occupation, dtype: int64

#(9)下面對僱主也進行了同樣的處理:
print fec.contbr_employer.value_counts()[:10]
#輸出結果如下:
# RETIRED                                   206675
# SELF-EMPLOYED                              94505
# NOT EMPLOYED                               45877
# INFORMATION REQUESTED                      36135
# SELF                                       24385
# INFORMATION REQUESTED PER BEST EFFORTS     22260
# NONE                                       19929
# HOMEMAKER                                  18269
# SELF EMPLOYED                               6274
# REQUESTED                                   4233
# Name: contbr_employer, dtype: int64
emp_mapping={'INFORMATION REQUESTED PER BEST EFFORTS':'NOT PROVIDED',
             'INFORMATION REQUESTED':'NOT PROVIDED',
             'SELF':'SELF-EMPLOYED',
             'SELF EMPLOYED':'SELF-EMPLOYED',}
#如果沒有提供相關的對映,則返回x:用get
f=lambda x:emp_mapping.get(x,x)
fec.contbr_employer=fec.contbr_employer.map(f)
print fec.contbr_employer.value_counts()[:10]
#輸出結果如下:
# RETIRED          206675
# SELF-EMPLOYED    125164
# NOT PROVIDED      58396
# NOT EMPLOYED      45877
# NONE              19929
# HOMEMAKER         18269
# REQUESTED          4233
# UNEMPLOYED         2514
# US ARMY            1817
# STUDENT            1786
# Name: contbr_employer, dtype: int64

#(10)可以通過pivot_table根據黨派和職業對資料進行聚合,然後過濾掉總出資額不足200萬美元的資料:
by_occupation=fec.pivot_table('contb_receipt_amt',index='contbr_occupation',
                              columns='party',aggfunc='sum')
print by_occupation
#輸出結果如下:
# party                                  Democrat  Republican
# contbr_occupation
#    MIXED-MEDIA ARTIST / STORYTELLER       100.0         NaN
#  AREA VICE PRESIDENT                      250.0         NaN
#  RESEARCH ASSOCIATE                       100.0         NaN
#  TEACHER                                  500.0         NaN
#  THERAPIST                               3900.0         NaN
# 'MIS MANAGER                                NaN      177.60
# (PART-TIME) SALES CONSULTANT & WRITER       NaN      285.00
# (RETIRED)                                   NaN      250.00
# -                                        5000.0     2114.80
# --                                          NaN       75.00
over_2mm=by_occupation[by_occupation.sum(1)>2000000]
print over_2mm
#輸出結果如下:
# party                 Democrat    Republican
# contbr_occupation
# ATTORNEY           11141982.97  7.477194e+06
# C.E.O.                 1690.00  2.592983e+06
# CEO                 2074284.79  1.640758e+06
# CONSULTANT          2459912.71  2.544725e+06
# ENGINEER             951525.55  1.818374e+06
# EXECUTIVE           1355161.05  4.138850e+06
# HOMEMAKER           4248875.80  1.363428e+07
# INVESTOR             884133.00  2.431769e+06
# LAWYER              3160478.87  3.912243e+05
# MANAGER              762883.22  1.444532e+06
# NOT PROVIDED        4866973.96  2.023715e+07
# OWNER               1001567.36  2.408287e+06
# PHYSICIAN           3735124.94  3.594320e+06
# PRESIDENT           1878509.95  4.720924e+06
# PROFESSOR           2165071.08  2.967027e+05
# REAL ESTATE          528902.09  1.625902e+06
# RETIRED            25305116.38  2.356124e+07
# SELF-EMPLOYED        672393.40  1.640253e+06

#(11)把這些資料做成柱狀圖:
over_2mm.plot(kind='barh')
plt.show()

對各黨派總出資額最高的職業:繼續上面的例子

pandas中的order:是對值進行排序,或者用sort_values也是對值進行排序

pandas中的sort:是對行或列進行排序

#獲取檔案的路徑
path1=os.path.abspath('..') #獲取當前指令碼所在路徑的上一級路徑
print path1

#(1)先將資料載入進來:
fec=pd.read_csv(path1+'/pydata-book-2nd-edition/datasets/fec/P00000001-ALL.csv')
# print fec.ix[123456] #ix是取行索引ix[2]按下標取,下標從0開始算
#輸出結果如下:
# cmte_id                             C00431445
# cand_id                             P80003338
# cand_nm                         Obama, Barack
# contbr_nm                         ELLMAN, IRA
# contbr_city                             TEMPE
# contbr_st                                  AZ
# contbr_zip                          852816719
# contbr_employer      ARIZONA STATE UNIVERSITY
# contbr_occupation                   PROFESSOR
# contb_receipt_amt                          50
# contb_receipt_dt                    01-DEC-11
# receipt_desc                              NaN
# memo_cd                                   NaN
# memo_text                                 NaN
# form_tp                                 SA17A
# file_num                               772372
# Name: 123456, dtype: object

fec=fec[fec.contb_receipt_amt>0]
print fec
#由於Barack Obama和Mitt Romney是最主要的兩名候選人,所以專門準備了一個子集,只包含針對他們兩人的競選活動的贊助商用:
fec_mrbo=fec[fec.cand_nm.isin(['Bachmann, Michelle','Perry, Rick'])] #無Obama的資料則換其它兩位
print fec_mrbo

#(1)看一下對Obama和Romney總出資額最高的職業和企業。
#我們先對候選人進行分組,然後使用本章前面介紹的那種求取最大值的方法:
def get_top_amounts(group,key,n=5):
    totals=group.groupby(key)['contb_receipt_amt'].sum() #按key分組,然後取出'contb_receipt_amt'值,再對它求和
    #根據key對totals進行降序排列:order已經沒了,改成sort_values也是對值進行排序
    return totals.sort_values(ascending=False)[n:]

#(2)然後根據職業和僱主進行聚合:
grouped=fec_mrbo.groupby('cand_nm')
print grouped.apply(get_top_amounts,'contbr_occupation',n=7)
#輸出結果如下
# cand_nm             contbr_occupation
# Bachmann, Michelle  ATTORNEY                           47084.00
#                     EXECUTIVE                          38526.00
#                     CONSULTANT                         32768.50
#                     C.E.O.                             32123.00
#                     FARMER                             25614.00
#                     MANAGER                            24621.00
#                     SALES                              22298.00
#                     CEO                                21526.00
#                     INVESTOR                           21111.00
#                     NONE                               20253.00
#                     BUSINESS OWNER                     15518.00
#                     SELF EMPLOYED                      14686.00
#                     ACCOUNTANT                         13304.00
#                     DIRECTOR                           12131.00
#                     N                                   9850.00
#                     DOCTOR                              9550.00
#                     CPA                                 9280.00
#                     REAL ESTATE                         9275.00
#                     PASTOR                              8870.00
#                     NURSE                               8622.00
#                     LAWYER                              8418.00
#                     CONTRACTOR                          8319.00
#                     SELF-EMPLOYED                       8182.00
#                     SELF                                8038.00
#                     PILOT                               7935.00
#                     PROFESSOR                           7920.00
#                     MACHINIST                           7905.00
#                     SMALL BUSINESS OWNER                7857.00
#                     VENTURE CAPITALIST                  7500.00
#                     DENTIST                             7335.00
#                                                          ...
# Perry, Rick         MANAGING ATTORNEY                    100.00
#                     SW TEST MGR                          100.00
#                     LAW ENFORCEMENT                      100.00
#                     CONSULTING ABSTRACTOR                100.00
#                     SALES PRINTING                       100.00
#                     PROGRAMMER/RETIRED                   100.00
#                     DIRECTOR ACQUISITIONS                100.00
#                     SALES MGR.                           100.00
#                     SELF-EMP                             100.00
#                     OPS SUPERVISOR                       100.00
#                     SENIOR AUDIO DSP ENGINEER            100.00
#                     FEDERAL AGENT                        100.00
#                     SENIOR LOAN OFFICER - CORPORATE      100.00
#                     AEROSPACE ENGINEER                   100.00
#                     NATIONAL ACCOUNTS MANAGER            100.00
#                     ASST. D. A. RET'D                    100.00
#                     TEXAS TEACHER                         75.00
#                     RESEARCH ASST.                        60.36
#                     JUNIOR BUYER                          50.00
#                     LAB TECHNICIAN                        50.00
#                     CUSTODIAN                             50.00
#                     CHEM LAB SUPER                        50.00
#                     SCHOOL COUNSELOR                      50.00
#                     PHYSICIAN/MEDICAL DIRECTOR            30.00
#                     SSGT / NCOIC EVALUATIONS              25.00
#                     PRES. &AMP; CEO                       25.00
#                     TAX DIRECTOR                          25.00
#                     CEO/IT CONSULTANT                     25.00
#                     APP DEV                               25.00
#                     SPEC. ED. INSTR. AIDE                 25.00
# Name: contb_receipt_amt, Length: 3211, dtype: float64

對出資額