Skip to content

数据规整:连接、联合与重塑

分层索引

import pandas as pd
import numpy as np
import re

分层索引是pandas的重要特性,允许你在一个轴向上拥有多个(两个或两个以上)索引层级。 分层索import re引提供了一种在更低维度的形式中处理更高维度数据的方式。

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]]
)

输出是一个以MultiIndex作为索引的Series的美化视图。 索引中的"间隙"表示"直接使用上面的标签"。

print(data)
# a  1    0.163468
#    2   -1.525926
#    3   -0.210247
# b  1   -0.956063
#    3   -1.839111
# c  1   -0.398905
#    2    0.595279
# d  2    0.034305
#    3   -0.896078
# dtype: float64

print(data.index)
# MultiIndex([('a', 1),
#             ('a', 2),
#             ('a', 3),
#             ('b', 1),
#             ('b', 3),
#             ('c', 1),
#             ('c', 2),
#             ('d', 2),
#             ('d', 3)],
#            )

通过分层索引对象,也可以称为部分索引,可以简洁地选择出数据的子集。

m = data['b']
print(m)
# 1   -0.956063
# 3   -1.839111
# dtype: float64

m = data['b': 'c']
print(m)
# b  1   -0.956063
#    3   -1.839111
# c  1   -0.398905
#    2    0.595279
# dtype: float64
m = data.loc[['b', 'c']]
print(m)
# b  1   -0.956063
#    3   -1.839111
# c  1   -0.398905
#    2    0.595279
# dtype: float64

m = data.loc[:, 2]
print(m)
# a   -1.525926
# c    0.595279
# d    0.034305
# dtype: float64

分层索引在重塑数据和数组透视表等分组操作中扮演了重要角色。 例如,你可以使用unstack方法将数据在DataFrame中重新排列。

m = data.unstack()
print(m)
#           1         2         3
# a  0.163468 -1.525926 -0.210247
# b -0.956063       NaN -1.839111
# c -0.398905  0.595279       NaN
# d       NaN  0.034305 -0.896078
n = m.stack()
print(n)  # 或者 print(data.unstack().stack())
# a  1    0.163468
#    2   -1.525926
#    3   -0.210247
# b  1   -0.956063
#    3   -1.839111
# c  1   -0.398905
#    2    0.595279
# d  2    0.034305
#    3   -0.896078
# dtype: float64

DataFrame索引分层

在DataFrame中,每个轴都可以拥有分层索引。参考

方法1:直接创建

直接通过给index(columns)参数传递多维数组,进而构建多维索引。 数组中每个维度对应位置的元素,组成每个索引值。

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']]
)

print(frame)
#      Ohio     Colorado
#     Green Red    Green
# a 1     0   1        2
#   2     3   4        5
# b 1     6   7        8
#   2     9  10       11

上面输出中的2个层级是没有名字。 分层的层级可以有名称(可以是字符串或Python对象)。 如果层级有名称,这些名称会在控制台输出中显示。

print(frame.index.names)
# [None, None]
print(frame.columns.names)
# [None, None]

给层级赋予名称。注意区分行标签中的索引名称statecolor

