在回答这个问题的过程中,我学到了很多东西,并想整理一些例子和解释的目录。
关于 levels
参数的具体答案将在最后给出。
pandas.concat
: 失落的手册
当前文档链接
导入和定义对象
import pandas as pd
d1 = pd.DataFrame(dict(A=.1, B=.2, C=.3), index=[2, 3])
d2 = pd.DataFrame(dict(B=.4, C=.5, D=.6), index=[1, 2])
d3 = pd.DataFrame(dict(A=.7, B=.8, D=.9), index=[1, 3])
s1 = pd.Series([1, 2], index=[2, 3])
s2 = pd.Series([3, 4], index=[1, 2])
s3 = pd.Series([5, 6], index=[1, 3])
参数
objs
我们遇到的第一个参数是objs
:
objs: 一个Series、DataFrame或Panel对象的序列或映射。
如果传递了字典,则排序后的键将用作键参数,除非传递了值(见下文)。任何None对象都将被默默删除,除非它们全部为None,在这种情况下会引发ValueError。
- 我们通常看到它与一组
Series
或DataFrame
对象一起使用。
- 我将展示
dict
也可以非常有用。
- 生成器也可以使用,并且在使用
map(f, list_of_df)
时非常有用。
现在,我们将坚持上面定义的一些DataFrame
和Series
对象的列表。
稍后我会展示如何利用字典来产生非常有用的MultiIndex
结果。
pd.concat([d1, d2])
A B C D
2 0.1 0.2 0.3 NaN
3 0.1 0.2 0.3 NaN
1 NaN 0.4 0.5 0.6
2 NaN 0.4 0.5 0.6
axis
我们遇到的第二个参数是axis
,其默认值为0
:
axis: {0/’index’, 1/’columns’}, default 0
要沿着连接的轴。
axis=0
的两个 DataFrame
s (堆叠)
对于0
或index
的值,我们的意思是:“沿列对齐并添加到索引中”。
如上所示,我们使用了axis=0
,因为0
是默认值,我们看到d2
的索引扩展到了d1
的索引尽管存在值2
的重叠:
pd.concat([d1, d2], axis=0)
A B C D
2 0.1 0.2 0.3 NaN
3 0.1 0.2 0.3 NaN
1 NaN 0.4 0.5 0.6
2 NaN 0.4 0.5 0.6
使用axis=1
(并排)的两个DataFrame
对于值1
或columns
,我们的意思是:“沿索引对齐并添加到列中”,
pd.concat([d1, d2], axis=1)
A B C B C D
1 NaN NaN NaN 0.4 0.5 0.6
2 0.1 0.2 0.3 0.4 0.5 0.6
3 0.1 0.2 0.3 NaN NaN NaN
我们可以看到结果的索引是索引的并集,结果的列是d1
的列通过d2
的列扩展而来。
当沿着axis=0
组合pandas.Series
时,我们将得到一个pandas.Series
。除非所有要合并的Series
具有相同的名称,否则生成的Series
的名称将为None
。当打印出生成的Series
时要注意'Name: A'
。如果不存在,则可以假设Series
名称为None
。
| | | pd.concat(
| pd.concat( | pd.concat( | [s1.rename('A'),
pd.concat( | [s1.rename('A'), | [s1.rename('A'), | s2.rename('B'),
[s1, s2]) | s2]) | s2.rename('A')]) | s3.rename('A')])
-------------- | --------------------- | ---------------------- | ----------------------
2 1 | 2 1 | 2 1 | 2 1
3 2 | 3 2 | 3 2 | 3 2
1 3 | 1 3 | 1 3 | 1 3
2 4 | 2 4 | 2 4 | 2 4
dtype: int64 | dtype: int64 | Name: A, dtype: int64 | 1 5
| | | 3 6
| | | dtype: int64
使用axis=1
在一侧并排组合两个(或三个)Series
当沿着axis=1
组合pandas.Series
时,我们需要引用name
属性以推断出结果中pandas.DataFrame
的列名。
| | pd.concat(
| pd.concat( | [s1.rename('X'),
pd.concat( | [s1.rename('X'), | s2.rename('Y'),
[s1, s2], axis=1) | s2], axis=1) | s3.rename('Z')], axis=1)
---------------------- | --------------------- | ------------------------------
0 1 | X 0 | X Y Z
1 NaN 3.0 | 1 NaN 3.0 | 1 NaN 3.0 5.0
2 1.0 4.0 | 2 1.0 4.0 | 2 1.0 4.0 NaN
3 2.0 NaN | 3 2.0 NaN | 3 2.0 NaN 6.0
混合使用 axis=0
的 Series
和 DataFrame
(堆叠)
在沿着 axis=0
进行 Series
和 DataFrame
合并时,我们将所有的 Series
转换为单列 DataFrame
。
请特别注意这是沿 axis=0
进行拼接;这意味着扩展索引(行),同时对齐列。在下面的示例中,我们可以看到索引变成了 [2, 3, 2, 3]
,这是一个不加区分地追加索引。除非我使用 to_frame
的参数强制命名 Series
列,否则列不会重叠:
pd.concat( |
[s1.to_frame(), d1]) | pd.concat([s1, d1])
------------------------- | ---------------------
0 A B C | 0 A B C
2 1.0 NaN NaN NaN | 2 1.0 NaN NaN NaN
3 2.0 NaN NaN NaN | 3 2.0 NaN NaN NaN
2 NaN 0.1 0.2 0.3 | 2 NaN 0.1 0.2 0.3
3 NaN 0.1 0.2 0.3 | 3 NaN 0.1 0.2 0.3
你可以看到
pd.concat([s1, d1])
的结果与我自己执行
to_frame
的结果相同。但是,我可以通过向
to_frame
提供一个参数来控制生成的列的名称。使用
rename
方法重命名
Series
不会控制生成的
DataFrame
中的列名。
# Effectively renames | |
# `s1` but does not align | # Does not rename. So | # Renames to something
# with columns in `d1` | # Pandas defaults to `0` | # that does align with `d1`
pd.concat( | pd.concat( | pd.concat(
[s1.to_frame('X'), d1]) | [s1.rename('X'), d1]) | [s1.to_frame('B'), d1])
---------------------------- | -------------------------- | ----------------------------
A B C X | 0 A B C | A B C
2 NaN NaN NaN 1.0 | 2 1.0 NaN NaN NaN | 2 NaN 1.0 NaN
3 NaN NaN NaN 2.0 | 3 2.0 NaN NaN NaN | 3 NaN 2.0 NaN
2 0.1 0.2 0.3 NaN | 2 NaN 0.1 0.2 0.3 | 2 0.1 0.2 0.3
3 0.1 0.2 0.3 NaN | 3 NaN 0.1 0.2 0.3 | 3 0.1 0.2 0.3
使用axis=1
(并排)混合Series
和DataFrame
这相当直观。当Series
对象没有name
属性时,Series
列名默认为这些Series
对象的枚举。
| pd.concat(
pd.concat( | [s1.rename('X'),
[s1, d1], | s2, s3, d1],
axis=1) | axis=1)
------------------- | -------------------------------
0 A B C | X 0 1 A B C
2 1 0.1 0.2 0.3 | 1 NaN 3.0 5.0 NaN NaN NaN
3 2 0.1 0.2 0.3 | 2 1.0 4.0 NaN 0.1 0.2 0.3
| 3 2.0 NaN 6.0 0.1 0.2 0.3
join
第三个参数是join
,它描述了生成的合并结果应该是外连接(默认)还是内连接。
join: {‘inner’, ‘outer’}, default ‘outer’
如何处理其他轴上的索引。
事实证明,pd.concat
不仅可以处理两个对象的合并,因此没有 left
或 right
选项。
对于 d1
和 d2
,选项如下:
outer
pd.concat([d1, d2], axis=1, join='outer')
A B C B C D
1 NaN NaN NaN 0.4 0.5 0.6
2 0.1 0.2 0.3 0.4 0.5 0.6
3 0.1 0.2 0.3 NaN NaN NaN
内部
pd.concat([d1, d2], axis=1, join='inner')
A B C B C D
2 0.1 0.2 0.3 0.4 0.5 0.6
join_axes
第四个参数是使我们能够进行left
合并等操作的东西。
join_axes: 索引对象列表
使用特定索引对象来代替执行内部/外部集合逻辑的其他 n - 1 轴。
左合并
pd.concat([d1, d2, d3], axis=1, join_axes=[d1.index])
A B C B C D A B D
2 0.1 0.2 0.3 0.4 0.5 0.6 NaN NaN NaN
3 0.1 0.2 0.3 NaN NaN NaN 0.7 0.8 0.9
右连接
pd.concat([d1, d2, d3], axis=1, join_axes=[d3.index])
A B C B C D A B D
1 NaN NaN NaN 0.4 0.5 0.6 0.7 0.8 0.9
3 0.1 0.2 0.3 NaN NaN NaN 0.7 0.8 0.9
ignore_index
ignore_index: 布尔型,默认值为False
如果设置为True,将不会沿着连接轴使用索引值。结果的轴将标记为0,...,n - 1。如果您连接的对象中连接轴没有有意义的索引信息,则此选项很有用。请注意,其他轴上的索引值仍然在连接中保持。
当我将d1
叠加在d2
之上时,如果我不关心索引值,我可以重置它们或忽略它们。
| pd.concat( | pd.concat(
| [d1, d2], | [d1, d2]
pd.concat([d1, d2]) | ignore_index=True) | ).reset_index(drop=True)
--------------------- | ----------------------- | -------------------------
A B C D | A B C D | A B C D
2 0.1 0.2 0.3 NaN | 0 0.1 0.2 0.3 NaN | 0 0.1 0.2 0.3 NaN
3 0.1 0.2 0.3 NaN | 1 0.1 0.2 0.3 NaN | 1 0.1 0.2 0.3 NaN
1 NaN 0.4 0.5 0.6 | 2 NaN 0.4 0.5 0.6 | 2 NaN 0.4 0.5 0.6
2 NaN 0.4 0.5 0.6 | 3 NaN 0.4 0.5 0.6 | 3 NaN 0.4 0.5 0.6
当使用 axis=1
时:
| pd.concat(
| [d1, d2], axis=1,
pd.concat([d1, d2], axis=1) | ignore_index=True)
------------------------------- | -------------------------------
A B C B C D | 0 1 2 3 4 5
1 NaN NaN NaN 0.4 0.5 0.6 | 1 NaN NaN NaN 0.4 0.5 0.6
2 0.1 0.2 0.3 0.4 0.5 0.6 | 2 0.1 0.2 0.3 0.4 0.5 0.6
3 0.1 0.2 0.3 NaN NaN NaN | 3 0.1 0.2 0.3 NaN NaN NaN
keys
我们可以传递一个标量值或元组的列表,以便将元组或标量值分配给相应的MultiIndex。传递的列表长度必须与要连接的项目数相同。
keys: 序列,默认为 None
如果传递了多个级别,则应包含元组。使用传递的键构造层次化索引,其中键作为最外层级别
axis=0
当沿 axis=0
(扩展索引)连接 Series
对象时。
这些键成为索引属性中 MultiIndex
对象的新初始级别。
pd.concat([s1, s2, s3], keys=['A', 'B', 'C']) pd.concat([s1, s2], keys=['A', 'B'])
---------------------------------------------- -------------------------------------
A 2 1 A 2 1
3 2 3 2
B 1 3 B 1 3
2 4 2 4
C 1 5 dtype: int64
3 6
dtype: int64
然而,我们可以在 keys
参数中使用不止标量值来创建更深的 MultiIndex
。在这里,我们传递长度为 2 的元组以在 MultiIndex
上前置两个新级别:
pd.concat(
[s1, s2, s3],
keys=[('A', 'X'), ('A', 'Y'), ('B', 'X')])
A X 2 1
3 2
Y 1 3
2 4
B X 1 5
3 6
dtype: int64
axis=1
沿着列扩展时有些不同。当我们使用 axis=0
(参见上面)时,我们的 keys
除了现有的索引外还作为 MultiIndex
的级别。对于 axis=1
,我们指的是 Series
对象没有的一条轴,即 columns
属性。
Series
axis=1
请注意,如果没有传递 keys
,则命名 s1
和 s2
很重要,但如果传递了 keys
,它将被覆盖。
| | | pd.concat(
| pd.concat( | pd.concat( | [s1.rename('U'),
pd.concat( | [s1, s2], | [s1.rename('U'), | s2.rename('V')],
[s1, s2], | axis=1, | s2.rename('V')], | axis=1,
axis=1) | keys=['X', 'Y']) | axis=1) | keys=['X', 'Y'])
-------------- | --------------------- | ---------------------- | ----------------------
0 1 | X Y | U V | X Y
1 NaN 3.0 | 1 NaN 3.0 | 1 NaN 3.0 | 1 NaN 3.0
2 1.0 4.0 | 2 1.0 4.0 | 2 1.0 4.0 | 2 1.0 4.0
3 2.0 NaN | 3 2.0 NaN | 3 2.0 NaN | 3 2.0 NaN
MultiIndex
Series
axis=1
pd.concat(
[s1, s2],
axis=1,
keys=[('W', 'X'), ('W', 'Y')])
W
X Y
1 NaN 3.0
2 1.0 4.0
3 2.0 NaN
DataFrame
axis=1
与
axis = 0
的示例类似,
keys
可以为
MultiIndex
添加级别,但是这次是添加到存储在
columns
属性中的对象。
pd.concat( | pd.concat(
[d1, d2], | [d1, d2],
axis=1, | axis=1,
keys=['X', 'Y']) | keys=[('First', 'X'), ('Second', 'X')])
------------------------------- | --------------------------------------------
X Y | First Second
A B C B C D | X X
1 NaN NaN NaN 0.4 0.5 0.6 | A B C B C D
2 0.1 0.2 0.3 0.4 0.5 0.6 | 1 NaN NaN NaN 0.4 0.5 0.6
3 0.1 0.2 0.3 NaN NaN NaN | 2 0.1 0.2 0.3 0.4 0.5 0.6
| 3 0.1 0.2 0.3 NaN NaN NaN
Series
DataFrame
axis=1
在这种情况下,当一个标量键值成为列并且同时充当DataFrame
的MultiIndex
的第一级时,它不能作为Series
对象的唯一索引级别。因此,Pandas将再次使用Series
对象的name
属性作为列名的来源。
pd.concat( | pd.concat(
[s1, d1], | [s1.rename('Z'), d1],
axis=1, | axis=1,
keys=['X', 'Y']) | keys=['X', 'Y'])
X Y | X Y
0 A B C | Z A B C
2 1 0.1 0.2 0.3 | 2 1 0.1 0.2 0.3
3 2 0.1 0.2 0.3 | 3 2 0.1 0.2 0.3
keys
MultiIndex
Pandas似乎只从Series名称中推断列名,但在不同列级别的数据框进行类似串联时,它不会填写空白。
d1_ = pd.concat(
[d1], axis=1,
keys=['One'])
d1_
One
A B C
2 0.1 0.2 0.3
3 0.1 0.2 0.3
然后将此与仅具有列对象中一个级别的另一个数据框连接起来,Pandas 将拒绝尝试创建 MultiIndex
对象的元组并将所有数据框组合为单个对象、标量和元组的级别。
pd.concat([d1_, d2], axis=1)
(One, A) (One, B) (One, C) B C D
1 NaN NaN NaN 0.4 0.5 0.6
2 0.1 0.2 0.3 0.4 0.5 0.6
3 0.1 0.2 0.3 NaN NaN NaN
传递字典而不是列表
当传递一个字典时,pandas.concat
函数将使用字典中键作为 keys
参数。
# axis=0 | # axis=1
pd.concat( | pd.concat(
{0: d1, 1: d2}) | {0: d1, 1: d2}, axis=1)
----------------------- | -------------------------------
A B C D | 0 1
0 2 0.1 0.2 0.3 NaN | A B C B C D
3 0.1 0.2 0.3 NaN | 1 NaN NaN NaN 0.4 0.5 0.6
1 1 NaN 0.4 0.5 0.6 | 2 0.1 0.2 0.3 0.4 0.5 0.6
2 NaN 0.4 0.5 0.6 | 3 0.1 0.2 0.3 NaN NaN NaN
levels
这个参数与keys
参数一起使用。当将 levels
的值保留为默认值None
时,Pandas会获取结果的每个级别的唯一值,并将其用作结果的index.levels
属性中的对象。
levels: 序列列表,默认值为None
用于构建多级索引的特定级别(唯一值)。否则,它们将从键推断出来。
如果Pandas已经推断出这些级别应该是什么,那么我们自己指定有什么优点呢?我将展示一个例子,让你想出其他可能有用之处。
例子
根据文档,levels
参数是一个序列列表。这意味着我们可以使用另一个pandas.Index
作为这些序列之一。
考虑数据框df
,它是d1
,d2
和d3
的连接:
df = pd.concat(
[d1, d2, d3], axis=1,
keys=['First', 'Second', 'Fourth'])
df
First Second Fourth
A B C B C D A B D
1 NaN NaN NaN 0.4 0.5 0.6 0.7 0.8 0.9
2 0.1 0.2 0.3 0.4 0.5 0.6 NaN NaN NaN
3 0.1 0.2 0.3 NaN NaN NaN 0.7 0.8 0.9
列对象的层级如下:
print(df, *df.columns.levels, sep='\n')
Index(['First', 'Second', 'Fourth'], dtype='object')
Index(['A', 'B', 'C', 'D'], dtype='object')
如果我们在 groupby
中使用 sum
,我们会得到:
df.groupby(axis=1, level=0).sum()
First Fourth Second
1 0.0 2.4 1.5
2 0.6 0.0 1.5
3 0.6 2.4 0.0
但是,如果除了['First', 'Second', 'Fourth']
之外还有其他缺失的分类,例如Third
和Fifth
,并且我希望它们包含在groupby
聚合的结果中,我们可以使用pandas.CategoricalIndex
来实现。我们可以通过levels
参数事先指定。
因此,我们将df
定义为:
cats = ['First', 'Second', 'Third', 'Fourth', 'Fifth']
lvl = pd.CategoricalIndex(cats, categories=cats, ordered=True)
df = pd.concat(
[d1, d2, d3], axis=1,
keys=['First', 'Second', 'Fourth'],
levels=[lvl]
)
df
First Fourth Second
1 0.0 2.4 1.5
2 0.6 0.0 1.5
3 0.6 2.4 0.0
但是 columns 对象的第一层是:
df.columns.levels[0]
CategoricalIndex(
['First', 'Second', 'Third', 'Fourth', 'Fifth'],
categories=['First', 'Second', 'Third', 'Fourth', 'Fifth'],
ordered=True, dtype='category')
我们的 groupby
汇总看起来像这样:
df.groupby(axis=1, level=0).sum()
First Second Third Fourth Fifth
1 0.0 1.5 0.0 2.4 0.0
2 0.6 1.5 0.0 0.0 0.0
3 0.6 0.0 0.0 2.4 0.0
names
此参数用于命名结果MultiIndex
的层级。 names
列表的长度应与结果MultiIndex
中的层数匹配。
names: 列表,默认为None
结果分层索引中各层级的名称
pd.concat( | pd.concat(
[d1, d2], | [d1, d2],
keys=[0, 1], | axis=1, keys=[0, 1],
names=['lvl0', 'lvl1']) | names=['lvl0', 'lvl1'])
----------------------------- | ----------------------------------
A B C D | lvl0 0 1
lvl0 lvl1 | lvl1 A B C B C D
0 2 0.1 0.2 0.3 NaN | 1 NaN NaN NaN 0.4 0.5 0.6
3 0.1 0.2 0.3 NaN | 2 0.1 0.2 0.3 0.4 0.5 0.6
1 1 NaN 0.4 0.5 0.6 | 3 0.1 0.2 0.3 NaN NaN NaN
2 NaN 0.4 0.5 0.6 |
verify_integrity
该函数用于检查数据连接后是否存在重复值,开启该选项会消耗大量计算资源。
verify_integrity: 布尔型,默认为False
是否检查新连接的轴是否有重复,相对于实际数据连接来说,这个操作可能非常耗费资源。
由于将 d1
和 d2
连接后形成的索引不是唯一的,因此它不会通过完整性检查。
pd.concat([d1, d2])
A B C D
2 0.1 0.2 0.3 NaN
3 0.1 0.2 0.3 NaN
1 NaN 0.4 0.5 0.6
2 NaN 0.4 0.5 0.6
并且
pd.concat([d1, d2], verify_integrity=True)
> 值错误:索引存在重叠值:[2]
pd.concat(..., levels=[lvl]).groupby(axis=1, level=0).sum()
与pd.concat(..., levels=[cats]).groupby(axis=1, level=0).sum()
产生了不同的结果。你知道为什么吗?文档只说levels
应该是一个序列列表。 - unutbudict
的例子,谢谢。原因是lvl
是一个分类索引,而cats
只是一个列表。当按分类类型分组时,缺失的类别会被填充为零和适当的空值。请参见此。 - piRSquaredpd.concat()
的join_axes
部分,作者还可以提到按列名连接而不仅仅是索引,如pandas文档中所述,例如pd.concat([d1, d2], axis=0, join_axes=[d1.columns])
。 - Pherdindy