1. 程式人生 > >pandas系列學習(五):資料連線

pandas系列學習(五):資料連線

作者:chen_h 微訊號 & QQ:862251340 微信公眾號:coderpai

利用 Python 處理任何實際的資料時,你就需要將 pandas DataFrame 合併或者連結在一起來分析資料集,但是這個過程還是非常花費時間的,大約是 10 分鐘。合併(merge)和連線(join)資料框 是任何有抱負的資料分析師需要掌握的核心過程。這篇文章介紹了合併和連線資料集的過程,即根據它們之間的公共列將兩個資料集連線在一起。主要分為這些主題:

  • 什麼是兩個資料幀的合併和連線?
  • 什麼是內合併,外合併,左合併,右合併?
  • 如何合併具有不同公共列名稱的兩個資料幀?(left_on 和 right_on 語法)

在這裡插入圖片描述

示例資料

對於這篇文章,我從 KillBiller 應用程式和一些別的地方下載的資料,這些資料包含在三個 CSV 檔案中:

  • user_usage.csv: 第一個資料集包含使用者每月移動使用情況統計資訊;
  • user_device.csv: 第二個資料集包含系統的單獨 “使用” 的詳細資訊,包括日期和裝置資訊;
  • android_devices.csv: 第三個資料集包含裝置和製造商資料,其中列出了從Google獲取的所有Android裝置及其型號程式碼;

我們可以使用 pandas read_csv() 命令將這些 csv 檔案作為 pandas DataFrame 載入到 pandas中,並使用 DataFrame head() 命令檢查內容。

user_usage.head()
outgoing_mins_per_month outgoing_sms_per_month monthly_mb use_id
0 21.97 4.82 1557.33 22787
1 1710.08 136.88 7267.55 22788
2 1710.08 136.88 7267.55 22789
3 94.46 35.17 519.12 22790
4 71.59 79.26 1557.33 22792

來自 KillBiller 應用程式的示例使用資訊,顯示一部分使用者的每月移動使用情況統計資訊。

user_device.
head()
use_id user_id platform platform_version device use_type_id
0 22782 26980 ios 10.2 iPhone7,2 2
1 22783 29628 android 6.0 Nexus 5 3
2 22784 28473 android 5.1 SM-G903F 1
3 22785 15200 ios 10.2 iPhone7,2 3
4 22786 28239 android 6.0 ONE E1003 1

來自 KillBiller 應用程式的使用者資訊,為 KillBiller 應用程式的各個 “用途” 提供裝置和作業系統版本。

devices.head(10)
Retail Branding Marketing Name Device Model
0 NaN NaN AD681H Smartfren Andromax AD681H
1 NaN NaN FJL21 FJL21
2 NaN NaN T31 Panasonic T31
3 NaN NaN hws7721g MediaPad 7 Youth 2
4 3Q OC1020A OC1020A OC1020A
5 7Eleven IN265 IN265 IN265
6 A.O.I. ELECTRONICS FACTORY A.O.I. TR10CS1_11 TR10CS1
7 AG Mobile AG BOOST 2 BOOST2 E4010
8 AG Mobile AG Flair AG_Flair Flair
9 AG Mobile AG Go Tab Access 2 AG_Go_Tab_Access_2 AG_Go_Tab_Access_2

Android 裝置資料,包含所有具有製造商和型號詳細資訊的 Android 裝置。

注意重要的樣本資料集之間存在連線屬性:user_usage 和 user_device 之間共享 use_id ,而 user_device 的 device 列和裝置資料集的 Model 列包含公共程式碼。

舉個例子

我們想知道不同裝置之間使用者的使用模式是否不同。例如,使用三星裝置的使用者使用通話時間是否比使用 LG 裝置的使用者多?考慮到這些資料集中的樣本量較小,這是一個demo問題,但它是需要合併的完美示例。

我們希望形成一個單獨的資料框,其中包含使用者使用數字(每月呼叫,每月簡訊等)以及包含裝置資訊(模型,製造商等)的列。我們需要將我們的樣本資料集進行合併(或者連線)到一個單獨的資料集中進行分析。

合併 DataFrame

合併兩個資料集是將兩個資料集合併為一個數據集,並根據公共屬性或者列對齊每個資料集的行的過程。

合併和連線著兩個詞在 pandas 和其他語言中相對可互換,即 SQL 和 R 。在 pandas 中,有單獨的 merge 和 join 函式,兩者都做類似的事情。

