1. 程式人生 > >第3章 Pandas資料處理(3.7-3.8)_Python資料科學手冊學習筆記

第3章 Pandas資料處理(3.7-3.8)_Python資料科學手冊學習筆記

3.7 合併資料集: Concat與Append操作

- pd.concat
- pd.merge
- pd.join
import pandas as pd
def make_df(cols,ind):
    data = {c: [str(c) + str(i) for i in ind]
           for c in cols}
    return pd.DataFrame(data,ind)     # 用字典建立DataFrame
make_df('ABC',range(3))
A B C
0 A0 B0 C0
1 A1 B1 C1
2 A2 B2 C2

3.7.1 知識回顧: NumPy陣列的合併

用np.concatenate完成
- 第一個引數表示要合併的陣列或元組, 第二個引數表示合併的座標軸方向

import pandas as pd
import numpy as np
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])
x = [[1,2],
    [3,4]]
np.concatenate([x,x], axis=1)
array([[1, 2, 1, 2],
       [3, 4, 3, 4]])

3.7.2 通過pd.concat實現簡易合併

- Pandas中的pd.concat()函式與np.concatenate()類似, 配置引數更多,功能更強大.

pd.concat()可以簡單的合併一維的Series和DataFrame物件

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
ser1 = pd.Series(['a','b','c'],index=[1,2,3])
ser2 = pd.Series(['d','e','f'],index=[4,5,6])
ser1 + ser2
1    NaN
2    NaN
3    NaN
4    NaN
5    NaN
6    NaN
dtype: object
ser1 = pd.Series(['a','b','c'],index=[1,2,3])
ser2 = pd.Series(['d','e','f'],index=[1,2,3])
ser1 + ser2
1    ad
2    be
3    cf
dtype: object
df1 = make_df('AB',[1,2])
df2 = make_df('AB',[3,4])
print(df1);print(df2);print(pd.concat([df1,df2]))
    A   B
1  A1  B1
2  A2  B2
    A   B
3  A3  B3
4  A4  B4
    A   B
1  A1  B1
2  A2  B2
3  A3  B3
4  A4  B4
df1 = make_df('AB',[1,2])
df2 = make_df('BD',[3,4])
print(df1);print(df2);print(pd.concat([df1,df2]))
    A   B
1  A1  B1
2  A2  B2
    B   D
3  B3  D3
4  B4  D4
     A   B    D
1   A1  B1  NaN
2   A2  B2  NaN
3  NaN  B3   D3
4  NaN  B4   D4


D:\Anaconda3\lib\site-packages\ipykernel_launcher.py:3: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.

To accept the future behavior, pass 'sort=True'.

To retain the current behavior and silence the warning, pass sort=False

  This is separate from the ipykernel package so we can avoid doing imports until

索引重疊

np.concatenate與pd.concat最主要的差異是,Pandas在合併時會保留索引, 即使索引是重複的.

x = make_df('AB',[0,1])
y = make_df('AB',[2,3])
y.index = x.index
print(x);print(y);print(pd.concat([x,y]))
    A   B
0  A0  B0
1  A1  B1
    A   B
0  A2  B2
1  A3  B3
    A   B
0  A0  B0
1  A1  B1
0  A2  B2
1  A3  B3

捕捉索引重複的錯誤: 可以設定verify_integrity引數. 將引數設定為true. 合併時若有重複就會觸發異常.

try:
    pd.concat([x,y],verify_integrity=True)
except ValueError as e:
    print('ValueError:',e)
ValueError: Indexes have overlapping values: Int64Index([0, 1], dtype='int64')

忽略索引: 設定ignore_index引數, 設定為True是會建立一個新的整數索引

pd.concat([x,y],ignore_index=True)
A B
0 A0 B0
1 A1 B1
2 A2 B2
3 A3 B3

增加多級索引: 通過keys引數為資料來源設定多級索引

print(x);print(y);print(pd.concat([x,y],keys=['x','y']))
    A   B
0  A0  B0
1  A1  B1
    A   B
0  A2  B2
1  A3  B3
      A   B
x 0  A0  B0
  1  A1  B1
y 0  A2  B2
  1  A3  B3

類似join的合併

df5 = make_df('ABC',[1,2])
df6 = make_df('BCD',[3,4])
print(df5);print(df6);print(pd.concat([df5,df6]))
    A   B   C
1  A1  B1  C1
2  A2  B2  C2
    B   C   D
3  B3  C3  D3
4  B4  C4  D4
     A   B   C    D
1   A1  B1  C1  NaN
2   A2  B2  C2  NaN
3  NaN  B3  C3   D3
4  NaN  B4  C4   D4


