利用Python进行数据分析:数据聚合与分组运算(基于DataFrame)

利用Python进行数据分析:数据聚合与分组运算

在将数据集加载、融合、准备好之后,通常就是计算分组统计或生成透视表。pandas提供了一个灵活高效的groupby功能,对数据集进行切片、切块、摘要等操作。

  • 使用一个或多个键(形式可以是函数、数组或DataFrame列名)分割pandas对象。
  • 计算分组的概述统计,比如数量、平均值或标准差,或是用户定义的函数。
  • 应用组内转换或其他运算,如规格化、线性回归、排名或选取子集等。
  • 计算透视表或交叉表。
  • 执行分位数分析以及其它统计分组分析。

GroupBy机制

下图为pandas的GroupBy机制的简单示意图:

  • split: 根据提供的一个或多个键拆分pandas对象(如,Series、DataFrame)
  • apply: 将一个函数应用到各个分组并产生一个新值
  • combine: 将函数执行结果合并到最终对象中

在这里插入图片描述

# 导入包
import pandas as pd
import numpy as np
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)})
df
key1key2data1data2
0aone0.1155820.283518
1atwo-0.413824-0.333613
2bone-0.138182-0.462852
3btwo0.8876250.756710
4aone1.404586-0.084644

对分组进行迭代

GroupBy对象支持迭代,可以产生一组二元元组(由分组名和数据块组成)。可以对数据片段执行自定义操作。

for (k1,k2), group in df.groupby(['key1','key2']):
    print((k1,k2))
    print(group)
('a', 'one')
  key1 key2     data1     data2
0    a  one  0.115582  0.283518
4    a  one  1.404586 -0.084644
('a', 'two')
  key1 key2     data1     data2
1    a  two -0.413824 -0.333613
('b', 'one')
  key1 key2     data1     data2
2    b  one -0.138182 -0.462852
('b', 'two')
  key1 key2     data1    data2
3    b  two  0.887625  0.75671

groupby默认是在axis=0上进行分组的,通过设置也可以在其他任何轴上进行分组。拿上面例子中的df来说,我们可以根据dtype对列进行分组:

grouped = df.groupby(df.dtypes, axis=1)
for dtype, group in grouped:
    print(dtype)
    print(group)
float64
      data1     data2
0  0.115582  0.283518
1 -0.413824 -0.333613
2 -0.138182 -0.462852
3  0.887625  0.756710
4  1.404586 -0.084644
object
  key1 key2
0    a  one
1    a  two
2    b  one
3    b  two
4    a  one

分组方式

通过列名分组

通常,分组信息就位于相同的要处理DataFrame中。上一小节中的示例即是将列名(可以是字符串、数字或其他Python对象)用作分组键。

通过字典进行分组

现在,假设已知列的分组关系,并希望根据分组计算列的和:

people = pd.DataFrame(np.random.randn(5, 5),
                      columns=['a', 'b', 'c', 'd', 'e'],
                      index=['Joe', 'Steve', 'Wes', 'Jim', 'Travis'])
people
abcde
Joe0.577199-0.0176740.8224950.236686-0.186748
Steve0.403552-1.1628060.123285-0.534835-1.333789
Wes2.398688-1.2689450.1345791.2041430.569019
Jim0.4932041.2427340.3995001.080515-3.530037
Travis0.691376-0.2396791.5425021.4453700.035521

可以通过字典指定列的分组关系:

mapping = {'a': 'red', 'b': 'red', 'c': 'blue',
           'd': 'blue', 'e': 'red', 'f' : 'orange'}
people.groupby(mapping, axis =1).sum()
bluered
Joe1.0591810.372778
Steve-0.411549-2.093043
Wes1.3387221.698763
Jim1.480015-1.794099
Travis2.9878720.487218

通过函数进行分组

任何被当做分组键的函数都会在各个索引值上被调用一次,其返回值就会被用作分组名称。比如,在上一小节的DataFrame示例中,其索引值为人名,可以根据其字符串长度进行分组,传入len函数即可:

people.groupby(len).sum()
abcde
3-3.8757642.6032522.365206-2.1318220.033895
50.1978230.324744-0.4656131.0056410.491256
61.530411-0.863593-1.362848-0.701741-0.307557

此外,也可以将函数跟数组、列表、字典、Series混合使用。

通过索引级别分组

对于层次化索引数据集,可以根据轴索引的一个级别进行聚合:

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)
hier_df
ctyUSJP
tenor13513
0-1.2304041.1828420.0914813.0295580.422223
1-0.8484261.463393-0.220321-0.5764341.587866
20.036692-1.0201260.2477830.460074-0.047801
3-0.275390-0.554516-1.3011360.0792350.502412

要根据级别分组,使用level关键字传递级别序号或名字:

hier_df.groupby(level='cty', axis=1).count()
ctyJPUS
023
123
223
323

数据聚合

聚合指的是任何能够从数组产生标量值的数据转换过程。