frame.index.names = ['key1', 'key2']
frame.columns.names = ['state', 'color']
print(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
print(frame['Ohio'])
# color      Green  Red
# key1 key2
# a    1         0    1
#      2         3    4
# b    1         6    7
#      2         9   10

print(frame.index)
# MultiIndex([('a', 1),
#             ('a', 2),
#             ('b', 1),
#             ('b', 2)],
#            names=['key1', 'key2'])

通过MultiIndex类的相关方法,预先创建一个MultiIndex对象,然后作为Series与DataFrame中的index(或columns)参数值。同时,可以通过names参数指定多层索引的名称。

方法2:from_arrays

from_arrays:接收一个多维数组参数,高维指定高层索引,低维指定底层索引。

mindex = pd.MultiIndex.from_arrays(
    [['a', 'a', 'b', 'b'],
     [1, 2, 1, 2]],
    names=['key1', 'key2']
)
frame = pd.DataFrame(
    np.arange(12).reshape((4, 3)),
    index=mindex,
    columns=[['Ohio', 'Ohio', 'Colorado'],
             ['Green', 'Red', 'Green']]
)
frame.columns.names = ['state', 'color']
print(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

方法3:from_tuples

from_tuples:接收一个元组的列表,每个元组指定每个索引(高维索引,低维索引)。

mindex = pd.MultiIndex.from_tuples(
    [('a', 1),
     ('a', 2),
     ('b', 1),
     ('b', 2)]
)
frame = pd.DataFrame(
    np.arange(12).reshape((4, 3)),
    index=mindex,
    columns=[['Ohio', 'Ohio', 'Colorado'],
             ['Green', 'Red', 'Green']]
)
frame.index.names = ['key1', 'key2']
frame.columns.names = ['state', 'color']
print(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

方法4:from_product

from_product:接收一个可迭代对象的列表,根据多个可迭代对象元素的笛卡尔积进行创建索引。 使用笛卡尔积的方式来创建多层索引。参数为嵌套的可迭代对象。结果为使用每个一维数组中的元素与其他一维数组中的元素来生成。 笛卡尔积的方式的局限:两两组合必须都存在,否则,就不能使用这种方式。

mindex = pd.MultiIndex.from_product(
    [['a', 'b'],
     ['1', '2']],
    names=['key1', 'key2']
)
frame = pd.DataFrame(
    np.arange(12).reshape((4, 3)),
    index=mindex,
    columns=[['Ohio', 'Ohio', 'Colorado'],
             ['Green', 'Red', 'Green']]
)
frame.columns.names = ['state', 'color']
print(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

重排序和层级排序

如果需要重新排列轴上的层级顺序,或者按照特定层级的值对数据进行排序, 可以通过swaplevel接收两个层级序号或层级名称,返回一个进行了层级变更的新对象(但是数据是不变的)。

print(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
m = frame.swaplevel('key1', 'key2')
print(m)
# 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以使得结果按照层级进行字典排序。

m = frame.sort_index(level=1)  # 对key2排序,底层索引
print(m)
# 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
m = frame.sort_index(level=0)  # 对key1排序,高层索引
print(m)
# 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

m = frame.swaplevel(0, 1).sort_index(level=1)  # swaplevel(0, 1)等同于swaplevel(key1, key2),交换后key1变成了底层索引
print(m)
# 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

按层级进行汇总统计

DataFrame和Series中很多描述性和汇总性统计有一个level选项,通过level选项你可以指定你想要在某个特定的轴上进行聚合。

print(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
m = frame.groupby(level='key2').sum()
print(m)
# state  Ohio     Colorado
# color Green Red    Green
# key2
# 1         6   8       10
# 2        12  14       16
m = frame.groupby(level='color', axis=1).sum()
print(m)
# color      Green  Red
# key1 key2
# a    1         2    1
#      2         8    4
# b    1        14    7
#      2        20   10

使用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]
     }
)
print(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,新的DataFrame使用一个或多个列作为索引。 默认情况下这些索引列会从DataFrame中移除,也可以将它们留在DataFrame中。

frame2 = frame.set_index(['c', 'd'], drop=False)
print(frame2)
#        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

frame2 = frame.set_index(['c', 'd'])
print(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

reset_indexset_index的反操作,分层索引的索引层级会被移动到列中。 注意:如果在set_index时使用了drop=False,在使用reset_index会报错。

m = frame2.reset_index()
print(m)
#      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根据一个或多个键将行进行连接。对于SQL或其他关系型数据库的用户来说,这种方式比较熟悉,它实现的是数据库的连接操作。
  • pandas.concat使对象在轴向上进行黏合或“堆叠”。
  • combine_first实例方法允许将重叠的数据拼接在一起,以使用一个对象中的值填充另一个对象中的缺失值。

数据库风格的DataFrame连接

合并或连接操作通过一个或多个键连接行来联合数据集。 这些操作是关系型数据库的核心内容(例如基于SQL的数据库)。 pandas中的merge函数主要用于将各种join操作算法运用在数据上。 在进行列-列连接时,传递的DataFrame索引对象会被丢弃。 合并操作也要考虑如何处理重叠的列名(suffixes后缀选项)。

下面是一个多对一连接的例子。 df1的数据有多个行的标签为ab,而df2key列中每个值仅有一行。

df1 = pd.DataFrame(
    {
        'key': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],
        'data1': range(7)
     }
)
df2 = pd.DataFrame(
    {
        'key': ['a', 'b', 'd'],
        'data1': range(3)
     }
)
print(df1)
#   key  data1
# 0   b      0
# 1   b      1
# 2   a      2
# 3   c      3
# 4   a      4
# 5   a      5
# 6   b      6
print(df2)
#   key  data1
# 0   a      0
# 1   b      1
# 2   d      2

调用merge处理,推荐显式地指定连接键。

result = pd.merge(df1, df2)
print(result)
#   key  data1
# 0   b      1
result = pd.merge(df1, df2, on=['key', 'data1'])
print(result)
#   key  data1
# 0   b      1
result = pd.merge(df1, df2, on='key')
print(result)
#   key  data1_x  data1_y
# 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)
     }
)
print(df3)
#   lkey  data1
# 0    b      0
# 1    b      1
# 2    a      2
# 3    c      3
# 4    a      4
# 5    a      5
# 6    b      6
print(df4)
#   rkey  data2
# 0    a      0
# 1    b      1
# 2    d      2

默认情况下,merge做的是内连接('inner' join),结果中的键是两张表的交集。

result = pd.merge(df3, df4, left_on='lkey', right_on='rkey')  # df4的[a,0]对应df3的所有[a,?]记录(通过重复来填充不足)
print(result)
#   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

外连接(outer join)是键的并集,联合了左连接和右连接的效果。 多对多连接是行的笛卡尔积。

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)
     }
)
print(df1.sort_values(by='key'))
#   key  data1
# 2   a      2
# 4   a      4
# 0   b      0
# 1   b      1
# 5   b      5
# 3   c      3
print(df2.sort_values(by='key'))
#   key  data2
# 0   a      0
# 2   a      2
# 1   b      1
# 3   b      3
# 4   d      4