D:\Anaconda3\lib\site-packages\ipykernel_launcher.py:3: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.

To accept the future behavior, pass 'sort=True'.

To retain the current behavior and silence the warning, pass sort=False

  This is separate from the ipykernel package so we can avoid doing imports until

設定join引數為inner或者outer

print(df5);print(df6);print(pd.concat([df5,df6],join='inner'))
    A   B   C
1  A1  B1  C1
2  A2  B2  C2
    B   C   D
3  B3  C3  D3
4  B4  C4  D4
    B   C
1  B1  C1
2  B2  C2
3  B3  C3
4  B4  C4

直接確定結果使用的列

print(df5);print(df6);print(pd.concat([df5,df6],join_axes=[df5.columns]))
    A   B   C
1  A1  B1  C1
2  A2  B2  C2
    B   C   D
3  B3  C3  D3
4  B4  C4  D4
     A   B   C
1   A1  B1  C1
2   A2  B2  C2
3  NaN  B3  C3
4  NaN  B4  C4
print(df1);print(df2);print(df1.append(df2))
    A   B
1  A1  B1
2  A2  B2
    A   B
3  A3  B3
4  A4  B4
    A   B
1  A1  B1
2  A2  B2
3  A3  B3
4  A4  B4

3.8 合併資料集: 合併與連線

- Pandas的主要介面是pd.merge()函式

3.8.1 關係代數

pd.merge()實現的功能基於關係代數的一部分. 關係代數式處理關係型資料的通用理論,絕大部分資料庫的可用操作都以此為理論基礎. 關係代數方法論的強大之處在於, 它提出的若干簡單的操作規則經過組合就可以為任意資料構建十分複雜的操作.
Pandas在pd.merge()函式與Series和DataFrame的join()方法裡實現了這些基本操作規則.

3.8.2 資料連線的型別

pd.merge()函式實現三種資料連線的型別: 一對一,多對一和多對多.

** 一對一連線 **

import pandas as pd
df1 = pd.DataFrame({'僱員':['張三','李四','王五','趙六'],'部門':['生產','技術','技術','人力']})   # 需要加大括號
df2 = pd.DataFrame({'僱員':['趙六','李四','張三','王五'],'入職年份':[2010,2011,2012,2011]})
print(df1);print(df2)
   僱員  部門
0  張三  生產
1  李四  技術
2  王五  技術
3  趙六  人力
   僱員  入職年份
0  趙六  2010
1  李四  2011
2  張三  2012
3  王五  2011
df3 = pd.merge(df1,df2)
print(df3)
   僱員  部門  入職年份
0  張三  生產  2012
1  李四  技術  2011
2  王五  技術  2011
3  趙六  人力  2010

pd.merge()方法會發現兩個DataFrame都有’僱員’列, 並自動以這列為鍵進行連線. 兩個輸入的合併結果是一個新的DataFrame. 共同列的位置可以不一致.
pd.merge()會預設丟棄原來的行索引, 不過也可以自定義.

多對一連線 能否也叫一對多連線

需要連線的兩個列中, 有一列的值有重複. 通過多對一連接獲得的結果DataFrame將會保留重複值.

df4 = pd.DataFrame({'部門':['生產','技術','人力'],'負責人':['生產經理','技術經理','人力經理']})
print(df3);print(df4);print(pd.merge(df3,df4))
   僱員  部門  入職年份
0  張三  生產  2012
1  李四  技術  2011
2  王五  技術  2011
3  趙六  人力  2010
   部門   負責人
0  生產  生產經理
1  技術  技術經理
2  人力  人力經理
   僱員  部門  入職年份   負責人
0  張三  生產  2012  生產經理
1  李四  技術  2011  技術經理
2  王五  技術  2011  技術經理
3  趙六  人力  2010  人力經理

多對多連線

df5 = pd.DataFrame({'部門':['生產','生產','技術','技術','人力','人力'],'子部門':['生產1','生產2','技術1','技術2','人力1','人力2']})
print(df1);print(df5);print(pd.merge(df1,df5))
   僱員  部門
0  張三  生產
1  李四  技術
2  王五  技術
3  趙六  人力
   部門  子部門
0  生產  生產1
1  生產  生產2
2  技術  技術1
3  技術  技術2
4  人力  人力1
5  人力  人力2
   僱員  部門  子部門
0  張三  生產  生產1
1  張三  生產  生產2
2  李四  技術  技術1
3  李四  技術  技術2
4  王五  技術  技術1
5  王五  技術  技術2
6  趙六  人力  人力1
7  趙六  人力  人力2

3.8.3 設定資料合併的鍵