在這個場景中,我們需要執行兩個步驟:

  1. 對於 user_usage 資料集中的每一行——建立一個包含 user_device 資料幀中的 device 程式碼的心裂。即對於第一行,use_id 為 22787,因此我們轉到 user_devices 資料集,找到 use_id 22787,並複製 device 列中的值;
  2. 完成此操作後,我們將獲取新裝置列,並從裝置資料集中找到相應的“零售品牌”和“模型”;
  3. 最後,我們可以檢視使用的裝置製造商對使用情況進行拆分和分組資料的不同統計資料。

我可以使用 for 迴圈嗎?

是的,你可以為此任務編寫 for 迴圈。第一個迴圈遍歷 user_usage 資料集中的 use_id ,然後在 user_devices 中找到正確的元素。第二個 for 迴圈將為裝置重複此過程。

但是,使用 for 迴圈比使用 pandas 合併功能要慢得多,也更冗長。所以,如果你遇到這種情況——不要使用 for 迴圈。

合併 user_usage 和 user_devices

讓我們看看如何使用 merge 命令將 device 和 platform 列正確新增到 user_usage 資料幀。

result = pd.merge(user_usage,
                 user_device[['use_id', 'platform', 'device']],
                 on='use_id')
result.head()
outgoing_mins_per_month outgoing_sms_per_month monthly_mb use_id platform device
0 21.97 4.82 1557.33 22787 android GT-I9505
1 1710.08 136.88 7267.55 22788 android SM-G930F
2 1710.08 136.88 7267.55 22789 android SM-G930F
3 94.46 35.17 519.12 22790 android D2303
4 71.59 79.26 1557.33 22792 android SM-G361F

以上是基於公共列將 user usage 和 user devices 合併的結果。

從結果中,我們看到這個非常有效,而且很容易實現,那麼 merge 命令做了一些什麼呢?

在這裡插入圖片描述

pandas merge 命令如何工作。至少,合併需要左資料集,右資料集和公共列以合併。

merge 命令是這篇文章的主要學習目標。最簡單的合併操作採用左資料幀(第一個引數),右資料幀(第二個引數),然後是合併列名稱,或合併 “on” 的列。在輸出結果中,左側和右側資料幀中的行匹配,其中 “on” 指定的合併列的公共值。

有了這個結果,我們現在可以繼續從 device 資料集中獲取製造商和型號。但是,首先我們需要了解更多有關合並型別和輸出資料幀大小的資訊。

內部(inner),左側(left)和右側(right)合併型別

在上面的例子中,我們將 user_usage 和 user_devices 進行合併。利用 head() 檢視結果發現非常棒,但是除此之外,我們還能獲得更多的東西。首先,讓我們看看 merge 命令的輸入和輸出的大小:

print("user_usage dimensions: {}".format(user_usage.shape))
print("user_device dimensions: {}".format(user_device[['use_id', 'platform', 'device']].shape))
print("result dimensions: {}".format(result.shape))
user_usage dimensions: (240, 4)
user_device dimensions: (272, 3)
result dimensions: (159, 6)

合併操作後資料集 result 的大小與預期不符合,即它的大小不是兩個資料集大小之和。因為預設情況下, pandas merge() 預設為 inner 合併操作。內部合併(或內部連線)僅保留結果的左側和右側資料幀中的公共值。在上面的示例中,只有包含 user_usage 和 user_device 之間通用的 use_id 值的行扔保留在 result 資料集中。我們可以通過檢視常見的值來驗證這一點:

user_usage['use_id'].isin(user_device['use_id']).value_counts()
True     159
False     81
Name: use_id, dtype: int64

預設情況下,在 pandas 中僅保留左右資料幀之間的公共值,即我們使用了內部合併。

user_usage 中有 159 個 use_id 值出現在 user_device 中,這些值也出現在最終結果資料框 159 行中。

其他合併型別

pandas 中有三種不停型別的合併。這些合併型別在大多數資料庫和麵向資料的語言(SQL,R,SAS)中都很常見,通常稱為 join 操作。如果你對合並操作不是很熟悉,可以看看下面的介紹:

  1. inner merge:預設的 pandas 行為,僅保留左側和右側資料框中存在合併 on 值的行;
  2. left merge:保留左資料框中的每一行。如果右側資料框中存在 on 變數的缺失值,那麼請在結果中新增 NaN 值;
  3. right merge:保留右資料框中的每一行。如果左側資料框中存在 on 變數的缺失值,那麼請在結果中新增 NaN 值;
  4. outer merge:完全外部連線返回左側資料框中的所有行,右側資料框中的所有行,並在可能額情況下匹配行,其他地方使用 NaN 值;

要使用的合併型別是使用 merge 命令中的 how 引數指定的,取值為 left,right,inner(預設值)或者 outer。具體可以看下圖:

在這裡插入圖片描述

左合併例子