result = pd.merge(df1, df2, on='key', how='left')
print(result.sort_values(by='key'))
#    key  data1  data2
# 4    a      2    0.0
# 5    a      2    2.0
# 7    a      4    0.0
# 8    a      4    2.0
# 0    b      0    1.0
# 1    b      0    3.0
# 2    b      1    1.0
# 3    b      1    3.0
# 9    b      5    1.0
# 10   b      5    3.0
# 6    c      3    NaN

result = pd.merge(df1, df2, on='key', how='outer')  # 多对多连接
print(result.sort_values(by='key'))
#    key  data1  data2
# 6    a    2.0    0.0
# 7    a    2.0    2.0
# 8    a    4.0    0.0
# 9    a    4.0    2.0
# 0    b    0.0    1.0
# 1    b    0.0    3.0
# 2    b    1.0    1.0
# 3    b    1.0    3.0
# 4    b    5.0    1.0
# 5    b    5.0    3.0
# 10   c    3.0    NaN
# 11   d    NaN    4.0

多键合并。

df1 = pd.DataFrame(
    {
        'key1': ['foo', 'foo', 'bar'],
        'key2': ['one', 'two', 'one'],
        'lval': [1, 2, 3]
     }
)
df2 = pd.DataFrame(
    {
        'key1': ['foo', 'foo', 'bar', 'bar'],
        'key2': ['one', 'one', 'one', 'two'],
        'rval': [4, 5, 6, 7]
     }
)
print(df1.sort_values(by=['key1', 'key2']))
#   key1 key2  lval
# 2  bar  one     3
# 0  foo  one     1
# 1  foo  two     2
print(df2.sort_values(by=['key1', 'key2']))
#   key1 key2  rval
# 2  bar  one     6
# 3  bar  two     7
# 0  foo  one     4
# 1  foo  one     5

result = pd.merge(df1, df2, on=['key1', 'key2'], how='outer')
print(result.sort_values(by=['key1', 'key2']))
#   key1 key2  lval  rval
# 3  bar  one   3.0   6.0
# 4  bar  two   NaN   7.0
# 0  foo  one   1.0   4.0  # 重复填充
# 1  foo  one   1.0   5.0  # 重复填充
# 2  foo  two   2.0   NaN

处理重叠列名。

result = pd.merge(df1, df2, on='key1')
print(result.sort_values(by='key1'))
#   key1 key2_x  lval key2_y  rval
# 4  bar    one     3    one     6
# 5  bar    one     3    two     7
# 0  foo    one     1    one     4
# 1  foo    one     1    one     5
# 2  foo    two     2    one     4
# 3  foo    two     2    one     5
result = pd.merge(df1, df2, on='key1', suffixes=('_left', '_right'))
print(result.sort_values(by='key1'))
#   key1 key2_left  lval key2_right  rval
# 4  bar       one     3        one     6
# 5  bar       one     3        two     7
# 0  foo       one     1        one     4
# 1  foo       one     1        one     5
# 2  foo       two     2        one     4
# 3  foo       two     2        one     5

根据索引合并

在某些情况下,DataFrame中用于合并的键是它的索引。可以传递left_index=Trueright_index=True(或者都传)来表示索引需要用来作为合并的键。

