R|資料處理|merge資料詳解
Dwzb , R語言中文社群專欄作者,廈門大學統計專業學生。
知乎專欄:https://zhuanlan.zhihu.com/Data-AnalysisR
前文推送:
由於merge資料在現實應用中使用非常廣泛,而且真實的資料遠遠沒有我們練習時使用的資料那麼幹淨,所以有了這篇文章。本文會針對現實資料中可能遇到的一些問題,更深入地講解merge資料的使用。
資料:自己有針對性地建立,故意踩一些坑並解決
函式:包括dplyr包的join族函式,data.table包的[DT, ]方法,基礎及data.table包的merge函式
主要解決如下問題
要合併的兩個資料集,合併所依據的列,名稱不同
要合併的兩個資料集,有一些相同的列名,合併後需要更改防止兩列同名
合併所依據的列不是完全相同的,展示多種取捨方法
合併所依據的列中,內容不是唯一的,展示幾種情況的處理方法
全文分dplyr包和data.table包來講
資料建立
先簡單說明一下建立的資料
第一組一共四個資料框
data_map 有兩列可以用於merge,作為其他資料之間的橋樑,將所有資料merge在一起
data_good 用於merge的列形式很好,和data_map中的列一一對應,用於展示最簡單的資料框merge合併。但是為了演示一些引數,在列名上挖了兩個坑
data_diff 用於merge的列和data_map中的列有交集,都不完全包括對方,用於展示多種取捨方法在兩個包中的 實現
data_bad 用於merge的列有重複,與data_map這個沒有重複的資料來merge
第二組兩個資料框: bad1和bad2,用於merge的列都有重複,用於展示都有重複時的merge
這裡主要展示資料框建立程式碼,資料框長什麼樣子,在下面的例子中都會展示
library(dplyr)
library(data.table)
library(lubridate)
# 設定隨機種子保證隨機試驗可以重複
set.seed(1234)
# 兩列每個值都是唯一的
data_map <- data.table(name_id = letters[1:10], card_id = LETTERS[1:10])
m2 <- sample(letters[1:10], 15, replace = T)
m1 <- sample(LETTERS[1:10], 10, replace = F)
# object_id列值是唯一的,且正好和data_map的name_id列一一對應
data_good <- data.table(object_id = m1, # 與card_id列對應來合併,故意製造合併列名稱不同的情況
name_id = paste(m1, m1, sep='')) # 故意製造同名情況
# name_id列值唯一,與data_map的name_id只有4個重合的
data_diff <- data.table(name_id = sample(letters, 10, replace = F),
x1 = c(rep('m',4), rep('n',3), rep('p',3)))
# name_id列值不唯一,與data_map的name_id只有7個重合的,但是data_bad中有的在data_map中都能找到
data_bad <- data.table(name_id = m2, # 與name_id對應來合併
date = ymd('20171218') + ddays(rowid(m2)),
content1 = paste(m2, 1:15, sep='_content'),
content2 = paste(m2, 1:15, sep='another'))
bad1 <- data.table(name_id = c(1,2,3,1,4,5,1,6),
text1 = letters[1:8])
bad2 <- data.table(name_id = c(1,2,3,1,2,5,3,7),
text12 = LETTERS[1:8])
join族函式
dplyr包中,join族函式分為如下幾個函式(對於 *_join(new, data_diff, by='name_id') 來說)
left_join 以new中name_id(記為A)為準。data_diff的name_id中,不在A中的全部捨棄,其餘的對應匹配上去
right_join 和left相反,以data_diff為準
inner_join 保留兩個資料name_id交叉的部分
full_join 兩個資料所有內容都保留,相當於left_join結果,加上right_join結果,減去inner_join的結果
semi_join 與 anti_join 其實是對行篩選,不算是merge合併
下面程式碼結果分別展示
1. 這部分展示
join族函式中的兩個引數 by suffix
各個join函式有什麼區別
程式碼展示如下
new <- left_join(data_map, data_good, # 這是兩個完全匹配的資料
by = c('card_id'='object_id'), # 對應匹配的列名不同,在此指定
suffix = c('', '_good')) # 兩個資料有相同列名,指定融合後,兩個資料集相同列名,分別加什麼樣的字尾
head(new)
left_join(new, data_diff, by = 'name_id')
right_join(new, data_diff, by = 'name_id')
left_join(data_diff, new, by = 'name_id') # 除了列順序之外,內容和上一條是一樣的
inner_join(new, data_diff, by = 'name_id')
full_join(new, data_diff, by = 'name_id')
semi_join(new, data_diff, by = 'name_id')
new[new$name_id %in% data_diff$name_id, ] # 同上
anti_join(new, data_diff, by = 'name_id')
new[!new$name_id %in% data_diff$name_id, ] # 同上
結果展示如下
首先合併的是data_map和data_good得到new
data_map data_good
name_id card_id || object_id name_id
a A || I II
b B || C CC
c C || J JJ
d D || B BB
e E || G GG
f F || F FF
g G || E EE
h H || A AA
i I || H HH
j J || D DD
new
card_id name_id name_id_good
A a AA
B b BB
C c CC
D d DD
E e EE
F f FF
G g GG
H h HH
I i II
J j JJ
下一步是new和data_diff的多種合併結果
new (name_id只有4個重合) data_diff
name_id card_id name_id_good || name_id x1
a A AA || v m
b B BB || n m
c C CC || z m
d D DD || t m
e E EE || b n
f F FF || j n
g G GG || f n
h H HH || w p
i I II || u p
j J JJ || d p
left_join(保留new)
name_id card_id name_id_good x1
a A AA <NA>
b B BB n
c C CC <NA>
d D DD p
e E EE <NA>
f F FF n
g G GG <NA>
h H HH <NA>
i I II <NA>
j J JJ n
right_join(保留data_diff)
name_id card_id name_id_good x1
v <NA> <NA> m
n <NA> <NA> m
z <NA> <NA> m
t <NA> <NA> m
b B BB n
j J JJ n
f F FF n
w <NA> <NA> p
u <NA> <NA> p
d D DD p
inner_join(取交集)
name_id card_id name_id_good x1
b B BB n
d D DD p
f F FF n
j J JJ n
full_join(取並集)
name_id card_id name_id_good x1
a A AA <NA>
b B BB n
c C CC <NA>
d D DD p
e E EE <NA>
f F FF n
g G GG <NA>
h H HH <NA>
i I II <NA>
j J JJ n
v <NA> <NA> m
n <NA> <NA> m
z <NA> <NA> m
t <NA> <NA> m
w <NA> <NA> p
u <NA> <NA> p
semi_join(在new中篩選出data_diff有的)
name_id card_id name_id_good
b B BB
d D DD
f F FF
j J JJ
anti_join(在new中篩選出data_diff沒有的)
name_id card_id name_id_good
a A AA
c C CC
e E EE
g G GG
h H HH
i I II
2.接下來考慮一個數據merge列有重複的情況
# 只有一個數據的name_id有重複,就會自動擴充
left_join(new, data_bad, by = 'name_id')
right_join(new, data_bad, by = 'name_id')
結果展示如下
new
name_id card_id name_id_good
a A AA
b B BB
c C CC
d D DD
e E EE
f F FF
g G GG
h H HH
i I II
j J JJ
data_bad
name_id date content1 content2
b 2017-12-19 b_content1 banother1
g 2017-12-19 g_content2 ganother2
g 2017-12-20 g_content3 ganother3
g 2017-12-21 g_content4 ganother4
i 2017-12-19 i_content5 ianother5
g 2017-12-22 g_content6 ganother6
a 2017-12-19 a_content7 aanother7
c 2017-12-19 c_content8 canother8
g 2017-12-23 g_content9 ganother9
f 2017-12-19 f_content10 fanother10
g 2017-12-24 g_content11 ganother11
f 2017-12-20 f_content12 fanother12
c 2017-12-20 c_content13 canother13
j 2017-12-19 j_content14 janother14
c 2017-12-21 c_content15 canother15
left_join(new, data_bad, by = 'name_id')
name_id card_id name_id_good date content1 content2
a A AA 2017-12-19 a_content7 aanother7
b B BB 2017-12-19 b_content1 banother1
c C CC 2017-12-19 c_content8 canother8
c C CC 2017-12-20 c_content13 canother13
c C CC 2017-12-21 c_content15 canother15
d D DD <NA> <NA> <NA>
e E EE <NA> <NA> <NA>
f F FF 2017-12-19 f_content10 fanother10
f F FF 2017-12-20 f_content12 fanother12
g G GG 2017-12-19 g_content2 ganother2
g G GG 2017-12-20 g_content3 ganother3
g G GG 2017-12-21 g_content4 ganother4
g G GG 2017-12-22 g_content6 ganother6
g G GG 2017-12-23 g_content9 ganother9
g G GG 2017-12-24 g_content11 ganother11
h H HH <NA> <NA> <NA>
i I II 2017-12-19 i_content5 ianother5
j J JJ 2017-12-19 j_content14 janother14
right_join(new, data_bad, by = 'name_id')
name_id card_id name_id_good date content1 content2
b B BB 2017-12-19 b_content1 banother1
g G GG 2017-12-19 g_content2 ganother2
g G GG 2017-12-20 g_content3 ganother3
g G GG 2017-12-21 g_content4 ganother4
i I II 2017-12-19 i_content5 ianother5
g G GG 2017-12-22 g_content6 ganother6
a A AA 2017-12-19 a_content7 aanother7
c C CC 2017-12-19 c_content8 canother8
g G GG 2017-12-23 g_content9 ganother9
f F FF 2017-12-19 f_content10 fanother10
g G GG 2017-12-24 g_content11 ganother11
f F FF 2017-12-20 f_content12 fanother12
c C CC 2017-12-20 c_content13 canother13
j J JJ 2017-12-19 j_content14 janother14
c C CC 2017-12-21 c_content15 canother15
當我們要把多張表merge到一起時,如果有幾張都是merge列有重複的情況,就會面臨要merge的兩個資料都是有重複的的情況
3.兩個資料集merge列都有重複
程式碼展示如下(直接使用left_join看預設情況即可)
> bad1
name_id text1
1: 1 a
2: 2 b
3: 3 c
4: 1 d
5: 4 e
6: 5 f
7: 1 g
8: 6 h
> bad2
name_id text12
1: 1 A
2: 2 B
3: 3 C
4: 1 D
5: 2 E
6: 5 F
7: 3 G
8: 7 H
> left_join(bad1, bad2, by = 'name_id')
name_id text1 text12
1 1 a A
2 1 a D
3 2 b B
4 2 b E
5 3 c C
6 3 c G
7 1 d A
8 1 d D
9 4 e <NA>
10 5 f F
11 1 g A
12 1 g D
13 6 h <NA>
從上面結果可見,比如1重複,則bad1中的每個1都和bad2的每個1構成一行,所以產生了3*2=6個name_id是1的行。
這樣merge有一個缺點就是產生了大量重複資料,還有另一種處理方法,長表變寬表,這樣可以讓name_id不會重複,但是列就會增加很多。
這裡只展示data_bad如何將name_id唯一化的,即把date變到列上去,即看每個name_id在各個date上的content1和content2分別是什麼
> data_bad
name_id date content1 content2
1: b 2017-12-19 b_content1 banother1
2: g 2017-12-19 g_content2 ganother2
3: g 2017-12-20 g_content3 ganother3
4: g 2017-12-21 g_content4 ganother4
5: i 2017-12-19 i_content5 ianother5
6: g 2017-12-22 g_content6 ganother6
7: a 2017-12-19 a_content7 aanother7
8: c 2017-12-19 c_content8 canother8
9: g 2017-12-23 g_content9 ganother9
10: f 2017-12-19 f_content10 fanother10
11: g 2017-12-24 g_content11 ganother11
12: f 2017-12-20 f_content12 fanother12
13: c 2017-12-20 c_content13 canother13
14: j 2017-12-19 j_content14 janother14
15: c 2017-12-21 c_content15 canother15
> data_bad %>% melt(id = c('name_id', 'date')) %>%
+ dcast(name_id~date+variable)
name_id 2017-12-19_content1 2017-12-19_content2 2017-12-20_content1 2017-12-20_content2
1: a a_content7 aanother7 NA NA
2: b b_content1 banother1 NA NA
3: c c_content8 canother8 c_content13 canother13
4: f f_content10 fanother10 f_content12 fanother12
5: g g_content2 ganother2 g_content3 ganother3
6: i i_content5 ianother5 NA NA
7: j j_content14 janother14 NA NA
2017-12-21_content1 2017-12-21_content2 2017-12-22_content1 2017-12-22_content2
1: NA NA NA NA
2: NA NA NA NA
3: c_content15 canother15 NA NA
4: NA NA NA NA
5: g_content4 ganother4 g_content6 ganother6
6: NA NA NA NA
7: NA NA NA NA
2017-12-23_content1 2017-12-23_content2 2017-12-24_content1 2017-12-24_content2
1: NA NA NA NA
2: NA NA NA NA
3: NA NA NA NA
4: NA NA NA NA
5: g_content9 ganother9 g_content11 ganother11
6: NA NA NA NA
7: NA NA NA NA
這樣name_id唯一再merge到data_map表中,不會增加太多重複的行,但是這個方法也有一個很大的弊端,就是增加了太多列,產生了非常多的缺失值。
其實這種name_id不唯一的表,稱為動態表;而唯一的是靜態表
靜態表表示這個id的靜態資料,特徵等,一個id就對應該指標的一個值
動態表則反映這個id的動態情況,比如哪一天這個id發生了什麼,換一天又發生了什麼,這樣一個表就會出現多次同一個id,即name_id列有重複的內容
真正遇到這種問題時,其實沒有必要非要把靜態表和動態表合併在一起了,合併後資料格式都不好了,肯定也沒辦法分析
data.table
使用data.table執行結果和join族函式一樣,所以這裡只列程式碼
使用[]來合併,[]的在merge上的功能比較弱,而且彆扭,只能做下面這些事
new <- data_map[data_good, on="card_id==object_id"]
new
# data_map[data_good, name_id_good:= i.name_id,on="card_id==object_id"][] # 要改名就在原資料上修改,改名沒有join族函式方便
# 以data_diff為準
new[data_diff, on='name_id']
# 以new為準
data_diff[new, on='name_id']
但是[]中使用merge可以結合[]中其他位置的引數一起使用,在只需要簡單merge時,可以達到多步合併為一行的簡潔效果。
下面我們來看一下merge函式,data.table中的merge會比基礎包中merge更快,同時更改了一些預設選項,基本使用以及引數都沒有改變,這裡的資料因為建立時使用的是data.table函式,所以使用merge時自動用的是data.table包中的函式
new <- merge(data_map, data_good, by.x='card_id', by.y='object_id', suffixes=c('','.good'))
new
# 全部保留
merge(new, data_diff, by='name_id', all=T)
# 保留new的
merge(new, data_diff, by='name_id', all.x=T)
# 保留data_diff的
merge(new, data_diff, by='name_id', all.y=T)
# 保留交集
merge(new, data_diff, by='name_id', all=F)
merge(new, data_bad, by='name_id', all.x=T)
merge(new, data_bad, by='name_id', all.y=T)
merge(bad1,bad2, by='name_id', all.x=T)
大家都在看
公眾號後臺回覆關鍵字即可學習
回覆 爬蟲 爬蟲三大案例實戰
回覆 Python1小時破冰入門回覆 資料探勘 R語言入門及資料探勘
回覆 人工智慧 三個月入門人工智慧
回覆 資料分析師 資料分析師成長之路
回覆 機器學習 機器學習的商業應用
回覆 資料科學 資料科學實戰
回覆 常用演算法 常用資料探勘演算法