04-2pandas的拼接操作
pandas的拼接操作
pandas的拼接分為兩種:
- 級聯:pd.concat, pd.append (沒有重複資料)
- 合併:pd.merge, pd.join (有重複資料)
0. 回顧numpy的級聯
============================================
練習12:
- 生成2個3*3的矩陣,對其分別進行兩個維度上的級聯
============================================
import numpy as np import pandas as pd from pandas import Series,DataFrame
nd1 = np.random.randint(0,10,size=(3,3))
nd2 = np.random.randint(10,100,size=(3,3))
display(nd1,nd2)
結果為:
array([[7, 2, 9],
[4, 6, 5],
[6, 0, 8]])
array([[18, 19, 44],
[33, 71, 91],
[69, 42, 33]])
np.concatenate((nd1,nd2)) 結果為: array([[ 5, 3, 9], [ 9, 4, 7], [ 3, 1, 5], [11, 46, 23], [40, 61, 70], [28, 47, 12]])
np.concatenate((nd1,nd2),axis=1)
結果為:
array([[ 7, 0, 8, 54, 81, 24],
[ 3, 0, 3, 24, 66, 91],
[ 3, 9, 5, 28, 40, 68]])
1. 使用pd.concat()級聯
為方便講解,我們首先定義一個生成DataFrame的函式:
df1 = DataFrame(nd1)
df2 = DataFrame(nd2)
display(df1,df2)
0 | 1 | 2 | |
---|---|---|---|
0 | 7 | 0 | 8 |
1 | 3 | 0 | 3 |
2 | 3 | 9 | 5 |
0 | 1 | 2 | |
---|---|---|---|
0 | 54 | 81 | 24 |
1 | 24 | 66 | 91 |
2 | 28 | 40 | 68 |
1) 簡單級聯
pandas使用pd.concat函式,與np.concatenate函式類似
df3 = pd.concat((df1,df2)) #預設 axis是0 是縱向拼接
df3
0 | 1 | 2 | |
---|---|---|---|
0 | 7 | 0 | 8 |
1 | 3 | 0 | 3 |
2 | 3 | 9 | 5 |
0 | 54 | 81 | 24 |
1 | 24 | 66 | 91 |
2 | 28 | 40 | 68 |
索引有重複 會產生一些問題
df3.loc[0]
0 | 1 | 2 | |
---|---|---|---|
0 | 7 | 0 | 8 |
0 | 54 | 81 | 24 |
可以通過 重置索引的方式 去重新讓索引不重複
# ignore_index=False 忽略原索引 建立新索引 預設是False
df3 = pd.concat((df1,df2),ignore_index=True,axis=0) # 預設是 0 豎直方向這樣寫:df3 = pd.concat((df1,df2),ignore_index=True)
df3
0 | 1 | 2 | |
---|---|---|---|
0 | 7 | 0 | 8 |
1 | 3 | 0 | 3 |
2 | 3 | 9 | 5 |
3 | 54 | 81 | 24 |
4 | 24 | 66 | 91 |
5 | 28 | 40 | 68 |
df3 = pd.concat((df1,df2),axis=1) # 1 水平方向拼接
df3
0 | 1 | 2 | 0 | 1 | 2 | |
---|---|---|---|---|---|---|
0 | 7 | 0 | 8 | 54 | 81 | 24 |
1 | 3 | 0 | 3 | 24 | 66 | 91 |
2 | 3 | 9 | 5 | 28 | 40 | 68 |
df3 = pd.concat((df1,df2),ignore_index=True,axis=1)
df3
0 | 1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|---|
0 | 7 | 0 | 8 | 54 | 81 | 24 |
1 | 3 | 0 | 3 | 24 | 66 | 91 |
2 | 3 | 9 | 5 | 28 | 40 | 68 |
df3 = pd.concat((df1,df2),keys=["第一個","第二個"])
df3
0 | 1 | 2 | ||
---|---|---|---|---|
第一個 | 0 | 7 | 0 | 8 |
1 | 3 | 0 | 3 | |
2 | 3 | 9 | 5 | |
第二個 | 0 | 54 | 81 | 24 |
1 | 24 | 66 | 91 | |
2 | 28 | 40 | 68 |
2) 不匹配級聯
不匹配指的是級聯的維度的索引不一致。例如縱向級聯時列索引不一致,橫向級聯時行索引不一致
有3種連線方式:
-
外連線:補NaN(預設模式)
-
內連線:只連線匹配的項
-
連線指定軸 join_axes
nd1,nd2
結果為:
(array([[7, 0, 8],
[3, 0, 3],
[3, 9, 5]]),
array([[54, 81, 24],
[24, 66, 91],
[28, 40, 68]]))
把相同索引的行或列進行級聯,如果存在不匹配的行列標籤,補nan
df3 = DataFrame(data=nd1,columns=list("ABC"))
df4 = DataFrame(data=nd2,columns=list("BCD"))
display(df3,df4)
A | B | C | |
---|---|---|---|
0 | 7 | 0 | 8 |
1 | 3 | 0 | 3 |
2 | 3 | 9 | 5 |
B | C | D | |
---|---|---|---|
0 | 54 | 81 | 24 |
1 | 24 | 66 | 91 |
2 | 28 | 40 | 68 |
pd.concat((df3,df4),sort=False) # dataframe 拼接 預設是 外聯
A | B | C | D | |
---|---|---|---|---|
0 | 7.0 | 0 | 8 | NaN |
1 | 3.0 | 0 | 3 | NaN |
2 | 3.0 | 9 | 5 | NaN |
0 | NaN | 54 | 81 | 24.0 |
1 | NaN | 24 | 66 | 91.0 |
2 | NaN | 28 | 40 | 68.0 |
pd.concat((df3,df4),sort=False,join="inner")
B | C | |
---|---|---|
0 | 0 | 8 |
1 | 0 | 3 |
2 | 9 | 5 |
0 | 54 | 81 |
1 | 24 | 66 |
2 | 28 | 40 |
pd.concat((df3,df4),sort="False",axis=1,ignore_index="True")
0 | 1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|---|
0 | 7 | 0 | 8 | 54 | 81 | 24 |
1 | 3 | 0 | 3 | 24 | 66 | 91 |
2 | 3 | 9 | 5 | 28 | 40 | 68 |
使用keys引數,可以自動設定為多層級索引,避免索引重複
pd.concat((df3,df4),keys=['期中','期末'],sort="False")
A | B | C | D | ||
---|---|---|---|---|---|
期中 | 0 | 7.0 | 0 | 8 | NaN |
1 | 3.0 | 0 | 3 | NaN | |
2 | 3.0 | 9 | 5 | NaN | |
期末 | 0 | NaN | 54 | 81 | 24.0 |
1 | NaN | 24 | 66 | 91.0 | |
2 | NaN | 28 | 40 | 68.0 |
index = pd.Index(["B","C"])#改變列標題
pd.concat((df3,df4),sort="False",join_axes=[index])
B | C | |
---|---|---|
0 | 0 | 8 |
1 | 0 | 3 |
2 | 9 | 5 |
0 | 54 | 81 |
1 | 24 | 66 |
2 | 28 | 40 |
總結:pd.concat() 引數
- objs 傳入列表或者元素 裡面是要拼接的DataFrame
- axis 拼接的時候是沿著什麼方向 預設值是0 縱向 如果是1就是橫向
- join 指定了拼接的方式 預設是outer
outer 外聯 所有的列都會拼進來
inner 內聯 只有那些兩個DataFrame都有的列才會拼進來 - join_axes 直接指定那些列要放進來
- ignore_index=False 忽略原有索引建立新的索引 (如果索引有重複可以通過忽略原索引來重置)
- keys 可以把不同的DataFrame分成多組 也可以用來解決index重複的問
============================================
練習13:
- 想一想級聯的應用場景?
- 使用昨天的知識,建立一個期中考試張三、李四的成績表df
- 假設新增考試學科"計算機",如何實現?
- 新增王老五同學的成績,如何實現?
============================================
import numpy as np
data = np.random.randint(0,150,size=(2,3))
index = ["張三","李四"]
columns = ["語文","數學","外語"]
df1 = DataFrame(data,index,columns)
df1
語文 | 數學 | 外語 | |
---|---|---|---|
張三 | 112 | 12 | 72 |
李四 | 123 | 79 | 2 |
data = np.random.randint(0,150,size=(2,1))
index = ["張三","李四"]
columns = ["計算機"]
df2 = DataFrame(data,index,columns)
df2
計算機 | |
---|---|
張三 | 110 |
李四 | 37 |
df5 = pd.concat((df1,df2),axis=1,sort=False)
df5
語文 | 數學 | 外語 | 計算機 | |
---|---|---|---|---|
張三 | 112 | 12 | 72 | 110 |
李四 | 123 | 79 | 2 | 37 |
data = np.random.randint(0,150,size=(1,4))
index = ["王老五"]
columns = ["語文","數學","外語","計算機"]
df3 = DataFrame(data,index,columns)
df3
語文 | 數學 | 外語 | 計算機 | |
---|---|---|---|---|
王老五 | 120 | 115 | 140 | 11 |
pd.concat((df1,df3),sort=False)
語文 | 數學 | 外語 | 計算機 | |
---|---|---|---|---|
張三 | 112 | 12 | 72 | NaN |
李四 | 123 | 79 | 2 | NaN |
王老五 | 120 | 115 | 140 | 11.0 |
3) 使用append()函式新增
由於在後面級聯的使用非常普遍,因此有一個函式append專門用於在後面新增
df5.append(df3)
語文 | 數學 | 外語 | 計算機 | |
---|---|---|---|---|
張三 | 112 | 12 | 72 | 110 |
李四 | 123 | 79 | 2 | 37 |
王老五 | 120 | 115 | 140 | 11 |
============================================
練習15:
新建一個只有張三李四王老五的期末考試成績單ddd3,使用append()與期中考試成績表ddd級聯
============================================
2. 使用pd.merge()合併
merge與concat的區別在於,merge需要依據某一共同的行或列來進行合併
使用pd.merge()合併時,會自動根據兩者相同column名稱的那一列,作為key來進行合併。
注意每一列元素的順序不要求一致
1) 一對一合併
2) 多對一合併
3) 多對多合併
table1 = pd.read_excel("./關係表.xls",sheet_name=0)
table2 = pd.read_excel("./關係表.xls",sheet_name=1)
table3 = pd.read_excel("./關係表.xls",sheet_name=2)
table4 = pd.read_excel("./關係表.xls",sheet_name=3)
table5 = pd.read_excel("./關係表.xls",sheet_name=4)
display(table1,table2)
手機型號 | 參考價格 | |
---|---|---|
0 | windowsPhone | 2500 |
1 | iPhone | 7500 |
2 | Android | 4000 |
手機型號 | 重量 | |
---|---|---|
0 | windowsPhone | 0.50 |
1 | iPhone | 0.40 |
2 | Android | 0.45 |
3 | other | 0.60 |
一一對應的表格 通過merge融合 把資料對應上就可以了
pd.merge(table1,table2)
手機型號 | 參考價格 | 重量 | |
---|---|---|---|
0 | windowsPhone | 2500 | 0.50 |
1 | iPhone | 7500 | 0.40 |
2 | Android | 4000 | 0.45 |
how指的是如何拼接 預設是inner 內聯 (兩個表格都有的專案才留下){‘left’, ‘right’, ‘outer’, ‘inner’}, default ‘inner’
pd.merge(table1,table2,how="inner") # inner 是取交集 兩個都有的專案才出現
手機型號 | 參考價格 | 重量 | |
---|---|---|---|
0 | windowsPhone | 2500 | 0.50 |
1 | iPhone | 7500 | 0.40 |
2 | Android | 4000 | 0.45 |
pd.merge(table1,table2,how="outer") # outer 是取並集 任何一個表格裡出現的專案都會出現
手機型號 | 參考價格 | 重量 | |
---|---|---|---|
0 | windowsPhone | 2500.0 | 0.50 |
1 | iPhone | 7500.0 | 0.40 |
2 | Android | 4000.0 | 0.45 |
3 | other | NaN | 0.60 |
pd.merge(table1,table2,how="left") # 左邊的表格有多少專案 這裡就有多少專案
手機型號 | 參考價格 | 重量 | |
---|---|---|---|
0 | windowsPhone | 2500 | 0.50 |
1 | iPhone | 7500 | 0.40 |
2 | Android | 4000 | 0.45 |
pd.merge(table1,table2,how="right") # 右邊的表格有多少專案 這裡就有多少專案
手機型號 | 參考價格 | 重量 | |
---|---|---|---|
0 | windowsPhone | 2500.0 | 0.50 |
1 | iPhone | 7500.0 | 0.40 |
2 | Android | 4000.0 | 0.45 |
3 | other | NaN | 0.60 |
一對多
display(table2,table3)
手機型號 | 重量 | |
---|---|---|
0 | windowsPhone | 0.50 |
1 | iPhone | 0.40 |
2 | Android | 0.45 |
3 | other | 0.60 |
經銷商 | 發貨地區 | 手機型號 | |
---|---|---|---|
0 | pegge | beijing | iPhone |
1 | lucy | beijing | Android |
2 | tom | guangzhou | iPhone |
3 | petter | shenzhen | windowsPhone |
4 | mery | guangzhou | Android |
一對多 的 表格拼接 兩個表格需要有一個相同的column 然後在把1*多個 計算出新的行
pd.merge(table2,table3,how="inner")
手機型號 | 重量 | 經銷商 | 發貨地區 | |
---|---|---|---|---|
0 | windowsPhone | 0.50 | petter | shenzhen |
1 | iPhone | 0.40 | pegge | beijing |
2 | iPhone | 0.40 | tom | guangzhou |
3 | Android | 0.45 | lucy | beijing |
4 | Android | 0.45 | mery | guangzhou |
pd.merge(table2,table3,how="outer")
手機型號 | 重量 | 經銷商 | 發貨地區 | |
---|---|---|---|---|
0 | windowsPhone | 0.50 | petter | shenzhen |
1 | iPhone | 0.40 | pegge | beijing |
2 | iPhone | 0.40 | tom | guangzhou |
3 | Android | 0.45 | lucy | beijing |
4 | Android | 0.45 | mery | guangzhou |
5 | other | 0.60 | NaN | NaN |
pd.merge(table2,table3,how="left")
手機型號 | 重量 | 經銷商 | 發貨地區 | |
---|---|---|---|---|
0 | windowsPhone | 0.50 | petter | shenzhen |
1 | iPhone | 0.40 | pegge | beijing |
2 | iPhone | 0.40 | tom | guangzhou |
3 | Android | 0.45 | lucy | beijing |
4 | Android | 0.45 | mery | guangzhou |
5 | other | 0.60 | NaN | NaN |
pd.merge(table2,table3,how="right")
手機型號 | 重量 | 經銷商 | 發貨地區 | |
---|---|---|---|---|
0 | windowsPhone | 0.50 | petter | shenzhen |
1 | iPhone | 0.40 | pegge | beijing |
2 | iPhone | 0.40 | tom | guangzhou |
3 | Android | 0.45 | lucy | beijing |
4 | Android | 0.45 | mery | guangzhou |
多對多
display(table3,table4)
經銷商 | 發貨地區 | 手機型號 | |
---|---|---|---|
0 | pegge | beijing | iPhone |
1 | lucy | beijing | Android |
2 | tom | guangzhou | iPhone |
3 | petter | shenzhen | windowsPhone |
4 | mery | guangzhou | Android |
發貨地區 | 手機型號 | 價格 | |
---|---|---|---|
0 | beijing | iPhone | 7000 |
1 | beijing | windowsPhone | 2300 |
2 | beijing | Android | 3600 |
3 | guangzhou | iPhone | 7600 |
4 | guangzhou | windowsPhone | 2800 |
5 | guangzhou | Android | 4200 |
6 | shenzhen | iPhone | 7400 |
7 | shenzhen | windowsPhone | 2750 |
8 | shenzhen | Android | 3900 |
方式1 我們通過on 指定 按照哪一列進行拼接 然後可以通過suffixes指定重複的列的字尾
pd.merge(table3,table4,on="手機型號",suffixes=["_1","_2"])
經銷商 | 發貨地區_1 | 手機型號 | 發貨地區_2 | 價格 | |
---|---|---|---|---|---|
0 | pegge | beijing | iPhone | beijing | 7000 |
1 | pegge | beijing | iPhone | guangzhou | 7600 |
2 | pegge | beijing | iPhone | shenzhen | 7400 |
3 | tom | guangzhou | iPhone | beijing | 7000 |
4 | tom | guangzhou | iPhone | guangzhou | 7600 |
5 | tom | guangzhou | iPhone | shenzhen | 7400 |
6 | lucy | beijing | Android | beijing | 3600 |
7 | lucy | beijing | Android | guangzhou | 4200 |
8 | lucy | beijing | Android | shenzhen | 3900 |
9 | mery | guangzhou | Android | beijing | 3600 |
10 | mery | guangzhou | Android | guangzhou | 4200 |
11 | mery | guangzhou | Android | shenzhen | 3900 |
12 | petter | shenzhen | windowsPhone | beijing | 2300 |
13 | petter | shenzhen | windowsPhone | guangzhou | 2800 |
14 | petter | shenzhen | windowsPhone | shenzhen | 2750 |
第二種方式 指定兩個相同的列 這兩列中的專案必須都對應上才會顯示到新的表格中
pd.merge(table3,table4,on=["手機型號","發貨地區"])
經銷商 | 發貨地區 | 手機型號 | 價格 | |
---|---|---|---|---|
0 | pegge | beijing | iPhone | 7000 |
1 | lucy | beijing | Android | 3600 |
2 | tom | guangzhou | iPhone | 7600 |
3 | petter | shenzhen | windowsPhone | 2750 |
4 | mery | guangzhou | Android | 4200 |
display(table4,table5)
發貨地區 | 手機型號 | 價格 | |
---|---|---|---|
0 | beijing | iPhone | 7000 |
1 | beijing | windowsPhone | 2300 |
2 | beijing | Android | 3600 |
3 | guangzhou | iPhone | 7600 |
4 | guangzhou | windowsPhone | 2800 |
5 | guangzhou | Android | 4200 |
6 | shenzhen | iPhone | 7400 |
7 | shenzhen | windowsPhone | 2750 |
8 | shenzhen | Android | 3900 |
型號 | 價格 | |
---|---|---|
0 | iPhone | 7000 |
1 | windowsPhone | 2300 |
2 | Android | 3600 |
3 | iPhone | 7600 |
4 | windowsPhone | 2800 |
5 | Android | 4200 |
6 | iPhone | 7400 |
7 | windowsPhone | 2750 |
8 | Android | 3900 |
4) key的規範化
-
使用on=顯式指定哪一列為key,當有多個key相同時使用
-
使用left_on和right_on指定左右兩邊的列作為key,當左右兩邊的key都不想等時使用
============================================
練習16:
- 假設有兩份成績單,除了ddd是張三李四王老五之外,還有ddd4是張三和趙小六的成績單,如何合併?
- 如果ddd4中張三的名字被打錯了,成為了張十三,怎麼辦?
- 自行練習多對一,多對多的情況
- 自學left_index,right_index
============================================
5) 內合併與外合併
-
內合併:只保留兩者都有的key(預設模式)
-
外合併 how=‘outer’:補NaN
-
左合併、右合併:how=‘left’,how=‘right’,
============================================
練習17:
- 考慮應用情景,使用多種方式合併ddd與ddd4
============================================
6) 列衝突的解決
當列衝突時,即有多個列名稱相同時,需要使用on=來指定哪一個列作為key,配合suffixes指定衝突列名
可以使用suffixes=自己指定字尾
============================================
練習18:
假設有兩個同學都叫李四,ddd5、ddd6都是張三和李四的成績表,如何合併?
============================================
知識補充
s1 = Series(["A","B","C","B"])
s1
結果為:
0 A
1 B
2 C
3 B
dtype: object
s1.unique() #去重
結果為:
array(['A', 'B', 'C'], dtype=object)
作業
3. 案例分析:美國各州人口資料分析
首先匯入檔案,並檢視資料樣本
df_abbr = pd.read_csv("./data/state-abbrevs.csv") # csv檔案的資料匯入後 會變成DataFrame供我們使用
df_areas = pd.read_csv("./data/state-areas.csv")
df_pop = pd.read_csv("./data/state-population.csv")
合併popu與abbrevs兩個DataFrame,分別依據state/region列和abbreviation列來合併。為了保留所有資訊,使用外合併。
df_pop2 = pd.merge(df_abbr,df_pop,left_on="abbreviation",right_on="state/region",how="outer")
去除abbreviation的那一列(axis=1)
df_pop3 = df_pop2.drop(labels="abbreviation",axis=1)
df_pop3
檢視存在缺失資料的列。使用.isnull().any(),只有某一列存在一個缺失資料,就會顯示True。
df_pop3.isnull().any() # isnull()有空值是True 沒有空值是False any()只要有True就是True 合在一起使用 就是 這一列中 只要有空值就是true
state True
state/region False
ages False
year False
population True
dtype: bool
找到有哪些state/region使得state的值為NaN,使用unique()檢視非重複值
df_pop3["state"].isnull() #這是一個序列 有值是False 沒有值是True
df_pop3[df_pop3["state"].isnull()] #DataFrame 後面 的中括號 中可以傳入 序列 如果序列中是布林值 False這這一項不取 True就取出這一項
df_pop3[df_pop3["state"].isnull()]["state/region"].unique()
結果為:
array(['PR', 'USA'], dtype=object)
為找到的這些state/region的state項補上正確的值,從而去除掉state這一列的所有NaN!記住這樣清除缺失資料NaN的方法!
df_pop3[df_pop3["state"].isnull()]["state"] = "Puerto Rico" # 為了安全不能直接設定值
temp = df_pop3[df_pop3["state"].isnull()].copy()
temp["state"] = "Puerto Rico"
df_pop3[df_pop3["state"].isnull()] = temp # 不能直接操作值 但是可以把DataFrame賦值給DataFrame
temp2 = df_pop3[df_pop3["state/region"]=="USA"].copy()
temp2["state"]="United States"
df_pop3[df_pop3["state/region"]=="USA"] = temp2
df_pop3.isnull().any()
結果為:
state False
state/region False
ages False
year False
population True
dtype: bool
df_pop4 = df_pop3.dropna() #清除人口中為空的資料
df_pop4.isnull().any()
結果為:
state False
state/region False
ages False
year False
population False
dtype: bool
合併各州人口資料和麵積資料areas,使用外合併。
思考一下為什麼使用外合併?
df_pop_area = pd.merge(df_pop4,df_areas,how="outer")
df_pop_area.dropna() #清除面積為空的資料
找出2010年的全民人口資料,df.query(查詢語句)
df_2010 = df_pop_area.query("year==2010 & ages=='total'")
df_2010.dropna()
對查詢結果進行處理,以state列作為新的行索引:set_index
df_2010.set_index("state")
計算人口密度。注意是Series/Series,其結果還是一個Series。
df_2010 = df_2010.dropna()
dens = df_2010["population"]/df_2010["area (sq. mi)"]
排序,並找出人口密度最高的五個州sort_values()的密度
dens.sort_values().tail(5)
找出人口密度最低的五個州的密度
dens.sort_values().head()
要點總結:
- 統一用loc()索引
- 善於使用.isnull().any()找到存在NaN的列
- 善於使用.unique()確定該列中哪些key是我們需要的
- 一般使用外合併、左合併,目的只有一個:寧願該列是NaN也不要丟棄其他列的資訊