🏠 學習重點 :
對照 Datacamp 第四章: Data Manipulation with dplyr
■ group
在 dplyr 的作用
■ group_by()
… summarise()
■ group_by()
… mutate()
■ 介紹三種不同的 R 功能 …
。 匯總功能 (summary functions,例: sum
, mean
) 會將向量轉為單值
。 向量功能 (vector functions,例: sqrt
, log
) 會將向量轉為等長的向量
。 窗口功能 (window functions,例: order
, lag
) 會將向量轉為等長的向量
■ 介紹 ggplot2
: dplyr
的姐妹套件
載入所需套件 & 讀取 babynames 資料集
# A tibble: 6 x 5
year sex name n prop
<dbl> <chr> <chr> <int> <dbl>
1 1880 F Mary 7065 0.0724
2 1880 F Anna 2604 0.0267
3 1880 F Emma 2003 0.0205
4 1880 F Elizabeth 1939 0.0199
5 1880 F Minnie 1746 0.0179
6 1880 F Margaret 1578 0.0162
這是對美國出生嬰兒姓名的統計資料,搜集了1880年後每一年、每一個嬰兒名字的登記次數。是一個相當大的資料集,共有1,924,665
筆紀錄與5個欄位 …
tibble [1,924,665 x 5] (S3: tbl_df/tbl/data.frame)
$ year: num [1:1924665] 1880 1880 1880 1880 1880 1880 1880 1880 1880 1880 ...
$ sex : chr [1:1924665] "F" "F" "F" "F" ...
$ name: chr [1:1924665] "Mary" "Anna" "Emma" "Elizabeth" ...
$ n : int [1:1924665] 7065 2604 2003 1939 1746 1578 1472 1414 1320 1288 ...
$ prop: num [1:1924665] 0.0724 0.0267 0.0205 0.0199 0.0179 ...
🌻 group_by()
在資料框(tibbles)中標注群Groups
), 組結構本身不會改變資料,但會影響資料處理管線(%>%
)之中後續功能的運作方式。
# A tibble: 1,924,665 x 5
year sex name n prop
<dbl> <chr> <chr> <int> <dbl>
1 1880 F Mary 7065 0.0724
2 1880 F Anna 2604 0.0267
3 1880 F Emma 2003 0.0205
4 1880 F Elizabeth 1939 0.0199
# i 1,924,661 more rows
# A tibble: 1,924,665 x 5
# Groups: year [138]
year sex name n prop
<dbl> <chr> <chr> <int> <dbl>
1 1880 F Mary 7065 0.0724
2 1880 F Anna 2604 0.0267
3 1880 F Emma 2003 0.0205
4 1880 F Elizabeth 1939 0.0199
# i 1,924,661 more rows
❓ 比較上面兩段程式,他們有什麼不一樣嗎?
group_by()
指令後面最常接的函數為 summarise()
.
group_by(B, year) %>% summarise(
no_records = n(), # 各年份的資料筆數
no.names = n_distinct(name), # 各年份出現幾種不同的名字
no.baby = sum(n) # 各年份的嬰兒數量
)
# A tibble: 138 x 4
year no_records no.names no.baby
<dbl> <int> <int> <int>
1 1880 2000 1889 201484
2 1881 1935 1830 192696
3 1882 2127 2012 221533
4 1883 2084 1962 216946
# i 134 more rows
🌻 group_by() %>% summarise()
: 由於summarise()
之中通常只用回傳單值的運算式來定義新變數 (通常是使用像mean()
,max()
,n()
這一類的summary functions), 所以執行完summarise()
之後,每個組結構會被彙總成一筆資料,如此一來,這個群組結構就沒有用了,因此每一次執行完summarise()
之後,資料框之中的最後一個組結構就會被移除。由於它只會自動移除最後一個組結構,所以如果你在group_by()
裡面一次放很多個群組變數,summarise()
之後資料框之中就會有一些殘留下來的組結構,假如你不再需要這一些結構,最好在資料處理管線之中用ungroup()
或者在summarise()
裡面用.groups='drop'
將它們移除,以免造成錯誤或者拖慢管線執行的速度。
group_by(B, year, sex) %>% summarise(
no_records = n(), # 各年份的資料筆數
no.names = n_distinct(name), # 各年份出現幾種不同的名字
no.baby = sum(n) # 各年份的嬰兒數量
)
`summarise()` has grouped output by 'year'. You can override using the
`.groups` argument.
# A tibble: 276 x 5
# Groups: year [138]
year sex no_records no.names no.baby
<dbl> <chr> <int> <int> <int>
1 1880 F 942 942 90993
2 1880 M 1058 1058 110491
3 1881 F 938 938 91953
4 1881 M 997 997 100743
# i 272 more rows
Group mutate 比 group summarise 更有用,但要它用需要注意比較多的細節!
🌻 在群組結構之中,mutate()
裡面通常只使用會回傳跟群組資料等長向量的運算式
🌷 但是如果運算式中只用到 vector functions 或者是數學運算符號,像這樣子單純的向量運算,並不需要用到群組結構,單純的向量運算在整個資料框執行會比較有效率;單純的向量運算直接在整個資料框做和在組結構裡面做的結果是一樣的,在組結構裡面做純向量運算會拖慢資料管線的運算速度,卻不能得到任何好處。
# A tibble: 1,924,665 x 7
# Groups: name [97,310]
year sex name n prop logN sqrtN
<dbl> <chr> <chr> <int> <dbl> <dbl> <dbl>
1 1880 F Mary 7065 0.0724 8.86 84.1
2 1880 F Anna 2604 0.0267 7.86 51.0
3 1880 F Emma 2003 0.0205 7.60 44.8
4 1880 F Elizabeth 1939 0.0199 7.57 44.0
# i 1,924,661 more rows
這個Group Mutate會在每一個組結構裡面做一次運算,所以這一段程式總共會執行97,310 次,
但其結果卻和…
# A tibble: 1,924,665 x 7
year sex name n prop logN sqrtN
<dbl> <chr> <chr> <int> <dbl> <dbl> <dbl>
1 1880 F Mary 7065 0.0724 8.86 84.1
2 1880 F Anna 2604 0.0267 7.86 51.0
3 1880 F Emma 2003 0.0205 7.60 44.8
4 1880 F Elizabeth 1939 0.0199 7.57 44.0
# i 1,924,661 more rows
對整個tibble只做一次向量運算結果完全相同
🌷 所以,直覺上group mutate好像是兩相矛盾的 …
mutate()
裡面只允許使用向量運算式實際上,在 mutate()
中,除了向量函數之外,我們也可以使用向量運算式來定義新欄位,只要運算式所產生的向量長度與Group的長度相同就可以。 例如,我們有時候會想要將數值轉換成比例,例如,我們想知道某個名字在某年受歡迎的程度,我們應該要看的是該名字在當年度的佔比,而不是數量。 這個工作就可以透過一個向量運算式來完成,像在 ….
# A tibble: 1,924,665 x 6
# Groups: year [138]
year sex name n prop frac
<dbl> <chr> <chr> <int> <dbl> <dbl>
1 1880 F Mary 7065 0.0724 3.51
2 1880 F Anna 2604 0.0267 1.29
3 1880 F Emma 2003 0.0205 0.994
4 1880 F Elizabeth 1939 0.0199 0.962
# i 1,924,661 more rows
之中sum()
可以根據 Groups: year [138]
的結構,產生每一年所有n
的總和。 以這個方式 ,向量運算式就會對每一年的所有名稱做一次運算並產生每年每個名字的百分比
請注意,原始的資料集已經有一個 prop
的欄位 (column),但這個欄位是按照年分和性別分開去計算的,因此prop
大約是 frac
的兩倍。
Windows功能是向量功能中的一種特別的類型,就像一般的向量函數,它也會回傳一個和輸入相同長度的向量。 然而,大部分的向量功能的運算結果都不會被 Groups
結構所影響,但是windows功能的運算結果卻會取決於Groups
。
Windows 功能大部分都和向量中元素的排列順序有關,例如:
rank(v)
產生一個基於v
的值排序的索引向量,lag(v, n)
將向量向下移動 n
個位置 (預設值為n=1
)lead(v, n)
將向量向上移動 n
個位置舉一個實際的例子,如果我們想調查"Mary"
這個名字的流行度隨時間變化的速度,我們可以…
B %>% filter(name=="Mary", sex=="F") %>%
arrange(year) %>%
mutate(last.prop=lag(prop), dProp=prop-last.prop)
# A tibble: 138 x 7
year sex name n prop last.prop dProp
<dbl> <chr> <chr> <int> <dbl> <dbl> <dbl>
1 1880 F Mary 7065 0.0724 NA NA
2 1881 F Mary 6919 0.0700 0.0724 -0.00239
3 1882 F Mary 8148 0.0704 0.0700 0.000435
4 1883 F Mary 8012 0.0667 0.0704 -0.00369
# i 134 more rows
我們可以計算出全部的名字在每一年的變化,然後做個排序 …
B %>% group_by(sex, name) %>%
arrange(year) %>% # 先將資料根據年份升冪排序
mutate( # 然後使用lag來計算
dProp = 100 * (prop-lag(prop)) # 每年 prop 增加的百分比
) %>% #
arrange(desc(dProp)) # 根據delta降冪排序
# A tibble: 1,924,665 x 6
# Groups: sex, name [107,973]
year sex name n prop dProp
<dbl> <chr> <chr> <int> <dbl> <dbl>
1 1947 F Linda 99686 0.0548 2.22
2 1935 F Shirley 42355 0.0390 1.79
3 1983 F Ashley 33293 0.0186 1.04
4 1934 F Shirley 22841 0.0211 0.742
# i 1,924,661 more rows
prop
欄位增加最多的是 1947 年女生的名字 Linda
讓我們用我們學到的東西應用在嬰兒姓名資料集上面,並且配合ggplot2
和plotly
套件做互動式的資料視覺化。
找出前20名的名字,並計算出他們的累計百分比
# A tibble: 20 x 5
sex name n pc cumpc
<chr> <chr> <int> <dbl> <dbl>
1 M James 5150472 0.0148 0.0148
2 M John 5115466 0.0147 0.0295
3 M Robert 4814815 0.0138 0.0433
4 M Michael 4350824 0.0125 0.0558
5 F Mary 4123200 0.0118 0.0677
6 M William 4102604 0.0118 0.0794
7 M David 3611329 0.0104 0.0898
8 M Joseph 2603445 0.00748 0.0973
9 M Richard 2563082 0.00736 0.105
10 M Charles 2386048 0.00685 0.112
11 M Thomas 2304948 0.00662 0.118
12 M Christopher 2022164 0.00581 0.124
13 M Daniel 1907357 0.00548 0.129
14 F Elizabeth 1629679 0.00468 0.134
15 M Matthew 1590440 0.00457 0.139
16 F Patricia 1571692 0.00451 0.143
17 F Jennifer 1466281 0.00421 0.147
18 M George 1464186 0.00421 0.152
19 F Linda 1452249 0.00417 0.156
20 F Barbara 1434060 0.00412 0.160
將曾經是年度最受歡迎的女性名字都找出來,放在fChamp
裡面。
fChamp = filter(B, sex=="F") %>%
group_by(year) %>% top_n(1, n) %>% ungroup %>%
count(name, sort=T); fChamp
# A tibble: 10 x 2
name n
<chr> <int>
1 Mary 76
2 Jennifer 15
3 Emily 12
4 Jessica 9
5 Lisa 8
6 Linda 6
7 Emma 5
8 Sophia 3
9 Ashley 2
10 Isabella 2
並將它們每一年的佔比用折線圖繪製在互動式的圖表中
gg = filter(B, sex=="F", name%in%fChamp$name) %>%
ggplot(aes(x=year, y=prop, color=name)) + geom_line() +
labs(color="",title="最受歡迎的女生名字")
ggplotly(gg) %>% layout(legend=list(tracegroupgap=4))
對男生名字也做同樣的事情
filter(B, sex=="M") %>%
group_by(year) %>% top_n(1, n) %>% ungroup %>%
count(name, sort=T) -> mChamp
gg = filter(B, sex=="M", name%in%mChamp$name) %>%
ggplot(aes(x=year, y=prop, color=name)) + geom_line()+
labs(color="",title="最受歡迎的男生名字")
ggplotly(gg) %>% layout(legend=list(tracegroupgap=4))
🏄 這次忍者道場中我們可以學到兩件事……
dplyr
的語法比較冗長,不過它比較有擴充性,也比較清楚。dplyr
加上ggplot
,可以用接水管的方式,透過很精緻的圖表,作互動式的資料探索。