查看原文
其他

《利用Python进行数据分析·第2版》第8章(上) 数据规整:聚合、合并和重塑

SeanCheney Python爱好者社区 2019-04-07

作者:SeanCheney   Python爱好者社区专栏作者

简书专栏:https://www.jianshu.com/u/130f76596b02


前文传送门:

【翻译】《利用Python进行数据分析·第2版》第1章 准备工作

【翻译】《利用Python进行数据分析·第2版》第2章(上)Python语法基础,IPython和Jupyter

【翻译】《利用Python进行数据分析·第2版》第2章(中)Python语法基础,IPython和Jupyter

【翻译】《利用Python进行数据分析·第2版》第2章(下)Python语法基础,IPython和Jupyter

【翻译】《利用Python进行数据分析·第2版》第3章(上)Python的数据结构、函数和文件

【翻译】《利用Python进行数据分析·第2版》第3章(中)Python的数据结构、函数和文件

【翻译】《利用Python进行数据分析·第2版》第3章(下)Python的数据结构、函数和文件

【翻译】《利用Python进行数据分析·第2版》第4章(上)NumPy基础:数组和矢量计算

【翻译】《利用Python进行数据分析·第2版》第4章(中)NumPy基础:数组和矢量计算

【翻译】《利用Python进行数据分析·第2版》第4章(下)NumPy基础:数组和矢量计算

【翻译】《利用Python进行数据分析·第2版》第5章(上)pandas入门

【翻译】《利用Python进行数据分析·第2版》第5章(中)pandas入门

【翻译】《利用Python进行数据分析·第2版》第5章(下)pandas入门

【翻译】《利用Python进行数据分析·第2版》第6章(上) 数据加载、存储与文件格式

【翻译】《利用Python进行数据分析·第2版》第6章(中) 数据加载、存储与文件格式

【翻译】《利用Python进行数据分析·第2版》第6章(下) 数据加载、存储与文件格式

【翻译】《利用Python进行数据分析·第2版》第7章(上)数据清洗和准备

【翻译】《利用Python进行数据分析·第2版》第7章(中) 数据清洗和准备

【翻译】《利用Python进行数据分析·第2版》第7章(下) 数据清洗和准备


在许多应用中,数据可能分散在许多文件或数据库中,存储的形式也不利于分析。本章关注可以聚合、合并、重塑数据的方法。


首先,我会介绍pandas的层次化索引,它广泛用于以上操作。然后,我深入介绍了一些特殊的数据操作。在第14章,你可以看到这些工具的多种应用。


8.1 层次化索引


层次化索引(hierarchical indexing)是pandas的一项重要功能,它使你能在一个轴上拥有多个(两个以上)索引级别。抽象点说,它使你能以低维度形式处理高维度数据。我们先来看一个简单的例子:创建一个Series,并用一个由列表或数组组成的列表作为索引:

In [9]: data = pd.Series(np.random.randn(9),   ...:                  index=[['a', 'a', 'a', 'b', 'b', 'c', 'c', 'd', 'd'],   ...:                         [1, 2, 3, 1, 3, 1, 2, 2, 3]]) In [10]: data Out[10]: a  1   -0.204708   2    0.478943   3   -0.519439 b  1   -0.555730   3    1.965781 c  1    1.393406   2    0.092908 d  2    0.281746   3    0.769023 dtype: float64

看到的结果是经过美化的带有MultiIndex索引的Series的格式。索引之间的“间隔”表示“直接使用上面的标签”:

In [11]: data.index Out[11]: MultiIndex(levels=[['a', 'b', 'c', 'd'], [1, 2, 3]],           labels=[[0, 0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 2, 0, 2, 0, 1, 1, 2]])

对于一个层次化索引的对象,可以使用所谓的部分索引,使用它选取数据子集的操作更简单:

In [12]: data['b'] Out[12]: 1   -0.555730 3    1.965781 dtype: float64 In [13]: data['b':'c'] Out[13]: b  1   -0.555730   3    1.965781 c  1    1.393406   2    0.092908 dtype: float64 In [14]: data.loc[['b', 'd']] Out[14]: b  1   -0.555730   3    1.965781 d  2    0.281746   3    0.769023 dtype: float64

有时甚至还可以在“内层”中进行选取:

In [15]: data.loc[:, 2] Out[15]: a    0.478943 c    0.092908 d    0.281746 dtype: float64

