大家好,今天想跟大家介紹一個使用dplyr
時的一個小撇步。他可以讓我們在使用group_by
之後,有更多的彈性,並會更理解group_by
這好用功能的原理。
背景簡介
我由於工作上要研究網路廣告,常常要比較各種不同方法間的數據。舉例來說,我可能手上有一大筆看起來像這樣的資料:
廣告 | 網站 | 點擊 | |
---|---|---|---|
1 | 廣告D | 網站C | 0 |
2 | 廣告A | 網站C | 0 |
3 | 廣告A | 網站C | 0 |
4 | 廣告B | 網站C | 1 |
5 | 廣告B | 網站B | 0 |
6 | 廣告B | 網站B | 0 |
而我想要知道各種廣告在網站A
上的表現。透過dplyr
,這並不困難:
filter(sample, 網站 == "網站A") %>%
group_by(廣告) %>%
summarise(曝光 = length(點擊), 點擊數 = sum(點擊))
廣告 | 曝光 | 點擊數 | |
---|---|---|---|
1 | 廣告A | 329 | 0 |
2 | 廣告B | 588 | 4 |
3 | 廣告C | 81 | 0 |
4 | 廣告D | 71 | 0 |
然而,以這個資料來說,我並不確定廣告C
和廣告D
的成效是不是明顯的不同。我想大家能同意,廣告的點擊是有機率在背後的,所以我們必須要透過一些計算才能知道,數據上顯示的:「廣告B
比其他廣告好」,是運氣比較好,還是真的比較好。
R 有一個製作95%信賴區間很方便的套件:binom
。只要簡單利用binom.confint
,就可以快速算出各種方法的confidence interval。
以下就是一個簡單的範例,我們使用預設的95%信心水準(可以使用參數conf.level
來調整),統計方法是"exact"
(由Clopper and Pearson (1934)所設計出來的方法)。binom
套件提供了10種方法給使用者計算信賴區間。
library(binom)
channel.a.summary <- filter(sample, 網站 == "網站A") %>%
group_by(廣告) %>%
summarise(曝光 = length(點擊), 點擊數 = sum(點擊))
binom.confint(x = channel.a.summary$點擊數, n = channel.a.summary$曝光, methods = "exact")
method | x | n | mean | lower | upper | |
---|---|---|---|---|---|---|
1 | exact | 0 | 329 | 0.00 | 0.00 | 0.01 |
2 | exact | 4 | 588 | 0.01 | 0.00 | 0.02 |
3 | exact | 0 | 81 | 0.00 | 0.00 | 0.04 |
4 | exact | 0 | 71 | 0.00 | 0.00 | 0.05 |
所以根據binom.confint
的結果,其實這樣的差距很有可能是來自於運氣,因為他們的信賴區間交錯的不少。
結合group_by
+ binom.confint
然而剛剛的程式碼,其實可以利用dplyr
的do
來改進:
filter(sample, 網站 == "網站A") %>%
group_by(廣告) %>%
do(binom.confint(sum(.$點擊), length(.$點擊), methods = "exact"))
廣告 | method | x | n | mean | lower | upper | |
---|---|---|---|---|---|---|---|
1 | 廣告A | exact | 0 | 329 | 0.00 | 0.00 | 0.01 |
2 | 廣告B | exact | 4 | 588 | 0.01 | 0.00 | 0.02 |
3 | 廣告C | exact | 0 | 81 | 0.00 | 0.00 | 0.04 |
4 | 廣告D | exact | 0 | 71 | 0.00 | 0.00 | 0.05 |
這裡的do
是一個很有趣的函數。當我們傳遞一個group_by
處理過得的table,do
會依照group_by
的欄位將原本的table切割成若干的子table,並且運用變數.
來代表這個子table。然後,再利用do
的參數函數來個別計算出結果,最後再拼裝成原本的輸出。
do
和原本summarise
的差別,就是更有彈性,並且可以一次輸出多欄位。舉例來說,上述的binom.confint
就一次輸出mean
、lower
和upper
等資訊,而不像summarise,裡面使用的函數一次只能輸出一個值。
do
細解
接下來,我簡單說明do
的用法。請有興趣的朋友執行以下的程式碼:
group_by(iris, Species) %>% do(browser())
由於browser
的緣故,在do
處理子table的時候會暫停,所以各位朋友就可以看到:
Browse[1]> .
Source: local data frame [50 x 5]
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
(dbl) (dbl) (dbl) (dbl) (fctr)
1 5.1 3.5 1.4 0.2 setosa
2 4.9 3.0 1.4 0.2 setosa
3 4.7 3.2 1.3 0.2 setosa
4 4.6 3.1 1.5 0.2 setosa
5 5.0 3.6 1.4 0.2 setosa
6 5.4 3.9 1.7 0.4 setosa
7 4.6 3.4 1.4 0.3 setosa
8 5.0 3.4 1.5 0.2 setosa
9 4.4 2.9 1.4 0.2 setosa
10 4.9 3.1 1.5 0.1 setosa
.. ... ... ... ... ...
Browse[1]> nrow(.)
[1] 50
第一次暫停的時候,這個.
就代表所有Species
等於"setosa"
的table,也就是filter(iris, Species == "setosa")
的結果。
各位可以從nrow(.)
得到50
看出。
這樣的暫停會有三次,第二次是filter(iris, Species == "versicolor")
,第三次是filter(iris, Species == "virginica")
。
do
裡面的函數必須輸出data.frame
讓dplyr
最後做組裝。舉例來說:
group_by(iris, Species) %>% do(sum(.$Petal.Width))
## Error: Results are not data frames at positions: 1, 2, 3
由於sum(.$Petal.Width)
不是一個data.frame
,所以就發生錯誤了。必須要用:
group_by(iris, Species) %>% do(data.frame(sum = sum(.$Petal.Width)))
## Source: local data frame [3 x 2]
## Groups: Species [3]
##
## Species sum
## (fctr) (dbl)
## 1 setosa 12.3
## 2 versicolor 66.3
## 3 virginica 101.3
結語
透過group_by
+ do
,我們就可以再簡化程式碼,一次group_by
就可以輸出信賴區間相關的分析結果,程式碼也簡潔。這一招是我最近學到的密技,今天就簡單跟大家作個說明,希望以上的內容能幫上大家囉。