pd.merge()的預設行為, 它會將兩個輸入的一個或多個共同列作為鍵進行合併. 但由於兩個輸入要合併的列通常都不是同名的, 因此pd.merge()提供了一些引數處理這個問題.

引數on的用法
- 這個引數只能在兩個DataFrame有共同列名的時候才可以使用.

print(df1);print(df2);print(pd.merge(df1,df2,on='僱員'))
   僱員  部門
0  張三  生產
1  李四  技術
2  王五  技術
3  趙六  人力
   僱員  入職年份
0  趙六  2010
1  李四  2011
2  張三  2012
3  王五  2011
   僱員  部門  入職年份
0  張三  生產  2012
1  李四  技術  2011
2  王五  技術  2011
3  趙六  人力  2010

left_on與right_on引數
- 有時候也需要合併兩個列名不同的資料集.

df3 = pd.DataFrame({'姓名':['張三','李四','王五','趙六'],'工資':[5000,6000,7000,5500]})
print(df1);print(df2);print(pd.merge(df1,df3,left_on='僱員',right_on='姓名'))
   僱員  部門
0  張三  生產
1  李四  技術
2  王五  技術
3  趙六  人力
   僱員  入職年份
0  趙六  2010
1  李四  2011
2  張三  2012
3  王五  2011
   僱員  部門  姓名    工資
0  張三  生產  張三  5000
1  李四  技術  李四  6000
2  王五  技術  王五  7000
3  趙六  人力  趙六  5500

彙總的結果會多一列, 可以用drop()方法去掉.

pd.merge(df1,df3,left_on='僱員',right_on='姓名').drop('姓名',axis=1)
僱員 部門 工資
0 張三 生產 5000
1 李四 技術 6000
2 王五 技術 7000
3 趙六 人力 5500

left_index與right_index引數
- 除了合併列之外, 還可以合併索引.

df1a = df1.set_index('僱員')
df2a = df2.set_index('僱員')
print(df1a);print(df2a)
    部門
僱員    
張三  生產
李四  技術
王五  技術
趙六  人力
    入職年份
僱員      
趙六  2010
李四  2011
張三  2012
王五  2011

可以通過設定pd.merge()中的left_index或right_index引數將索引設定為鍵來實現合併.

print(df1a);print(df2a);
print(pd.merge(df1a,df2a,left_index=True,right_index=True))  # 是True , 不是 = 索引的名稱
    部門
僱員    
張三  生產
李四  技術
王五  技術
趙六  人力
    入職年份
僱員      
趙六  2010
李四  2011
張三  2012
王五  2011
    部門  入職年份
僱員          
張三  生產  2012
李四  技術  2011
王五  技術  2011
趙六  人力  2010

為了方便考慮, DataFrame實現了join()方法, 它可以按照索引進行資料合併.

print(df1a);print(df2a);print(df1a.join(df2a))
    部門
僱員    
張三  生產
李四  技術
王五  技術
趙六  人力
    入職年份
僱員      
趙六  2010
李四  2011
張三  2012
王五  2011
    部門  入職年份
僱員          
張三  生產  2012
李四  技術  2011
王五  技術  2011
趙六  人力  2010

如果想將索引與列混合使用, 可以使用left_index與right_on, 或者結合left_on與right_index

print(df1a);print(df3)
print(pd.merge(df1a,df3,left_index=True,right_on='姓名'))    # 多個索引怎麼辦
    部門
僱員    
張三  生產
李四  技術
王五  技術
趙六  人力
   姓名    工資
0  張三  5000
1  李四  6000
2  王五  7000
3  趙六  5500
   部門  姓名    工資
0  生產  張三  5000
1  技術  李四  6000
2  技術  王五  7000
3  人力  趙六  5500

3.8.4 設定資料連線的集合操作規則

df6 = pd.DataFrame({'姓名':['張三','李四','王五','趙六'],
                   '食物':['魚','西瓜','冬瓜','大頭菜']})
df7 = pd.DataFrame({'姓名':['張三','老李'],
                   '喝酒':['白酒','紅酒']})
print(df6);print(df7);print(pd.merge(df6,df7))
   姓名   食物
0  張三    魚
1  李四   西瓜
2  王五   冬瓜
3  趙六  大頭菜
   姓名  喝酒
0  張三  白酒
1  老李  紅酒
   姓名 食物  喝酒
0  張三  魚  白酒

和別的兩個資料集, 在’姓名’列中只有一個共同的值’張三’. 預設情況下, 結果中只出現會包含兩個輸入集合的交集(inner join). 這種連線方式被稱為內連線. 我們可以通過how引數設定連線方式,預設為’inner’

