与公众号同步更新,详细内容及相关ipynb文件在公众号中,公众号:AI入门小白
import numpy as np
import pandas as pd
pd.options.display.max_rows = 20
np.random.seed(12345)
import matplotlib.pyplot as plt
plt.rc('figure', figsize=(10, 6))
np.set_printoptions(precision=4, suppress=True)
层次化索引
层次化索引(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
看到的结果是经过美化的带有MultiIndex
索引的Series
的格式。索引之间的“间隔”表示“直接使⽤上⾯的标签”:
data.index
对于⼀个层次化索引的对象,可以使⽤所谓的部分索引,使⽤它选取数据⼦集的操作更简单:
data['b']
data['b':'c']
data.loc[['b', 'd']]
还可以在“内层”中进⾏选取:
data.loc[:, 2]
层次化索引在数据重塑和基于分组的操作(如透视表⽣成)中扮演着重要的⻆⾊。例如,可以通过unstack
⽅法将这段数据重新安排到⼀个DataFrame
中:
data.unstack()
unstack
的逆运算是stack
:
data.unstack().stack()
对于⼀个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
各层都可以有名字(可以是字符串,也可以是别的Python
对象)。如果指定了名称,它们就会显示在控制台输出中:
frame.index.names = ['key1', 'key2']
frame.columns.names = ['state', 'color']
frame
有了部分列索引,因此可以轻松选取列分组
frame['Ohio']
可以单独创建MultiIndex
然后复⽤。上⾯那个DataFrame
中的(带有分级名称)列可以这样创建:
pd.MultiIndex.from_arrays([['Ohio', 'Ohio', 'Colorado'], ['Green', 'Red', 'Green']],
names=['state', 'color'])
重排与分级排序
有时,你需要重新调整某条轴上各级别的顺序,或根据指定级别上的值对数据进⾏排序。swaplevel
接受两个级别编号或名称,并返回⼀个互换了级别的新对象(但数据不会发⽣变化):
frame.swaplevel('key1', 'key2')
⽽sort_index
则根据单个级别中的值对数据进⾏排序。交换级别时,常常也会⽤到sort_index
,这样最终结果就是按照指定顺序进⾏字⺟排序了:
frame.sort_index(level=1)
frame.swaplevel(0, 1).sort_index(level=0)
根据级别汇总统计
许多对DataFrame
和Series
的描述和汇总统计都有⼀个level
选项,它⽤于指定在某条轴上求和的级别。再以上⾯那个DataFrame
为例,我们可以根据⾏或列上的级别来进⾏求和:
frame.sum(level='key2')
frame.sum(level='color', axis=1)
使用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
DataFrame
的set_index
函数会将其⼀个或多个列转换为⾏索引,并创建⼀个新的DataFrame
:
frame2 = frame.set_index(['c', 'd'])
frame2
默认情况下,那些列会从DataFrame
中移除,但也可以将其保留下来:
frame.set_index(['c', 'd'], drop=False)
reset_index
的功能跟set_index
刚好相反,层次化索引的级别会被转移到列⾥⾯:
frame2.reset_index()
合并数据集
pandas
对象中的数据可以通过⼀些⽅式进⾏合并:
pandas.merge
可根据⼀个或多个键将不同DataFrame
中的⾏连接起来。SQL
或其他关系型数据库的⽤户对此应该会⽐较熟悉,因为它实现的就是数据库的join
操作。pandas.concat
可以沿着⼀条轴将多个对象堆叠到⼀起。- 实例⽅法
combine_first
可以将重复数据编接在⼀起,⽤⼀个对象中的值填充另⼀个对象中的缺失值。
数据库风格的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
df2
这是⼀种多对⼀的合并。df1
中的数据有多个被标记为a
和b
的⾏,⽽df2
中key
列的每个值则仅对应⼀⾏。对这些对象调⽤merge
即可得到:
pd.merge(df1, df2)
注意,我并没有指明要⽤哪个列进⾏连接。如果没有指定,merge
就会将重叠列的列名当做键。不过,最好明确指定⼀下:
pd.merge(df1, df2, on='key')
如果两个对象的列名不同,也可以分别进⾏指定:
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')
可能你已经注意到了,结果⾥⾯c
和d
以及与之相关的数据消失了。默认情况下,merge
做的是“内连接”;结果中的键是交集。其他⽅式还有"left"、"right"
以及"outer"
。外连接求取的是键的并集,组合了左连接和右连接的效果:
pd.merge(df1, df2, how='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
df2
pd.merge(df1, df2, on='key', how='left')
多对多连接产⽣的是⾏的笛卡尔积。由于左边的DataFrame
有3个"b"⾏,右边的有2个,所以最终结果中就有6个"b"⾏。连接⽅式只影响出现在结果中的不同的键的值:
pd.merge(df1, df2, how='inner')
要根据多个键进⾏合并,传⼊⼀个由列名组成的列表即可:
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')
结果中会出现哪些键组合取决于所选的合并⽅式,你可以这样来理解:多个键形成⼀系列元组,并将其当做单个连接键(当然,实际上并不是这么回事)。
注意:在进⾏列-列连接时,DataFrame
对象中的索引会被丢弃。
重塑和轴向旋转
有许多⽤于重新排列表格型数据的基础运算。这些函数也称作重塑(reshape)或轴向旋转(pivot)运算。
重塑层次化索引
层次化索引为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
对该数据使⽤stack
⽅法即可将列转换为⾏,得到⼀个Series
:
result = data.stack()
result
对于⼀个层次化索引的Series
,你可以⽤unstack
将其重排为⼀个DataFrame
:
result.unstack()
默认情况下,unstack
操作的是最内层(stack
也是如此)。传⼊分层级别的编号或名称即可对其它级别进⾏unstack
操作:
result.unstack(0)
result.unstack('state')
如果不是所有的级别值都能在各分组中找到的话,则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
data2.unstack()
stack
默认会滤除缺失数据,因此该运算是可逆的:
data2.unstack()
data2.unstack().stack()
data2.unstack().stack(dropna=False)
在对DataFrame
进⾏unstack
操作时,作为旋转轴的级别将会成为结果中的最低级别:
df = pd.DataFrame({'left': result, 'right': result + 5},
columns=pd.Index(['left', 'right'], name='side'))
df
df.unstack('state')
当调⽤stack
,我们可以指明轴的名字:
df.unstack('state').stack('side')