讓我們重複我們的合併操作,但這次在 pandas 中執行 “左合併”。

  • 最初,result 資料幀有 159 行,因為我們的左右資料幀之間共有 159 個 use_id 值,預設使用 inner 合併。
  • 對於我們的左合併,我們期望 result 與我們的左資料幀 user_usage (240行)具有相同的行數,除了 159 個行是沒有缺失值的,剩餘 81 行都是有缺失值的;
  • 我們希望 result 與左資料幀具有相同的行數,因為 user_usage 中的每個 use_id 在 user_device 中只出現一次。一對一的對映並非總是如此。在合併操作中,左側資料框中的單行與右側資料框中的多行匹配,將生成多個 result 行。即如果 user_usage 中的 use_id 值在 user_device 資料幀中出現兩次,則在連線 result 中將有兩行用於該 use_id 。

你可以使用 merge 命令的 how 引數將合併更改為 left merge。result 資料框的頂部包含成功匹配的項,而底部包含 user_usage 中的 user_device 中沒有相應 use_id 的行。

result = pd.merge(user_usage,
                 user_device[['use_id', 'platform', 'device']],
                 on='use_id', 
                 how='left')
print("user_usage dimensions: {}".format(user_usage.shape))
print("result dimensions: {}".format(result.shape))
print("There are {} missing values in the result.".format(result['device'].isnull().sum()))
user_usage dimensions: (240, 4)
result dimensions: (240, 6)
There are 81 missing values in the result.
result.head()
outgoing_mins_per_month outgoing_sms_per_month monthly_mb use_id platform device
0 21.97 4.82 1557.33 22787 android GT-I9505
1 1710.08 136.88 7267.55 22788 android SM-G930F
2 1710.08 136.88 7267.55 22789 android SM-G930F
3 94.46 35.17 519.12 22790 android D2303
4 71.59 79.26 1557.33 22792 android SM-G361F
result.tail()
outgoing_mins_per_month outgoing_sms_per_month monthly_mb use_id platform device
235 260.66 68.44 896.96 25008 NaN NaN
236 97.12 36.50 2815.00 25040 NaN NaN
237 355.93 12.37 6828.09 25046 NaN NaN
238 632.06 120.46 1453.16 25058 NaN NaN
239 488.70 906.92 3089.85 25220 NaN NaN

在 pandas 中左連線示例。在 how 命令中指定連線型別。

右連線的例子

例如,我們可以使用右連接合並重復此過程,只需要在 pandas merge 命令中將 how

==left 替換成 how==right。

result = pd.merge(user_usage,
                 user_device[['use_id', 'platform', 'device']],
                 on='use_id', 
                 how='right')

預期結果將於正確的資料框 user_device 具有相同的行數,但是來源於左資料框中的資料就會具有多個空值或者 NaN 值。user_usage 中的 outgoing_mins_per_month,outgoing_sms_per_month 和 monthly_mb 會有空值,但是右側資料框 user_device 中的列中沒有缺失值。

result = pd.merge(user_usage,
                 user_device[['use_id', 'platform', 'device']],
                 on='use_id', 
                 how='right')

print("user_device dimensions: {}".format(user_device.shape))
print("result dimensions: {}".format(result.shape))
print("There are {} missing values in the 'monthly_mb' column in the result.".format(result['monthly_mb'].isnull().sum()))
print("There are {} missing values in the 'platform' column in the result.".format(result['platform'].isnull().sum()))
user_device dimensions: (272, 6)
result dimensions: (272, 6)
There are 113 missing values in the 'monthly_mb' column in the result.
There are 0 missing values in the 'platform' column in the result.

外部合併的例子

最後,我們將使用 pandas 執行外部合併,也稱為 “完全外部連線” 或者 “外部連線”。外連線可以看做是左連線和右連線的組合,或者是內連線的相反。在外部連線中,左側和右側資料框中的每一行都會保留在結果中,其中 NaN 為沒有匹配到的變數。

因此,我們希望 result 具有與 user_device 和 user_usage 之間的 use_id 的不同值相同的行數,即來自左資料幀的每個連線值將與來自右資料幀的每個值一起出現在 result 中。

print("There are {} unique values of use_id in our dataframes.".format(
    pd.concat([user_usage['use_id'], user_device['use_id']]).unique().shape[0]))
result = pd.merge(user_usage,
                 user_device[['use_id', 'platform', 'device']],
                 on='use_id', 
                 how='outer')
print("Outer merge result has {} rows.".format(result.shape))
print("There are {} rows with no missing values.".format((result.apply(lambda x: x.isnull().sum(), axis=1) == 0).sum()))
There are 353 unique values of use_id in our dataframes.
Outer merge result has (353, 6) rows.
There are 159 rows with no missing values.

