Pandas 筆記
合併資料集 - 01 pandas.concat() 函式
在實際的應用當中,資料集合併是常用的功能之一,直覺上就是把兩個或以上的資料集上下或左右串接再一起。Pandas 提供幾種不同的方法合併資料集,其中 pandas.concat() 函式在串接資料上扮演相當重要的角色。pandas.concat() 函式對 pandas.Series 物件或 pandas.DataFrame 物件沿著其中一軸串接,並對pandas.DataFrame 物件其他軸有選擇性的邏輯判斷,譬如:聯集(join 參數設定為 'outer')、交集(join 參數設定為 'inner')等等。
pandas.concat() 函式根據特定的「軸 axis」和參數設定合併 pandas.Series 和 pandas.DataFrame 物件。在這一小節裡包含了所有參數的說明,並酌以範例,讓讀者更容易了解 pandas.concat() 函式的使用。
1. 當要被合併的物件都是 pandas.Series 並隨著索引(index)方向(axis = 0)時,pandas.concat() 函式回覆的是 pandas.Series 物件。
2. 當要被合併的物件含有至少一個 pandas.DataFrame 物件時,pandas.concat() 函式回覆的是一個 pandas.DataFrame 物件。
3. 當被合併的物件是沿著欄(columns)的方向時,pandas.concat() 函式回覆的也是一個 pandas.DataFrame 物件。
我們先來看 pandas.concat() 函式包含哪些參數。
pandas.concat(objs, axis = 0, join = 'outer', ignore_index = False, keys = None, levels = None, names = None, verify_integrity = False, sort = False, copy = True)
關於 pandas.concat() 函式更多詳細說明,可參考 Pandas 官方網站 https://pandas.pydata.org/docs/reference/api/pandas.concat.html?highlight=pandas%20concat#pandas.concat 。
在這一節中,我們分別以 pandas.Series 物件和 pandas.DataFrame 物件為例來說明 pandas.concat() 函式。
import pandas as pd pd.__version__
'1.0.3'
串接 pandas.Series 物件
在 pandas.concat() 函式的參數當中,只有 objs 參數是必要的,其餘參數都是 optinal。
objs 參數是一串的 pandas.Series 或 pandas.DataFrame 物件。
ser1 = pd.Series(['Albert', 'Bell', 'Cherry'], index = [1, 2, 3]) ser1
1 Albert 2 Bell 3 Cherry dtype: object
ser2 = pd.Series(['David', 'Ellen', 'Flora'], index = [4, 5, 6]) ser2
4 David 5 Ellen 6 Flora dtype: object
pd.concat([ser1, ser2])
1 Albert 2 Bell 3 Cherry 4 David 5 Ellen 6 Flora dtype: object
串接 pandas.Series - 重複索引
ser3 = pd.Series(['Glen', 'Hedy', 'Ian'], index = [5, 6, 7]) ser3
5 Glen 6 Hedy 7 Ian dtype: object
pd.concat([ser1, ser2, ser3])
1 Albert 2 Bell 3 Cherry 4 David 5 Ellen 6 Flora 5 Glen 6 Hedy 7 Ian dtype: object
請注意,pandas.concat() 函式在預設上會保留原來物件的索引,在上面的例子中,因為物件 ser2 和 ser3 都有索引 5 和 6,在合併後的 pandas.Series 物件中 5 和 6 各出現了兩次。
串接 pandas.DataFrame 物件
為了說明 pandas.DataFrame 物件的例子,這裡借用 'Python Data Science Handbook' 裡 make_df() 函式定義,以方便建立不同的 pandas.DataFrame 物件。
def make_df(columns, index): """ DataFrame creator """ data_dict = {c: [str(c) + '-' + str(i) for i in index] for c in columns} return pd.DataFrame(data_dict, index)
df1 = make_df(['Albert', 'Bell', 'Cherry'], [1, 2, 3]) df1
| Albert | Bell | Cherry | |
|---|---|---|---|
| 1 | Albert-1 | Bell-1 | Cherry-1 |
| 2 | Albert-2 | Bell-2 | Cherry-2 |
| 3 | Albert-3 | Bell-3 | Cherry-3 |
df2 = make_df(['Albert', 'Bell', 'Cherry'], [4, 5, 6]) df2
| Albert | Bell | Cherry | |
|---|---|---|---|
| 4 | Albert-4 | Bell-4 | Cherry-4 |
| 5 | Albert-5 | Bell-5 | Cherry-5 |
| 6 | Albert-6 | Bell-6 | Cherry-6 |
pd.concat([df1, df2])
| Albert | Bell | Cherry | |
|---|---|---|---|
| 1 | Albert-1 | Bell-1 | Cherry-1 |
| 2 | Albert-2 | Bell-2 | Cherry-2 |
| 3 | Albert-3 | Bell-3 | Cherry-3 |
| 4 | Albert-4 | Bell-4 | Cherry-4 |
| 5 | Albert-5 | Bell-5 | Cherry-5 |
| 6 | Albert-6 | Bell-6 | Cherry-6 |
合併 pandas.DataFrame 在預設的情形下是對列(align with row)執行合併,後面會介紹透過參數設定為對欄(align with column)執行合併。
串接 pandas.DataFrame 物件 - 重複索引
df3 = make_df(['Albert', 'Bell', 'Cherry'], [5, 6, 7]) df3
| Albert | Bell | Cherry | |
|---|---|---|---|
| 5 | Albert-5 | Bell-5 | Cherry-5 |
| 6 | Albert-6 | Bell-6 | Cherry-6 |
| 7 | Albert-7 | Bell-7 | Cherry-7 |
pd.concat([df1, df2, df3])
| Albert | Bell | Cherry | |
|---|---|---|---|
| 1 | Albert-1 | Bell-1 | Cherry-1 |
| 2 | Albert-2 | Bell-2 | Cherry-2 |
| 3 | Albert-3 | Bell-3 | Cherry-3 |
| 4 | Albert-4 | Bell-4 | Cherry-4 |
| 5 | Albert-5 | Bell-5 | Cherry-5 |
| 6 | Albert-6 | Bell-6 | Cherry-6 |
| 5 | Albert-5 | Bell-5 | Cherry-5 |
| 6 | Albert-6 | Bell-6 | Cherry-6 |
| 7 | Albert-7 | Bell-7 | Cherry-7 |
上面例子與串接 Sries 物件相同,pandas.concat() 函式在預設上會保留原來物件的索引,在上面的例子中,因為物件 df2 和 df3 都有索引 5 和 6,在合併後的 pandas.DataFrame 物件中 5 和 6 各出現了兩次。
verify_integrity 參數
verify_integrity 參數是一個布林值,預設是 False。它決定是否檢查合併後的新索引(index)有無重複的索引,或是合併後的新欄名(columns)有無重複的欄。
在前一個例子中,預設的情形下是允許重複的索引的,對於龐大的資料集,無法用人工比對合併的資料集是否重複,但又想檢查是否有重複索引,這可以使用 verify_integrity 參數達到這個目的。
try: pd.concat([df2, df3], verify_integrity = True) except ValueError as err: print(f"Exception: {err}")
Exception: Indexes have overlapping values: Int64Index([5, 6], dtype='int64')
axis 參數
axis 參數指定要對哪一軸(axis)執行合併,它的值 0 (或 'index')表示對列合併,值 1 (或 'columns')表示對欄合併,預設值是 0 (或 'index')。
合併 pandas.DataFrame 在預設的情形下是對列(axis = 0 或 axis = 'index')執行合併,就如同前面幾個例子,我們可以使用 axis 參數指定要對哪一軸執行合併。以下的例子是對欄(axis = 1 或 axis = 'columns')執行合併。
df4 = make_df(['Albert', 'Bell', 'Cherry'], [1, 2, 3]) df4
| Albert | Bell | Cherry | |
|---|---|---|---|
| 1 | Albert-1 | Bell-1 | Cherry-1 |
| 2 | Albert-2 | Bell-2 | Cherry-2 |
| 3 | Albert-3 | Bell-3 | Cherry-3 |
df5 = make_df(['David', 'Ellen', 'Flora'], [1, 2, 3]) df5
| David | Ellen | Flora | |
|---|---|---|---|
| 1 | David-1 | Ellen-1 | Flora-1 |
| 2 | David-2 | Ellen-2 | Flora-2 |
| 3 | David-3 | Ellen-3 | Flora-3 |
pd.concat([df4, df5], axis = 1)
| Albert | Bell | Cherry | David | Ellen | Flora | |
|---|---|---|---|---|---|---|
| 1 | Albert-1 | Bell-1 | Cherry-1 | David-1 | Ellen-1 | Flora-1 |
| 2 | Albert-2 | Bell-2 | Cherry-2 | David-2 | Ellen-2 | Flora-2 |
| 3 | Albert-3 | Bell-3 | Cherry-3 | David-3 | Ellen-3 | Flora-3 |
ignore_index 參數
ignore_index 參數是一個布林值,預設是 False。當設定為 True 時,合併後資料集將不使用合併前資料集的索引(index)或是欄(columns),取而代之的是數字 0、1、2、...。
在某些資料集的應用中,資料集原先的索引並不重要,資料集之間的索引是否有重複也不在意,這時候可以使用 ignore_index 參數,忽略原先的索引,合併後的新資料集會重新指定索引。
pd.concat([df2, df3], ignore_index = True)
| Albert | Bell | Cherry | |
|---|---|---|---|
| 0 | Albert-4 | Bell-4 | Cherry-4 |
| 1 | Albert-5 | Bell-5 | Cherry-5 |
| 2 | Albert-6 | Bell-6 | Cherry-6 |
| 3 | Albert-5 | Bell-5 | Cherry-5 |
| 4 | Albert-6 | Bell-6 | Cherry-6 |
| 5 | Albert-7 | Bell-7 | Cherry-7 |
pd.concat([df4, df5], axis = 1, ignore_index = True)
| 0 | 1 | 2 | 3 | 4 | 5 | |
|---|---|---|---|---|---|---|
| 1 | Albert-1 | Bell-1 | Cherry-1 | David-1 | Ellen-1 | Flora-1 |
| 2 | Albert-2 | Bell-2 | Cherry-2 | David-2 | Ellen-2 | Flora-2 |
| 3 | Albert-3 | Bell-3 | Cherry-3 | David-3 | Ellen-3 | Flora-3 |
join 參數
join 參數指定當列和欄名稱不完全相同時,如何處理其它軸上的索引(indexes)的資料。它的值有 'inner' 和 'outer',預設是 'outer'。
當對列(axis = 0)執行合併時,如果遇到要合併的資料集的欄位不完全相同時,pandas.concat() 函式預設是擴大包含所有的欄位(join = 'outer'),相當於是原先資料集的聯集,這時在合併後的欄位與對應的索引沒有資料存在會以 NA 來取代。
如果要刪除沒有資料的整個欄位,可以使用 join = 'inner',這相當於是原先資料集的交集。
df6 = make_df(['Albert', 'Bell', 'Cherry'], [1, 2, 3]) df6
| Albert | Bell | Cherry | |
|---|---|---|---|
| 1 | Albert-1 | Bell-1 | Cherry-1 |
| 2 | Albert-2 | Bell-2 | Cherry-2 |
| 3 | Albert-3 | Bell-3 | Cherry-3 |
df7 = make_df(['Bell', 'Cherry', 'David'], [4, 5, 6]) df7
| Bell | Cherry | David | |
|---|---|---|---|
| 4 | Bell-4 | Cherry-4 | David-4 |
| 5 | Bell-5 | Cherry-5 | David-5 |
| 6 | Bell-6 | Cherry-6 | David-6 |
pd.concat([df6, df7])
| Albert | Bell | Cherry | David | |
|---|---|---|---|---|
| 1 | Albert-1 | Bell-1 | Cherry-1 | NaN |
| 2 | Albert-2 | Bell-2 | Cherry-2 | NaN |
| 3 | Albert-3 | Bell-3 | Cherry-3 | NaN |
| 4 | NaN | Bell-4 | Cherry-4 | David-4 |
| 5 | NaN | Bell-5 | Cherry-5 | David-5 |
| 6 | NaN | Bell-6 | Cherry-6 | David-6 |
pd.concat([df6, df7]) 相當於 pd.concat([df6, df7], join = 'outer'),這時 pandas.concat() 函式會擴大包含所有的欄位,包括沒有資料的部份。而 pd.concat([df6, df7], join = 'inner') 則會刪除沒有資料的欄位。
pd.concat([df6, df7], join = 'inner')
| Bell | Cherry | |
|---|---|---|
| 1 | Bell-1 | Cherry-1 |
| 2 | Bell-2 | Cherry-2 |
| 3 | Bell-3 | Cherry-3 |
| 4 | Bell-4 | Cherry-4 |
| 5 | Bell-5 | Cherry-5 |
| 6 | Bell-6 | Cherry-6 |
keys 參數
keys 參數是一個序列(sequence),預設值是 None。在建立的階層式索引資料集中這 keys 參數被當成最外層的名稱。
在合併資料集時,有時候想明確區隔合併前原先的資料集,這時可以使用 keys 參數建立階層式索引(MultiIndex)的 pandas.Series 和 pandas.DataFrame 物件,在 level 0 的地方指定階層名稱。
在下面這個例子當中,對列執行合併,'df2' 和 'df3' 分別是階層式索引 level 0 的索引名稱,而 level 1 的索引名稱則沿用合併前原先資料集的索引。
df = pd.concat([df2, df3], keys = ['df2', 'df3']) df
| Albert | Bell | Cherry | ||
|---|---|---|---|---|
| df2 | 4 | Albert-4 | Bell-4 | Cherry-4 |
| 5 | Albert-5 | Bell-5 | Cherry-5 | |
| 6 | Albert-6 | Bell-6 | Cherry-6 | |
| df3 | 5 | Albert-5 | Bell-5 | Cherry-5 |
| 6 | Albert-6 | Bell-6 | Cherry-6 | |
| 7 | Albert-7 | Bell-7 | Cherry-7 |
df.index.levels
FrozenList([['df2', 'df3'], [4, 5, 6, 7]])
在這個例子中合併後的資料集是一個列的多重索引(MultiIndex),它 level 0 的索引名稱是 'df2'、 'df3',level 1 的索引名稱是 4、 5、 6、 7。
keys 參數同樣可以用在對欄(axis = 1 或 axis = 'columns')執行合併時,這時,keys 參數指定的是多重欄 level 0 的名稱,而 level 1 則沿用合併前原先資料集的欄名。
df = pd.concat([df4, df5], axis = 1, keys = ['df4', 'df5']) df
| df4 | df5 | |||||
|---|---|---|---|---|---|---|
| Albert | Bell | Cherry | David | Ellen | Flora | |
| 1 | Albert-1 | Bell-1 | Cherry-1 | David-1 | Ellen-1 | Flora-1 |
| 2 | Albert-2 | Bell-2 | Cherry-2 | David-2 | Ellen-2 | Flora-2 |
| 3 | Albert-3 | Bell-3 | Cherry-3 | David-3 | Ellen-3 | Flora-3 |
df.columns.levels
FrozenList([['df4', 'df5'], ['Albert', 'Bell', 'Cherry', 'David', 'Ellen', 'Flora']])
在這個範例中,合併後的資料集是一個欄的多重索引(MultiIndex),它 level 0 的索引名稱是 'df4'、 'df5',level 1 的索引名稱是 'Albert'、'Bell', 'Cherry'、'David'、'Ellen'、'Flora'。
levels 參數
levels 參數是一個序列形成的串列(a list of sequence),預設值是 None。
前面 keys 參數設定讓合併後的資料集變成一個階層式索引(Hierarchical Indexing)物件,它擁有兩個或以上的列索引或欄名稱,這些多重索引列或欄可以透過 pandas.DataFrame.index.levels 或 pandas.DataFrame.columns.levels 屬性列出列或欄的階層索引名稱。
在這建構出的階層式索引物件的列或欄的階層索引名稱之外,如果要指定其它索引名稱,可以透過 levels 參數達到這個目的。
df = pd.concat([df2, df3], keys = ['df2', 'df3'], levels = [['df1', 'df2', 'df3', 'df6']]) df
| Albert | Bell | Cherry | ||
|---|---|---|---|---|
| df2 | 4 | Albert-4 | Bell-4 | Cherry-4 |
| 5 | Albert-5 | Bell-5 | Cherry-5 | |
| 6 | Albert-6 | Bell-6 | Cherry-6 | |
| df3 | 5 | Albert-5 | Bell-5 | Cherry-5 |
| 6 | Albert-6 | Bell-6 | Cherry-6 | |
| 7 | Albert-7 | Bell-7 | Cherry-7 |
df.index.levels
FrozenList([['df1', 'df2', 'df3', 'df6'], [4, 5, 6, 7]])
請注意,'df1' 和 'df6' 是新增其它索引名稱,雖然它並沒有出現在合併後的資料集中,但它的屬性卻已包含這些名稱。
df = pd.concat([df4, df5], axis = 1, keys = ['df4', 'df5'], levels = [['df0', 'df4', 'df5', 'df7']]) df
| df4 | df5 | |||||
|---|---|---|---|---|---|---|
| Albert | Bell | Cherry | David | Ellen | Flora | |
| 1 | Albert-1 | Bell-1 | Cherry-1 | David-1 | Ellen-1 | Flora-1 |
| 2 | Albert-2 | Bell-2 | Cherry-2 | David-2 | Ellen-2 | Flora-2 |
| 3 | Albert-3 | Bell-3 | Cherry-3 | David-3 | Ellen-3 | Flora-3 |
df.columns.levels
FrozenList([['df0', 'df4', 'df5', 'df7'], ['Albert', 'Bell', 'Cherry', 'David', 'Ellen', 'Flora']])
這欄的多重索引也可使用 levels 參數,這個範例中 'df0', 'df7' 是新增其它的欄名稱,也只出現在物件屬性中。
names 參數
names 參數式一個串列(list),預設值是 None。
names 參數指定階層式索引的列和欄多重索引的名稱。
df = pd.concat([df4, df5], axis = 1, keys = ['df4', 'df5'], levels = [['df0', 'df4', 'df5', 'df7']], names = ['upper', 'lower']) df
| upper | df4 | df5 | ||||
|---|---|---|---|---|---|---|
| lower | Albert | Bell | Cherry | David | Ellen | Flora |
| 1 | Albert-1 | Bell-1 | Cherry-1 | David-1 | Ellen-1 | Flora-1 |
| 2 | Albert-2 | Bell-2 | Cherry-2 | David-2 | Ellen-2 | Flora-2 |
| 3 | Albert-3 | Bell-3 | Cherry-3 | David-3 | Ellen-3 | Flora-3 |
在這個範例中 'upper' 是 level 0 這層索引的名稱,而 'lower' 是 level 1 這層索引的名稱。
sort 參數
sort 參數式一個布林值,是在 0.23.0 版新增的參數,預設值是 None,在 1.0.0 版時變更預設值為 False。
在合併資料集時,當 join 參數為 'outer' 時,sort 參數設為 True 會對非串接的那一軸排序。
df8 = make_df(['Albert', 'Cherry', 'David', 'Bell'], [1, 2, 3, 4]) df8
| Albert | Cherry | David | Bell | |
|---|---|---|---|---|
| 1 | Albert-1 | Cherry-1 | David-1 | Bell-1 |
| 2 | Albert-2 | Cherry-2 | David-2 | Bell-2 |
| 3 | Albert-3 | Cherry-3 | David-3 | Bell-3 |
| 4 | Albert-4 | Cherry-4 | David-4 | Bell-4 |
df9 = make_df(['Bell', 'Flora', 'Ellen', 'David'], [2, 3, 5, 6]) df9
| Bell | Flora | Ellen | David | |
|---|---|---|---|---|
| 2 | Bell-2 | Flora-2 | Ellen-2 | David-2 |
| 3 | Bell-3 | Flora-3 | Ellen-3 | David-3 |
| 5 | Bell-5 | Flora-5 | Ellen-5 | David-5 |
| 6 | Bell-6 | Flora-6 | Ellen-6 | David-6 |
pd.concat([df8, df9], sort = False)
| Albert | Cherry | David | Bell | Flora | Ellen | |
|---|---|---|---|---|---|---|
| 1 | Albert-1 | Cherry-1 | David-1 | Bell-1 | NaN | NaN |
| 2 | Albert-2 | Cherry-2 | David-2 | Bell-2 | NaN | NaN |
| 3 | Albert-3 | Cherry-3 | David-3 | Bell-3 | NaN | NaN |
| 4 | Albert-4 | Cherry-4 | David-4 | Bell-4 | NaN | NaN |
| 2 | NaN | NaN | David-2 | Bell-2 | Flora-2 | Ellen-2 |
| 3 | NaN | NaN | David-3 | Bell-3 | Flora-3 | Ellen-3 |
| 5 | NaN | NaN | David-5 | Bell-5 | Flora-5 | Ellen-5 |
| 6 | NaN | NaN | David-6 | Bell-6 | Flora-6 | Ellen-6 |
在上面這個範例中,資料集合併的軸是列(axis = 0),因為 sort = False ,因此並不會對欄這個方向排序。
而下面這個範例中,sort = True ,因此資料集合併之後會再對欄排序。
pd.concat([df8, df9], sort = True)
| Albert | Bell | Cherry | David | Ellen | Flora | |
|---|---|---|---|---|---|---|
| 1 | Albert-1 | Bell-1 | Cherry-1 | David-1 | NaN | NaN |
| 2 | Albert-2 | Bell-2 | Cherry-2 | David-2 | NaN | NaN |
| 3 | Albert-3 | Bell-3 | Cherry-3 | David-3 | NaN | NaN |
| 4 | Albert-4 | Bell-4 | Cherry-4 | David-4 | NaN | NaN |
| 2 | NaN | Bell-2 | NaN | David-2 | Ellen-2 | Flora-2 |
| 3 | NaN | Bell-3 | NaN | David-3 | Ellen-3 | Flora-3 |
| 5 | NaN | Bell-5 | NaN | David-5 | Ellen-5 | Flora-5 |
| 6 | NaN | Bell-6 | NaN | David-6 | Ellen-6 | Flora-6 |
依照官網說明,當 join = 'inner' 時,sort 參數沒有任何作用,因為它已經保留了非串接軸的順序。(Pandas 官網:This has no effect when join='inner', which already preserves the order of the non-concatenation axis.)
但,以下的範例結果似乎與官網說明不同。
pd.concat([df8, df9], join = 'inner')
| David | Bell | |
|---|---|---|
| 1 | David-1 | Bell-1 |
| 2 | David-2 | Bell-2 |
| 3 | David-3 | Bell-3 |
| 4 | David-4 | Bell-4 |
| 2 | David-2 | Bell-2 |
| 3 | David-3 | Bell-3 |
| 5 | David-5 | Bell-5 |
| 6 | David-6 | Bell-6 |
當 join = 'inner' 時,沒有指定 sort 參數值, sort 參數使用預設值 False,得到的合併結果是「沒有排序」。
pd.concat([df8, df9], join = 'inner', sort = False)
| David | Bell | |
|---|---|---|
| 1 | David-1 | Bell-1 |
| 2 | David-2 | Bell-2 |
| 3 | David-3 | Bell-3 |
| 4 | David-4 | Bell-4 |
| 2 | David-2 | Bell-2 |
| 3 | David-3 | Bell-3 |
| 5 | David-5 | Bell-5 |
| 6 | David-6 | Bell-6 |
當 join = 'inner',sort = False 時,得到的合併結果和 sort 參數使用預設值一樣是「沒有排序」。
pd.concat([df8, df9], join = 'inner', sort = True)
| Bell | David | |
|---|---|---|
| 1 | Bell-1 | David-1 |
| 2 | Bell-2 | David-2 |
| 3 | Bell-3 | David-3 |
| 4 | Bell-4 | David-4 |
| 2 | Bell-2 | David-2 |
| 3 | Bell-3 | David-3 |
| 5 | Bell-5 | David-5 |
| 6 | Bell-6 | David-6 |
但當 join = 'inner',sort = True 時,得到的合併結果和 sort 參數使用預設值是「有重新排序」。
copy 參數
copy 參數是一個布林值,預設值是 True。
按照官方網站的解釋,如果 copy = False 的話,將不會拷貝不必要的資料。(If False, do not copy data unnecessarily.)筆者試著搜尋官方和非官方網站包含追朔原始碼,但並沒有找到明確的文件說明 copy 參數的用途。
關於 copy 參數的說明就留給其他 Pandas 專家來解釋。
參考資料:
- pandas 官方網站(https://pandas.pydata.org/)
- 'Python Data Science Handbook', Jake VanderPlas
老驥 於 2020/5/7
