• 第八篇 数据规整:聚合、合并和重塑


    在许多应⽤中,数据可能分散在许多⽂件或数据库中,存储的形式也不利于分析。本章关注可以聚合、合并、重塑数据的⽅法。⾸先,介绍pandas的层次化索引,它⼴泛⽤于以上操作。然后,深⼊介绍了⼀些特殊的数据操作。

    一、层次化索引
    层次化索引hierarchical indexing)是pandas的⼀项重要功能,它使你能在⼀个轴上拥有多个(两个以上)索引级别。抽象点说,它使你能以低维度形式处理⾼维度数据。我们先来看⼀个简单的例⼦:创建⼀个Series,并⽤⼀个由列表或数组组成的列表作为索引:
    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]])
    data        # 输出如下:
    a   1    1.007189
         2   -1.296221
         3    0.274992
    b   1    0.228913
         3    1.352917
    c   1    0.886429
         2   -2.001637
    d   2   -0.371843
         3    1.669025
    dtype: float64
    看到的结果是经过美化的带有MultiIndex索引的Series的格式。索引之间的“间隔”表示“直接使⽤上⾯的标签”:
    data.index        # 输出如下:
    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]])
    对于⼀个层次化索引的对象,可以使⽤所谓的部分索引,使⽤它选取数据⼦集的操作更简单:
    data['b']        # 输出如下:
    1    0.228913
    3    1.352917
    dtype: float64
    data['b':'c']    # 输出如下:
    b   1    0.228913
         3    1.352917
    c   1    0.886429
         2   -2.001637
    dtype: float64
    data.loc[['b', 'd']]    # 输出如下:
    b   1    0.228913
         3    1.352917
    d   2   -0.371843
         3    1.669025
    dtype: float64
    有时甚⾄还可以在“内层”中进⾏选取:
    data.loc[:, 2]    # 输出如下:(选取带2的标签)
    a   -1.296221
    c   -2.001637
    d   -0.371843
    dtype: float64

    层次化索引在数据重塑和基于分组的操作(如透视表⽣成)中扮演着重要的⻆⾊。例如,可以通过unstack⽅法将这段数据重新安排到⼀个DataFrame中:
    data.unstack()    # 输出如下:
                    1              2             3
    a  1.007189 -1.296221  0.274992
    b  0.228913         NaN   1.352917
    c  0.886429 -2.001637         NaN
    d         NaN  -0.371843  1.669025
    unstack的逆运算是stack
    data.unstack().stack()    # 输出如下:
    a   1    1.007189
         2   -1.296221
         3    0.274992
    b   1    0.228913
         3    1.352917
    c   1    0.886429
         2   -2.001637
    d   2   -0.371843
         3    1.669025
    dtype: float64
    stack和unstack将在后⾯详细讲解。

    对于⼀个DataFrame,每条轴都可以有分层索引
    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']])
    frame        # 输出如下:
               Ohio            Colorado
              Green Red        Green
    a  1           0      1              2
        2           3      4              5
    b  1           6      7              8
        2           9    10             11
    各层都可以有名字(可以是字符串,也可以是别的Python对象)。如果指定了名称,它们就会显示在控制台输出中:
    frame.index.names = ['key1', 'key2']          # 设置行索引属性名称
    frame.columns.names = ['state', 'color']    # 设置列索引属性名称
    frame        # 输出如下:
    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与⾏标签。

    有了部分列索引,因此可以轻松选取列分组
    frame['Ohio']    # 输出如下:
    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'])

    1、重排与分级排序
    有时,你需要重新调整某条轴上各级别的顺序,或根据指定级别上的值对数据进⾏排序。swaplevel接受两个级别编号或名称,并返回⼀个互换了级别的新对象(但数据不会发⽣变化):
    frame.swaplevel('key1', 'key2')    # 输出如下:(互换'key1'和'key2')
    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,这样最终结果就是按照指定顺序进⾏字⺟排序了:
    frame.sort_index(level=1)    # 输出如下:(对第二个索引排序('key2'))
    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
    frame.swaplevel(0, 1).sort_index(level=0)    # 输出如下:(互换两个索引,对第一个索引排序)
    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


    2、根据级别汇总统计
    许多对DataFrame和Series的描述和汇总统计都有⼀个level选项,它⽤于指定在某条轴上求和的级别。再以上⾯那个DataFrame为例,我们可以根据⾏或列上的级别来进⾏求和:
    frame.sum(level='key2')    # 输出如下:(注意level参数后面给的是行索引或列索引的属性名称)
    state      Ohio          Colorado
    color   Green Red    Green
    key2
    1               6     8       10
    2            12    14       16
    frame.sum(level='color', axis=1)    # 输出如下:(根据相同的颜色(Green,Red)按行求和)
    color             Green  Red
    key1     key2
    a                1         2      1
                      2         8      4
    b               1        14      7
                     2        20     10
    这其实是利⽤了pandas的groupby功能,稍后将对其进⾏详细讲解。


    3、使⽤DataFrame的列进⾏索引
    经常要将DataFrame的⼀个或多个列当做⾏索引来⽤,或者可能希望将⾏索引变成DataFrame的列。以下⾯这个DataFrame为例:
    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]})
    frame        # 输出如下:
        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:
    frame2 = frame.set_index(['c', 'd'])    # c、d列转换为行索引
    frame2    # 输出如下:
                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中移除,但也可以将其保留下来:
    frame.set_index(['c', 'd'], drop=False)    # 输出如下:
                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刚好相反,层次化索引的级别会被转移到列⾥⾯
    frame2.reset_index()        # 输出如下:
             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


    二、合并数据集
    pandas对象中的数据可以通过⼀些⽅式进⾏合并:
            pandas.merge可根据⼀个或多个键将不同DataFrame中的⾏连接起来。SQL或其他关系型数据库的⽤户对此应该会⽐较熟悉,因为它实现的就是数据库的join操作。
           pandas.concat可以沿着⼀条轴将多个对象堆叠到⼀起。实例⽅法combine_first可以将重复数据编接在⼀起,⽤⼀个对象中的值填充另⼀个对象中的缺失值。

    接下来对它们进⾏讲解,并给出⼀些例⼦。后面部分的示例中将经常⽤到它们。


    1、数据库⻛格的DataFrame合并
    数据集的合并(merge)或连接(join)运算是通过⼀个或多个键将⾏链接起来的。这些运算是关系型数据库(基于SQL)的核⼼。pandas的merge函数是对数据应⽤这些算法的主要切⼊点。

    以⼀个简单的例⼦开始:
    df1 = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'a', 'b'], 'data1': range(7)})
    df2 = pd.DataFrame({'key': ['a', 'b', 'd'], 'data2': range(3)})
    df1        # 输出如下:
       key  data1
    0   b      0
    1   b      1
    2   a      2
    3   c      3
    4   a      4
    5   a      5
    6   b      6
    df2        # 输出如下:
       key  data2
    0   a      0
    1   b      1
    2   d      2
    这是⼀种多对⼀的合并。df1中的数据有多个被标记为a和b的⾏,⽽df2中key列的每个值则仅
    对应⼀⾏。对这些对象调⽤merge即可得到:
    pd.merge(df1,df2)    # 输出如下:(合并具有相同的行索引,键的并集)
       key  data1  data2
    0   b      0      1
    1   b      1      1
    2   b      6      1
    3   a      2      0
    4   a      4      0
    5   a      5      0
    注意,我并没有指明要⽤哪个列进⾏连接。如果没有指定,merge就会将重叠列的列名当做键。不过,最好明确指定⼀下:
    pd.merge(df1, df2, on='key')    # 输出如下:
       key  data1  data2
    0   b      0      1
    1   b      1      1
    2   b      6      1
    3   a      2      0
    4   a      4      0
    5   a      5      0

    如果两个对象的列名不同,也可以分别进⾏指定:
    df3 = pd.DataFrame({'lkey': ['b', 'b', 'a', 'c', 'a', 'a', 'b'], 'data1': range(7)})
    df4 = pd.DataFrame({'rkey': ['a', 'b', 'd'], 'data2': range(3)})
    pd.merge(df3, df4, left_on='lkey', right_on='rkey')    # 输出如下:
       lkey  data1 rkey  data2
    0    b         0     b         1
    1    b         1     b         1
    2    b         6     b         1
    3    a         2     a         0
    4    a         4     a         0
    5    a         5     a         0
    可能你已经注意到了,结果⾥⾯c和d以及与之相关的数据消失了。默认情况下,merge做的是“内连接”;结果中的键是交集。其他⽅式还有"left"、"right"以及"outer"。外连接求取的是键的并集,组合了左连接和右连接的效果:
    pd.merge(df1, df2, how='outer')    # 输出如下:(结果是键的并集)
       key  data1  data2
    0   b       0.0      1.0
    1   b       1.0      1.0
    2   b       6.0      1.0
    3   a       2.0      0.0
    4   a       4.0      0.0
    5   a       5.0      0.0
    6   c       3.0    NaN
    7   d    NaN      2.0

    表8-1对这些选项进⾏了总结
    选项         说明
    'inner'     使用两个表都有的键(默认)
    'left'        使用左表中所有的键
    'right'     使用右表中所有的键
    'outer'    使用两个表中所有的键

    多对多的合并有些不直观。看下⾯的例⼦:
    df1 = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'b'], 'data1': range(6)})
    df2 = pd.DataFrame({'key': ['a', 'b', 'a', 'b', 'd'], 'data2': range(5)})
    df1        # 输出如下:
       key  data1
    0   b      0
    1   b      1
    2   a      2
    3   c      3
    4   a      4
    5   b      5
    df2        # 输出如下:
       key  data2
    0   a      0
    1   b      1
    2   a      2
    3   b      3
    4   d      4
    pd.merge(df1, df2, on='key', how='left')    # 输出如下:
        key  data1  data2
    0    b         0       1.0
    1    b         0       3.0
    2    b         1       1.0
    3    b         1       3.0
    4    a         2       0.0
    5    a         2       2.0
    6    c         3     NaN
    7    a         4       0.0
    8    a         4       2.0
    9    b         5       1.0
    10   b        5       3.0
    多对多连接产⽣的是⾏的笛卡尔积。由于左边的DataFrame有3个"b"⾏,右边的有2个,所以最终结果中就有6个"b"⾏。连接⽅式只影响出现在结果中的不同的键的值:
    pd.merge(df1, df2, how='inner')    # 输出如下:(等同于:pd.merge(df1, df2))
       key  data1  data2
    0   b      0      1
    1   b      0      3
    2   b      1      1
    3   b      1      3
    4   b      5      1
    5   b      5      3
    6   a      2      0
    7   a      2      2
    8   a      4      0
    9   a      4      2

    要根据多个键进⾏合并,传⼊⼀个由列名组成的列表即可:
    left = pd.DataFrame({'key1': ['foo', 'foo', 'bar'], 'key2': ['one', 'two', 'one'], 'lval': [1, 2, 3]})
    right = pd.DataFrame({'key1': ['foo', 'foo', 'bar', 'bar'], 'key2': ['one', 'one', 'one', 'two'], 'rval': [4, 5, 6, 7]})
    pd.merge(left, right, on=['key1', 'key2'], how='outer')    # 输出如下:(根据多个键合并)
       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对象的重叠列名上的字符串:
    pd.merge(left, right, on='key1')        # 输出如下:
        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
    pd.merge(left, right, on='key1', suffixes=('_left', '_right'))    # 输出如下(指定后缀)
       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

    表8-2 merge函数的参数

    image


    2、索引上的合并
    有时候,DataFrame中的连接键位于其索引中。在这种情况下,你可以传⼊left_index=True或right_index=True(或两个都传)以说明索引应该被⽤作连接键:
    left1 = pd.DataFrame({'key': ['a', 'b', 'a', 'a', 'b', 'c'], 'value': range(6)})
    right1 = pd.DataFrame({'group_val': [3.5, 7]}, index=['a', 'b'])
    left1        # 输出如下:
       key  value
    0   a      0
    1   b      1
    2   a      2
    3   a      3
    4   b      4
    5   c      5
    right1        # 输出如下:
        group_val
    a        3.5
    b        7.0
    pd.merge(left1, right1, left_on='key', right_index=True)    # 输出如下:
       key   value  group_val
    0   a          0        3.5
    2   a          2        3.5
    3   a          3        3.5
    1   b          1        7.0
    4   b          4        7.0
    由于默认的merge⽅法是求取连接键的交集,因此你可以通过外连接的⽅式得到它们的并集:
    pd.merge(left1, right1, left_on='key', right_index=True, how='outer')    # 输出如下:
       key  value  group_val
    0   a      0        3.5
    2   a      2        3.5
    3   a      3        3.5
    1   b      1        7.0
    4   b      4        7.0
    5   c      5        NaN

    对于层次化索引的数据,事情就有点复杂了,因为索引的合并默认是多键合并:
    lefth = pd.DataFrame({'key1': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada'],
                                          'key2': [2000, 2001, 2002, 2001, 2002],
                                         'data': np.arange(5.)})
    righth = pd.DataFrame(np.arange(12).reshape((6, 2)),
                                           index=[['Nevada', 'Nevada', 'Ohio', 'Ohio', 'Ohio', 'Ohio'],
                                                       [2001, 2000, 2000, 2000, 2001, 2002]],
                                          columns=['event1', 'event2'])
    lefth        # 输出如下:
             key1  key2  data
    0      Ohio  2000   0.0
    1      Ohio  2001   1.0
    2      Ohio  2002   2.0
    3  Nevada  2001   3.0
    4  Nevada  2002   4.0
    righth        # 输出如下:
                               event1  event2
    Nevada    2001           0       1
                    2000           2       3
    Ohio        2000           4       5
                    2000           6       7
                    2001           8       9
                    2002         10      11
    这种情况下,必须以列表的形式指明⽤作合并键的多个列(注意⽤how='outer'对重复索引值的处理):
    pd.merge(lefth, righth, left_on=['key1', 'key2'], right_index=True)    # 输出如下:
             key1  key2  data  event1  event2
    0      Ohio  2000   0.0           4       5
    0      Ohio  2000   0.0           6       7
    1      Ohio  2001   1.0           8       9
    2      Ohio  2002   2.0         10      11
    3  Nevada  2001   3.0           0       1
    pd.merge(lefth, righth, left_on=['key1', 'key2'], right_index=True, how='outer')    # 输出如下:
             key1  key2  data  event1  event2
    0      Ohio  2000   0.0        4.0     5.0
    0      Ohio  2000   0.0        6.0     7.0
    1      Ohio  2001   1.0        8.0     9.0
    2      Ohio  2002   2.0      10.0    11.0
    3  Nevada  2001   3.0       0.0     1.0
    4  Nevada  2002   4.0     NaN     NaN
    4  Nevada  2000   NaN     2.0     3.0

    同时使⽤合并双⽅的索引也没问题:
    left2 = pd.DataFrame([[1., 2.], [3., 4.], [5., 6.]],
                                          index=['a', 'c', 'e'],
                                          columns=['Ohio', 'Nevada'])
    right2 = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [13, 14]],
                                            index=['b', 'c', 'd', 'e'],
                                            columns=['Missouri', 'Alabama'])
    left2        # 输出如下:
        Ohio  Nevada
    a   1.0     2.0
    c   3.0     4.0
    e   5.0     6.0
    right2        # 输出如下:
        Missouri  Alabama
    b        7.0      8.0
    c        9.0     10.0
    d      11.0     12.0
    e      13.0     14.0
    pd.merge(left2, right2, how='outer', left_index=True, right_index=True)  # 输出如下:
          Ohio  Nevada  Missouri  Alabama
    a      1.0         2.0       NaN      NaN
    b   NaN       NaN         7.0        8.0
    c      3.0         4.0         9.0       10.0
    d   NaN       NaN       11.0       12.0
    e      5.0         6.0       13.0       14.0

    DataFrame还有⼀个便捷的join实例⽅法,它能更为⽅便地实现按索引合并。它还可⽤于合并多个带有相同或相似索引的DataFrame对象,但要求没有重叠的列。在上⾯那个例⼦中,我们可以编写:
    left2.join(right2, how='outer')    # 输出如下:
          Ohio  Nevada  Missouri  Alabama
    a      1.0         2.0       NaN      NaN
    b   NaN       NaN         7.0        8.0
    c      3.0         4.0         9.0       10.0
    d   NaN       NaN       11.0       12.0
    e      5.0         6.0       13.0       14.0
    因为⼀些历史版本的遗留原因,DataFrame的join⽅法默认使⽤的是左连接,保留左边表的⾏索引。它还⽀持在调⽤的DataFrame的列上,连接传递的DataFrame索引:
    left1.join(right1, on='key')    # 输出如下:
       key  value  group_val
    0   a        0          3.5
    1   b        1          7.0
    2   a        2          3.5
    3   a        3          3.5
    4   b        4          7.0
    5   c        5        NaN

    最后,对于简单的索引合并,还可以向join传⼊⼀组DataFrame,后面会介绍更为通⽤的concat函数,也能实现此功能:
    another = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [16., 17.]],
                                                index=['a', 'c', 'e', 'f'],
                                                columns=['New York', 'Oregon'])
    another        # 输出如下:
        New York  Oregon
    a            7.0     8.0
    c            9.0    10.0
    e          11.0    12.0
    f           16.0    17.0
    left2.join([right2, another])    # 输出如下:(注意参数是列表)
        Ohio  Nevada  Missouri  Alabama  New York  Oregon
    a   1.0          2.0        NaN         NaN            7.0         8.0
    c   3.0          4.0           9.0         10.0             9.0       10.0
    e   5.0          6.0         13.0         14.0           11.0       12.0
    left2.join([right2, another], how='outer')    # 输出如下:(行索引合并)
          Ohio  Nevada  Missouri  Alabama  New York  Oregon
    a      1.0        2.0         NaN         NaN            7.0         8.0
    b   NaN     NaN            7.0           8.0           NaN      NaN
    c      3.0        4.0            9.0         10.0             9.0       10.0
    d   NaN     NaN           11.0         12.0           NaN      NaN
    e      5.0        6.0          13.0          14.0           11.0       12.0
    f   NaN      NaN          NaN          NaN          16.0       17.0


    3、轴向连接
    另⼀种数据合并运算也被称作连接(concatenation)、绑定(binding)或堆叠(stacking)
    NumPy的concatenation函数可以⽤NumPy数组来做:
    arr = np.arange(12).reshape((3, 4))
    arr        # 输出如下:
    array([[ 0,  1,  2,  3],
               [ 4,  5,  6,  7],
               [ 8,  9, 10, 11]])
    np.concatenate([arr, arr], axis=1)    # 输出如下:
    array([[ 0,  1,  2,  3,  0,  1,  2,  3],
               [ 4,  5,  6,  7,  4,  5,  6,  7],
               [ 8,  9, 10, 11,  8,  9, 10, 11]])

    对于pandas对象(如Series和DataFrame),带有标签的轴使你能够进⼀步推⼴数组的连接运算。
    具体点说,你还需要考虑以下这些东⻄:
         如果对象在其它轴上的索引不同,我们应该合并这些轴的不
             同元素还是只使⽤交集?
         连接的数据集是否需要在结果对象中可识别?
         连接轴中保存的数据是否需要保留?许多情况下,
             DataFrame默认的整数标签最好在连接时删掉。
    pandas的concat函数提供了⼀种能够解决这些问题的可靠⽅式。
    下面将给出⼀些例⼦来讲解其使⽤⽅式。假设有三个没有重叠索引的Series:
    s1 = pd.Series([0, 1], index=['a', 'b'])
    s2 = pd.Series([2, 3, 4], index=['c', 'd', 'e'])
    s3 = pd.Series([5, 6], index=['f', 'g'])
    对这些对象调⽤concat可以将值和索引粘合在⼀起:
    pd.concat([s1, s2, s3])    # 输出如下:
    a    0
    b    1
    c    2
    d    3
    e    4
    f     5
    g    6
    dtype: int64
    默认情况下,concat是在axis=0上⼯作的,最终产⽣⼀个新的Series。如果传⼊axis=1,则结果就会变成⼀个DataFrame(axis=1是列):
    pd.concat([s1, s2, s3], axis=1)    # 输出如下:
             0        1       2
    a    0.0  NaN   NaN
    b    1.0  NaN   NaN
    c  NaN     2.0  NaN
    d  NaN     3.0  NaN
    e  NaN     4.0  NaN
    f   NaN  NaN     5.0
    g  NaN  NaN     6.0
    这种情况下,另外的轴上没有重叠,从索引的有序并集(外连接)上就可以看出来。
    传⼊join='inner'即可得到它们的交集:
    s4 = pd.concat([s1, s3])   
    s4            # 输出如下:
    a    0
    b    1
    f     5
    g    6
    dtype: int64
    pd.concat([s1, s4], axis=1)    # 输出如下:(并集)
             0   1
    a    0.0   0
    b    1.0   1
    f   NaN  5
    g  NaN  6
    pd.concat([s1, s4], axis=1, join='inner')    # 输出如下:(交集)
         0  1
    a  0  0
    b  1  1
    在这个例⼦中,f和g标签消失了,是因为使⽤的是join='inner'选项。
    你可以通过join_axes指定要在其它轴上使⽤的索引:
    pd.concat([s1, s4], axis=1, join_axes=[['a', 'c', 'b', 'e']])      # 输出如下:(指定行索引)
             0        1
    a    0.0     0.0
    c  NaN  NaN
    b    1.0     1.0
    e  NaN  NaN
    不过有个问题,参与连接的⽚段在结果中区分不开。假设你想要在连接轴上创建⼀个层次化索引。使⽤keys参数即可达到这个⽬的:
    result = pd.concat([s1, s1, s3], keys=['one', 'two', 'three'])    # 层次化索引
    result        # 输出如下:
    one     a    0
               b    1
    two     a    0
               b    1
    three   f     5
               g    6
    dtype: int64
    result.unstack()    # 输出如下:
                    a       b       f       g
    one       0.0    1.0  NaN  NaN
    two       0.0    1.0  NaN  NaN
    three  NaN  NaN    5.0     6.0
    如果沿着axis=1对Series进⾏合并,则keys就会成为DataFrame的列头:
    pd.concat([s1, s2, s3], axis=1, keys=['one', 'two', 'three'])    # 输出如下:
          one    two   three
    a    0.0   NaN    NaN
    b    1.0   NaN    NaN
    c  NaN     2.0    NaN
    d  NaN     3.0    NaN
    e  NaN     4.0    NaN
    f   NaN  NaN       5.0
    g  NaN  NaN       6.0

    同样的逻辑也适⽤于DataFrame对象:
    df1 = pd.DataFrame(np.arange(6).reshape(3, 2), index=['a', 'b', 'c'],
                                       columns=['one', 'two'])
    df2 = pd.DataFrame(5 + np.arange(4).reshape(2, 2), index=['a', 'c'],
                                       columns=['three', 'four'])
    df1    # 输出如下:
        one  two
    a    0    1
    b    2    3
    c    4    5
    df2    # 输出如下:
        three  four
    a      5     6
    c      7     8
    pd.concat([df1, df2], axis=1, keys=['level1', 'level2'])    # 输出如下:
          level1      level2
          one two  three    four
    a      0      1      5.0     6.0
    b      2      3    NaN  NaN
    c      4      5       7.0     8.0
    如果传⼊的不是列表⽽是⼀个字典,则字典的键就会被当做keys选项的值:
    pd.concat({'level1': df1, 'level2': df2}, axis=1)    # 输出如下
          level1      level2
          one two  three    four
    a      0      1      5.0     6.0
    b      2      3    NaN  NaN
    c      4      5       7.0     8.0

    此外还有两个⽤于管理层次化索引创建⽅式的参数(参⻅表8-3)。举个例⼦,我们可以⽤names参数命名创建的轴级别:
    pd.concat([df1, df2], axis=1, keys=['level1', 'level2'],
                      names=['upper', 'lower'])    # 输出如下
    upper    level1     level2
    lower    one two  three   four
    a               0    1      5.0     6.0
    b               2    3    NaN  NaN
    c               4    5       7.0     8.0
    最后⼀个关于DataFrame的问题是,DataFrame的⾏索引不包含任何相关数据:
    df1 = pd.DataFrame(np.random.randn(3, 4), columns=['a', 'b', 'c', 'd'])
    df2 = pd.DataFrame(np.random.randn(2, 3), columns=['b', 'd', 'a'])
    df1        # 输出如下
                     a               b                c               d
    0  1.721685 -0.160918 -2.419580 -1.634277
    1 -1.039395 -0.595996  0.788547  0.455259
    2  0.342471 -0.118662  1.402167  0.280451
    df2        # 输出如下
                     b              d               a
    0  0.236757 -0.909635  0.986049
    1 -0.038411 -0.034186 -0.879236
    在这种情况下,传⼊ignore_index=True即可:
    pd.concat([df1, df2], ignore_index=True)    # 输出如下:
                     a               b               c                  d
    0   1.721685 -0.160918 -2.419580   -1.634277
    1  -1.039395 -0.595996  0.788547    0.455259
    2   0.342471 -0.118662  1.402167    0.280451
    3   0.986049  0.236757         NaN   -0.909635
    4  -0.879236 -0.038411         NaN   -0.034186

    表8-3 concat函数的参数

    image


    4、合并重叠数据
    还有⼀种数据组合问题不能⽤简单的合并(merge)或连接(concatenation)运算来处理。⽐如说,你可能有索引全部或部分重叠的两个数据集。举个有启发性的例⼦,我们使⽤NumPy的where函数,它表示⼀种等价于⾯向数组的if-else:
    a = pd.Series([np.nan, 2.5, np.nan, 3.5, 4.5, np.nan],
                            index=['f', 'e', 'd', 'c', 'b', 'a'])
    b = pd.Series(np.arange(len(a), dtype=np.float64),
                           index=['f', 'e', 'd', 'c', 'b', 'a'])
    b[-1] = np.nan
    a        # 输出如下:
    f     NaN
    e      2.5
    d    NaN
    c      3.5
    b      4.5
    a    NaN
    dtype: float64
    b        # 输出如下:
    f       0.0
    e      1.0
    d      2.0
    c      3.0
    b      4.0
    a    NaN
    dtype: float64
    np.where(pd.isnull(a), b, a)    # 输出:array([0. , 2.5, 2. , 3.5, 4.5, nan])
    Series有⼀个combine_first⽅法,实现的也是⼀样的功能,还带有pandas的数据对⻬:
    b[:-2].combine_first(a[2:])    # 输出如下:(a[2:]为b[:-2]缺失的数据打补丁)
    a    NaN
    b    4.5
    c    3.0
    d    2.0
    e    1.0
    f    0.0
    dtype: float64

    对于DataFrame,combine_first⾃然也会在列上做同样的事情,因此你可以将其看做:⽤传递对象中的数据为调⽤对象的缺失数据“打补丁”:
    df1 = pd.DataFrame({'a': [1., np.nan, 5., np.nan],
                                        'b': [np.nan, 2., np.nan, 6.],
                                        'c': range(2, 18, 4)})
    df2 = pd.DataFrame({'a': [5., 4., np.nan, 3., 7.],
                                        'b': [np.nan, 3., 4., 6., 8.]})
    df1        # 输出如下:
              a       b   c
    0     1.0  NaN   2
    1  NaN     2.0   6
    2     5.0  NaN  10
    3  NaN     6.0  14
    df2        # 输出如下:
              a       b
    0     5.0  NaN
    1     4.0     3.0
    2  NaN     4.0
    3     3.0     6.0
    4     7.0     8.0
    df1.combine_first(df2)    # 输出如下:相当于df1缺失的数据用df2来填充
          a        b        c
    0  1.0  NaN     2.0
    1  4.0     2.0     6.0
    2  5.0     4.0    10.0
    3  3.0     6.0    14.0
    4  7.0     8.0   NaN


    三、重塑和轴向旋转
    有许多⽤于重新排列表格型数据的基础运算。这些函数也称作重塑(reshape)或轴向旋转(pivot)运算

    1、重塑层次化索引
    层次化索引为DataFrame数据的重排任务提供了⼀种具有良好⼀致性的⽅式。主要功能有⼆:
         stack:将数据的列“旋转”为⾏。
         unstack:将数据的⾏“旋转”为列。
    下面通过⼀系列的范例来讲解这些操作。接下来看⼀个简单的DataFrame,其中的⾏列索引均为字符串数组:
    data = pd.DataFrame(np.arange(6).reshape((2, 3)),
                                         index=pd.Index(['Ohio', 'Colorado'], name='state'),
                                         columns=pd.Index(['one', 'two', 'three'], name='number'))
    data         # 输出如下:
    number    one  two  three
    state
    Ohio            0      1         2
    Colorado     3      4         5
    对该数据使⽤stack⽅法即可将列转换为⾏,得到⼀个Series:
    result = data.stack()
    result        # 输出如下:
    state       number
    Ohio              one       0
                          two       1
                        three       2
    Colorado       one       3
                          two       4
                        three       5
    dtype: int32
    对于⼀个层次化索引的Series,你可以⽤unstack将其重排为⼀个DataFrame:
    result.unstack()    # 输出如下:
    number    one  two  three
    state
    Ohio            0      1         2
    Colorado     3      4         5
    默认情况下,unstack操作的是最内层(stack也是如此)。传⼊分层级别的编号或名称即可对其它级别进⾏unstack操作:
    result.unstack(0)    # 输出如下:将第1层索引变为列,索引从0开始,0表示第1层
    state       Ohio  Colorado
    number
    one               0              3
    two               1              4
    three             2              5
    result.unstack('state')    # 输出如下:使用列索引的属性名称
    state       Ohio  Colorado
    number
    one               0              3
    two               1              4
    three             2              5

    如果不是所有的级别值都能在各分组中找到的话,则unstack操作可能会引⼊缺失数据:
    s1 = pd.Series([0, 1, 2, 3], index=['a', 'b', 'c', 'd'])
    s2 = pd.Series([4, 5, 6], index=['c', 'd', 'e'])
    data2 = pd.concat([s1, s2], keys=['one', 'two'])
    data2        # 输出如下:
    one   a    0
             b    1
             c    2
             d    3
    two   c    4
             d    5
             e    6
    dtype: int64
    data2.unstack()    # 输出如下:
                 a        b    c     d      e
    one    0.0     1.0  2.0  3.0  NaN
    two  NaN  NaN  4.0  5.0    6.0
    stack默认会滤除缺失数据,因此该运算是可逆的:
      data2.unstack().stack()    # 输出如下:
    one   a     0.0
             b    1.0
             c     2.0
             d    3.0
    two   c     4.0
             d    5.0
             e    6.0
    dtype: float64
    data2.unstack().stack(dropna=False)    # 输出如下:参数dropna=False不滤除缺失数据
    one  a       0.0
             b      1.0
             c       2.0
             d      3.0
             e    NaN
    two   a    NaN
             b    NaN
             c       4.0
             d       5.0
             e       6.0
    dtype: float64

    在对DataFrame进⾏unstack操作时,作为旋转轴的级别将会成为结果中的最低级别:
    result        # result的输出如下:
    state     number
    Ohio            one       0
                        two       1
                      three       2
    Colorado     one       3
                        two       4
                      three       5
    dtype: int32
    df = pd.DataFrame({'left': result, 'right': result + 5},
                                      columns=pd.Index(['left', 'right'], name='side'))
    df        # 输出如下:
    side                               left  right
    state        number
    Ohio             one               0      5
                         two               1      6
                         three             2      7
    Colorado      one               3      8
                         two               4      9
                         three            5     10
    df.unstack('state')    # 输出如下:
    side           left                      right
    state         Ohio Colorado  Ohio Colorado
    number
    one                0              3       5             8
    two                1              4       6             9
    three              2              5       7            10
    当调⽤stack,我们可以指明轴的名字:
    df.unstack('state').stack('side')    # 输出如下:
    state                     Colorado   Ohio
    number     side
    one            left                   3        0
                     right                  8        5
    two            left                   4        1
                     right                  9        6
    three          left                   5        2
                     right                10        7

    2、将“⻓格式”旋转为“宽格式”
    多个时间序列数据通常是以所谓的“⻓格式”(long)或“堆叠格式”(stacked)存储在数据库和CSV中的。我们先加载⼀些示例数据,做⼀些时间序列规整和数据清洗:
    data = pd.read_csv('examples/macrodata.csv')
    data.head()        # 输出如下:
             year   quarter    realgdp  realcons    realinv  realgovt  realdpi      cpi 
    0  1959.0          1.0  2710.349     1707.4  286.898   470.045   1886.9  28.98
    1  1959.0          2.0  2778.801     1733.7  310.859   481.301   1919.7  29.15
    2  1959.0          3.0  2775.488     1751.8  289.226   491.260   1916.4  29.35
    3  1959.0          4.0  2785.204     1753.7  299.356   484.052   1931.3  29.37
    4  1960.0          1.0  2847.699     1770.5  331.722   462.199   1955.5  29.54
            m1  tbilrate  unemp        pop   infl  realint
    0  139.7       2.82        5.8  177.146  0.00     0.00
    1  141.7       3.08        5.1  177.830  2.34     0.74
    2  140.5       3.82        5.3  178.657  2.74     1.09
    3  140.0       4.33        5.6  179.386  0.27     4.06
    4  139.6       3.50        5.2  180.007  2.31     1.19
    periods = pd.PeriodIndex(year=data.year, quarter=data.quarter, name='date')
    columns = pd.Index(['realgdp', 'infl', 'unemp'], name='item')
    data = data.reindex(columns=columns)
    data.index = periods.to_timestamp('D', 'end')
    ldata = data.stack().reset_index().rename(columns={0: 'value'})
    在后面的某个章节看到PeriodIndex会更加频繁。总之,它结合了年度(year)和季度(quarter)来创建一种时间间隔类型的列。ldata的数据像这样:
    ldata[:10]        # 输出如下:
                   date        item       value
    0 1959-03-31   realgdp  2710.349
    1 1959-03-31         infl        0.000
    2 1959-03-31    unemp       5.800
    3 1959-06-30   realgdp  2778.801
    4 1959-06-30         infl        2.340
    5 1959-06-30    unemp        5.100
    6 1959-09-30   realgdp   2775.488
    7 1959-09-30         infl         2.740
    8 1959-09-30    unemp         5.300
    9 1959-12-31   realgdp    2785.204
    这就是多个时间序列(或者其它带有两个或多个键的可观察数据,这⾥,我们的键是date和item)的⻓格式。表中的每⾏代表⼀次观察。
    关系型数据库(如MySQL)中的数据经常都是这样存储的,因为固定架构(即列名和数据类型)有⼀个好处:随着表中数据的添加,item列中的值的种类能够增加。在前⾯的例⼦中,date和item通常就是主键(⽤关系型数据库的说法),不仅提供了关系完整性,⽽且提供了更为简单的查询⽀持。有的情况下,使⽤这样的数据会很麻烦,你可能会更喜欢DataFrame,不同的item值分别形成⼀列,date列中的时间戳则⽤作索引。DataFrame的pivot⽅法完全可以实现这个转换:
    pivoted = ldata.pivot('date', 'item', 'value')
    pivoted        # 输出如下:
    item               infl     realgdp  unemp
    date
    1959-03-31  0.00   2710.349    5.8
    1959-06-30  2.34   2778.801    5.1
    1959-09-30  2.74   2775.488    5.3
    1959-12-31  0.27   2785.204    5.6
    1960-03-31  2.31   2847.699    5.2
    1960-06-30  0.14   2834.390    5.2
    1960-09-30  2.70   2839.022    5.6
    1960-12-31  1.21   2802.616    6.3
    1961-03-31 -0.40   2819.264    6.8
    1961-06-30  1.47   2872.005    7.0
    ...          ...        ...    ...
    2007-06-30  2.75  13203.977    4.5
    2007-09-30  3.45  13321.109    4.7
    2007-12-31  6.38  13391.249    4.8
    2008-03-31  2.82  13366.865    4.9
    2008-06-30  8.53  13415.266    5.4
    2008-09-30 -3.16  13324.600    6.0
    2008-12-31 -8.79  13141.920    6.9
    2009-03-31  0.94  12925.410    8.1
    2009-06-30  3.37  12901.504    9.2
    2009-09-30  3.56  12990.341    9.6
    [203 rows x 3 columns]

    前两个传递的值分别⽤作⾏和列索引,最后⼀个可选值则是⽤于填充DataFrame的数据列。假设有两个需要同时重塑的数据列:
    ldata['value2'] = np.random.randn(len(ldata))
    ldata[:10]        # 输出如下:
                  date        item       value      value2
    0 1959-03-31   realgdp  2710.349 -0.844315
    1 1959-03-31         infl        0.000  2.452349
    2 1959-03-31    unemp       5.800  0.408969
    3 1959-06-30   realgdp  2778.801 -0.874402
    4 1959-06-30         infl        2.340 -2.426008
    5 1959-06-30    unemp       5.100 -1.148190
    6 1959-09-30   realgdp  2775.488  0.976518
    7 1959-09-30         infl        2.740 -1.178615
    8 1959-09-30    unemp       5.300 -0.019268
    9 1959-12-31   realgdp  2785.204 -1.245152

    如果忽略最后⼀个参数,得到的DataFrame就会带有层次化的列:
    pivoted = ldata.pivot('date', 'item')
    pivoted[:5]    # 输出如下:
                           value                                            value2
    item               infl     realgdp   unemp                 infl        realgdp      unemp
    date
    1959-03-31  0.00  2710.349          5.8         2.452349    -0.844315    0.408969
    1959-06-30  2.34  2778.801          5.1       -2.426008    -0.874402   -1.148190
    1959-09-30  2.74  2775.488          5.3       -1.178615     0.976518   -0.019268
    1959-12-31  0.27  2785.204          5.6         0.945661   -1.245152     1.050657
    1960-03-31  2.31  2847.699          5.2        -0.522355   -0.979312   -1.675772
    pivoted['value'][:5]    # 输出如下:
    item                infl     realgdp  unemp
    date
    1959-03-31  0.00  2710.349    5.8
    1959-06-30  2.34  2778.801    5.1
    1959-09-30  2.74  2775.488    5.3
    1959-12-31  0.27  2785.204    5.6
    1960-03-31  2.31  2847.699    5.2
    注意,pivot其实就是⽤set_index创建层次化索引,再⽤unstack重塑:
    unstacked = ldata.set_index(['date', 'item']).unstack('item')
    unstacked[:7]    # 输出如下:
                           value                                      value2
    item                infl     realgdp   unemp          infl     realgdp    unemp
    date
    1959-03-31  0.00  2710.349          5.8  2.452349 -0.844315  0.408969
    1959-06-30  2.34  2778.801          5.1 -2.426008 -0.874402 -1.148190
    1959-09-30  2.74  2775.488          5.3 -1.178615  0.976518 -0.019268
    1959-12-31  0.27  2785.204          5.6  0.945661 -1.245152  1.050657
    1960-03-31  2.31  2847.699          5.2 -0.522355 -0.979312 -1.675772
    1960-06-30  0.14  2834.390          5.2  0.367315 -1.547255 -0.502727
    1960-09-30  2.70  2839.022          5.6 -0.629023  0.244908 -0.188626


    3、将“宽格式”旋转为“⻓格式”
    旋转DataFrame的逆运算是pandas.melt。它不是将⼀列转换到多个新的DataFrame,⽽是合并多个列成为⼀个,产⽣⼀个⽐输⼊⻓的DataFrame。看⼀个例⼦:
    df = pd.DataFrame({'key': ['foo', 'bar', 'baz'],
                                      'A': [1, 2, 3],
                                      'B': [4, 5, 6],
                                      'C': [7, 8, 9]})
    df    # 输出如下:
         key  A  B  C
    0   foo  1  4  7
    1   bar  2  5  8
    2   baz  3  6  9
    key列可能是分组指标,其它的列是数据值。当使⽤pandas.melt,必须指明哪些列是分组指标。下⾯使⽤key作为唯⼀的分组指标:
    melted = pd.melt(df, ['key'])
    melted        # 输出如下:
        key variable  value
    0  foo        A      1
    1  bar        A      2
    2  baz        A      3
    3  foo        B      4
    4  bar        B      5
    5  baz        B      6
    6  foo        C      7
    7  bar        C      8
    8  baz        C      9
    使⽤pivot,可以重塑回原来的样⼦:
    reshaped = melted.pivot('key', 'variable', 'value')
    reshaped        # 输出如下:
    variable  A  B  C
    key
    bar         2  5  8
    baz        3   6  9
    foo        1   4  7
    因为pivot的结果从列创建了⼀个索引,⽤作⾏标签,我们可以使⽤reset_index将数据移回列:
    reshaped.reset_index()    # 输出如下:
    variable  key  A  B  C
    0             bar  2  5  8
    1            baz  3  6  9
    2            foo  1  4  7
    你还可以指定列的⼦集,作为值的列:
    pd.melt(df, id_vars=['key'], value_vars=['A', 'B'])    # 输出如下:
        key variable  value
    0  foo        A      1
    1  bar        A      2
    2  baz        A      3
    3  foo        B      4
    4  bar        B      5
    5  baz        B      6

    pandas.melt也可以不⽤分组指标:
    pd.melt(df, value_vars=['A', 'B', 'C'])    # 输出如下:
       variable  value
    0        A      1
    1        A      2
    2        A      3
    3        B      4
    4        B      5
    5        B      6
    6        C      7
    7        C      8
    8        C      9
    pd.melt(df, value_vars=['key', 'A', 'B'])    # 输出如下:
       variable value
    0      key   foo
    1      key   bar
    2      key   baz
    3        A     1
    4        A     2
    5        A     3
    6        B     4
    7        B     5
    8        B     6

  • 相关阅读:
    C#读取Excel设置(亲测可用)
    vue element-ui的对话框dialog没有height怎么解决?
    sqlserver不同服务器的不同数据库如何复制
    es6-对象与数组的解构赋值
    win10电脑上不了网了
    sqlserver数据库备份之后再还原
    “相对路径”以及“绝对路径”使用之坑
    sqlserver表-添加大量测试数据
    vue文件命名规范
    Git大小写问题
  • 原文地址:https://www.cnblogs.com/Micro0623/p/10144729.html
Copyright © 2020-2023  润新知