7. 累计和分组

对大数据进行分析的时候,一项基本工作就是数据累计(summarization),通常包括:

  • sum: 求和
  • mean:平均数
  • median:中位数
  • min:最小值
  • max:最大值
  • count:计数
  • first/last: 第一项和最后一项
  • std: 标准差
  • var:方差
  • mad:均值绝对方差
  • prod:所有项乘积
# 准备数据
# 我们这次准备数据时候用seaborn提供的行星数据,包括天文学家观测到的围绕恒星运行的行星数据
# 下载地址:
#  https://github.com/mwaskom/seaborn-data
# Github的原因,下载很慢,可能会出现传输链接错误,需要多试几遍才行

import seaborn as sns

# Planets是行星观测数据,包括观测方法,数量,轨道周期等待
planets = sns.load_dataset('planets')
print(planets.shape)

# 显示数据头部
print(planets.head())
(1035, 6)
            method  number  orbital_period   mass  distance  year
0  Radial Velocity       1         269.300   7.10     77.40  2006
1  Radial Velocity       1         874.774   2.21     56.95  2008
2  Radial Velocity       1         763.000   2.60     19.84  2011
3  Radial Velocity       1         326.030  19.40    110.62  2007
4  Radial Velocity       1         516.220  10.50    119.47  2009

7.1. describe函数

describe函数计算每一列的常用统计值,给出一个比较概括性的数值。

通过下面describe计算的数据,我们可以对数据总体做出一个大概的判断,比如各个列的均值,最大值,均值等等。

在统计之前,我们需要删除掉缺失值以免影响最后结果。

d = planets.dropna().describe()
print(d)
          number  orbital_period        mass    distance         year
count  498.00000      498.000000  498.000000  498.000000   498.000000
mean     1.73494      835.778671    2.509320   52.068213  2007.377510
std      1.17572     1469.128259    3.636274   46.596041     4.167284
min      1.00000        1.328300    0.003600    1.350000  1989.000000
25%      1.00000       38.272250    0.212500   24.497500  2005.000000
50%      1.00000      357.000000    1.245000   39.940000  2009.000000
75%      2.00000      999.600000    2.867500   59.332500  2011.000000
max      6.00000    17337.500000   25.000000  354.000000  2014.000000

7.2. GroupBy

GroupBy借用的是SQL的命令,通过GroupBy可以对数据进行灵活的几乎各种操作, 其核心思想是:

  • split:分割
  • applay:应用
  • combine:组合

通过运用GroupBy命令和不同的累计函数进行组合使用,对某些标签或索引进行累计分析。

# 小案例
import pandas as pd

df = pd.DataFrame({"name":list("ABCABC"), "data":range(100,106)}, columns=["name", "data"])

print("df = \n", df)

# 按name列进行分组,然后求魅族的平均数
a = df.groupby("name").mean()
print("\n a = \n", a)
df = 
   name  data
0    A   100
1    B   101
2    C   102
3    A   103
4    B   104
5    C   105

 a = 
        data
name       
A     101.5
B     102.5
C     103.5

7.3. GroupBy对象

GroupBy返回的结果是一个抽象类型,可以看组是一个DataFrame的集合。

7.3.1. 按列取值返回的结果

# 按method列进行组合

gb = planets.groupby("method")

# gb是一个抽象数据类型
print(gb)

# 对GroupBy结果还可以再次抽取数
print(gb["orbital_period"])
<pandas.core.groupby.groupby.DataFrameGroupBy object at 0x7f755623f5f8>
<pandas.core.groupby.groupby.SeriesGroupBy object at 0x7f755623fa90>
# 下面句子相当于按找method进行组合,然后选取orbital_period列,选取后求每个组的中位数

a = planets.groupby("method")['orbital_period'].median()
print("组合后统计结果: \n", a)
组合后统计结果: 
 method
Astrometry                         631.180000
Eclipse Timing Variations         4343.500000
Imaging                          27500.000000
Microlensing                      3300.000000
Orbital Brightness Modulation        0.342887
Pulsar Timing                       66.541900
Pulsation Timing Variations       1170.000000
Radial Velocity                    360.200000
Transit                              5.714932
Transit Timing Variations           57.011000
Name: orbital_period, dtype: float64

7.3.2. 按组迭代

