数据聚合与分组运算
在将数据集加载、融合、准备好之后,通常就是计算分组统计或生成透视表, pandas提供了一个灵活高效的groupby功能,它使你能以一种自然的方式对数据集进行切片、切块、摘要等操作。
GroupBy机制
分组运算"split-apply-combine"(拆分-应用-合并)。第一个阶段,pandas对象(无论是 Series、DataFrame还是其他的)中的数据会根据你所提供的一个或多个键被拆分(split)为 多组。拆分操作是在对象的特定轴上执行的。例如,DataFrame可以在其行(axis=0)或列 (axis=1)上进行分组。然后,将一个函数应用(apply)到各个分组并产生一个新值。最后,所有这些函数的执行结果会被合并(combine)到最终的结果对象中。
GroupBy机制df = pd.DataFrame({'key1' : ['a', 'a', 'b', 'b', 'a'],
'key2' : ['one', 'two', 'one', 'two', 'one'],
'data1' : np.random.randn(5),
'data2' : np.random.randn(5)})
grouped = df['data1'].groupby(df['key1'])
变量grouped是一个GroupBy对象。它实际上还没有进行任何计算,只是含有一些有关分组键 df[‘key1’]的中间数据而已
分组应用数据(Series)根据分组键进行了聚合,产生了一个新的Series,其索引为key1列中的唯一值。
一次传入多个数组的列表
means = df['data1'].groupby([df['key1'], df['key2']]).mean()
means.unstack()
传数组列表
GroupBy的size方法,它可以返回一个含有分组大小的Series
df.groupby(['key1', 'key2']).size()
size方法
对分组进行迭代
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)
多重键使用元组
可以将这些数据片段做成一个字典
数据片段做成字典可以根据dtype对列进行分组
根据类型分组选取一列或列的子集
df.groupby('key1')['data1']
df.groupby('key1')[['data2']]
for name, data in df.groupby('key1')['data1']:
print(name)
print(data)
选取一列或列的子集
对于大数据集,很可能只需要对部分列进行聚合,只需计算data2列的平均值并以DataFrame形式得到结果
计算数据多个元素的平均值通过字典或Series进行分组
people = pd.DataFrame(np.random.randn(5, 5),
columns=['a', 'b', 'c', 'd', 'e'],
index=['Joe', 'Steve', 'Wes', 'Jim', 'Travis'])
people.iloc[2:3, [1, 2]] = np.nan # Add a few NA values
mapping = {'a': 'red', 'b': 'red', 'c': 'blue', 'd': 'blue', 'e': 'red', 'f' : 'orange'}
by_column = people.groupby(mapping, axis=1)
by_column.sum()
字典分组并求和
series实现分组计数
通过函数进行分组
使用Python函数是一种更原生的方法定义分组映射。任何被当做分组键的函数都会在各个索引 值上被调用一次,其返回值就会被用作分组名称。
计算一个字符串长度的数组,更简单的方法是传入len函数
people.groupby(len).sum()
key_list = ['one', 'one', 'one', 'two', 'two']
people.groupby([len, key_list]).min()
多键分组取最小
根据索引级别分组
层次化索引数据集最方便的地方就在于它能够根据轴索引的一个级别进行聚合
columns = pd.MultiIndex.from_arrays([['US', 'US', 'US', 'JP', 'JP'],
[1, 3, 5, 1, 3]],
names=['cty', 'tenor'])
hier_df = pd.DataFrame(np.random.randn(4, 5), columns=columns)
要根据级别分组,使用level关键字传递级别序号或名字
hier_df.groupby(level='cty', axis=1).count()
索引分组
数据聚合
聚合指的是任何能够从数组产生标量值的数据转换过程,比如mean、count、min以及sum 等。
函数及说明
函数名 | 说明 |
---|---|
count | 分组中非NA值的数量 |
sum | 分组中非NA值的和 |
mean | 分组中非NA值的平均值 |
median | 分组中非NA值的算术中位数 |
std、var | 无偏(分母为n-1)标准差和方差 |
min、max | 非NA值的最小值和最大值 |
prod | 非NA值的积 |
first、last | 第一个和最后一个非NA值 |
使用你自己的聚合函数,只需将其传入aggregate或agg方法即可
聚合函数的使用面向列的多函数应用
数据准备对不同的列使用不同的聚合函数,或一次应用多个函数
grouped = tips.groupby(['day', 'smoker'])
grouped_pct = grouped['tip']
grouped_pct.agg('mean')
应用函数
如果传入一组函数或函数名,得到的DataFrame的列就会以相应的函数命名
grouped_pct.agg(['mean', 'std', peak_to_peak])
平均值和方差
传入的是一个由(name,function)元组组成的列表,则各元组的第一个元素就会被用作 DataFrame的列名
grouped_pct.agg([('foo', 'mean'), ('bar', np.std)])
image.png
想要对tip_pct和total_bill列计算三个统计信息
functions = ['count', 'mean', 'max']
result = grouped['tip_pct', 'total_bill'].agg(functions)
多列分组计算统计信息
也可以传入带有自定义名称的一组元组
ftuples = [('Durchschnitt', 'mean'),('Abweichung', np.var)]
grouped['tip_pct', 'total_bill'].agg(ftuples)
自定义统计信息名称
对一个列或不同的列应用不同的函数,具体的办法是向agg传入一个从列名映射到函数的字典
grouped.agg({'tip' : np.max, 'size' : 'sum'})
grouped.agg({'tip_pct' : ['min', 'max', 'mean', 'std'],'size' : 'sum'})
字典映射
以“没有行索引”的形式返回聚合数据
tips.groupby(['day', 'smoker'], as_index=False).mean()
原始数据聚合
对结果调用reset_index也能得到这种形式的结果。使用as_index=False方法可以避免一些不必要的计算。
小费数据集,假设你想要根据分组选出最高的5个tip_pct值。首先,编写一个选取指定列具有最大值的行的函数。
def top(df, n=5, column='tip_pct'):
return df.sort_values(by=column)[-n:]
top(tips)
选出最高的tip_pct值
对smoker分组并用该函数调用apply
tips.groupby('smoker').apply(top)
分组之后调用自定义函数
如果传给apply的函数能够接受其他参数或关键字,则可以将这些内容放在函数名后面一并传入
tips.groupby(['smoker', 'day']).apply(top, n=1, column='total_bill')
多重分组取每天消费最高的顾客
禁止分组键
分组键会跟原始对象的索引共同构成结果对象中的层次化索引。将group_keys=False传入 groupby即可禁止该效果
tips.groupby('smoker', group_keys=False).apply(top)
禁止分组
分位数和桶分析
将数据拆分成多块的工具(比如cut和qcut)。将这些函数跟groupby结合起来,就能非常轻松地实现对数据集的桶(bucket)或分位数(quantile)分析
frame = pd.DataFrame({'data1': np.random.randn(1000),
'data2': np.random.randn(1000)})
quartiles = pd.cut(frame.data1, 4)
def get_stats(group):
return {'min': group.min(), 'max': group.max(), 'count': group.count(), 'mean': group.mean()}
grouped = frame.data2.groupby(quartiles)
grouped.apply(get_stats).unstack()
cut与groupby结合实现桶分析
这些都是长度相等的桶。要根据样本分位数得到大小相等的桶,使用qcut即可
grouping = pd.qcut(frame.data1, 10, labels=False)
grouped = frame.data2.groupby(grouping)
grouped.apply(get_stats).unstack()
qcut与groupby结合实现桶分析
cut 与 qcut 的区别:
cut 划分桶的大小相同
qcut 划分桶中的容量相同
用特定于分组的值填充缺失值
假设需要对不同的分组填充不同的值。一种方法是将数据分组,并使用apply和一个能够对各数据块调用fillna的函数即可。下面是一些有关美国几个州的示例数据,这些州又被分为东部和西部
states = ['Ohio', 'New York', 'Vermont', 'Florida', 'Oregon', 'Nevada', 'California', 'Idaho']
group_key = ['East'] * 4 + ['West'] * 4
data = pd.Series(np.random.randn(8), index=states)
data[['Vermont', 'Nevada', 'Idaho']] = np.nan
data.groupby(group_key).mean()
数据准备
用分组平均值去填充NA值
fill_mean = lambda g: g.fillna(g.mean())
data.groupby(group_key).apply(fill_mean)
平均数填充NA值
预定义各组的填充值
fill_values = {'East': 0.5, 'West': -1}
fill_func = lambda g: g.fillna(fill_values[g.name])
data.groupby(group_key).apply(fill_func)
自定义值填充NA
示例:分组加权平均数和相关系数
可以进行DataFrame的列与列之间或两个Series之间的运算(比如分组加权平均)
df = pd.DataFrame({'category': ['a', 'a', 'a', 'a', 'b', 'b', 'b', 'b'],
'data': np.random.randn(8),'weights': np.random.rand(8)})
数据准备
用category计算分组加权平均数
grouped = df.groupby('category')
get_wavg = lambda g: np.average(g['data'], weights=g['weights'])
grouped.apply(get_wavg)
加权平均数
Yahoo!Finance的数据集,其中含有几只股票和标准普尔500指数(符号SPX)的收盘价
close_px = pd.read_csv('examples/stock_px_2.csv', parse_dates=True, index_col=0)
数据展示
计算一个由日收益率(通过百分数变化计算)与SPX之间的年度相关系数组成的DataFrame。 创建一个函数,用它计算每列和SPX列的成对相关系数:
spx_corr = lambda x: x.corrwith(x['SPX'])
rets = close_px.pct_change().dropna()
get_year = lambda x: x.year
by_year = rets.groupby(get_year)
by_year.apply(spx_corr)
关系系数
计算Apple和Microsoft的年相关系数
by_year.apply(lambda g: g['AAPL'].corr(g['MSFT']))
Apple和Microsoft的年相关系数
透视表和交叉表
小费数据集,假设我想要根据day和smoker计算分组平均数(pivot_table的默认聚合类型), 并将day和smoker放到行上
tips.pivot_table(index=['day', 'smoker'])
计算分组平均数
只想聚合tip_pct和size,而且想根据time进行分组。我将smoker放到列上,把day放到行上
tips.pivot_table(['tip_pct', 'size'], index=['time', 'day'],columns='smoker')
聚合分组
传入margins=True添加分项小计。这将会添加标签为All的行和列,其值对应于单个等级中所有数据的分组统计
tips.pivot_table(['tip_pct', 'size'], index=['time', 'day'],columns='smoker', margins=True)
tips.pivot_table('tip_pct', index=['time', 'smoker'], columns='day',aggfunc=len, margins=True)
添加分项小计
交叉表(cross-tabulation,简称crosstab)是一种用于计算分组频率的特殊透视表。
根据国籍和用手习惯对这段数据进行统计汇总
pd.crosstab(data.Nationality, data.Handedness, margins=True)