「这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战」。
1.分层索引
分层索引是pandas的重要特性,允许在一个轴向上拥有多个(两个或两个以上)索引层级。
-
分层索引在重塑数据和数组透视表等分组操作中扮演了重要角色,例如,可以使用unstack方法将数据在DataFrame中重新排列;
-
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 [out] Ohio 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 [out] state Ohio 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 -
一个MultiIndex对象可以使用其自身的构造函数创建并复用。
1.1 重排序和层级排序
应用场景:需要重新排列轴上的层级顺序,或者按照特定层级的值对数据进行排序。
- swaplevel接收两个层级序号或层级名称,返回一个进行了层级变更的新对象(数据是不变的)
- sort_index只能在单一层级上对数据进行排序,在进行层级变换时,可以使用sort_index以使得结果按照层级进行字段排序;
- 如果索引按照字典顺序从最外层开始排序,那么数据选择性能会更好-调用sort_index(level = 0)或sort_index可以得到这样的结果。
1.2 按照层级进行汇总统计
DataFrame和Series中很多描述性和汇总性统计有一个level选项,通过level选项可以指定某个特定轴进行聚合(默认是行,设置axis = 1就是在列上进行聚合)。
1.3 使用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
[out]
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使用一个或多个列作为索引:
frame2 = frame.set_index(['c', 'd']) frame2 [out] 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中移除,也可以将它们留在DataFrame中(设置参数drop = False即可):
frame.set_index(['c', 'd'], drop = False) [out] 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() [out] 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
2.联合与合并数据集
包含在pandas对象的数据可以通过多种方式联合在一起:
- pandas.merge:根据一个或多个键将行进行连接,对于SQL或其他关系型数据库的用户来说,这种方式比较熟悉,它实现的是数据库的连接操作;
- pandas.concat:使对象在轴向上进行黏合或“堆叠”;
- combine_first:该实例方法允许将重叠的数据拼接在一起,以使用一个对象中的值填充另一个对象中的缺失值。
2.1 数据库风格的DataFrame连接
合并或连接操作通过一个或多个键连接行来联合数据集,pandas中的merge函数主要用于将各种join操作算法运用到数据上。
-
用法:pd.merge(df1, df2);
-
如果连接的键信息没有指定,merge会自动将重叠列名作为连接的键,但显式地指定连接键才是好的实现:设置参数on = 用于连接的键名;
-
如果每个对象的列名是不同的,可以分别为它们指定列名(指定参数left_on和right_on的值);
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') [out] 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 -
默认情况下,merge做的是内连接('inner' join),结果中的键是两张表中的交集,其他可选的选项有'left'、'right'和'outer',外连接(outer join)是键的并集(这些选项可以通过指定参数how的值来实现,缺失值用NaN处理)。
pd.merge(df1, df2, how = 'outer') [out] 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
how选项的总结:
| 选项 | 行为 |
|---|---|
| 'inner' | 只对两张表都有的键的交集进行联合 |
| 'left' | 对所有左表的键进行联合 |
| 'right' | 对所有右表的键进行联合 |
| 'outer' | 对两张表都有的键的并集进行联合 |
-
多对多连接是行的笛卡尔积,连接方法仅影响结果中显示的不同键值;
-
使用多个键进行合并时,传入一个列名的列表(on = [列名列表]):
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') [out] 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', suffixes = ('_left', '_right')) [out] key1 key2_left lval key2_right rval 0 foo one 1 one 4 1 foo one 1 one 5 2 foo two 2 one 4 3 foo two 2 one 5 4 bar one 3 one 6 5 bar one 3 two 7
merge函数参数
| 参数 | 描述 |
|---|---|
| left | 合并操作中左边的DataFrame |
| right | 合并操作中右边的DataFrame |
| how | 'inner'、'outer'、'left'、'right'之一,默认是'inner' |
| on | 需要连接的列名,必须是在两边的DataFrame对象都有的列名,并以left和right中的列名的交集作为连接键 |
| left_on | left DataFrame中用作连接键的列 |
| right_on | right DataFrame中用作连接键的列 |
| left_index | 使用left的行索引作为它的连接键(如果是MultiIndex,则是多个键) |
| right_index | 使用right的行索引作为它的连接键(如果是MultiIndex,则是多个键) |
| sort | 通过连接键按字母顺序对合并的数据进行排序,在默认情况下为True |
| suffixes | 在重叠情况下,添加到列名后的字符串元组,默认是('_x', '_y') |
| copy | 如果为False,则在某些特殊情况下避免将数据复制到结果数据结构中,默认情况下总是复制 |
| indicator | 添加一个特殊的列_merge,指示每一行的来源,值将根据每行中连接数据的来源分别为'left_only','right_only'或'both' |
2.2 根据索引合并
在某些情况下,DataFrame中用于合并的键是它的索引。在这种情况下,可以传递left_index = True或right_index = True(或者两者都传)来表示索引需要用来作为合并的键:
left1 = pd.DataFrame({'key': ['a', 'b', 'a', 'a', 'b', 'c'], 'value': range(6)})
left1
[out]
key value
0 a 0
1 b 1
2 a 2
3 a 3
4 b 4
5 c 5
right1 = pd.DataFrame({'group_val': [3.5, 7]}, index = ['a', 'b'])
right1
[out]
group_val
a 3.5
b 7.0
pd.merge(left1, right1, left_on = 'key', right_index = True)
[out]
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
-
默认的合并方法是连接键相交,可以使用外连接来进行合并;
pd.merge(left1, right1, left_on = 'key', right_index = True, how = 'outer') [out] 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 -
在多层索引数据的情况下,索引上连接是一个隐式的多键合并,这种情况下,必须以列表的方式指明合并所需多个列(注意需使用how = 'outer'处理重复的索引值);
lefth = pd.DataFrame({'key1': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada'], 'key2': [2000, 2001, 2002, 2001, 2002], 'data': np.arange(5.)}) lefth [out] 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 = pd.DataFrame(np.arange(12).reshape((6, 2)), index = [['Nevada', 'Nevada', 'Ohio', 'Ohio', 'Ohio', 'Ohio'], [2001, 2000, 2000, 2000, 2001, 2002]], columns = ['event1', 'event2']) righth [out] event1 event2 Nevada 2001 0 1 2000 2 3 Ohio 2000 4 5 2000 6 7 2001 8 9 2002 10 11 pd.merge(lefth, righth, left_on = ['key1', 'key2'], right_index = True, how = 'outer') [out] 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 -
使用两边的索引进行合并(left_index和right_index均设置为True);
left2 = pd.DataFrame([[1., 2.], [3., 4.], [5., 6.]], index = ['a', 'c', 'e'], columns = ['Ohio', 'Nevada']) left2 [out] Ohio Nevada a 1.0 2.0 c 3.0 4.0 e 5.0 6.0 right2 = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [13., 14.]], index = ['b', 'c', 'd', 'e'], columns = ['Missouri', 'Alabama']) right2 [out] 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) [out] 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 3.0 14.0 -
DataFrame有一个方便的join实例方法,用于按照索引合并,该方法也可以用于合并多个索引相同或相似但没有重叠列的DataFrame对象;
left2.join(right2, how = 'outer') [out] 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的某一列上连接传递的DataFrame的索引;
left1.join(right1, on = 'key') [out] 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列表。
another = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [16., 17.]],index = ['a', 'c', 'e', 'f'], columns = ['New York', 'Oregon']) another [out] 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], how = 'outer') [out] 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 b NaN NaN 7.0 8.0 NaN NaN d NaN NaN 11.0 12.0 NaN NaN f NaN NaN NaN NaN 16.0 17.0
2.3 沿轴向连接
另一种数据组合操作可互换地称为拼接、绑定或堆叠。NumPy的concatenate函数可以在NumPy数组上实现该功能。
- 在Series和DataFrame等pandas对象的上下文中,使用标记的轴可以进一步泛化数组连接。
考虑以下几种情况:
- 如果对象在其他轴上的索引不同,是否应该将不同的元素组合在这些轴上,还是只使用共享的值(交集)?
- 连接的数据块是否需要在结果对象中被识别?
- “连接轴”是否包含需要保存的数据?(在很多情况下,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])
[out]
a 0
b 1
c 2
d 3
e 4
f 5
g 6
dtype: int64
-
默认情况下,concat方法是沿着axis = 0的轴向生效的,生成另一个Series,如果传递axis = 1,返回的结果则是一个DataFrame:
pd.concat([s1, s2, s3], axis = 1) [out] 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 [out] a 0 b 1 f 5 g 6 dtype: int64 pd.concat([s1, s4], axis = 1) [out] 0 1 a 0.0 0 b 1.0 1 f NaN 5 g NaN 6 pd.concat([s1, s4], axis = 1, join = 'inner') [out] 0 1 a 0 0 b 1 1 -
拼接在一起的各部分无法在结果中区分的问题:可以使用keys参数来解决:
result = pd.concat([s1, s2, s3], keys = ['one', 'two', 'three']) result [out] one a 0 b 1 two c 2 d 3 e 4 three f 5 g 6 dtype: int64 -
沿着轴向axis = 1连接Series时,keys则成为DataFrame的列头:
pd.concat([s1, s2, s3], axis = 1, keys = ['one', 'two', 'three']) [out] 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对象(这里的keys与前面的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']) pd.concat([df1, df2], axis = 1, keys = ['level1', 'level2']) [out] 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({'level3': df1, 'level4': df2}, axis = 1) [out] level3 level4 one two three four a 0 1 5.0 6.0 b 2 3 NaN NaN c 4 5 7.0 8.0 -
还有一些额外的参数负责多层索引生成,例如,可以使用Names参数命名生成的轴层级:
pd.concat([df1, df2], axis = 1, keys = ['level1', 'level2'], names = ['upper', 'lower']) [out] 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(即没有指定index的值,指定参数ignore_index = True):
pd.concat([df1, df2], ignore_index = True) [out] a b c d 0 1.438888 -0.058913 -0.014392 -1.385697 1 0.994368 1.167432 -1.793285 0.460772 2 -0.824447 0.577400 1.270614 -1.108228 3 -1.528036 0.625684 NaN 0.964432 4 -0.422344 0.973821 NaN -2.203932
concat函数的参数
| 参数 | 描述 |
|---|---|
| objs | 需要连接的pandas对象列表或字典,这是必选参数 |
| axis | 连接的轴向,默认是0(沿着行方向) |
| join | 可以是'inner'或'outer'(默认是'outer'),用于指定连接方式是内连接还是外连接 |
| join_axes | 用于指定其他n - 1轴的特定索引,可以替代内/外连接的逻辑 |
| keys | 与要连接的对象关联的值,沿着连接轴形成分层索引,可以是任意值的列表或数组,也可以是元组的数组,也可以是数组的列表(如果向levels参数传入多层数组) |
| levels | 在键值传递时,该参数用于指定多层索引的层级 |
| names | 如果传入了keys和/或levels参数,该参数用于多层索引的层级名称 |
| verify_integrity | 检查连接对象中的新轴是否重复,如果是,则引发异常,默认(False)允许重复 |
| ignore_index | 不沿着连接轴保留索引,而产生的一段新的(长度为total_length)索引 |
2.4 联合重叠数据
-
NumPy的where函数,这个函数可以进行面向数组的
if-else等价操作(np.where(condition, x, y),满足条件输出x,不满足输出y):a = pd.Series([np.nan, 2.5, 0.0, 3.5, 4.5, np.nan], index = ['f', 'e', 'd', 'c', 'b', 'a']) b = pd.Series([0., np.nan, 2., np.nan, np.nan, 5.], index = ['a', 'b', 'c', 'd', 'e', 'f']) a [out] f NaN e 2.5 d 0.0 c 3.5 b 4.5 a NaN dtype: float64 b [out] a 0.0 b NaN c 2.0 d NaN e NaN f 5.0 dtype: float64 np.where(pd.isnull(a), b, a) [out] array([0. , 2.5, 0. , 3.5, 4.5, 5. ]) -
Series有一个combine_first方法,该方法可以等价于下面这种使用pandas常见数据对齐逻辑的轴向操作(combine_first:将前者为np.nan的用后者相同位置的数来补充,相当于打补丁):
b.combine_first(a) [out] b.combine_first(a) b.combine_first(a) a 0.0 b 4.5 c 2.0 d 0.0 e 2.5 f 5.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)}) df1 [out] a b c 0 1.0 NaN 2 1 NaN 2.0 6 2 5.0 NaN 10 3 NaN 6.0 14 df2 = pd.DataFrame({'a': [5., 4., np.nan, 3., 7.], 'b': [np.nan, 3., 4., 6., 8.]}) df2 [out] 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) [out] 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
3.重塑和透视
3.1 使用多层索引进行重塑
多层索引在DataFrame中提供了一种一致性方式用于重排列数据,以下是两个基础操作:
-
stack(堆叠):该操作会“旋转”或将列中的数据透视到行;
-
unstack(拆堆):该操作会将行中的数据透视到列;
-
数据透视的概念:在交叉表中将行旋转成列和将列旋转成行;
-
对一个DataFrame使用stack()方法会产生一个新的Series对象,对该对像再使用unstack()方法会将数据重排列后放入一个DataFrame中;
-
默认情况下,最内层是已拆堆的(与stack方法一样),可以传入一个层级序号或名称来拆分一个不同的层级:
-
如果层级中的所有值并未包含于每个子分组中,拆分可能会引入缺失值:
- 默认情况下,堆叠会过滤出缺失值,因此堆叠拆堆的操作是可逆的:
-
当在DataFrame中拆堆时,被拆堆的层级会变为结果中最低的层级:
- 在调用stack方法时,可以指明需要堆叠的轴向名称:
3.2 将“长”透视为“宽”
使用DataFrame的pivot方法。
应用场景:数据通常以键 + 值的形式存在于关系型数据库中,处理这种格式的数据,可能更倾向于获取一个按特定列索引的且每个不同的item独立一列的DataFrame,DataFrame的pivot方法就是进行这种转换的。
先载入一些数据,然后做少量的时间序列归整和其他的数据清洗操作:
- 读取数据:
- 使用PeriodIndex将year和quarter进行联合并生成一种时间间隔类型:
-
对data重建索引:
- 生成ldata:
- 对ldata使用pivot方法(传递的前两个值分别用作行和列索引的列,然后是可选的数值列以填充DataFrame):
-
假设有两个数值列,现在想同时进行重塑:
- 如果遗漏最后一个参数(即数值),会得到一个含有多层列的DataFrame(默认取全部的可选数值):
- 注意:pivot方法等价于使用set_index创建分层索引,然后调用unstack。
3.3 将“宽”透视为“长”
在DataFrame中,pivot方法的反操作是pandas.melt。与将一列变换为新的DataFrame中的多列不同,它将多列合并成一列,产生一个新的DataFrame,其长度比输入更长。
- 准备数据:
-
'key'列可以作为分组指标,其他列均为数据值。当使用melt时,必须指明哪些列是分组指标(如果有的话)。此处,使用'key'作为唯一的分组指标:
-
使用pivot方法,可以将数据重塑会原先的布局:
- 也可以指定列的子集作为值列:
- pandas.melt的使用也可以无须任何分组指标: