🏠 學習重點 :
對照 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 資料集

pacman::p_load(dplyr,tidyr,ggplot2,babynames)
B = babynames
B %>% head
# 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個欄位 …

str(B)
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 ...



1. Group_By

🌻 group_by() 在資料框(tibbles)中標注群組結構(Groups), 組結構本身不會改變資料,但會影響資料處理管線(%>%)之中後續功能的運作方式。

B
# 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
group_by(B, year)
# 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


❓ 比較上面兩段程式,他們有什麼不一樣嗎?



2. Group Summaries

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



3. Group Mutate

Group mutate 比 group summarise 更有用,但要它用需要注意比較多的細節!

3.1 純向量運算 (Pure Vector Operations)

🌻 在群組結構之中,mutate()裡面通常只使用會回傳跟群組資料等長向量的運算式

🌷 但是如果運算式中只用到 vector functions 或者是數學運算符號,像這樣子單純的向量運算,並不需要用到群組結構,單純的向量運算在整個資料框執行會比較有效率;單純的向量運算直接在整個資料框做和在組結構裡面做的結果是一樣的,在組結構裡面做純向量運算會拖慢資料管線的運算速度,卻不能得到任何好處。

B %>% group_by(name) %>% mutate(logN=log(n), sqrtN=sqrt(n))
# 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 次,

但其結果卻和…

B %>% mutate(logN=log(n), sqrtN=sqrt(n))
# 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()裡面只允許使用向量運算式
  • 另一方面,對純向量運算進行group mutate卻是沒有意義的
  • 然而,group mutate其實是非常有用的,我們將在下面提到它的正確使用方法


3.2 使用Summary功能的向量運算式(Vector Expression)

實際上,在 mutate()中,除了向量函數之外,我們也可以使用向量運算式來定義新欄位,只要運算式所產生的向量長度與Group的長度相同就可以。 例如,我們有時候會想要將數值轉換成比例,例如,我們想知道某個名字在某年受歡迎的程度,我們應該要看的是該名字在當年度的佔比,而不是數量。 這個工作就可以透過一個向量運算式來完成,像在 ….

group_by(B, year) %>% mutate(frac = 100*n/sum(n))
# 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 的兩倍。


3.3 Window 功能

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




忍者道場 . . . . .

讓我們用我們學到的東西應用在嬰兒姓名資料集上面,並且配合ggplot2plotly套件做互動式的資料視覺化。

找出前20名的名字,並計算出他們的累計百分比

count(B, sex, name, wt=n, sort=T) %>% 
  mutate(pc=n/sum(n), cumpc=cumsum(pc)) %>% 
  head(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,可以用接水管的方式,透過很精緻的圖表,作互動式的資料探索。