df1 = pd.DataFrame(
    {
        'key1': ['foo', 'foo', 'bar'],
        'key2': ['one', 'two', 'one'],
        'lval': [1, 2, 3]
     }
)
df2 = pd.DataFrame(
    {
        'key1': ['foo', 'foo', 'bar', 'bar'],
        'key2': ['one', 'one', 'one', 'two'],
        'rval': [4, 5, 6, 7]
     },
    index=['foo', 'foo', 'bar', 'bar']
)
print(df1)
#   key1 key2  lval
# 0  foo  one     1
# 1  foo  two     2
# 2  bar  one     3
print(df2)
#     key1 key2  rval
# foo  foo  one     4
# foo  foo  one     5
# bar  bar  one     6
# bar  bar  two     7
result = pd.merge(df1, df2, left_on='key1', right_index=True, suffixes=('_left', '_right'))
print(result.sort_index())
#   key1 key1_left key2_left  lval key1_right key2_right  rval
# 0  foo       foo       one     1        foo        one     4
# 0  foo       foo       one     1        foo        one     5
# 1  foo       foo       two     2        foo        one     4
# 1  foo       foo       two     2        foo        one     5
# 2  bar       bar       one     3        bar        one     6
# 2  bar       bar       one     3        bar        two     7
result = pd.merge(df1, df2, left_on='key1', right_index=True, how='outer', suffixes=('_left', '_right'))  # 和上述结果一样
print(result.sort_index())
#   key1 key1_left key2_left  lval key1_right key2_right  rval
# 0  foo       foo       one     1        foo        one     4
# 0  foo       foo       one     1        foo        one     5
# 1  foo       foo       two     2        foo        one     4
# 1  foo       foo       two     2        foo        one     5
# 2  bar       bar       one     3        bar        one     6
# 2  bar       bar       one     3        bar        two     7

在更复杂多层索引数据的多键合并,在索引上连接是一个隐式的多键合并。 必须以列表的方式指明合并所需多个列(注意使用how='outer'处理重复的索引值)。

df1 = pd.DataFrame(
    {
        'key1': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada'],
        'key2': [2000, 2001, 2002, 2001, 2002],
        'data': np.arange(5.)
    }
)
df2 = pd.DataFrame(
    np.arange(12).reshape((6, 2)),
    index=[
        ['Nevada', 'Nevada', 'Ohio', 'Ohio', 'Ohio', 'Ohio'],
        [2001, 2000, 2000, 2000, 2001, 2002]
    ],
    columns=['event1', 'event2']
)
print(df1)
#      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
print(df2)
#              event1  event2
# Nevada 2001       0       1
#        2000       2       3
# Ohio   2000       4       5
#        2000       6       7
#        2001       8       9
#        2002      10      11
result = pd.merge(df1, df2, left_on=['key1', 'key2'], right_index=True)
print(result)
#      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
result = pd.merge(df1, df2, left_on=['key1', 'key2'], right_index=True, how='outer')
print(result)
#      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

使用两边的索引进行合并也是可以的,前提是用两边用来合并的索引有交集(公共部分)。 在使用merge时,参数on=['key1', 'key2']不能和 left_index=True, right_index=True同时存在。 对于重复索引,如果值不同,则多行显示,和数据库SQL的full join类似概念。 如果出现相同列名,则会自动添加后缀字符以示区别。

df1 = pd.DataFrame(
    [[1, 2], [3, 4], [5, 6]],
    index=['a', 'c', 'e'],
    columns=['Ohio', 'Nevada']
)
print(df1)
#    Ohio  Nevada
# a     1       2
# c     3       4
# e     5       6
df2 = pd.DataFrame(
    [[7, 8], [9, 10], [11, 12], [13, 14]],
    index=['b', 'c', 'c', 'e'],
    columns=['Missouri', 'Alabama']
)
print(df2)
#    Missouri  Alabama
# b         7        8
# c         9       10
# c        11       12
# e        13       14
df3 = pd.DataFrame(
    [[7, 8], [9, 10], [11, 12], [13, 14]],
    index=['a', 'c', 'e', 'f'],
    columns=['Nevada', 'Alabama']
)
print(df3)
#    Nevada  Alabama
# a       7        8
# c       9       10
# e      11       12
# f      13       14
result = pd.merge(df1, df2, left_index=True, right_index=True, how='outer')
print(result)
#    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
# c   3.0     4.0      11.0     12.0
# e   5.0     6.0      13.0     14.0
result = pd.merge(df1, df3, left_index=True, right_index=True, how='outer')
print(result)
#    Ohio  Nevada_x  Nevada_y  Alabama
# a   1.0       2.0         7        8
# c   3.0       4.0         9       10
# e   5.0       6.0        11       12
# f   NaN       NaN        13       14

另一种写法:

result = df1.join(df2, how='outer')
print(result)
#    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
# c   3.0     4.0      11.0     12.0
# e   5.0     6.0      13.0     14.0

也可以向join方法传入一个DataFrame列表,类似于对三个数据集进行join操作。

result = df1.join([df2, df3])
print(result)
#    Ohio  Nevada_x  Missouri  Alabama_x  Nevada_y  Alabama_y
# a     1         2       NaN        NaN         7          8
# c     3         4       9.0       10.0         9         10
# c     3         4      11.0       12.0         9         10
# e     5         6      13.0       14.0        11         12

沿轴向连接

另一种数据组合操作可称为拼接、绑定或堆叠。NumPy的concatenate函数可以在NumPy数组上实现该功能。

