Pandas基础教程学习(三)

第3章 Pandas分组

Pandas基础(三):分组、聚合、变换、过滤

DataWhale第十二期组队学习:python Pandas

文章目录

  • 第3章 Pandas分组
    • 导入数据集
    • 一、groupby函数
      • 1.1 分组函数的基本内容
      • 1.2 groupby对象的特点
    • 二、聚合、变换和过滤
      • 2.1 聚合
      • 2.2 过滤
      • 2.3 变换
    • 三、apply函数

导入数据集

本文所使用的数据集如下所示,获取链接:joyful-pandas

import numpy as np
import pandas as pd
df = pd.read_csv('data/table.csv',index_col='ID')
df.head(10)
School Class Gender Address Height Weight Math Physics
ID
1101 S_1 C_1 M street_1 173 63 34.0 A+
1102 S_1 C_1 F street_2 192 73 32.5 B+
1103 S_1 C_1 M street_2 186 82 87.2 B+
1104 S_1 C_1 F street_2 167 81 80.4 B-
1105 S_1 C_1 F street_4 159 64 84.8 B+
1201 S_1 C_2 M street_5 188 68 97.0 A-
1202 S_1 C_2 F street_4 176 94 63.5 B-
1203 S_1 C_2 M street_6 160 53 58.8 A+
1204 S_1 C_2 F street_5 162 63 33.8 B
1205 S_1 C_2 F street_6 167 63 68.4 B-

该数据集包括了一个学生的以下信息

学校、班级、性别、地址、身高、体重、数学、物理

一、groupby函数

分组操作涉及拆分对象应用函数以及重组结果三种,或是它们的组合。

这可用于对大量数据进行分组并在这些组上进行计算操作, 经过groupby后会生成一个groupby对象,该对象本身不会返回任何东西,只有当相应的方法被调用才会起作用

1.1 分组函数的基本内容

通过学生的学校进行分组,得到一个groupby对象grouped_single(后续会用到),并通过groups属性显示分组的信息(学校1和学校2)

grouped_single = df.groupby('School')
grouped_single.groups 
{'S_1': Int64Index([1101, 1102, 1103, 1104, 1105, 1201, 1202, 1203, 1204, 1205, 1301,
             1302, 1303, 1304, 1305],
            dtype='int64', name='ID'),
 'S_2': Int64Index([2101, 2102, 2103, 2104, 2105, 2201, 2202, 2203, 2204, 2205, 2301,
             2302, 2303, 2304, 2305, 2401, 2402, 2403, 2404, 2405],
            dtype='int64', name='ID')}

知道了分组的信息之后,如果要取出一个组,比如显示学校1分组信息,可以通过get_group属性来查看。结果是一个DataFrame

grouped_single.get_group('S_1')

如果我想要查看学校2班级4的学生信息,这里的grouped_mul后续也会用到,其中的每一个分组都对应一个学校的一个班级

grouped_mul = df.groupby(['School','Class'])
grouped_mul.get_group(('S_2','C_4'))

当分组的要求和信息多起来之后,如果我们要查看整体的信息,比如有几个组每个组多少样本

# 查看每个组各有多少个样本
grouped_single.size()
grouped_mul.size()

# 查看总共有几个组
grouped_single.ngroups	# 2
grouped_mul.ngroups    	# 7

如果我们想要遍历一个组,该如何操作?

其中name对应的是我们的组名,是一个字符串,group就是每个组对应的DataFrame

for name,group in grouped_single:
    print(name)
    display(group.head())

那那那如果是多级索引呢,可以看看下面这个例子。如果和之前一样还是通过学校进行分类,那么每一个类别下,比如学校1类别下,每一个样本都具有两个标签。

简单来说,就是多级索引的时候要多设置一个level标签来决定以哪级索引为准进行分组

# 先把原来的df设定为多级索引
df.set_index(['Gender','School'])
# 以学校进行分类
df.set_index(['Gender','School']).groupby(level=1)
# 以性别进行分类
df.set_index(['Gender','School']).groupby(level=0)

1.2 groupby对象的特点

之前提到,groupby函数会返回一个groupby对象,那这个对象有哪些可以用的方法呢,前面几个是对应的属性,后面的就是对应的方法了

