Python 数据分析(二 本实验将学习利用 Python 数据聚合与分组运算,时间序列,金融与经济数据应用等相关知识

Python 数据分析(二)

本实验将学习利用 Python 数据聚合与分组运算,时间序列,金融与经济数据应用等相关知识

第1节
groupby 技术
第2节
数据聚合
第3节
分组级运算和转换
第4节
透视表和交叉表
第5节
时间序列
第6节
日期的规范、频率以及移动
第7节
时区处理
第8节
时期及算术运算
第9节
重采样及频率转换
第10节
时间序列绘图

groupby 技术


一、实验简介

  Python 数据分析(二)需要同学们先行学完 Python 数据分析(一)的课程。

  对数据集进行分组并对各组应用一个函数(无论是聚合还是转换),这是数据分析工作中的重要环节。在数据集准备好之后,通常的任务就是计算分组统 计或生成透视表。pandas 提供了一个灵活高效的 groupby 功能,它使我们能以一种自然的方式对数据集进行切片、切块、摘要等操作。

  分组运算的第一阶段,pandas 对象(无论是 Series、DataFrame 还是其他的)中的数据会根据你所提供的一个或多个键被拆分(split)为多组。拆分操作是在对象的特定轴上执行的。例如,DataFrame 可以在其行(axis=0)或列(axis=1)上进行分组。然后,将一个函数应用到各个分组并产生一个新值。最后,所有这些函数的执行结果会被合并到最 终的结果对象中。结果对象的形式一般取决于数据上所执行的操作。

Python 数据分析(二  本实验将学习利用 Python 数据聚合与分组运算,时间序列,金融与经济数据应用等相关知识

  分组键可以有多种形式,且类型不必相同:

  • 列表或数组,其长度与待分组的轴一样
  • 表示 DataFrame 某个列名的值
  • 字典或 Series,给出待分组轴上的值与分组名之间的对应关系
  • 函数、用于处理轴索引中的各个标签

  注意,后三种都只是快捷方式而已,其最终目的仍然是产生一组用于拆分对象的值。

In [5]: df = DataFrame({'key1':['a','a','b','b','a'],
                'key2':['one','two','one','two','one'],
                'data1':np.random.randn(5),
                'data2':np.random.randn(5)})

In [6]: df
Out[6]:
      data1     data2 key1 key2
0 -1.884515  0.735152    a  one
1  0.320270  1.364803    a  two
2  1.190752 -0.877677    b  one
3 -2.714275 -0.641703    b  two
4  0.586653 -0.451896    a  one

  在上面的例子中,假设我们想要按 key1 进行分组,并计算 data1 列的平均值。实现该功能的方式有很多,而我们这里要用的是:访问 data1,并根据 key1调用 groupby:

In [7]: grouped = df['data1'].groupby(df['key1'])

In [8]: grouped
Out[8]: <pandas.core.groupby.SeriesGroupBy object at 0x112d53290>

  变量 grouped 是一个 GroupBy 对象。它实际上还没有进行任何计算,只是含有一些有关分组键 df['key1']的中间数据而已。换句话说,该对象已经有了接下来对各分组执行运算所需的一切信息。例如,我们可以调用 GroupBy 的 mean 方法来计算分组平均值:

In [10]: grouped.mean()
Out[10]:
key1
a   -0.325864
b   -0.761762
Name: data1, dtype: float64

  数据(Series)根据分组键进行了聚合,产生了一个新的 Series,其索引为 key1列中的唯一值。如果我们传入多个数组,就会得到不同的结果:

In [11]: means = df['data1'].groupby([df['key1'],df['key2']]).mean()

In [12]: means
Out[12]:
key1  key2
a     one    -0.648931
      two     0.320270
b     one     1.190752
      two    -2.714275
Name: data1, dtype: float64

  这里,我们通过两个键对数据进行了分组,得到的 Series 具有一个层次化索引(由唯一的键对组成):

In [13]: means.unstack()
Out[13]:
key2       one       two
key1
a    -0.648931  0.320270
b     1.190752 -2.714275

  在上面这些示例中,分组键均为 Series。实际上,分组键可以是任何长度适当的数组:

In [14]: states = np.array(['Ohio','California','California','Ohio','Ohio'])

In [15]: years = np.array([2005, 2005, 2006, 2005, 2006])

In [16]: df['data1'].groupby([states,years]).mean()
Out[16]:
California  2005    0.320270
            2006    1.190752
Ohio        2005   -2.299395
            2006    0.586653
Name: data1, dtype: float64

In [17]: # 此外,还可以将列名用作分组键

In [18]: df.groupby('key1').mean()
Out[18]:
         data1     data2
key1
a    -0.325864  0.549353
b    -0.761762 -0.759690

In [19]: df.groupby(['key1','key2']).mean()
Out[19]:
              data1     data2
key1 key2
a    one  -0.648931  0.141628
     two   0.320270  1.364803
b    one   1.190752 -0.877677
     two  -2.714275 -0.641703

  在执行 df.groupby('key1').mean()时,结果中没有 key2 列。这是因为 df['key2']不是数值数据,所以从结果中排除了。默认情况下,所有数值列都会被聚合,虽然有时可能会被过滤为一个子集(稍后会讲到)。

  GroupBy 的 size 方法可以返回一个含有分组大小的 Series:

In [20]: df.groupby(['key1','key2']).size()
Out[20]:
key1  key2
a     one     2
      two     1
b     one     1
      two     1
dtype: int64

 

二、对分组进行迭代

   GroupBy 对象支持迭代,可以产生一组二元元组(由分组名和数据块组成)

In [21]: for name,group in df.groupby('key1'):
   ....:     print name
   ....:     print group
   ....:
a
      data1     data2 key1 key2
0 -1.884515  0.735152    a  one
1  0.320270  1.364803    a  two
4  0.586653 -0.451896    a  one
b
      data1     data2 key1 key2
2  1.190752 -0.877677    b  one
3 -2.714275 -0.641703    b  two

In [22]: # 对于多重键的情况,元组的第一个元素将会是由键值组成的元组

In [23]: for (k1, k2),group in df.groupby(['key1','key2']):
   ....:     print k1, k2
   ....:     print group
   ....:
a one
      data1     data2 key1 key2
0 -1.884515  0.735152    a  one
4  0.586653 -0.451896    a  one
a two
     data1     data2 key1 key2
1  0.32027  1.364803    a  two
b one
      data1     data2 key1 key2
2  1.190752 -0.877677    b  one
b two
      data1     data2 key1 key2
3 -2.714275 -0.641703    b  two

  当然,我们可以对这些数据片段做任何操作。有一个你可能会觉得有用的运算:将这些数据片段做成一个字典。

In [25]: pieces = dict(list(df.groupby('key1')))

In [26]: pieces['b']
Out[26]:
      data1     data2 key1 key2
2  1.190752 -0.877677    b  one
3 -2.714275 -0.641703    b  two

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

In [27]: df.dtypes
Out[27]:
data1    float64
data2    float64
key1      object
key2      object
dtype: object

In [28]: grouped = df.groupby(df.dtypes, axis =1)

In [29]: dict(list(grouped))
Out[29]:
{dtype('float64'):       data1     data2
 0 -1.884515  0.735152
 1  0.320270  1.364803
 2  1.190752 -0.877677
 3 -2.714275 -0.641703
 4  0.586653 -0.451896, dtype('O'):   key1 key2
 0    a  one
 1    a  two
 2    b  one
 3    b  two
 4    a  one}

  

三、选取一个或一组列

  对于由 DataFrame 产生的 GroupBy 对象,如果用一个(单个字符串)或一组(字符串数组)列名对其进行索引,就能实现选取部分列进行聚合的目的,也就是说:


df.groupby('key1')['data1']
df.groupby('key1')[['data2']]

  是以下代码的语法糖:

df['data1'].groupby(df['key1'])
df[['data2']].groupby(df['key1'])

  尤其对于大量数据集,很有可能只需要对部分列进行聚合。例如,在前面那个数据集中,如果只需计算 data2 列的平均值并以 DataFrame 形式得到结果,我们可以编写:

In [21]: df.groupby(['key1','key2'])[['data2']].mean()
Out[21]:
              data2
key1 key2
a    one  -0.035973
     two  -1.650808
b    one   0.610671
     two   0.407438

  这种索引操作所返回的对象是一个已分组的 DataFrame(如果传入的是列表或数组)或已分组的 Series(如果传入的是标量形式的单个列名):

In [22]: s_groupby = df.groupby(['key1','key2'])['data2']

In [23]: s_groupby.mean()
Out[23]:
key1  key2
a     one    -0.035973
      two    -1.650808
b     one     0.610671
      two     0.407438
Name: data2, dtype: float64

  注意观察行21和22之间的区别,前面返回的是一个 DataFrame,后面返回的是一个 Series

四、通过字典或 Series 进行分组

  除数组以外,分组信息还可以其他形式存在。来看一个示例:

In [4]: people = DataFrame(np.random.randn(5,5),
                   columns = ['a','b','c','d','e'],
                   index = ['Joe','Steve','Wes','Jim','Travis'])

In [5]: people.ix[2:3,['b','c']] = np.nan

In [6]: people
Out[6]:
               a         b         c         d         e
Joe    -0.131332  1.361534  0.885761  1.250524  0.723013
Steve  -0.968665 -0.024584  1.213288  0.564578 -0.666432
Wes     0.108587       NaN       NaN  0.678532 -1.182141
Jim     0.643063  0.065343  0.008009 -1.651852 -1.156628
Travis  0.113156  0.076969  0.353941 -0.096054 -0.351033

In [7]: #假设已知列的分组关系,并希望根据分组计算列的总计

In [8]: mapping = {'a':'red','b':'red','c':'blue',
           'd':'blue','e':'red','f':'orange'}

In [9]: #现在,只需将这个字典传给 groupby 即可

In [10]: by_column = people.groupby(mapping,axis=1)

In [11]: by_column.sum()
Out[11]:
            blue       red
Joe     2.136285  1.953214
Steve   1.777866 -1.659681
Wes     0.678532 -1.073554
Jim    -1.643843 -0.448222
Travis  0.257887 -0.160908

  Series也有同样的功能,它可以被看做一个固定大小的映射。对于上面那个例子,如果用 Series 作为分组键,则 pandas 会检查 Series 以确保其索引跟分组轴是对齐的:

In [13]: map_series = Series(mapping)

In [14]: map_series
Out[14]:
a       red
b       red
c      blue
d      blue
e       red
f    orange
dtype: object

In [15]: people.groupby(map_series,axis=1).count()
Out[15]:
        blue  red
Joe        2    3
Steve      2    3
Wes        1    2
Jim        2    3
Travis     2    3

五、通过函数进行分组

  相较于字典或 Series,Python 函数在定义分组映射关系时可以更有创意且更为抽象。任何被当做分组键的函数都会在各个索引值上被调用一次,其返回值就会被用作分组名称。具体点说,以上一 小节的示例 DataFrame 为例,其索引值为人的名字。假设你希望根据人名的长度进行分组,虽然可以求取一个字符串长度数组,但其实仅仅传入 len 函数就可以了:

In [16]: people.groupby(len).sum()
Out[16]:
          a         b         c         d         e
3  0.620317  1.426876  0.893770  0.277204 -1.615756
5 -0.968665 -0.024584  1.213288  0.564578 -0.666432
6  0.113156  0.076969  0.353941 -0.096054 -0.351033

  将函数跟数组、列表、字典、Series 混合使用也不是问题,因为任何东西最终都会被转换为数组:


In [17]: key_list = ['one','one','one','two','two']

In [18]: people.groupby([len,key_list]).min()
Out[18]:
              a         b         c         d         e
3 one -0.131332  1.361534  0.885761  0.678532 -1.182141
  two  0.643063  0.065343  0.008009 -1.651852 -1.156628
5 one -0.968665 -0.024584  1.213288  0.564578 -0.666432
6 two  0.113156  0.076969  0.353941 -0.096054 -0.351033

  层次化索引数据集最方便的地方就在于它能够根据索引级别进行聚合。要实现该目的,通过 level 关键字传入级别编号或名称即可:

In [22]: columns = pd.MultiIndex.from_arrays([['US','US',
  'US','CN','CN'],[1,3,5,1,3]],names=['cty','tenor'])

In [23]: hier_df = DataFrame(np.random.randn(4,5),
                   columns = columns)

In [24]: hier_df
Out[24]:
cty          US                            CN
tenor         1         3         5         1         3
0      0.823673  0.783013  0.777291 -0.065750  0.507580
1     -2.173389 -0.339692 -1.793867 -1.075630 -0.235964
2      1.973584  0.526835 -1.274129 -0.355864 -0.917485
3      0.984408  0.246716  0.383760 -2.521464  0.078212

In [25]: hier_df.groupby(level='cty',axis=1).count()
Out[25]:
cty  CN  US
0     2   3
1     2   3
2     2   3
3     2   3

六、作业

  好了同学们,你们将上面的代码操练操练,想明白 groupby 分组技术的本质。

上一篇:MySQL数据库入门———常用基础命令


下一篇:8天学通MongoDB——第八天 驱动实践