基于Series的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方法会将值和索引粘在一起: 默认情况下,concat方法是沿着axis=0的轴向生效的,生成另一个Series。 如果传递axis=1,返回的结果则是一个DataFrame(axis=1时是列)。

result = pd.concat([s1, s2, s3])
print(result)
# a    0
# b    1
# c    2
# d    3
# e    4
# f    5
# g    6
# dtype: int64
result = pd.concat([s1, s2, s3], keys=['one', 'two', 'three'])  # 通过keys参数,在连接轴向上创建一个多层索引,以便在结果中区分各部分
print(result)
# one    a    0
#        b    1
# two    c    2
#        d    3
#        e    4
# three  f    5
#        g    6
# dtype: int64
print(result.unstack())  # 把原索引作为列标签展开
#          a    b    c    d    e    f    g
# one    0.0  1.0  NaN  NaN  NaN  NaN  NaN
# two    NaN  NaN  2.0  3.0  4.0  NaN  NaN
# three  NaN  NaN  NaN  NaN  NaN  5.0  6.0
result = pd.concat([s1, s2, s3], axis=1)  # 在这个案例中axis=1轴向上并没有重叠
print(result)
#      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
result = pd.concat([s1, s2, s3], axis=1, keys=['one', 'two', 'three'])  # 在这个案例中axis=1轴向上并没有重叠
print(result)
#    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
print(result.unstack())  # 对比axis=0的多层索引,当axis=1时对输出各index的并集做了分组。
# one    a    0.0
#        b    1.0
#        c    NaN
#        d    NaN
#        e    NaN
#        f    NaN
#        g    NaN
# two    a    NaN
#        b    NaN
#        c    2.0
#        d    3.0
#        e    4.0
#        f    NaN
#        g    NaN
# three  a    NaN
#        b    NaN
#        c    NaN
#        d    NaN
#        e    NaN
#        f    5.0
#        g    6.0
# dtype: float64

s4 = pd.concat([s1, s3])
print(s4)
# a    0
# b    1
# f    5
# g    6
# dtype: int64
result = pd.concat([s1, s4])
print(result)
# a    0
# b    1
# a    0
# b    1
# f    5
# g    6
# dtype: int64
result = pd.concat([s1, s4], axis=1)  # 现在在中axis=1轴向上有重叠
print(result)
#      0  1
# a  0.0  0
# b  1.0  1
# f  NaN  5
# g  NaN  6
result = pd.concat([s1, s4], axis=1, keys=['one', 'two', 'three'])
print(result)
#    one  two
# a  0.0    0
# b  1.0    1
# f  NaN    5
# g  NaN    6
result = pd.concat([s1, s4], axis=0, keys=['one', 'two', 'three'])  # 通过keys参数,在连接轴向上创建一个多层索引
print(result)
# one  a    0
#      b    1
# two  a    0
#      b    1
#      f    5
#      g    6
# dtype: int64
result = pd.concat([s1, s4], axis=1, join='inner')  # 内连接方式合并索引(索引交集)
print(result)
#    0  1
# a  0  0
# b  1  1
result = pd.concat([s1, s4], axis=1).reindex(['a', 'c', 'b', 'e'])  # 使用join_axes(已被替换成reindex)来指定用于连接其他轴向的轴
print(result)
#      0    1
# a  0.0  0.0
# c  NaN  NaN
# b  1.0  1.0
# e  NaN  NaN

基于DataFrame的pandas的concat函数的工作机制分析。

df1 = pd.DataFrame(
    np.arange(12).reshape((6, 2)),
    index=[
        ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'],
        [2000, 2001, 2002, 2000, 2001, 2002]
    ],
    columns=['event1', 'event2']
)
df2 = pd.DataFrame(
    np.arange(12).reshape((6, 2)),
    index=[
        ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'],
        [2000, 2001, 2002, 2000, 2001, 2002]
    ],
    columns=['event3', 'event4']
)
print(df1)
#              event1  event2
# Ohio   2000       0       1
#        2001       2       3
#        2002       4       5
# Nevada 2000       6       7
#        2001       8       9
#        2002      10      11
print(df2)
#              event3  event4
# Ohio   2000       0       1
#        2001       2       3
#        2002       4       5
# Nevada 2000       6       7
#        2001       8       9
#        2002      10      11
result = np.concatenate([df1, df2], axis=0)  # 沿0轴拼接
print(result)
# [[ 0  1]
#  [ 2  3]
#  [ 4  5]
#  [ 6  7]
#  [ 8  9]
#  [10 11]
#  [ 0  1]
#  [ 2  3]
#  [ 4  5]
#  [ 6  7]
#  [ 8  9]
#  [10 11]]
result = np.concatenate([df1, df2], axis=1)  # 沿1轴拼接
print(result)
# [[ 0  1  0  1]
#  [ 2  3  2  3]
#  [ 4  5  4  5]
#  [ 6  7  6  7]
#  [ 8  9  8  9]
#  [10 11 10 11]]
result = np.concatenate([df1, df2], axis=None)  # 将数组展平
print(result)
# [ 0  1  2  3  4  5  6  7  8  9 10 11  0  1  2  3  4  5  6  7  8  9 10 11]