print([attr for attr in dir(grouped_single) if not attr.startswith('_')])
['Address', 'Class', 'Gender', 'Height', 'Math', 'Physics', 'School', 'Weight', 'agg', 'aggregate', 'all', 'any', 'apply', 'backfill', 'bfill', 'boxplot', 'corr', 'corrwith', 'count', 'cov', 'cumcount', 'cummax', 'cummin', 'cumprod', 'cumsum', 'describe', 'diff', 'dtypes', 'expanding', 'ffill', 'fillna', 'filter', 'first', 'get_group', 'groups', 'head', 'hist', 'idxmax', 'idxmin', 'indices', 'last', 'mad', 'max', 'mean', 'median', 'min', 'ndim', 'ngroup', 'ngroups', 'nth', 'nunique', 'ohlc', 'pad', 'pct_change', 'pipe', 'plot', 'prod', 'quantile', 'rank', 'resample', 'rolling', 'sem', 'shift', 'size', 'skew', 'std', 'sum', 'tail', 'take', 'transform', 'tshift', 'var']

这里与普通的DataFrame对象不一样的,如果使用head函数,返回的是每个组的前几行,而不是数据集的前几行,另外first显示的是以分组为索引的每组的第一个分组信息

grouped_single.head(2)
grouped_single.first()

之前我们的分组是通过自身的例如学校、班级这样的属性来进行分组的,但对于groupby函数而言,分组的依据是非常自由的,只要是与数据框长度相同的列表即可,同时支持函数型分组

比如我们用abc来给这些数据进行分组,每一条都可以归为其中一种

例如这里就是随机生成了一个长度与数据长度相同的,包含了abc其中一个的列表。并以此分组

df.groupby(np.random.choice(['a','b','c'],df.shape[0]))

也可以通过一样的get_group方式来进行查看

df.groupby(np.random.choice(['a','b','c'],df.shape[0])).get_group('a')

之前提到了分组也可以通过函数进行分组,通过函数进行分组时,传入的对象就是索引

# x就是每一条数据的索引,这里将其打印出来
df[:5].groupby(lambda x:print(x)).head()
# 1101 1102 1103 1104 1105

如果是多层索引,那么函数型分组时,传入的就是元组

也可以根据奇偶行来进行分组

  • if条件为真返回前面的内容,为假返回后面的内容

  • if not条件为真返回后面的内容,为假返回前面的内容

df.groupby(lambda x:'奇数行' if not df.index.get_loc(x)%2==1 else '偶数行').groups

我们的groupby对象中有数据自身例如学校、班级这样的属性,但如果是通过方法的形式调用,即通过.的方式调用,返回的依然是一个SeriesGroupBy对象

我们可以通过groupby[]操作来选出它的某个或某几个列

下面的代码返回的就是每个分类以及一个对应的布尔值

df.groupby(['Gender','School'])['Math'].mean()>=60
Gender  School
F       S_1        True
        S_2        True
M       S_1        True
        S_2       False
Name: Math, dtype: bool

也可以选出多列

df.groupby(['Gender','School'])[['Math','Height']].mean()

如果是连续性的变量,甚至可以通过cut函数来进行分组

bins = [0,40,60,80,90,100]
cuts = pd.cut(df['Math'],bins=bins) #可选label添加自定义标签
df.groupby(cuts)['Math'].count()
Math
(0, 40]       7
(40, 60]     10
(60, 80]      9
(80, 90]      7
(90, 100]     2
Name: Math, dtype: int64

二、聚合、变换和过滤

2.1 聚合

所谓聚合,就是把一堆数变成一个标量,因此

mean/sum/size/count/std/var/sem/describe/first/last/nth/min/max都是聚合函数

假设我们现在有这样一个数据集,来自原来的grouped_single,现在仅用数学这一列,那这就是一个根据学校进行分类的学生的数学成绩的Series

group_m = grouped_single['Math']
group_m.head() # 每一组显示前5行
ID
1101    34.0
1102    32.5
1103    87.2
1104    80.4
1105    84.8
2101    83.3
2102    50.6
2103    52.5
2104    72.2
2105    34.2
Name: Math, dtype: float64

如果我们要调用单个聚合函数,可以像这样

group_m.sem()
School
S_1    5.958578
S_2    3.933088
Name: Math, dtype: float64

如果我们要调用多个聚合函数,可以像这样

group_m.agg(['sum','mean','std'])
sum mean std
School
S_1 956.2 63.746667 23.077474
S_2 1191.1 59.555000 17.589305

如果不想用summean等等作为列名,可以自己修改

group_m.agg([('rename_sum','sum'),('rename_mean','mean')])

