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

通过函数进行分组:

扫描二维码关注公众号,回复: 3957546 查看本文章
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

对出资额分组:

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

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)还可以对该数据做另一种非常实用的分析:利用cut函数根据出资额的大小将数据离散化到多个面元中。
bins=np.array([0,1,10,100,1000,10000,100000,1000000,10000000])
labels=pd.cut(fec_mrbo.contb_receipt_amt,bins) #bins是将cut成bin的等矩离的区间
print labels
#输出结果如下:
# ............
# 1001729      (100, 1000]
# 1001730    (1000, 10000]
# Name: contb_receipt_amt, Length: 25791, dtype: category
# Categories (8, interval[int64]): [(0, 1] < (1, 10] < (10, 100] < (100, 1000] < (1000, 10000] <
#                                   (10000, 100000] < (100000, 1000000] < (1000000, 10000000]]

#(2)根据候选人姓名以及面元标签对数据进行分组:
grouped=fec_mrbo.groupby(['cand_nm',labels])
print grouped.size().unstack(0)  #size多少个元素值。unstack行列转换
#输出结果如下:
# cand_nm            Bachmann, Michelle  Perry, Rick
# contb_receipt_amt
# (0, 1]                           98.0          NaN
# (1, 10]                         118.0         14.0
# (10, 100]                      7480.0       1044.0
# (100, 1000]                    5121.0       5039.0
# (1000, 10000]                   265.0       6612.0
#从上面可以看出在小额赞助方面Michelle要更多。
# 你还可以对出资额求和并在面元内规格化,以便图形化显示两们候选 人各种赞助额度的比例:
bucket_sums=grouped.contb_receipt_amt.sum().unstack(0)
print bucket_sums
#输出结果如下:
# cand_nm            Bachmann, Michelle  Perry, Rick
# contb_receipt_amt
# (0, 1]                          98.00          NaN
# (1, 10]                        876.50       108.00
# (10, 100]                   465668.63     74645.82
# (100, 1000]                1654273.16   2959292.45
# (1000, 10000]               590523.00  17271707.73
normed_sums=bucket_sums.div(bucket_sums.sum(axis=1),axis=0)
print normed_sums
#输出结果如下:
# cand_nm            Bachmann, Michelle  Perry, Rick
# contb_receipt_amt
# (0, 1]                       1.000000          NaN
# (1, 10]                      0.890300     0.109700
# (10, 100]                    0.861847     0.138153
# (100, 1000]                  0.358567     0.641433
# (1000, 10000]                0.033060     0.966940
normed_sums[:-2].plot(kind='barh',stacked=True) #排除了两个最大的面元,因为这些不是由个人捐赠的。
plt.show()

根据州统计赞助信息:
 


#获取文件的路径
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)首先自然是根据候选人和州对数据进行聚合
grouped=fec_mrbo.groupby(['cand_nm','contbr_st'])
totals=grouped.contb_receipt_amt.sum().unstack(0).fillna(0) #取grouped的contb_receipt_amt分组求和,然后行列互换,NA用0填充
totals=totals[totals.sum(1)>100000] #取出totals.sum>100000的州的数据(totals.sum>100000结果为True,False)
print totals[:10] #取前10条信息
#输出结果如下:
# cand_nm    Bachmann, Michelle  Perry, Rick
# contbr_st
# AZ                   64472.00     84374.00
# CA                  360100.77   1720880.60
# CO                   59754.65    233033.33
# FL                  193223.00    882626.89
# GA                   42785.25    200536.00
# IL                   99219.75    410185.00
# LA                   47748.00    631891.02
# MD                   64964.25    144020.00
# MN                  260459.75     11400.00
# MO                   52611.00     74550.00

#(2)如果对各行除以总赞助额,就会得到各候选人在各州的总赞助额比例:
percent=totals.div(totals.sum(1),axis=0) #axis=0行,
print percent[:10]
#输出结果如下:
# cand_nm    Bachmann, Michelle  Perry, Rick
# contbr_st
# AZ                   0.433146     0.566854
# CA                   0.173044     0.826956
# CO                   0.204088     0.795912
# FL                   0.179600     0.820400
# GA                   0.175839     0.824161
# IL                   0.194776     0.805224
# LA                   0.070255     0.929745
# MD                   0.310857     0.689143
# MN                   0.958067     0.041933
# MO                   0.413735     0.586265
#(2)可以用地图画出,但是basemap没有安装成功,没有完成下面的操作
from mpl_toolkits.basemap import Basemap,cm
import numpy as np
from matplotlib import rcParams
from matplotlib.collections import LineCollection
import matplotlib.pyplot as plt

from shapelib import ShapeFile
import dbflib

obama=percent['Obama,Barack']

fig=plt.figure(figsize=(12,12))
ax=fig.add_axes([0.1,0.1,0.8,0.8])

lllat=21
urlat=53
lllon=-1118
urlon=-62

m=Basemap(ax=ax,projection='stere',
          lon_O=(urlon+lllon)/2,lat_O=(urlat+lllat)/2,
          llcrnrlat=lllat,urcrnrlat=urlat,llcrnrlon=lllon,
          urcrnrlon=urlon,resolution='I')
m.draswcoastlines()
m.drawcountries()

shp=ShapeFile('../states/statesp020')
dbf=dbflib.open('../states/statesp020')

for npoly in range(shp.info()[0]):
    #在地图上绘制彩色多边形
    shpsegs=[]
    shp_object=shp.read_object(npoly)
    verts=shp_object.vertices()
    rings=len(verts)
    for ring in range(rings):
        lons,lats=zip(*verts[ring])
        x,y=m(lons,lats)
        shpsegs.append(zip(x,y))
        if ring==0:
            shapedict=dbf.read_record(npoly)
        name=shapedict['STATE']
    lines=LineCollection(shpsegs.antialiaseds=(1,))
    
    try:
        per=obama[state_to_code[name.upper()]]
    except KeyError:
        continue
    
    
    lines.set_facecolors('k')
    lines.set_alpha(0.75*per) #把百分比变小一点
    lines.set_edgecolors('k')
    lines.set_linewidth(0.3)
plt.show()

猜你喜欢

转载自blog.csdn.net/u012474716/article/details/81083895