联合重叠数据

另一个数据联合场景,既不是合并操作,也不是连接操作。 假如有两个数据集,这两个数据集的索引全部或部分重叠,通过NumPy的where函数可以进行面向数组的if-else等价操作。

s1 = pd.Series(
    [np.nan, 2.5, 0.0, 3.5, 4.5, np.nan],
    index=['f', 'e', 'd', 'c', 'b', 'a']
)
s2 = pd.Series(
    [0.0, np.nan, 2.0, np.nan, np.nan, 5.0],
    index=['a', 'b', 'c', 'd', 'e', 'f']
)

print(s1)
# f    NaN
# e    2.5
# d    0.0
# c    3.5
# b    4.5
# a    NaN
# dtype: float64
print(s2)
# a    0.0
# b    NaN
# c    2.0
# d    NaN
# e    NaN
# f    5.0
# dtype: float64

方法1,通过Numpy的where函数。

result = np.where(pd.isnull(s1), s2, s1)  # An array with elements from 'x'(s2) where 'condition'(isnull(s1)) is True, and elements from 'y'(s1) elsewhere.
print(result)
# [0.  2.5 0.  3.5 4.5 5. ]
# s1         # s2         # result
# f    NaN   # a    0.0   0.       条件中s1该元素为null,所以where函数取对应x(s2)的元素(注意,与索引顺序无关)
# e    2.5   # b    NaN   2.5      条件中s1该元素不为null,所以where函数取对应y(s1)的元素
# d    0.0   # c    2.0   0.
# c    3.5   # d    NaN   3.5
# b    4.5   # e    NaN   4.5
# a    NaN   # f    5.0   5.0      条件中s1该元素为null,所以where函数取对应x(s2)的元素

result = np.where(pd.isnull(s2), s1, s2)
print(result)
# [0.  2.5 2.  3.5 4.5 5. ]

方法2,通过Series的combine_first方法。

result = s2.combine_first(s1)  # 注意,combine_first是按照s2的索引顺序检索的,相同索引的s1的值会填充对应s2的null
print(result)
# a    0.0
# b    4.5
# c    2.0
# d    0.0
# e    2.5
# f    5.0
# dtype: float64

方法3:Pandas的combine_first方法。