选项说明
count分组中非NA值的数量
nunique分组中非重复值的数量(NA值也统计)
unique分组中非重复值(NA值也统计)
sum非NA值的和
mean非NA值的平均值
median非NA值的算术中位数
std、var无偏(分母为n-1)标准差和方差
min、max非NA值的最小值和最大值
prod非NA值的积
first、last非NA值的第一个和最后一个

下面具体列出一些常用示例:

统计数量

统计分组后,各组内数量:GroupBy的size方法,它可以返回一个含有分组大小的Series。

比如,以下使用key1、key2列对df进行分组,并统计每组记录数量:

df
key1key2data1data2
0aone-0.2137400.082804
1atwo-0.871891-0.232010
2bone0.4549730.246116
3btwo-0.7024331.957607
4aone-1.1361741.726216
df.groupby(['key1','key2']).size()
key1  key2
a     one     2
      two     1
b     one     1
      two     1
dtype: int64

使用reset_index()可以将结果转化为DataFrame形式:

df.groupby(['key1','key2']).size().reset_index()
key1key20
0aone2
1atwo1
2bone1
3btwo1

描述统计

df.groupby('key1')['data1'].describe()
countmeanstdmin25%50%75%max
key1
a3.0-0.7406010.475025-1.136174-1.004032-0.871891-0.542815-0.213740
b2.0-0.1237300.818410-0.702433-0.413082-0.1237300.1656210.454973

组内排名及排序后筛选

比如,以下记录为学生英语四六级考试成绩数据,选取学号(xh)、考试时间(kssj)、英语级别(yyjb)、总分(zf)四列,并筛选少量记录用于示例:

score = pd.read_csv('xs_yysljkscjsj.csv', usecols=['xh','kssj','yyjb','zf'],low_memory=False)
# 仅保留英语四六级考试成绩
score = score[score.yyjb.isin(['CET4','CET6'])]
# 随机选择5名学生
score = score[score.xh.isin(score.xh.sample(5))]
score.sort_values('xh').head(10)
yyjbxhkssjzf
46568CET40992447201006488
49582CET60992447201012390
58822CET60992447201106396
64551CET60992447201112421
80369CET60992447201212416
127382CET61143140201412356
109293CET61143140201312319
118854CET61143140201406333
92735CET61143140201212327
75478CET41143140201206439

对于同一名学生、同一外语水平考试,存在多次考试的情况:

score.groupby(['xh','yyjb']).size()
xh        yyjb
0992447   CET4    1
          CET6    4
1143140   CET4    1
          CET6    5
1932108   CET4    1
          CET6    2
1957104   CET4    1
          CET6    3
q0151111  CET6    1
dtype: int64

场景一: 每名学生、每门考试仅保留最高成绩记录。可以先根据总分进行排序,后执行分组及数据聚合操作。

score.sort_values('zf',ascending=False).groupby(['xh','yyjb']).first()
kssjzf
xhyyjb
0992447CET4201006488
CET6201112421
1143140CET4201206439
CET6201412356
1932108CET4202012466
CET6202106311
1957104CET4201912500
CET6202112539
q0151111CET62005060

如果希望选取最高的几条记录,可以使用head()命令:

score.sort_values('zf',ascending=False).groupby(['xh','yyjb']).head(2)
yyjbxhkssjzf
283135CET61957104202112539
197433CET41957104201912500
279294CET61957104202106498
46568CET40992447201006488
266499CET41932108202012466
75478CET41143140201206439
64551CET60992447201112421
80369CET60992447201212416
127382CET61143140201412356
118854CET61143140201406333
276130CET61932108202106311
251893CET6q01511112005060
282211CET619321082021120

场景二:每名学生、每门考试仅保留最新的一次成绩记录。可以先根据时间戳进行排序,后执行分组及数据聚合操作。

score.sort_values('kssj',ascending = False).groupby(['xh','yyjb']).first()
kssjzf
xhyyjb
0992447CET4201006488
CET6201212416
1143140CET4201206439
CET6201412356
1932108CET4202012466
CET62021120
1957104CET4201912500
CET6202112539
q0151111CET62005060

场景三:计算每门考试的成绩排名。根据英语级别分组后,在组内使用rank()函数。

score['rank'] = score.groupby('yyjb')['zf'].rank(ascending = False)
score[score.yyjb == 'CET4']
yyjbxhkssjzfrank
46568CET409924472010064882.0
75478CET411431402012064394.0
197433CET419571042019125001.0
266499CET419321082020124663.0

多函数应用

希望对不同的列使用不同的聚合函数,或一次应用多个函数,需要使用agg()方法。

一次应用多个函数:如果传入一组函数或函数名,得到的DataFrame的列就会以相应的函数命名:

grouped = score.groupby('yyjb')
functions = ['mean', 'median', 'count']
grouped['zf'].agg(functions)
meanmediancount
yyjb
CET4473.254774
CET6316.2035615

如果传入的是一个由(name,function)元组组成的列表,则各元组的第一个元素就会被用作DataFrame的列名(可以将这种二元元组列表看做一个有序映射):