GroupBy对象支持进行迭代,返回每一组都是Series或者DataFrame数据。

# 迭代回来的数据大概是每组以method作为名称,多行六列的一个DataFrame

for (method, group) in planets.groupby("method"):
    print(method, group.shape)
    
Astrometry (2, 6)
Eclipse Timing Variations (9, 6)
Imaging (38, 6)
Microlensing (23, 6)
Orbital Brightness Modulation (3, 6)
Pulsar Timing (5, 6)
Pulsation Timing Variations (1, 6)
Radial Velocity (553, 6)
Transit (397, 6)
Transit Timing Variations (4, 6)

7.3.3. 调用方法

借助于Python强大的类方法(@classmethod), 可以直接对GroupBy的每一组对象添加功能,无论是DataFrame或者Series都可以使用。

a = planets.groupby("method")['year'].describe().unstack()
print(a)
       method                       
count  Astrometry                          2.000000
       Eclipse Timing Variations           9.000000
       Imaging                            38.000000
       Microlensing                       23.000000
       Orbital Brightness Modulation       3.000000
       Pulsar Timing                       5.000000
       Pulsation Timing Variations         1.000000
       Radial Velocity                   553.000000
       Transit                           397.000000
       Transit Timing Variations           4.000000
mean   Astrometry                       2011.500000
       Eclipse Timing Variations        2010.000000
       Imaging                          2009.131579
       Microlensing                     2009.782609
       Orbital Brightness Modulation    2011.666667
       Pulsar Timing                    1998.400000
       Pulsation Timing Variations      2007.000000
       Radial Velocity                  2007.518987
       Transit                          2011.236776
       Transit Timing Variations        2012.500000
std    Astrometry                          2.121320
       Eclipse Timing Variations           1.414214
       Imaging                             2.781901
       Microlensing                        2.859697
       Orbital Brightness Modulation       1.154701
       Pulsar Timing                       8.384510
       Pulsation Timing Variations              NaN
       Radial Velocity                     4.249052
       Transit                             2.077867
       Transit Timing Variations           1.290994
                                           ...     
50%    Astrometry                       2011.500000
       Eclipse Timing Variations        2010.000000
       Imaging                          2009.000000
       Microlensing                     2010.000000
       Orbital Brightness Modulation    2011.000000
       Pulsar Timing                    1994.000000
       Pulsation Timing Variations      2007.000000
       Radial Velocity                  2009.000000
       Transit                          2012.000000
       Transit Timing Variations        2012.500000
75%    Astrometry                       2012.250000
       Eclipse Timing Variations        2011.000000
       Imaging                          2011.000000
       Microlensing                     2012.000000
       Orbital Brightness Modulation    2012.000000
       Pulsar Timing                    2003.000000
       Pulsation Timing Variations      2007.000000
       Radial Velocity                  2011.000000
       Transit                          2013.000000
       Transit Timing Variations        2013.250000
max    Astrometry                       2013.000000
       Eclipse Timing Variations        2012.000000
       Imaging                          2013.000000
       Microlensing                     2013.000000
       Orbital Brightness Modulation    2013.000000
       Pulsar Timing                    2011.000000
       Pulsation Timing Variations      2007.000000
       Radial Velocity                  2014.000000
       Transit                          2014.000000
       Transit Timing Variations        2014.000000
Length: 80, dtype: float64

7.4. 累计,过滤和转换

GroupBy基本功能是分组,但分组之后相应的操作,为数据分析提供了很多高效的方法。 此类方法大概分为:

  • aggregate:累计
  • filter:过滤
  • transform:变换
  • apply:应用
## 数据准备
import numpy as np
import pandas as pd

rng = np.random.RandomState(0)
df = pd.DataFrame({'key':list("ABCABC"),
                  'data_1':range(100,106),
                  'data_2': rng.randint(0,10, 6)},
                  columns=['key', 'data_1', 'data_2'])
print("df = \n", df)
df = 
   key  data_1  data_2
0   A     100       5
1   B     101       0
2   C     102       3
3   A     103       3
4   B     104       7
5   C     105       9

7.4.1. 累计

相比较于sum和median之类的功能,累计(aggregate)能实现比较复杂的操作,代表操作的参数可以是字符串,函数或者函数列表,并且能一次性计算所有累计值。