如果不只是数学一列,而是有多列怎么办,那可以指定列和对应函数

grouped_mul.agg({
     'Math':['mean','max'],'Height':'var'})

当然也可以使用自定义的函数进行处理,agg函数的传入是分组逐列进行的,例如计算极差

grouped_single['Math'].agg(lambda x:x.max()-x.min())

这里要介绍另一个函数NamedAgg,可以指定聚合的列,可以指定输出的列,就不按照之前分组逐列进行了。

其有两个参数,一个是指定输出列名,一个指定要使用的方法,例如这个官网中的例子

In [79]: animals = pd.DataFrame({
     'kind': ['cat', 'dog', 'cat', 'dog'],
   ....:                         'height': [9.1, 6.0, 9.5, 34.0],
   ....:                         'weight': [7.9, 7.5, 9.9, 198.0]})
   ....: 

In [80]: animals
Out[80]: 
  kind  height  weight
0  cat     9.1     7.9
1  dog     6.0     7.5
2  cat     9.5     9.9
3  dog    34.0   198.0

In [81]: animals.groupby("kind").agg(
   ....:     min_height=pd.NamedAgg(column='height', aggfunc='min'),
   ....:     max_height=pd.NamedAgg(column='height', aggfunc='max'),
   ....:     average_weight=pd.NamedAgg(column='weight', aggfunc=np.mean),
   ....: )
   ....: 
Out[81]: 
      min_height  max_height  average_weight
kind                                        
cat          9.1         9.5            8.90
dog          6.0        34.0          102.75  

2.2 过滤

filter函数的作用就是返回一个DataFrame的副本,并且通过布尔值判断删除掉那些不满足要求的数据。

比如下面这个,先按照学校分组,然后取出数学、物理两列,针对每组每列的数学成绩进行一个布尔表达式的判断,然后把每一组的所有布尔值进行“与”操作,最终每组(即每个学校)都有一个布尔值。(这个值表达什么我也不知道)

grouped_single[['Math','Physics']].filter(lambda x:(x['Math']>32).all())

2.3 变换

transform就是通过传入的函数对DataFrame的值进行一个值的变换,按照分组分列进行

# 两列都减去均值
grouped_single[['Math','Height']].transform(lambda x:x-x.min())

如果是一个标量值,那么组内所有的元素都会被广播为这个值

# 这两列都变成均值
grouped_single[['Math','Height']].transform(lambda x:x.mean())

我们可以用这个方法来进行组内的标准化

grouped_single[['Math','Height']].transform(lambda x:(x-x.mean())/x.std())

也可以用这个方法来填充组内的缺失值

# 先生成一个有缺失值的数据表
df_nan = df[['Math','School']].copy().reset_index()
df_nan.loc[np.random.randint(0,df.shape[0],25),['Math']]=np.nan
# 填充缺失值
df_nan.groupby('School').transform(lambda x: x.fillna(x.mean()))

三、apply函数

apply也是一种通过函数来对DataFrame进行操作的函数

传递给apply的函数必须以dataframe作为其第一个参数,并返回dataframeSeriesscalar。然后,apply将负责将结果重新组合到单个dataframeSeries中。因此,apply是一种高度灵活的分组方法

虽然apply是一种非常灵活的方法,但它的缺点是使用它可能比使用aggtransform等更具体的方法要慢一些

apply函数的灵活性很大程度来源于其返回值的多样性

# 标量返回值
# 找到每个分组中数学和身高的最大值
df[['School','Math','Height']].groupby('School').apply(lambda x:x.max())
# 列表返回值
df[['School','Math','Height']].groupby('School').apply(lambda x:x-x.min()).head()
# 数据框返回值
df[['School','Math','Height']].groupby('School')\
    .apply(lambda x:pd.DataFrame({
     'col1':x['Math']-x['Math'].max(),
                                  'col2':x['Math']-x['Math'].min(),
                                  'col3':x['Height']-x['Height'].max(),
                                  'col4':x['Height']-x['Height'].min()}))

用apply同时统计多个指标

# 有序字典类
from collections import OrderedDict

def f(df):
    data = OrderedDict()
    data['M_sum'] = df['Math'].sum()
    data['W_var'] = df['Weight'].var()
    data['H_mean'] = df['Height'].mean()
    print(data)
    return pd.Series(data)

grouped_single.apply(f)

你可能感兴趣的:(Pandas,python,数据分析)