df1 = pd.DataFrame(
    {
        'a': [1.0, np.nan, 5.0, np.nan],
        'b': [np.nan, 2.0, np.nan, 6.0],
        'c': [2.0, 6.0, 10.0, 15.0]
    }
)
df2 = pd.DataFrame(
    {
        'a': [5.0, 4.0, np.nan, 3.0, 7.0],
        'b': [np.nan, 3.0, 4.0, 6.0, 8.0]
    }
)
print(df1)
#      a    b     c
# 0  1.0  NaN   2.0
# 1  NaN  2.0   6.0
# 2  5.0  NaN  10.0
# 3  NaN  6.0  15.0
print(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
result = df2.combine_first(df1)  # 用df1的值去填充df2对应索引位置的null值
print(result)
#      a    b     c
# 0  5.0  NaN   2.0
# 1  4.0  3.0   6.0
# 2  5.0  4.0  10.0
# 3  3.0  6.0  15.0
# 4  7.0  8.0   NaN

重塑和透视

重新排列表格型数据有多种基础操作。这些操作被称为重塑或透视。

import numpy as np
import pandas as pd

使用多层索引进行重塑

多层索引在DataFrame中提供了一种一致性方式用于重排列数据。以下是两个基础操作:

  • statck(堆叠)该操作会“旋转”或将列中的数据透视到行。
  • unstack(拆堆)该操作会将行中的数据透视到列。
df = pd.DataFrame(
    np.arange(6).reshape((2, 3)),
    index=pd.Index(['Ohio', 'Colorado'], name='state'),
    columns=pd.Index(['one', 'two', 'three'], name='number')
)
print(df)
# number    one  two  three
# state
# Ohio        0    1      2
# Colorado    3    4      5

在这份数据上使用stack方法会将列透视到行,产生一个新的Series:

result = df.stack()
print(result)
# state     number
# Ohio      one       0
#           two       1
#           three     2
# Colorado  one       3
#           two       4
#           three     5
# dtype: int64

从一个多层索引序列中,你可以使用unstack方法将数据重排列后放入一个DataFrame中:

print(result.unstack())
# number    one  two  three
# state
# Ohio        0    1      2
# Colorado    3    4      5
print(result.unstack(0))  # 可以传入一个层级序号或名称来拆分一个不同的层级
# state   Ohio  Colorado
# number
# one        0         3
# two        1         4
# three      2         5
print(result.unstack(1))
# number    one  two  three
# state
# Ohio        0    1      2
# Colorado    3    4      5
print(result.unstack('state'))  # 输出结果和传入层级0一样
# state   Ohio  Colorado
# number
# one        0         3
# two        1         4
# three      2         5
print(result.unstack('number'))  # 输出结果和传入层级1一样
# number    one  two  three
# state
# Ohio        0    1      2
# Colorado    3    4      5

如果层级中的所有值并未包含于每个子分组中时,拆分可能会引入缺失值:

s1 = pd.Series([0, 1, 2, 3], index=['a', 'b', 'c', 'd'])
s2 = pd.Series([4, 5, 6], index=['c', 'd', 'e'])
s3 = pd.concat([s1, s2], keys=['one', 'two'])
print(s3)
# one  a    0
#      b    1
#      c    2
#      d    3
# two  c    4
#      d    5
#      e    6
# dtype: int64
print(s3.unstack(0))
#    one  two
# a  0.0  NaN
# b  1.0  NaN
# c  2.0  4.0
# d  3.0  5.0
# e  NaN  6.0
print(s3.unstack(1))
print(s3.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

默认情况下,堆叠会过滤出缺失值,因此堆叠拆堆的操作是可逆的。

print(s3.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
print(s3.unstack().stack(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中拆堆时,被拆堆的层级会变为结果中最低的层级。 在调用stack方法时,我们可以指明需要堆叠的轴向名称。

df = pd.DataFrame(
    {'left': result, 'right': result + 5},
    columns=pd.Index(['left', 'right'], name='side')
)
print(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
print(df.unstack())
# side     left           right
# number    one two three   one two three
# state
# Ohio        0   1     2     5   6     7
# Colorado    3   4     5     8   9    10
print(df.unstack('state'))  # 被拆堆的层级(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方法时,可以指明需要堆叠的轴向名称:

print(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

将“长”透视为“宽”

在数据库和CSV中存储多时间序列的方式就是所谓的长格式或堆叠格式。

data = pd.read_csv('../examples/macrodata.csv')

print(data.head(3))
#      year  quarter   realgdp  realcons  ...  unemp      pop  infl  realint
# 0  1959.0      1.0  2710.349    1707.4  ...    5.8  177.146  0.00     0.00
# 1  1959.0      2.0  2778.801    1733.7  ...    5.1  177.830  2.34     0.74
# 2  1959.0      3.0  2775.488    1751.8  ...    5.3  178.657  2.74     1.09
# ......
# [3 rows x 14 columns]

# PeriodIndex将year和quarter等列进行联合并生成了一种时间间隔类型
periods = pd.PeriodIndex(
    year=data.year,
    quarter=data.quarter,
    name='date'
)
columns = pd.Index(
    ['realgdp', 'infl', 'unemp'],
    name='item'
)
data = data.reindex(columns=columns)
print(data)
# item    realgdp  infl  unemp
# 0      2710.349  0.00    5.8
# 1      2778.801  2.34    5.1
# 2      2775.488  2.74    5.3
# ......
# [203 rows x 3 columns]

data.index = periods.to_timestamp('D', 'end')
print(data.index)
# DatetimeIndex(['1959-03-31 23:59:59.999999999',
#                '1959-06-30 23:59:59.999999999',
#                ...
#                '2009-06-30 23:59:59.999999999',
#                '2009-09-30 23:59:59.999999999'],
#               dtype='datetime64[ns]', name='date', length=203, freq=None)

下面是ldata的数据样本。 这种数据即所谓的多时间序列的长格式,或称为具有两个或更多个键的其他观测数据(这里,我们的键是date和item)。 表中的每一行表示一个时间点上的单个观测值。

ldata = data.stack().reset_index().rename(columns={0: 'value'})
print(ldata)
#                              date     item      value
# 0   1959-03-31 23:59:59.999999999  realgdp   2710.349
# 1   1959-03-31 23:59:59.999999999     infl      0.000
# 2   1959-03-31 23:59:59.999999999    unemp      5.800
# 3   1959-06-30 23:59:59.999999999  realgdp   2778.801
# 4   1959-06-30 23:59:59.999999999     infl      2.340
# ..                            ...      ...        ...
# 604 2009-06-30 23:59:59.999999999     infl      3.370
# 605 2009-06-30 23:59:59.999999999    unemp      9.200
# 606 2009-09-30 23:59:59.999999999  realgdp  12990.341
# 607 2009-09-30 23:59:59.999999999     infl      3.560
# 608 2009-09-30 23:59:59.999999999    unemp      9.600
# [609 rows x 3 columns]

在上面的例子中:

数据通常以这种方式存储在关系型数据库中,比如MySQL,因为固定模式(列名称和数据类型)允许item列中不同值的数量随着数据被添加到表中而改变。

dateitem通常是主键(使用关系型数据库的说法),提供了关系完整性和更简单的连接。 在某些情况下,处理这种格式的数据更为困难。可能更倾向于获取一个按date列时间戳索引的且每个不同的item独立一列的DataFrame。

DataFrame的pivot方法就是进行这种转换的:

下面例子中,传递的前两个值是分别用作行和列索引的列,然后是可选的数值列以填充DataFrame。 注意,pivot方法等价于使用set_index创建分层索引,然后调用unstack。

pivoted = ldata.pivot('date', 'item', 'value')
print(pivoted)
# item                           infl    realgdp  unemp
# date
# 1959-03-31 23:59:59.999999999  0.00   2710.349    5.8
# 1959-06-30 23:59:59.999999999  2.34   2778.801    5.1
# ...                             ...        ...    ...
# 2009-06-30 23:59:59.999999999  3.37  12901.504    9.2
# 2009-09-30 23:59:59.999999999  3.56  12990.341    9.6
# [203 rows x 3 columns]

ldata['value2'] = np.random.randn(len(ldata))
print(ldata[:5])
#                            date     item     value    value2
# 0 1959-03-31 23:59:59.999999999  realgdp  2710.349 -1.268405
# 1 1959-03-31 23:59:59.999999999     infl     0.000  0.377691
# 2 1959-03-31 23:59:59.999999999    unemp     5.800 -0.342492
# 3 1959-06-30 23:59:59.999999999  realgdp  2778.801  0.132797
# 4 1959-06-30 23:59:59.999999999     infl     2.340  0.180290

此时ldata已经添加了一列。如果遗漏最后一个参数,会得到一个含有多层列的DataFrame,如下:

pivoted = ldata.pivot('date', 'item')
print(pivoted)
#                               value             ...    value2
# item                           infl    realgdp  ...   realgdp     unemp
# date                                            ...
# 1959-03-31 23:59:59.999999999  0.00   2710.349  ...  0.157467 -0.222464
# 1959-06-30 23:59:59.999999999  2.34   2778.801  ...  0.861501  0.368855
# ...                             ...        ...  ...       ...       ...
# 2009-06-30 23:59:59.999999999  3.37  12901.504  ...  0.279988  0.934972
# 2009-09-30 23:59:59.999999999  3.56  12990.341  ...  0.547914  1.842967
# [203 rows x 6 columns]

注意,pivot方法等价于使用set_index创建分层索引,然后调用unstack

unstacked = ldata.set_index(['date', 'item']).unstack('item')
print(unstacked[:5])
#                               value            ...    value2
# item                           infl   realgdp  ...   realgdp     unemp
# date                                           ...
# 1959-03-31 23:59:59.999999999  0.00  2710.349  ...  0.213120 -0.248004
# 1959-06-30 23:59:59.999999999  2.34  2778.801  ...  0.697763  0.112388
# 1959-09-30 23:59:59.999999999  2.74  2775.488  ...  1.291884 -1.046142
# 1959-12-31 23:59:59.999999999  0.27  2785.204  ...  0.363339 -0.307364
# 1960-03-31 23:59:59.999999999  2.31  2847.699  ...  0.377330  2.272980
# [5 rows x 6 columns]

将“宽”透视为“长”

在DataFrame中,pivot方法的反操作是pandas.melt。 与将一列变换为新的DataFrame中的多列不同,它将多列合并成一列,产生一个新的DataFrame,其长度比输入更长。

df = pd.DataFrame(
    {
        'key': ['foo', 'bar', 'baz'],
        'A': [1, 2, 3],
        'B': [4, 5, 6],
        'C': [7, 8, 9]
    }
)

print(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'])
print(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')
print(reshaped)
# variable  A  B  C
# key
# bar       2  5  8
# baz       3  6  9
# foo       1  4  7

由于pivot的结果根据作为行标签的列生成了索引,可使用reset_index来将数据回移一列:

print(reshaped.reset_index())
# variable  key  A  B  C
# 0         bar  2  5  8
# 1         baz  3  6  9
# 2         foo  1  4  7

pandas.melt的使用也可以无须任何分组指标。

result = pd.melt(df, value_vars=['A', 'B', 'C'])
print(result)
#   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

result = pd.melt(df, value_vars=['key', 'B', 'C'])
print(result)
#   variable value
# 0      key   foo
# 1      key   bar
# 2      key   baz
# 3        B     4
# 4        B     5
# 5        B     6
# 6        C     7
# 7        C     8
# 8        C     9