# 统计min,median,max
# 作为代表功能的参数,都放入一个列表中,可以是字符串,函数名等
rst = df.groupby('key').aggregate(['min', np.median, np.max])
print(rst)
    data_1             data_2            
       min median amax    min median amax
key                                      
A      100  101.5  103      3    4.0    5
B      101  102.5  104      0    3.5    7
C      102  103.5  105      3    6.0    9
# 或者还有其他的方法,对每列数据用不同的统计函数

rst = df.groupby('key').aggregate({'data_1':'min',
                                  'data_2':'max'})

print(rst)
     data_1  data_2
key                
A       100       5
B       101       7
C       102       9

7.4.2. 过滤

通过过了功能,可以保留我们需要的值,把不需要的去掉。

# 过滤函数

def my_filter(x):
    return x['data_2'].std() > 1.5

rst = df.groupby('key').std()
print('df.std = \n', rst)

# 使用过滤函数,把不符合要求的过滤掉
rst = df.groupby('key').filter(my_filter)
print("\n rst.filter_func = \n", rst)
df.std = 
       data_1    data_2
key                   
A    2.12132  1.414214
B    2.12132  4.949747
C    2.12132  4.242641

 rst.filter_func = 
   key  data_1  data_2
1   B     101       0
2   C     102       3
4   B     104       7
5   C     105       9

7.4.3. 转换

累计操作把数据集合进行了裁剪和选择,而转换操作是把全量数据进行加工,得到的数据格式与输入数据一致,常见的操作是对数据减去均值,实现数据标准化。

# 对数据进行标准化

rst = df.groupby('key').transform(lambda x: x - x.mean())

print(rst)
   data_1  data_2
0    -1.5     1.0
1    -1.5    -3.5
2    -1.5    -3.0
3     1.5    -1.0
4     1.5     3.5
5     1.5     3.0

7.4.4. 应用

apply可以让人在每个数据上应用任意方法,这个函数让输入一个DataFrane,返回的结果可以是pandas对象或者标量。

# 求每一项的百分比
def  norm_data_1(x):
    # 求百分比
    x['data_1'] = x['data_1'] / x['data_1'].sum()
    
    return x

rst = df.groupby('key').apply(norm_data_1)
print(rst)
  key    data_1  data_2
0   A  0.492611       5
1   B  0.492683       0
2   C  0.492754       3
3   A  0.507389       3
4   B  0.507317       7
5   C  0.507246       9

7.5. 设置分割的键

对DataFrame的分割可以根据列来,还可以有其他的方法,本节主要介绍对DataFrame的各种分割方法。

7.5.1. 将列表,数组,Series或者索引作为分组键

此时分组键可以是与DaytaFrame匹配的任意Series或者列表。

# 普通数组作为分组键

L = [0,1,1,0,2,1]
a = df.groupby(L).sum()
print(a)
   data_1  data_2
0     203       8
1     308      12
2     104       7
# 直接用键值也可以,比较啰嗦
a = df.groupby(df['key']).sum()
print(a)
     data_1  data_2
key                
A       203       8
B       205       7
C       207      12

7.5.2. 用字典或者Series将索引映射到分组的名称

提供字典,按照字典的键值进行分组,最后结果使用字典键值映射的值。

要求索引必须跟字典的键值匹配。

# 利用字典分组
D_mapping = {'A':"One", 'B':"Two", 'C':"Three"}

df = df.set_index('key')
a = df.groupby(D_mapping).sum()
print(a)
       data_1  data_2
One       203       8
Three     207      12
Two       205       7

7.5.3. 使用任意Python函数

我们还可以把Python函数传入groupby,与前面的内容类似,然后得到新的分组。

# 传入任意分组
a = df.groupby(str.lower).mean()
print(a)
   data_1  data_2
a   101.5     4.0
b   102.5     3.5
c   103.5     6.0

7.5.4. 多个有效的键组成的列表

有效的键值可以组合起来,从而返回一个多级索引的分组结果。

# 利用上面定义的字典,我们可以组合成多级索引

a = df.groupby([str.lower, D_mapping]).mean()
print(a)
         data_1  data_2
a One     101.5     4.0
b Two     102.5     3.5
c Three   103.5     6.0