层次化索引在数据重塑和基于分组的操作(如透视表生成)中扮演着重要的角色。


例如,可以通过unstack方法将这段数据重新安排到一个DataFrame中:

In [16]: data.unstack() Out[16]:          1         2         3 a -0.204708  0.478943 -0.519439 b -0.555730       NaN  1.965781 c  1.393406  0.092908       NaN d       NaN  0.281746  0.769023

unstack的逆运算是stack:

In [17]: data.unstack().stack() Out[17]: a  1   -0.204708   2    0.478943   3   -0.519439 b  1   -0.555730   3    1.965781 c  1    1.393406   2    0.092908 d  2    0.281746   3    0.769023 dtype: float64

stack和unstack将在本章后面详细讲解。


对于一个DataFrame,每条轴都可以有分层索引:

In [18]: frame = pd.DataFrame(np.arange(12).reshape((4, 3)),   ....:                      index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]],   ....:                      columns=[['Ohio', 'Ohio', 'Colorado'],   ....:                               ['Green', 'Red', 'Green']]) In [19]: frame Out[19]:     Ohio     Colorado    Green Red    Green a 1     0   1        2  2     3   4        5 b 1     6   7        8  2     9  10       11

各层都可以有名字(可以是字符串,也可以是别的Python对象)。如果指定了名称,它们就会显示在控制台输出中:

In [20]: frame.index.names = ['key1', 'key2'] In [21]: frame.columns.names = ['state', 'color'] In [22]: frame Out[22]: state      Ohio     Colorado color     Green Red    Green key1 key2                   a    1        0   1        2     2        3   4        5 b    1        6   7        8     2        9  10       11

注意:小心区分索引名state、color与行标签。


有了部分列索引,因此可以轻松选取列分组:

In [23]: frame['Ohio'] Out[23]: color      Green  Red key1 key2             a    1         0    1     2         3    4 b    1         6    7     2         9   10

可以单独创建MultiIndex然后复用。上面那个DataFrame中的(带有分级名称)列可以这样创建:

MultiIndex.from_arrays([['Ohio', 'Ohio', 'Colorado'], ['Green', 'Red', 'Green']],                           names=['state', 'color'])

重排与分级排序


有时,你需要重新调整某条轴上各级别的顺序,或根据指定级别上的值对数据进行排序。swaplevel接受两个级别编号或名称,并返回一个互换了级别的新对象(但数据不会发生变化):

In [24]: frame.swaplevel('key1', 'key2') Out[24]: state      Ohio     Colorado color     Green Red    Green key2 key1                   1    a        0   1        2 2    a        3   4        5 1    b        6   7        8 2    b        9  10       11

而sort_index则根据单个级别中的值对数据进行排序。交换级别时,常常也会用到sort_index,这样最终结果就是按照指定顺序进行字母排序了:

In [25]: frame.sort_index(level=1) Out[25]: state      Ohio     Colorado color     Green Red    Green key1 key2                   a    1        0   1        2 b    1        6   7        8 a    2        3   4        5 b    2        9  10       11 In [26]: frame.swaplevel(0, 1).sort_index(level=0) Out[26]: state      Ohio     Colorado color     Green Red    Green key2 key1                   1    a        0   1        2     b        6   7        8 2    a        3   4        5     b        9  10       11

根据级别汇总统计


许多对DataFrame和Series的描述和汇总统计都有一个level选项,它用于指定在某条轴上求和的级别。再以上面那个DataFrame为例,我们可以根据行或列上的级别来进行求和:

In [27]: frame.sum(level='key2') Out[27]: state  Ohio     Colorado color Green Red    Green key2                     1         6   8       10 2        12  14       16 In [28]: frame.sum(level='color', axis=1) Out[28]: color      Green  Red key1 key2             a    1         2    1     2         8    4 b    1        14    7     2        20   10

这其实是利用了pandas的groupby功能,本书稍后将对其进行详细讲解。


使用DataFrame的列进行索引


人们经常想要将DataFrame的一个或多个列当做行索引来用,或者可能希望将行索引变成DataFrame的列。以下面这个DataFrame为例:

In [29]: frame = pd.DataFrame({'a': range(7), 'b': range(7, 0, -1),   ....:                       'c': ['one', 'one', 'one', 'two', 'two',   ....:                             'two', 'two'],   ....:                       'd': [0, 1, 2, 0, 1, 2, 3]}) In [30]: frame Out[30]:   a  b    c  d 0  0  7  one  0 1  1  6  one  1 2  2  5  one  2 3  3  4  two  0 4  4  3  two  1 5  5  2  two  2 6  6  1  two  3