pd.merge(df6,df7,how='inner')
姓名 食物 喝酒
0 張三 白酒

how引數支援的連線方式: outer, left,inner 和right.
- outer(外連線)返回兩個輸入列的並集, 所有缺失值都用NaN填充.

print(df6);print(df7);print(pd.merge(df6,df7,how='outer'))
   姓名   食物
0  張三    魚
1  李四   西瓜
2  王五   冬瓜
3  趙六  大頭菜
   姓名  喝酒
0  張三  白酒
1  老李  紅酒
   姓名   食物   喝酒
0  張三    魚   白酒
1  李四   西瓜  NaN
2  王五   冬瓜  NaN
3  趙六  大頭菜  NaN
4  老李  NaN   紅酒

left join(左連線), right join(右連線)返回的結果分別只包括左列和右列

print(df6);print(df7);print(pd.merge(df6,df7,how='left'))
   姓名   食物
0  張三    魚
1  李四   西瓜
2  王五   冬瓜
3  趙六  大頭菜
   姓名  喝酒
0  張三  白酒
1  老李  紅酒
   姓名   食物   喝酒
0  張三    魚   白酒
1  李四   西瓜  NaN
2  王五   冬瓜  NaN
3  趙六  大頭菜  NaN

3.8.5 重複列名: suffixes引數

- 可能會遇到兩個輸入DataFrame有重名列的情況.
df8 = pd.DataFrame({'name':['bob','jake','lisa','sue'],
                   'rank':[1,2,3,4]})
df9 = pd.DataFrame({'name':['bob','jake','lisa','sue'],
                   'rank':[3,1,4,2]})
print(df8);print(df9);print(pd.merge(df8,df9,on='name'))
   name  rank
0   bob     1
1  jake     2
2  lisa     3
3   sue     4
   name  rank
0   bob     3
1  jake     1
2  lisa     4
3   sue     2
   name  rank_x  rank_y
0   bob       1       3
1  jake       2       1
2  lisa       3       4
3   sue       4       2

當有兩個重複的列名, pd.merge()函式會自動為它們增加字尾_x _y.
也可以通過suffixes引數自定義字尾

pd.merge(df8,df9,on='name',suffixes=['_l','_r'])
name rank_l rank_r
0 bob 1 3
1 jake 2 1
2 lisa 3 4
3 sue 4 2

3.8.6 案例: 美國各州的統計資料

# 檔案路徑不能有中文
pop = pd.read_csv(r'E:\Python\state-population.csv')
areas = pd.read_csv(r'E:\Python\state-abbrevs.csv')
abbrevs = pd.read_csv(r'E:\Python\state-areas.csv')
print(pop.head());print(areas.head());print(abbrevs.head())
  state/region     ages  year  population
0           AL  under18  2012   1117489.0
1           AL    total  2012   4817528.0
2           AL  under18  2010   1130966.0
3           AL    total  2010   4785570.0
4           AL  under18  2011   1125763.0
        state abbreviation
0     Alabama           AL
1      Alaska           AK
2     Arizona           AZ
3    Arkansas           AR
4  California           CA
        state  area (sq. mi)
0     Alabama          52423
1      Alaska         656425
2     Arizona         114006
3    Arkansas          53182
4  California         163707
merged = pd.merge(pop,areas,how='outer',left_on='state/region',right_on='abbreviation')
merged = merged.drop('abbreviation',1)   # 引數1不能少
print(merged.head())
  state/region     ages  year  population    state
0           AL  under18  2012   1117489.0  Alabama
1           AL    total  2012   4817528.0  Alabama
2           AL  under18  2010   1130966.0  Alabama
3           AL    total  2010   4785570.0  Alabama
4           AL  under18  2011   1125763.0  Alabama

檢查每個欄位是否有缺失值

merged.isnull().any()
state/region    False
ages            False
year            False
population       True
state            True
dtype: bool

部分population是缺失值, 檢視是哪些資料缺失值

merged[merged['population'].isnull()].head()
state/region ages year population state
2448 PR under18 1990 NaN NaN
2449 PR total 1990 NaN NaN
2450 PR total 1991 NaN NaN
2451 PR under18 1991 NaN NaN
2452 PR total 1993 NaN NaN
merged.loc[merged['state'].isnull(),'state/region'].unique()
array(['PR', 'USA'], dtype=object)
merged.loc[merged['state/region'] == 'PR','state'] = 'Puerto Rico'
merged.loc[merged['state/region'] == 'USA','state'] = 'Uinted States'
merged.isnull().any()
state/region    False
ages            False
year            False
population       True
state           False
dtype: bool
final = pd.merge(merged,abbrevs,on='state',how='left')
final.head()