数据规整:连接、联合与重塑 ¶
分层索引 ¶
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]
给层级赋予名称。注意区分行标签中的索引名称state
和color
。
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_index
是set_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
的数据有多个行的标签为a
和b
,而df2
在key
列中每个值仅有一行。
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=True
或right_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
列中不同值的数量随着数据被添加到表中而改变。
date
和item
通常是主键(使用关系型数据库的说法),提供了关系完整性和更简单的连接。 在某些情况下,处理这种格式的数据更为困难。可能更倾向于获取一个按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