文章目錄
- Pandas 合并數據集:concat 和 append
- 回顧:NumPy 數組的拼接
- 使用 pd.concat 進行簡單拼接
- 重復索引
- 將重復索引視為錯誤
- 忽略索引
- 添加多級索引(MultiIndex)鍵
- 使用連接(Join)方式拼接
- append 方法
Pandas 合并數據集:concat 和 append
一些最有趣的數據研究來自于合并不同的數據源。
這些操作可以包括從非常簡單的兩個數據集的拼接,到更復雜的數據庫式連接和合并,這些操作能夠正確處理數據集之間的重疊部分。
Series
和 DataFrame
都是為這種操作設計的,Pandas 提供了函數和方法,使得這種數據整理變得快速且簡單。
在這里,我們將首先介紹如何使用 pd.concat
函數對 Series
和 DataFrame
進行簡單拼接;稍后我們會深入講解 Pandas 中更復雜的內存合并和連接操作。
我們從標準的導入開始:
import pandas as pd
import numpy as np
為了方便起見,我們將定義一個函數,用于創建特定格式的 DataFrame
,這將在接下來的示例中非常有用。
# 快速創建DataFrame
def make_df(cols, ind):"""快速創建一個DataFrame"""data = {c: [str(c) + str(i) for i in ind]for c in cols}return pd.DataFrame(data, ind)# 創建一個示例DataFrame
df1 = make_df(['A', 'B', 'C'], [1, 2])
df2 = make_df(['A', 'B', 'C'], [3, 4])
df1
A | B | C | |
---|---|---|---|
1 | A1 | B1 | C1 |
2 | A2 | B2 | C2 |
df2
A | B | C | |
---|---|---|---|
3 | A3 | B3 | C3 |
4 | A4 | B4 | C4 |
make_df('ABC', range(3))
A | B | C | |
---|---|---|---|
0 | A0 | B0 | C0 |
1 | A1 | B1 | C1 |
2 | A2 | B2 | C2 |
此外,我們還將創建一個快速類,用于讓我們能夠并排顯示多個 DataFrame
。該代碼利用了特殊的 _repr_html_
方法,這是 IPython/Jupyter 用于實現其豐富對象顯示的機制:
class display(object):"""Display HTML representation of multiple objects"""template = """<div style="float: left; padding: 10px;"><p style='font-family:"Courier New", Courier, monospace'>{0}</p>{1}</div>"""def __init__(self, *args):self.args = argsdef _repr_html_(self):return '\n'.join(self.template.format(a, eval(a)._repr_html_())for a in self.args)def __repr__(self):return '\n\n'.join(a + '\n' + repr(eval(a))for a in self.args)
隨著我們在接下來的章節中繼續討論,這個類的用途將會變得更加清晰。
回顧:NumPy 數組的拼接
Series
和 DataFrame
對象的拼接行為類似于 NumPy 數組的拼接,可以通過 np.concatenate
函數實現,相關內容可參考 NumPy 數組基礎。
回顧一下,使用該函數可以將兩個或多個數組的內容合并為一個數組:
x = [1, 2, 3]
y = [4, 5, 6]
z = [7, 8, 9]
np.concatenate([x, y, z])
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
第一個參數是要連接的數組的列表或元組。
此外,對于多維數組,還可以使用 axis
關鍵字參數,指定沿哪個軸進行拼接:
x = [[1, 2],[3, 4]]
np.concatenate([x, x], axis=1)
array([[1, 2, 1, 2],[3, 4, 3, 4]])
使用 pd.concat 進行簡單拼接
pd.concat
函數提供了類似于 np.concatenate
的語法,但包含了許多我們稍后會討論的選項:
# Pandas v1.3.5 中的函數簽名
pd.concat(objs, axis=0, join='outer', ignore_index=False, keys=None,levels=None, names=None, verify_integrity=False,sort=False, copy=True)
pd.concat
可以用于簡單地拼接 Series
或 DataFrame
對象,就像 np.concatenate
可以用于簡單地拼接數組一樣:
ser1 = pd.Series(['A', 'B', 'C'], index=[1, 2, 3])
ser2 = pd.Series(['D', 'E', 'F'], index=[4, 5, 6])
pd.concat([ser1, ser2])
1 A
2 B
3 C
4 D
5 E
6 F
dtype: object
它同樣適用于拼接更高維度的對象,比如 DataFrame
:
df1 = make_df('AB', [1, 2])
df2 = make_df('AB', [3, 4])
display('df1', 'df2', 'pd.concat([df1, df2])')
df1
A | B | |
---|---|---|
1 | A1 | B1 |
2 | A2 | B2 |
df2
A | B | |
---|---|---|
3 | A3 | B3 |
4 | A4 | B4 |
pd.concat([df1, df2])
A | B | |
---|---|---|
1 | A1 | B1 |
2 | A2 | B2 |
3 | A3 | B3 |
4 | A4 | B4 |
它的默認行為是在 DataFrame
內按行拼接(即 axis=0
)。
與 np.concatenate
類似,pd.concat
允許指定拼接所沿的軸。
請看下面的例子:
df3 = make_df('AB', [0, 1])
df4 = make_df('CD', [0, 1])
display('df3', 'df4', "pd.concat([df3, df4], axis='columns')")
df3
A | B | |
---|---|---|
0 | A0 | B0 |
1 | A1 | B1 |
df4
C | D | |
---|---|---|
0 | C0 | D0 |
1 | C1 | D1 |
pd.concat([df3, df4], axis='columns')
A | B | C | D | |
---|---|---|---|---|
0 | A0 | B0 | C0 | D0 |
1 | A1 | B1 | C1 | D1 |
我們同樣可以指定 axis=1
;這里我們使用了更直觀的 axis='columns'
。
重復索引
np.concatenate
和 pd.concat
之間的一個重要區別是,Pandas 的拼接操作會保留索引,即使結果中會出現重復的索引!
請看下面這個簡短的例子:
x = make_df('AB', [0, 1])
y = make_df('AB', [2, 3])
y.index = x.index # make indices match
display('x', 'y', 'pd.concat([x, y])')
x
A | B | |
---|---|---|
0 | A0 | B0 |
1 | A1 | B1 |
y
A | B | |
---|---|---|
0 | A2 | B2 |
1 | A3 | B3 |
pd.concat([x, y])
A | B | |
---|---|---|
0 | A0 | B0 |
1 | A1 | B1 |
0 | A2 | B2 |
1 | A3 | B3 |
注意結果中重復的索引。
雖然在 DataFrame
中這是允許的,但這種結果通常并不是我們想要的。
pd.concat
為我們提供了幾種處理這種情況的方法。
將重復索引視為錯誤
如果你希望在 pd.concat
的結果中索引沒有重疊,可以設置 verify_integrity
參數為 True
。
這樣拼接時如果出現重復索引,就會拋出異常。
下面是一個示例,為了更清晰,我們會捕獲并打印錯誤信息:
try:pd.concat([x, y], verify_integrity=True)
except ValueError as e:print("ValueError:", e)
ValueError: Indexes have overlapping values: Index([0, 1], dtype='int64')
忽略索引
有時候索引本身并不重要,你可能更希望直接忽略它。
可以通過設置 ignore_index
參數來實現這一點。
當該參數設為 True
時,拼接后的結果會為新的 DataFrame
創建一個新的整數索引:
display('x', 'y', 'pd.concat([x, y], ignore_index=True)')
x
A | B | |
---|---|---|
0 | A0 | B0 |
1 | A1 | B1 |
y
A | B | |
---|---|---|
0 | A2 | B2 |
1 | A3 | B3 |
pd.concat([x, y], ignore_index=True)
A | B | |
---|---|---|
0 | A0 | B0 |
1 | A1 | B1 |
2 | A2 | B2 |
3 | A3 | B3 |
添加多級索引(MultiIndex)鍵
另一種選擇是使用 keys
選項為數據源指定標簽;結果將是一個包含這些數據的分層索引(MultiIndex)序列:
display('x', 'y', "pd.concat([x, y], keys=['x', 'y'])")
x
A | B | |
---|---|---|
0 | A0 | B0 |
1 | A1 | B1 |
y
A | B | |
---|---|---|
0 | A2 | B2 |
1 | A3 | B3 |
pd.concat([x, y], keys=['x', 'y'])
A | B | ||
---|---|---|---|
x | 0 | A0 | B0 |
1 | A1 | B1 | |
y | 0 | A2 | B2 |
1 | A3 | B3 |
我們可以使用分層索引中討論的工具,將這個多重索引的 DataFrame
轉換為我們感興趣的表示形式。
使用連接(Join)方式拼接
在前面的簡短示例中,我們主要拼接的是具有相同列名的 DataFrame
。
實際上,來自不同數據源的數據可能具有不同的列集合,此時 pd.concat
提供了多種選項。
請看下面兩個 DataFrame
的拼接示例,它們只有部分(而不是全部)列名相同:
df5 = make_df('ABC', [1, 2])
df6 = make_df('BCD', [3, 4])
display('df5', 'df6', 'pd.concat([df5, df6])')
df5
A | B | C | |
---|---|---|---|
1 | A1 | B1 | C1 |
2 | A2 | B2 | C2 |
df6
B | C | D | |
---|---|---|---|
3 | B3 | C3 | D3 |
4 | B4 | C4 | D4 |
pd.concat([df5, df6])
A | B | C | D | |
---|---|---|---|---|
1 | A1 | B1 | C1 | NaN |
2 | A2 | B2 | C2 | NaN |
3 | NaN | B3 | C3 | D3 |
4 | NaN | B4 | C4 | D4 |
默認行為是用 NA
值填充沒有數據的位置。
要更改這一點,我們可以調整 concat
函數的 join
參數。
默認情況下,join
是輸入列的并集(join='outer'
),但我們也可以通過設置 join='inner'
,將其改為只保留列的交集:
display('df5', 'df6',"pd.concat([df5, df6], join='inner')")
df5
A | B | C | |
---|---|---|---|
1 | A1 | B1 | C1 |
2 | A2 | B2 | C2 |
df6
B | C | D | |
---|---|---|---|
3 | B3 | C3 | D3 |
4 | B4 | C4 | D4 |
pd.concat([df5, df6], join='inner')
B | C | |
---|---|---|
1 | B1 | C1 |
2 | B2 | C2 |
3 | B3 | C3 |
4 | B4 | C4 |
另一種有用的模式是在拼接前使用 reindex
方法,以更精細地控制哪些列被舍棄:
pd.concat([df5, df6.reindex(df5.columns, axis=1)])
A | B | C | |
---|---|---|---|
1 | A1 | B1 | C1 |
2 | A2 | B2 | C2 |
3 | NaN | B3 | C3 |
4 | NaN | B4 | C4 |
append 方法
由于直接拼接數組非常常見,在Pandas 2.0.0版本之前,Series
和 DataFrame
對象都提供了 append
方法,可以用更少的代碼實現同樣的功能。
但值得注意的是,自 Pandas 2.0.0 開始,不再支持append
方法,故以下的示例將報 AttributeError。
display('df1', 'df2', 'df1.append(df2)')
請注意,與 Python 列表的 append
和 extend
方法不同,Pandas 中的 append
方法不會修改原始對象;它會創建一個包含合并數據的新對象。
此外,這種方法的效率并不高,因為它涉及新索引和數據緩沖區的創建。
因此,如果你打算進行多次 append
操作,通常更好的做法是先構建一個 DataFrame 對象的列表,然后一次性傳遞給 concat
函數。
Pandas有更強大的多數據源合并方法:pd.merge
實現的數據庫式合并/連接。
關于 concat
、append
及相關功能的更多信息,請參閱 Pandas 文檔的“合并、連接、拼接與比較”部分。