DataFrame的set_index函数会将其一个或多个列转换为行索引,并创建一个新的DataFrame:

In [31]: frame2 = frame.set_index(['c', 'd']) In [32]: frame2 Out[32]:       a  b c   d       one 0  0  7    1  1  6    2  2  5 two 0  3  4    1  4  3    2  5  2    3  6  1

默认情况下,那些列会从DataFrame中移除,但也可以将其保留下来:

In [33]: frame.set_index(['c', 'd'], drop=False) Out[33]:       a  b    c  d c   d               one 0  0  7  one  0    1  1  6  one  1    2  2  5  one  2 two 0  3  4  two  0    1  4  3  two  1    2  5  2  two  2    3  6  1  two  3

reset_index的功能跟set_index刚好相反,层次化索引的级别会被转移到列里面:

In [34]: frame2.reset_index() Out[34]: c  d  a  b 0  one  0  0  7 1  one  1  1  6 2  one  2  2  5 3  two  0  3  4 4  two  1  4  3 5  two  2  5  2 6  two  3  6  1

8.2 合并数据集


pandas对象中的数据可以通过一些方式进行合并:


  • pandas.merge可根据一个或多个键将不同DataFrame中的行连接起来。SQL或其他关系型数据库的用户对此应该会比较熟悉,因为它实现的就是数据库的join操作。

  • pandas.concat可以沿着一条轴将多个对象堆叠到一起。

  • 实例方法combine_first可以将重复数据编接在一起,用一个对象中的值填充另一个对象中的缺失值。


我将分别对它们进行讲解,并给出一些例子。本书剩余部分的示例中将经常用到它们。


数据库风格的DataFrame合并


数据集的合并(merge)或连接(join)运算是通过一个或多个键将行链接起来的。这些运算是关系型数据库(基于SQL)的核心。pandas的merge函数是对数据应用这些算法的主要切入点。


以一个简单的例子开始:

In [35]: df1 = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],   ....:                     'data1': range(7)}) In [36]: df2 = pd.DataFrame({'key': ['a', 'b', 'd'],   ....:                     'data2': range(3)}) In [37]: df1 Out[37]:   data1 key 0      0   b 1      1   b 2      2   a 3      3   c 4      4   a 5      5   a 6      6   b In [38]: df2 Out[38]:   data2 key 0      0   a 1      1   b 2      2   d

这是一种多对一的合并。df1中的数据有多个被标记为a和b的行,而df2中key列的每个值则仅对应一行。对这些对象调用merge即可得到:

In [39]: pd.merge(df1, df2) Out[39]:   data1 key  data2 0      0   b      1 1      1   b      1 2      6   b      1 3      2   a      0 4      4   a      0 5      5   a      0

注意,我并没有指明要用哪个列进行连接。如果没有指定,merge就会将重叠列的列名当做键。不过,最好明确指定一下:

In [40]: pd.merge(df1, df2, on='key') Out[40]:   data1 key  data2 0      0   b      1 1      1   b      1 2      6   b      1 3      2   a      0 4      4   a      0 5      5   a      0

如果两个对象的列名不同,也可以分别进行指定:

In [41]: df3 = pd.DataFrame({'lkey': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],   ....:                     'data1': range(7)}) In [42]: df4 = pd.DataFrame({'rkey': ['a', 'b', 'd'],   ....:                     'data2': range(3)}) In [43]: pd.merge(df3, df4, left_on='lkey', right_on='rkey') Out[43]:   data1 lkey  data2 rkey 0      0    b      1    b 1      1    b      1    b 2      6    b      1    b 3      2    a      0    a 4      4    a      0    a 5      5    a      0    a

可能你已经注意到了,结果里面c和d以及与之相关的数据消失了。默认情况下,merge做的是“内连接”;结果中的键是交集。其他方式还有"left"、"right"以及"outer"。外连接求取的是键的并集,组合了左连接和右连接的效果:

In [44]: pd.merge(df1, df2, how='outer') Out[44]:   data1 key  data2 0    0.0   b    1.0 1    1.0   b    1.0 2    6.0   b    1.0 3    2.0   a    0.0 4    4.0   a    0.0 5    5.0   a    0.0 6    3.0   c    NaN 7    NaN   d    2.0

