pandas笔记:ch09数据聚合与分组运算
ch09
数据聚合与分组运算¶
Data Aggregation and Group Operations
from __future__ import division
from numpy.random import randn
import numpy as np
import os
import matplotlib.pyplot as plt
np.random.seed(12345)
plt.rc('figure', figsize=(10, 6))
from pandas import Series, DataFrame
import pandas as pd
np.set_printoptions(precision=4)
pd.options.display.notebook_repr_html = False
%matplotlib inline
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)})
df
'按 key1列 分组 data1列'
grouped = df['data1'].groupby(df['key1'])
grouped
'变量 grouped 是一个GroupBy对象。它没有进行任何计算,只是包含了对各分组执行计算所需要的一切信息'
'计算分组平均值'
grouped.mean()
'传入多个数组[df["key1"], df["key2"]],会得到多个数组组成的层次化索引分组'
means = df['data1'].groupby([df['key1'], df['key2']]).mean()
means
'unstack 层次化索引'
means.unstack()
'分组键不一定必需是DataFrame的列,可以是任意长度适当的数组'
states = np.array(['Ohio', 'California', 'California', 'Ohio', 'Ohio'])
years = np.array([2005, 2005, 2006, 2005, 2006])
df['data1'].groupby([states, years]).mean()
'df.groupby()传入列名:“key1” (不是列)对所有数值数据分组(默认剔除非数值数据,所以这里没有 key2 的信息)'
df.groupby('key1').mean()
'传入两个列名进行分组'
df.groupby(['key1', 'key2']).mean()
'.size: 返回一个含有分组大小的Series,注意只有a-one有两行数据,其他的只有1行数据'
df.groupby(['key1', 'key2']).size()
对分组进行迭代¶
Iterating over groups
"GroupBy:对象支持迭代,可以产生一组二元元组(由分组名和数据块组成)"
for name, group in df.groupby('key1'):
print(name)
print(group)
"对于多重情况,元组的第一个元素将会是由键值组成的元组"
for (k1, k2), group in df.groupby(['key1', 'key2']):
print((k1, k2))
print(group)
'你可以对这些数据片段做任何事情,比如:将这些数据片段做成一个字典'
pieces = dict(list(df.groupby('key1')))
pieces['b']
list(df.groupby('key1'))
pieces
'此处用来学习notebook的用法,esc下按l健出现左端1,2,3,4等等数字序列,按下 o 屏蔽输出信息,按下 shift + o 键设置输出信息'
'为scrolling形式'
a = 3
b = 4
for i in range(50):
print(i)
df.dtypes
'GroupBy默认是在 axis=0上进行分组的,也可以在 axis=1 上分组,这里依据 df.dtypes 对列进行分组'
grouped = df.groupby(df.dtypes, axis=1)
dict(list(grouped))
选取一个或一组列¶
Selecting a column or subset of columns
df.groupby('key1')['data1']
df.groupby('key1')[['data2']]
是下面代码的等价
df['data1'].groupby(df['key1'])
df[['data2']].groupby(df['key1'])
df.groupby('key1')['data1'].mean()
'注意这里的 ["data1"] 等价于 "data1" '
df.groupby('key1')[['data1']].mean()
df.groupby(['key1', 'key2'])[['data2']].mean()
s_grouped = df.groupby(['key1', 'key2'])['data2']
s_grouped
s_grouped.mean()
通过字典或Series进行分组¶
Grouping with dicts and Series
people = DataFrame(np.random.randn(5, 5),
columns=['a', 'b', 'c', 'd', 'e'],
index=['Joe', 'Steve', 'Wes', 'Jim', 'Travis'])
people.ix[2:3, ['b', 'c']] = np.nan # Add a few NA values
people
'假设已经知道了列的分组信息'
mapping = {'a': 'red', 'b': 'red', 'c': 'blue',
'd': 'blue', 'e': 'red', 'f' : 'orange'}
'只需要将这个字典传给 groupby 即可'
by_column = people.groupby(mapping, axis=1)
by_column.sum()
'Series也有同样的功能,它可以被看作一个固定大小的映射。如果用Series作为分组键,则pandas会检查Series以确保其索引跟分组轴是对齐的'
map_series = Series(mapping)
map_series
people.groupby(map_series, axis=1).count()
通过函数进行分组¶
Grouping with functions
'任何被当作分组键的函数都会在各个索引值上被调用一次,其返回值就会被用作分组名称'
people.groupby(len).sum()
'将函数跟数组,列表,字典,Series混合使用也不是问题,因为任何东西最终都会被转换为数组'
key_list = ['one', 'one', 'one', 'two', 'two']
people.groupby([len, key_list]).min()
根据索引级别分组¶
Grouping by index levels
'层次化索引数据集最方便的地方就在于它能够根据索引级别进行聚合。要实现该目的,通过level关键字传入级别编号或名称即可:'
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)
hier_df
hier_df.groupby(level='cty', axis=1).count()
数据聚合¶
Data aggregation
聚合,指的是任何能够从数组产生标量值的数据转换过程。之前的例子已经用过一些,如mean,count,min,以及sum等。然而,并不是只能使用这些方法。你可以使用自己发明的聚合运算,还可以调用分组对象上已经定义好的任何方法。例如:quantile
df
'quantile:计算Series或DataFrame列的样本分位数'
'''虽然quantile并没有明确地实现与GroupBy,但他是一个Series方法,所以这里是能用的。实际上,GroupBy会高效地对Series进行切片,然后对
各片调用piece.quantile(0.9),最后将这些结果组装成最终结果。'''
grouped = df.groupby('key1')
grouped['data1'].quantile(0.9)
'如果要使用你自己的聚合函数,只需将其传入aggregate或agg方法即可'
def peak_to_peak(arr):
return arr.max() - arr.min()
grouped.agg(peak_to_peak)
'有些方法(如describe)也是可以用在这里的,即使严格来讲,他们并非聚合运算'
grouped.describe()
在后面关于分组及运算和转换的那一节中,我将详细说明这到底是怎么回事。
注:自定义聚合函数要比表9-1中那些经过优化的函数慢得多。这是因为在构造中间分组数据块时存在非常大的开销(函数调用,数据重排等)。
表9-1:经过优化的groupby的方法¶
函数名 说明 count 分组中非NA值得数量 sum 非NA值的和 mean 非NA值的平均值 median 非NA值的算术中位数 std,var 无偏(分母为n-1)标准差和方差 min,max 非NA值的最小值和最大值 prod 非NA值的积 first,last 第一个和最后一个非NA值
'这段代码显示了下节的高级的聚合功能所需要的初始化数据'
tips = pd.read_csv('ch08/tips.csv')
# Add tip percentage of total bill
tips['tip_pct'] = tips['tip'] / tips['total_bill']
tips[:6]
面向列的多函数应用¶
Column-wise and multiple function application
grouped = tips.groupby(['sex', 'smoker'])
grouped_pct = grouped['tip_pct']
'对于向 .agg 传入表9-1那样的函数,必须将函数名以字符串的形式传入,否则将认为是自定义的函数:'
grouped_pct.agg('mean')
'因此,下面代码将会出错!'
grouped_pct.agg(mean)
'如果传入一组函数或函数名,得到的DataFrame的列就会以相应的函数命名:注意,这时候传入lambda函数就惨了,他们的名称会是一样'
grouped_pct.agg(['mean', 'std', peak_to_peak])
'如果传入的是一个由(name, function)元组组成的列表,name就会作为Dataframe的列名(可以将这种二元元组列表看作一个有序映射)'
grouped_pct.agg([('foo', 'mean'), ('bar', np.std)])
"对多列传入多个函数(前面是对单个列传入函数),那么每个列都会执行这些函数(聚合),返回一个层次化列的Dataframe"
functions = ['count', 'mean', 'max']
result = grouped['tip_pct', 'total_bill'].agg(functions)
result
result['tip_pct']
'也可以传入带有自定义名称的元组列表'
ftuples = [('Durchschnitt', 'mean'), ('Abweichung', np.var)]
grouped['tip_pct', 'total_bill'].agg(ftuples)
'传入一个从列名映射到函数的字典:对不同的列应用不同的函数'
grouped.agg({'tip' : np.max, 'size' : 'sum'})
grouped.agg({'tip_pct' : ['min', 'max', 'mean', 'std'],
'size' : 'sum'})
从这里也可以看出,agg()可以实现对分组后的每一项用自定义的函数实现聚合,而之前的sum(),count()等仅仅是单一的聚合
以无索引的形式返回聚合数据¶
Returning aggregated data in "unindexed" form
'as_index=False: 不会把传入的数组作为索引'
tips.groupby(['sex', 'smoker'], as_index=False).mean()
注意:as_index=True 对应的是agg()函数,对表9-1的内置函数也成立,而group_keys=True 对应的是apply()函数,如下举例说明:
'上例 as_index=False 对内置函数成立,这里对 agg()函数也成立'
tips.groupby(['sex', 'smoker'], as_index=False).agg('mean')
'上例 as_index=False 对内置函数成立,这里对 agg()函数也成立'
'这里的np.mean对 agg 的 as_index=False有效,下面好像对 apply的 groups_keys无效,不知道为什么。'
tips.groupby(['sex', 'smoker'], as_index=False).agg(np.mean)
'as_index=False 对apply()函数无效'
tips.groupby(['sex', 'smoker'], as_index=False).apply(np.mean)
####???? 不知道这里为什么没有效果,而下面的就有效果。以后再研究
'这里不知道为什么这样,但是不管他了。下面的reset_index()也有解决办法'
tips.groupby(['sex', 'smoker'], group_keys=False).apply(np.mean)
####???? 不知道这里为什么没有效果,而下面的就有效果。以后再研究
'这里不知道为什么这样,但是不管他了。下面的reset_index()也有解决办法'
tips.groupby(['sex', 'smoker'], group_keys=False).apply(lambda x: x.mean())
'这样就好了'
tips.groupby(['sex', 'smoker'], group_keys=False).apply(np.mean).reset_index()
'group_keys=False对 apply有效。'
tips.groupby(['sex', 'smoker'], group_keys=False).apply(top)
tips.groupby(['sex', 'smoker'], group_keys=True).apply(top)
'group_keys=False对 agg()无效'
tips.groupby(['sex','smoker'], group_keys=False).agg('mean')
'group_keys=False 对 表9-1 的内置函数也无效'
tips.groupby(['sex','smoker'], group_keys=False).mean()
当然,对结果调用reset_index:(把index转化为columns的函数)也能得到这种形式的结果。
警告:groupby的这种用法比较缺乏灵活性
分组级运算和转换¶
Group-wise operations and transformations
聚合只不过是分组运算的其中一种而已。他是数据转换的一个特例,也就是说,他接受能够将一维数组简化为标量值的函数。在本节中,我将介绍transform和apply方法,他们能够执行更多其他的分组运算。
df
k1_means = df.groupby('key1').mean().add_prefix('mean_')
k1_means
'左边用 列key1, 右边用 索引 合并'
pd.merge(df, k1_means, left_on='key1', right_index=True)
key = ['one', 'two', 'one', 'two', 'one']
people.groupby(key).mean()
'transform会将一个函数应用到各个分组,然后将结果放置到适当的位置上。如果各分组产生的是一个标量值,则该值就会被广播出去。'
people.groupby(key).transform(np.mean)
'上面得出的值有重复的,因此,用agg函数不能得出上面的结果,尽管 使用 as_index=False参数'
'可以这么认为,transform是先分组,然后再还原。而agg只可以分组,因此只要len(分组index) < len(还原index) agg结果就不等于transform'
people.groupby(key, as_index=False).agg('mean')
'从各组中减去平均值'
def demean(arr):
return arr - arr.mean()
demeaned = people.groupby(key).transform(demean)
demeaned
#为什么应用apply结果一样? 猜测:demean函数返回的是 DataFrame数据,不是单个的值,即对apply使用demean没有聚合效果。
'原因可能是各分组应用apply后返回的是一个Dataframe,然后对这些DataFrame合并,最终结果是看起来就像没有分组一样。'
'适用于apply 的函数既可以是用于分组的函数,如 np.mean,也可以是没有分组效果但是按分组运算的函数,如本例的 demean '
demeaned = people.groupby(key).apply(demean)
demeaned
#这里的函数为什么对 agg 不能用?
'原因可能是适用于agg 的函数必须是一个有分组作用的函数。这里的 demean 函数显然没有分组的作用。'
demeaned = people.groupby(key).agg(demean)
demeaned
demeaned.groupby(key).mean()
apply:一般性的“拆分-应用-合并”¶
Apply: General split-apply-combine
和aggregate一样,transform也是一个有着严格条件的特殊函数:传入的函数只能产生两种结果,要么产生一个可以广播的标量值(如np.mean),要么产生一个相同大小的结果数组。最一般的GroupBy方法是apply,本节剩余部分将重点讲解它。apply会将待处理的对象拆分成多个片段,然后对各片段调用传入的函数,最后尝试将各片段组合到一起。
'根据 tip_pct 列中的最高的前5个值选出来行。'
def top(df, n=5, column='tip_pct'):
return df.sort_values(by=column)[-n:]
top(tips, n=6)
'根据 smoker 分组并用该函数调用apply 注意:这里默认把分组数据传递给了函数 top 作为第一个带数据的参数(就是这里的df)'
tips.groupby('smoker').apply(top)
'原理:top函数在DataFrame的各个片段上调用,然后结果由pandas.concat 组装到一起,并以分组名称进行了标记。于是,结果就有了一个层次化索引。'
'也可以在apply中传入 top函数 的其它参数或者关键字'
tips.groupby(['smoker', 'day']).apply(top, n=1, column='total_bill')
'除了这些基本的用法之外,能否充分发挥apply的威力很大程度上取决于你的创造力。传入的函数完全由你说的算,他只需要返回一个pandas对象或标量值即可'
result = tips.groupby('smoker')['tip_pct'].describe()
result
result.unstack('smoker')
在GroupBy中,当你调用诸如describe之类的方法时,实际上只是应用了下面两条代码的快捷方式而已
f = lambda x: x.describe()
grouped.apply(f)
print()
禁止分组键¶
Suppressing the group keys
'gruop_keys=False 禁止根据传入的分组键 smoker ,sex 与 原始对象的索引 共同构成层次化索引'
tips.groupby(['smoker','sex'], group_keys=False).apply(top)
数据聚合中的aggregate(或agg),transform 与 apply之间的联系¶
- aggregate(或agg):
- 传入单个函数: grouped[col1,col2].agg(func)
- 传入函数组成的列表:grouped[col1,col2].agg([func1,func2])
- 传入(name,func)组成的列表:grouped[col1,col2].agg([(name1,func1),(name2,func2)]):输出结果str(name1)代替str(func1)
- 传入字典:grouped.agg({col1:[func1,func2],col2:fnc3}):对不同的列应用不同的函数注意这里的 col的位置
- 对表9-1的内建函数函数func,必须传入函数名str(func),否则被认为是自定义的函数
- 对transform的第二种形式的函数不支持(因为它没有group效果)。
- transform:
- 传入的函数可以具有分组效果,如np.mean,那么结果是agg(np.mean)的展开。
- 传入的函数也可以是有分组功能但是没有分组效果的,如 lambda x: x - x.mean 。返回的没有分组效果但有分组运算的DataFrame
- apply:
- apply(func),对func先分组运算,然后再叠加起来,因此对transform 的两种函数都支持。
- apply(func, *args, ** kwargs),如apply(top, n=1, column='total_bill') 后面两个是 top 的参数
- 传入 lambda x:{'min': x.min(), 'max': x.max(), 'count': x.count(), 'mean': x.mean()} 对每一组返回4行group数据
总结:transform必须展开,agg不能展开,apply可以展开(传入transform的第二种函数)。
apply是具有一般性的函数,agg可以支持group的内置函数
分位数和桶分析¶
Quantile and bucket analysis
将pandas的一些根据指定面元或样本分位数将数据拆分成多块的工具(比如cut和qcut)。将这些函数与groupby结合起来,就能非常轻松地实现对数据集的桶(bucket)或分位数(quantile)分析了。
frame = DataFrame({'data1': np.random.randn(1000),
'data2': np.random.randn(1000)})
factor = pd.cut(frame.data1, 4)
factor[:10]
def get_stats(group):
return {'min': group.min(), 'max': group.max(),
'count': group.count(), 'mean': group.mean()}
grouped = frame.data2.groupby(factor)
grouped.apply(get_stats).unstack()
#ADAPT the output is not sorted in the book while this is the case now (swap first two lines)
grouped.apply(get_stats)
# 返回分位数编号 Return quantile numbers
grouping = pd.qcut(frame.data1, 10, labels=False)
grouped = frame.data2.groupby(grouping)
grouped.apply(get_stats).unstack()
实例:用特定于分组的值填充缺失值¶
Example: Filling missing values with group-specific values
这里用平均值去填充NA
'关于扩展的切片 list[I:J:K] 参见我的Python学习笔记'
s = Series(np.random.randn(6))
s[::2] = np.nan
s
'简单的填充平均值情况'
s.fillna(s.mean())
'要对不同的分组填充不同的值,只需要将数据分组,并使用apply和一个能够对各数据块调用fillna的函数即可。'
states = ['Ohio', 'New York', 'Vermont', 'Florida',
'Oregon', 'Nevada', 'California', 'Idaho']
group_key = ['East'] * 4 + ['West'] * 4
data = Series(np.random.randn(8), index=states)
data[['Vermont', 'Nevada', 'Idaho']] = np.nan
data
data.groupby(group_key).mean()
fill_mean = lambda g: g.fillna(g.mean())
data.groupby(group_key).apply(fill_mean)
'传入以分组为键的字典,也可以根据分组传入自定义的填充值'
fill_values = {'East': 0.5, 'West': -1}
fill_func = lambda g: g.fillna(fill_values[g.name])
data.groupby(group_key).apply(fill_func)
示例:随即采样和排列¶
Example: Random sampling and permutation
# 红桃 Hearts, 黑桃 Spades, 梅花 Clubs, 方片 Diamonds
suits = ['H', 'S', 'C', 'D']
card_val = (list(range(1, 11)) + [10] * 3) * 4
base_names = ['A'] + list(range(2, 11)) + ['J', 'K', 'Q']
cards = []
for suit in ['H', 'S', 'C', 'D']:
cards.extend(str(num) + suit for num in base_names)
deck = Series(card_val, index=cards)
deck[:13]
def draw(deck, n=5):
return deck.take(np.random.permutation(len(deck))[:n])
draw(deck)
'从每种花色中随机抽取两张牌。'
get_suit = lambda card: card[-1] # 注意:这里的card不是前面的card,它类似于未知数X 见下面的用法
deck.groupby(get_suit).apply(draw, n=2)
get_suit = lambda x: x[-1] # 默认条件(axis=0)下,func把index作为参数,参见前面groupby通过函数分组
deck.groupby(get_suit).apply(draw, n=2)
# alternatively
deck.groupby(get_suit, group_keys=False).apply(draw, n=2)
分组加权平均数和相关系数¶
Example: Group weighted average and correlation
df = DataFrame({'category': ['a', 'a', 'a', 'a', 'b', 'b', 'b', 'b'],
'data': np.random.randn(8),
'weights': np.random.rand(8)})
df
'先通过 category 进行分组,然后分组对 data 使用 weight 权重进行加权平均'
grouped = df.groupby('category')
get_wavg = lambda g: np.average(g['data'], weights=g['weights'])
grouped.apply(get_wavg) # a = sum(df.data[:4] * df.weights[:4])/ sum(df.weights[:4])
上面的例子比较无聊,所以看一个稍微实际点的例子
close_px = pd.read_csv('ch09/stock_px.csv', parse_dates=True, index_col=0)
close_px.info()
close_px[-4:]
rets = close_px.pct_change().dropna() # 计算百分比变化,并过滤 NA
spx_corr = lambda x: x.corrwith(x['SPX']) # corrwith 返回Series
by_year = rets.groupby(lambda x: x.year) # 按年份分组
by_year.apply(spx_corr)
# 微软和苹果的年度相关系数 Annual correlation of Apple with Microsoft
by_year.apply(lambda g: g['AAPL'].corr(g['MSFT']))
示例:面向分组的线性回归¶
Example: Group-wise linear regression
顺着上一个例子继续,你可以用groupby执行更为复杂的分组统计分析,只要函数返回的是pandas对象或标量值即可。
'定义regress函数(利用statsmodels库)对各数据块执行普通最小二乘法(Ordinary Least Squares,OLS)回归'
import statsmodels.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
by_year.apply(regress, 'AAPL', ['SPX'])
数据透视表和交叉图¶
Pivot tables and Cross-tabulation
透视表(pivot table)是各种电子表格程序和其他数据分析软件中一种常见的数据汇总工具。他根据一个或多个键对数据进行聚合,并根据行和列上的分组键将数据分配到各个矩形区域中。
在Python和pandas 中,可以通过本章所介绍的groupby功能以及(能够利用层次化索引的)重塑运算制作透视表。DataFrame有一个pivot_table方法,此外还有一个顶级的pandas.pivot_table函数。除能为groupby提供便利之外,pivot_table还可以添加分项小计(也叫作margins)。
'根据 sex 和 somker 计算分组平均数(pivot_table的默认聚合类型) 这里跳过了非数值类型 day 列数据'
tips.pivot_table(index=['sex', 'smoker'])
'我们只想聚合 tip_pct 和 size 根据 day 进行行分组 (注意传入的参数与上面的不同)'
tips.pivot_table(['tip_pct', 'size'], index=['sex', 'day'],
columns='smoker')
'margins=True 添加分项小计。这将会添加标签为ALL的行和列,其值对应于单个等级中所有数据的分组统计'
tips.pivot_table(['tip_pct', 'size'], index=['sex', 'day'],
columns='smoker', margins=True)
'aggfunc=func func是其他的聚合函数,这里使用的是 len'
tips.pivot_table('tip_pct', index=['sex', 'smoker'], columns='day',
aggfunc=len, margins=True)
'fill_value= 存在空的组合的默认值'
tips.pivot_table('size', index=['time', 'sex', 'smoker'],
columns='day', aggfunc='sum', fill_value=0)
表9-2:pivot_table的参数说明:¶
values 待聚合的列的名称。默认聚合所有数值列 index 用于分组的列名或其他分组键,出现在结果透视表的行 columns 用于分组的列名或其他分组键,出现在结果透视表的列 aggfunc 聚合函数或函数列表,默认为'mean'.可以是任何对groupby有效的函数 fill_value 用于替换结果表中的缺失值 dropna=True 过滤缺失值 margins 添加行/列小计和总计,默认为False margins_name='All'
交叉表:crosstab¶
Cross-tabulations: crosstab
交叉表(cross-tabulation,简称crosstab)是一种用于计算分组频率的特殊透视表。
#from StringIO import StringIO
data = """\
Sample Gender Handedness
1 Female Right-handed
2 Male Left-handed
3 Female Right-handed
4 Male Right-handed
5 Male Left-handed
6 Male Right-handed
7 Female Right-handed
8 Female Left-handed
9 Male Right-handed
10 Female Right-handed"""
f = open('data_save.txt','w')
f.write(data)
f.close()
data = pd.read_table('data_save.txt', sep='\s+', engine='python')
!type data_save.txt
data
'根据性别和用手习惯对这段数据进行统计汇总,虽然可以用pivot_table实现该功能,但是pandas.crosstab函数会更方便:'
pd.crosstab(data.Gender, data.Handedness, margins=True)
'对应的 pivot_table 使用方法如下:'
data.pivot_table('Sample', index='Gender', columns='Handedness', aggfunc='count', margins=True)
'crosstab的前两个参数可以是数组,Series或数组列表。'
pd.crosstab([tips.time, tips.day], tips.smoker, margins=True)
示例:2012联邦选举委员会数据库¶
Example: 2012 Federal Election Commission Database
这是一个美国联邦选举委员会发布了的有关政治精选赞助方面的数据。其中包括赞助者的姓名,职业,雇主,地址以及出资额等信息。
fec = pd.read_csv('ch09/P00000001-ALL.csv')
'信息摘要'
fec.info()
fec.ix[123456] # 查看第 123456 行的信息
'.unique(): 获取全部候选人名单'
unique_cands = fec.cand_nm.unique()
unique_cands
unique_cands[2]
'利用字典说明党派关系:'
parties = {'Bachmann, Michelle': 'Republican',
'Cain, Herman': 'Republican',
'Gingrich, Newt': 'Republican',
'Huntsman, Jon': 'Republican',
'Johnson, Gary Earl': 'Republican',
'McCotter, Thaddeus G': 'Republican',
'Obama, Barack': 'Democrat',
'Paul, Ron': 'Republican',
'Pawlenty, Timothy': 'Republican',
'Perry, Rick': 'Republican',
"Roemer, Charles E. 'Buddy' III": 'Republican',
'Romney, Mitt': 'Republican',
'Santorum, Rick': 'Republican'}
fec.cand_nm[123456:123461]
'通过字典映射以及Series对象的map方法,可以根据候选人姓名得到一组党派信息。'
fec.cand_nm[123456:123461].map(parties)
# 将其添加到一个新列 Add it as a column
fec['party'] = fec.cand_nm.map(parties)
fec['party'].value_counts()
'上面的数据既包括赞助的,也包括退款(负的出资额)'
(fec.contb_receipt_amt > 0).value_counts()
'选取只有正的出资额的数据集'
fec = fec[fec.contb_receipt_amt > 0]
"只选取包含'Obama, Barack'和 'Romney, Mitt'的数据集,因为他们是最主要的两名候选人。"
fec_mrbo = fec[fec.cand_nm.isin(['Obama, Barack', 'Romney, Mitt'])]
根据职业和雇主统计赞助信息¶
Donation statistics by occupation and employer
基于职业的赞助信息统计是另一种经常被研究的统计任务。例如,律师们更倾向于资助民主党,而企业主则更倾向于资助共和党。
'根据职业计算出资总额'
fec.contbr_occupation.value_counts()[:10]
'许多职业都涉及相同的基本工作类型,下面代码可以清理这样的数据(将一个职业信息映射到另一个)。注意:这里巧妙的运用了dict.get,'
'他允许没有映射关系的职业也能‘通过’'
occ_mapping = {
'INFORMATION REQUESTED PER BEST EFFORTS' : 'NOT PROVIDED',
'INFORMATION REQUESTED' : 'NOT PROVIDED',
'INFORMATION REQUESTED (BEST EFFORTS)' : 'NOT PROVIDED',
'C.E.O.': 'CEO'
}
# 如果没有提供相关映射,则返回x
f = lambda x: occ_mapping.get(x, x)
fec.contbr_occupation = fec.contbr_occupation.map(f)
emp_mapping = {
'INFORMATION REQUESTED PER BEST EFFORTS' : 'NOT PROVIDED',
'INFORMATION REQUESTED' : 'NOT PROVIDED',
'SELF' : 'SELF-EMPLOYED',
'SELF EMPLOYED' : 'SELF-EMPLOYED',
}
# 如果没有提供相关映射,则返回x
f = lambda x: emp_mapping.get(x, x)
fec.contbr_employer = fec.contbr_employer.map(f)
'通过pivot_table根据 党派 和 职业 对数据进行聚合'
by_occupation = fec.pivot_table('contb_receipt_amt',
index='contbr_occupation',
columns='party', aggfunc='sum')
'选对两个党派总赞助大于 2000000 的 职业'
over_2mm = by_occupation[by_occupation.sum(1) > 2000000]
over_2mm
'做成柱状图更加清楚'
over_2mm.plot(kind='barh')
def get_top_amounts(group, key, n=5):
# 根据 key 对 group 分组, 并对 'contb_receipt_amt' 数据进行分组 sum
totals = group.groupby(key)['contb_receipt_amt'].sum()
# 返回赞助最大的前n个 职业
return totals.sort_values(ascending=False)[:n]
'根据 候选人 + 职业 进行聚合'
grouped = fec_mrbo.groupby('cand_nm')
'注意这里可以看成是一个类的实例化,他的第一个参数 group 类似于 self ,默认会省略这个参数的传入。'
grouped.apply(get_top_amounts, 'contbr_occupation', n=7) # 二次分组(grouped已经是一个分组的数据)
'根据 候选人 + 雇主 进行聚合'
grouped.apply(get_top_amounts, 'contbr_employer', n=10)
对出资额分组¶
Bucketing donation amounts
'还可以对该数据做另一种非常实用的分析:利用cut函数根据出资额的大小将数据离散化到多个面元中'
bins = np.array([0, 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000])
labels = pd.cut(fec_mrbo.contb_receipt_amt, bins)
labels
'根据候选人姓名和面元标签对数据进行分组'
grouped = fec_mrbo.groupby(['cand_nm', labels])
'统计分组后的 数量'
grouped.size().unstack(0)
'对出资额求和并在面元内规格化,以便图形化显示两位候选人各种赞助额度的比例:'
bucket_sums = grouped.contb_receipt_amt.sum().unstack(0)
bucket_sums
'对上式数据规范化'
normed_sums = bucket_sums.div(bucket_sums.sum(axis=1), axis=0)
normed_sums
'画图:这里排除了2个最大的面元,因为这些不是有个人捐献的。'
normed_sums[:-2].plot(kind='barh', stacked=True)
当然,还可以对该分析过程做许多的提炼和改进。比如说,可以根据赞助人的姓名和邮编对数据进行聚合,以便找出哪些人进行了多次小额捐款,哪些人又进行了一次或多次大额捐款。我强烈建议你下载这些数据并自己摸索一下。
根据州统计赞助信息¶
Donation statistics by state
grouped = fec_mrbo.groupby(['cand_nm', 'contbr_st'])
totals = grouped.contb_receipt_amt.sum().unstack(0).fillna(0)
totals = totals[totals.sum(1) > 100000]
totals[:10]
percent = totals.div(totals.sum(1), axis=0)
percent[:10]
'下面的内容本来应该是有一大段代码(内容应该是把各州赞助的总额从地图上绘出来),但是本文档却没有给出任何信息。而且翻译的作者也说跑不出来这段代码,因此这里忽略了。'

浙公网安备 33010602011771号