使用 pandas 的外部合併 result 。左側和右側資料幀中的每一行都保留在 result 中,缺少值為 NaN 值。

在下圖中,顯示了外部合併 result 中的示例行,前兩個是 use_id 在資料幀之間最常見的示例,後兩個僅來自左側資料幀,最後兩個僅來自右側資料幀。

使用合併指示器跟蹤合併

為了幫助識別行的來源,pandas 提供了一個指標引數,可以與 merge 函式一起使用,該函式在輸出中建立一個名為 _merge 的附加列,用於標記每行的原始源。

result = pd.merge(user_usage,
                 user_device[['use_id', 'platform', 'device']],
                 on='use_id', 
                 how='outer', 
                 indicator=True)
result.iloc[[0,1,2,200,201,202]]
outgoing_mins_per_month outgoing_sms_per_month monthly_mb use_id platform device _merge
0 21.97 4.82 1557.33 22787 android GT-I9505 both
1 1710.08 136.88 7267.55 22788 android SM-G930F both
2 1710.08 136.88 7267.55 22789 android SM-G930F both
200 28.79 29.42 3114.67 23988 NaN NaN left_only
201 616.56 99.85 5414.14 24006 NaN NaN left_only
202 339.09 136.88 9854.79 24037 NaN NaN left_only

從上圖的 _merge 欄位我們就可以很清楚的看到每一行是來源於原始資料的左側資料框還是右側資料框。

最終合併——將裝置詳細資訊加入到結果中

回到我們原來的問題,我們已經將 user_usage 與 user_device 合併,因此我們為每個使用者提供了平臺和裝置。最初,我們在 pandas 中使用了 inner merge 作為預設值,因此,我們只為具有裝置資訊的使用者提供條目。我們將使用左連線重做此合併以保留所有使用者,然後使用第二個左合併最終使裝置製造商處於同一資料幀中。

# First, add the platform and device to the user usage - use a left join this time.
result = pd.merge(user_usage,
                 user_device[['use_id', 'platform', 'device']],
                 on='use_id',
                 how='left')
# At this point, the platform and device columns are included
# in the result along with all columns from user_usage
# Now, based on the "device" column in result, match the "Model" column in devices.
devices.rename(columns={"Retail Branding": "manufacturer"}, inplace=True)
result = pd.merge(result, 
                  devices[['manufacturer', 'Model']],
                  left_on='device',
                  right_on='Model',
                  how='left')
print(result.head())
outgoing_mins_per_month outgoing_sms_per_month monthly_mb use_id platform device manufacturer Model
0 21.97 4.82 1557.33 22787 android GT-I9505 Samsung GT-I9505
1 1710.08 136.88 7267.55 22788 android SM-G930F Samsung SM-G930F
2 1710.08 136.88 7267.55 22789 android SM-G930F Samsung SM-G930F
3 94.46 35.17 519.12 22790 android D2303 Sony D2303
4 71.59 79.26 1557.33 22792 android SM-G361F Samsung SM-G361F

使用 left_on 和 right_on 與不同的列名合併

合併運算子中使用的列不需要在左側和右側資料幀中命名相同。在上面的第二個合併中,請注意裝置 ID 在左側資料幀中稱為裝置,在右側資料幀中稱為 模型。

使用 left_on 和 right_on 引數為 pandas 中的合併指定了不同的列名,而不是僅使用 on 引數。

在這裡插入圖片描述

根據裝置計算統計資料

隨著我們的合併完成,我們可以使用 pandas 的資料聚合功能來快速計算出基於裝置製造商的使用者的平均使用情況。請注意,小樣本量會建立更小的組,因此我不會將這些特定結果歸因於任何統計意義。

result.groupby("manufacturer").agg({
        "outgoing_mins_per_month": "mean",
        "outgoing_sms_per_month": "mean",
        "monthly_mb": "mean",
        "use_id": "count"
    })
outgoing_mins_per_month outgoing_sms_per_month monthly_mb use_id
manufacturer
HTC 244.302026 105.192099 2178.809982 548
Huawei 81.526667 9.500000 1561.226667 3
LGE 111.530000 12.760000 1557.330000 2
Lava 60.650000 261.900000 12458.670000 2
Lenovo 215.920000 12.930000 1557.330000 8
Motorola 91.092326 57.906628 3338.662326 172
OnePlus 416.341667 43.740000 3576.103333 18
Samsung 169.136728 87.165864 5085.595556 162
Sony 254.425000 42.611765 4351.370294 34
Vodafone 42.750000 46.830000 5191.120000 4
ZTE 42.750000 46.830000 5191.120000 4