tfunctions = [('平均分','mean'),('中位数','median'), ('记录数','count')]
grouped['zf'].agg(tfunctions)
平均分中位数记录数
yyjb
CET4473.254774
CET6316.2035615

对不同的列应用不同的函数。具体的办法是向agg传入一个从列名映射到函数的字典:

grouped.agg({
    'zf': ['max','min','mean'],
    'kssj': 'nunique'
})
zfkssj
maxminmeannunique
yyjb
CET4500439473.254
CET65390316.2012

apply: 一般性的“拆分-应用-合并”

自定义函数,通过apply命令应用于各分组。

用特定于分组的值填充缺失值

假设你需要对不同的分组填充不同的值。一种方法是将数据分组,并使用apply和一个能够对各数据块调用fillna的函数即可。

states = ['Ohio', 'New York', 'Vermont', 'Florida',
          'Oregon', 'Nevada', 'California', 'Idaho']
regions = ['East'] * 4 + ['West'] * 4
data = pd.DataFrame({'data':np.random.randn(8),
                     'region':regions},
                    index=states)
data.loc[['Vermont', 'Nevada', 'Idaho'],'data'] = np.nan
data
dataregion
Ohio1.393924East
New York-0.522025East
VermontNaNEast
Florida0.484822East
Oregon1.547784West
NevadaNaNWest
California-0.953454West
IdahoNaNWest

使用同一地区的平均值填补缺失值,可以使用"region"字段分组,再用分组平均值填充NA值,定义缺失值填充函数:

fill_mean = lambda g:g.fillna(g.mean())
data.groupby('region').apply(fill_mean)
dataregion
region
EastOhio1.393924East
New York-0.522025East
Vermont0.452240East
Florida0.484822East
WestOregon1.547784West
Nevada0.297165West
California-0.953454West
Idaho0.297165West

从上面的例子中可以看出,分组键会跟原始对象的索引共同构成结果对象中的层次化索引。将group_keys=False传入groupby即可禁止该效果:

data.groupby('region',group_keys=False).apply(fill_mean)
dataregion
Ohio1.393924East
New York-0.522025East
Vermont0.452240East
Florida0.484822East
Oregon1.547784West
Nevada0.297165West
California-0.953454West
Idaho0.297165West

用特定于分组的值矫正记录

另一个类似于上一小节的应用场景时,数据在填写或转录过程存在错误或缺失,希望使用同一组内最高频出现的值填补缺失值:

data['region_code'] = [1,1,1,np.nan,0,0,0,0]
data
dataregionregion_code
Ohio1.393924East1.0
New York-0.522025East1.0
VermontNaNEast1.0
Florida0.484822EastNaN
Oregon1.547784West0.0
NevadaNaNWest0.0
California-0.953454West0.0
IdahoNaNWest0.0

下面希望,使用同一"region"出现次数最多的"region_code"对缺失值进行填充:

fill_mod = lambda g:g.fillna(g.value_counts().index[0])
data.groupby('region')['region_code'].apply(fill_mod)
Ohio          1.0
New York      1.0
Vermont       1.0
Florida       1.0
Oregon        0.0
Nevada        0.0
California    0.0
Idaho         0.0
Name: region_code, dtype: float64

有的时候,存在数据填写的错误,比如如下Oregon的区域码填写成了1。

data['region_code'] = [1,1,1,1,1,0,0,0]
data
dataregionregion_code
Ohio1.393924East1
New York-0.522025East1
VermontNaNEast1
Florida0.484822East1
Oregon1.547784West1
NevadaNaNWest0
California-0.953454West0
IdahoNaNWest0

下面希望,同一"region"的"region_code"应保持一致,注意此处使用的是transform功能:

data.groupby('region')['region_code'].transform(lambda g:g.value_counts().index[0])
Ohio          1
New York      1
Vermont       1
Florida       1
Oregon        0
Nevada        0
California    0
Idaho         0
Name: region_code, dtype: int64

分组加权平均

根据groupby的“拆分-应用-合并”范式,可以进行DataFrame的列与列之间或两个Series之间的运算(比如分组加权平均)。

 df = pd.DataFrame({'category': ['a', 'a', 'a', 'a',
                                 'b', 'b', 'b', 'b'],
                    'data': np.random.randn(8),
                    'weights': np.random.rand(8)})
df
categorydataweights
0a0.4421230.173591
1a-0.0330890.269083
2a0.8131260.440622
3a-0.6535770.526061
4b-0.3634920.471360
5b0.7595730.619617
6b0.3015650.453474
7b-1.9083210.596430

然后可以利用category计算分组加权平均数:

def get_wavg(g,value,weight):
    return np.average(g[value], weights=g[weight])
grouped = df.groupby('category')

注意下方为在apply函数中传入参数的方法:

grouped.apply(get_wavg,'data','weights')
category
a    0.058399
b   -0.327958
dtype: float64

往期:
利用Python进行数据分析:准备工作
利用Python进行数据分析:缺失数据(基于DataFrame)
利用Python进行数据分析:数据转换(基于DataFrame)
利用Python进行数据分析:数据规整(基于DataFrame)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值