表8-1对这些选项进行了总结。

表8-1 不同的连接类型


多对多的合并有些不直观。看下面的例子:

In [45]: df1 = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'b'],   ....:                     'data1': range(6)}) In [46]: df2 = pd.DataFrame({'key': ['a', 'b', 'a', 'b', 'd'],   ....:                     'data2': range(5)}) In [47]: df1 Out[47]:   data1 key 0      0   b 1      1   b 2      2   a 3      3   c 4      4   a 5      5   b In [48]: df2 Out[48]:   data2 key 0      0   a 1      1   b 2      2   a 3      3   b 4      4   d In [49]: pd.merge(df1, df2, on='key', how='left') Out[49]:    data1 key  data2 0       0   b    1.0 1       0   b    3.0 2       1   b    1.0 3       1   b    3.0 4       2   a    0.0 5       2   a    2.0 6       3   c    NaN 7       4   a    0.0 8       4   a    2.0 9       5   b    1.0 10      5   b    3.0

多对多连接产生的是行的笛卡尔积。由于左边的DataFrame有3个"b"行,右边的有2个,所以最终结果中就有6个"b"行。连接方式只影响出现在结果中的不同的键的值:

In [50]: pd.merge(df1, df2, how='inner') Out[50]:   data1 key  data2 0      0   b      1 1      0   b      3 2      1   b      1 3      1   b      3 4      5   b      1 5      5   b      3 6      2   a      0 7      2   a      2 8      4   a      0 9      4   a      2

要根据多个键进行合并,传入一个由列名组成的列表即可:

In [51]: left = pd.DataFrame({'key1': ['foo', 'foo', 'bar'],   ....:                      'key2': ['one', 'two', 'one'],   ....:                      'lval': [1, 2, 3]}) In [52]: right = pd.DataFrame({'key1': ['foo', 'foo', 'bar', 'bar'],   ....:                       'key2': ['one', 'one', 'one', 'two'],   ....:                       'rval': [4, 5, 6, 7]}) In [53]: pd.merge(left, right, on=['key1', 'key2'], how='outer') Out[53]:  key1 key2  lval  rval 0  foo  one   1.0   4.0 1  foo  one   1.0   5.0 2  foo  two   2.0   NaN 3  bar  one   3.0   6.0 4  bar  two   NaN   7.0

结果中会出现哪些键组合取决于所选的合并方式,你可以这样来理解:多个键形成一系列元组,并将其当做单个连接键(当然,实际上并不是这么回事)。


注意:在进行列-列连接时,DataFrame对象中的索引会被丢弃。


对于合并运算需要考虑的最后一个问题是对重复列名的处理。虽然你可以手工处理列名重叠的问题(查看前面介绍的重命名轴标签),但merge有一个更实用的suffixes选项,用于指定附加到左右两个DataFrame对象的重叠列名上的字符串:

In [54]: pd.merge(left, right, on='key1') Out[54]:  key1 key2_x  lval key2_y  rval 0  foo    one     1    one     4 1  foo    one     1    one     5 2  foo    two     2    one     4 3  foo    two     2    one     5 4  bar    one     3    one     6 5  bar    one     3    two     7 In [55]: pd.merge(left, right, on='key1', suffixes=('_left', '_right')) Out[55]:  key1 key2_left  lval key2_right  rval 0  foo       one     1        one     4 1  foo       one     1        one     5 2  foo       two     2        one     4 3  foo       two     2        one     5 4  bar       one     3        one     6 5  bar       one     3        two     7

merge的参数请参见表8-2。使用DataFrame的行索引合并是下一节的主题。


表8-2 merge函数的参数


indicator 添加特殊的列_merge,它可以指明每个行的来源,它的值有left_only、right_only或both,根据每行的合并数据的来源。


赞赏作者

Python爱好者社区历史文章大合集

Python爱好者社区历史文章列表(每周append更新一次)

福利:文末扫码立刻关注公众号,“Python爱好者社区”,开始学习Python课程:

关注后在公众号内回复“课程”即可获取:

小编的Python入门视频课程!!!

崔老师爬虫实战案例免费学习视频。

丘老师数据科学入门指导免费学习视频。

陈老师数据分析报告制作免费学习视频。

玩转大数据分析!Spark2.X+Python 精华实战课程免费学习视频。

丘老师Python网络爬虫实战免费学习视频。


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存