第15 天:資料處理技巧(2) · 輕鬆學習R 語言
文章推薦指數: 80 %
運算子稱作Pipeline,它是進階的R 語言使用者在需要呼叫多次函數時候會採用的運算 ... 在 select() 函數中我們輸入資料框的名稱,以及想要選取的變數名稱,舉例來說 ...
輕鬆學習R語言
第00天:關於本書
第01天:建立R語言的開發環境
第02天:暸解不同的變數類型
第03天:暸解不同的變數類型(2)
第04天:變數類型的判斷與轉換
第05天:把變數集結起來
第06天:把變數集結起來(2)
第07天:把變數集結起來(3)
第08天:函數
第09天:迴圈與流程控制
第10天:自訂函數
第11天:資料的輸入與輸出
第12天:探索資料分析
第13天:探索資料分析(2)
第14天:資料處理技巧
第15天:資料處理技巧(2)
第16天:撰寫資料分析報告
第17天:實用R語言技巧彙整
第18天:實用R語言技巧彙整(2)
附錄01:練習題參考解答
附錄02:統計機率分布函數
附錄03:推薦的學習資源
PoweredbyGitBook
第15天:資料處理技巧(2)
第15天
資料處理技巧(2)
在資料處理技巧我們已經對基礎的資料處理技巧駕輕就熟,在今天的章節我們想要討論一些更進階的資料處理技巧,包含如何使用%>%運算子、長寬表格的轉換、如何使用dplyr套件中的函數以及如何應用函數於資料框。
tidyverse套件
我們今天要使用幾個套件來輔助我們作進階的資料處理:
套件
主要開發者
目的
magrittr
StefanMiltonBache
能夠使用%>%運算子
tidyr
HadleyWickham
能夠進行長寬表格的轉換
dplyr
HadleyWickham
更有效率地作資料處理
我們當然可以分開安裝這些套件,但是我們另外介紹套件tidyverse,它包含了上表我們要使用的這些套件,因此可以很方便地裝一個套件即可。
我們可以透過命令列(Console)安裝:
>install.packages("tidyverse")
也可以透過RStudio介面安裝的步驟是在右下角的packages頁籤點選install:
接著是輸入安裝套件的名稱:tidyverse。
載入tidyverse套件
接著我們可以透過命令列(Console)載入:
>library(tidyverse)
或者透過RStudio介面載入的方法是在右下角的packages頁籤下找到tidyverse然後將前面的核取方框打勾即可。
%>%運算子
第一次使用%>%
%>%運算子稱作Pipeline,它是進階的R語言使用者在需要呼叫多次函數時候會採用的運算子,它稍微修改了我們傳統呼叫函數的習慣,例如傳統我們想要將內建資料集cars作為summary()函數的輸入時:
>summary(cars)#傳統呼叫函數
speeddist
Min.:4.0Min.:2.00
1stQu.:12.01stQu.:26.00
Median:15.0Median:36.00
Mean:15.4Mean:42.98
3rdQu.:19.03rdQu.:56.00
Max.:25.0Max.:120.00
我們是將輸入(inputs)放置在函數名稱的小括號中;但是在使用%>%運算子的時候,我們將輸入放置在%>%運算子的左邊,並且將函數放置在%>%運算子的右邊,看起來就像是把輸入丟入函數中的樣子:
>library(tidyverse)
>cars%>%summary()#使用%>%
speeddist
Min.:4.0Min.:2.00
1stQu.:12.01stQu.:26.00
Median:15.0Median:36.00
Mean:15.4Mean:42.98
3rdQu.:19.03rdQu.:56.00
Max.:25.0Max.:120.00
%>%的使用時機
那在什麼時候我們可以考慮不使用傳統呼叫函數的寫法改用%>%呢?舉例來說,R語言的內建函數Sys.Date()會回傳一個系統日期:
Sys.Date()
假設我們現在有一個需求是把系統日期的年份擷取出來,並且轉換為數字,我們可能會這樣寫:
>sys_datesys_date_yrsys_date_numsys_date_num
[1]2017
這樣子寫的可讀性(readibility)很高,但是為了得到我們要的答案,過程中額外建立了sys_date與sys_date_yr這兩個物件,覺得好像不是太有效率,那麼我們試著把上面這段程式改寫得精簡一點:
>sys_date_numsys_date_num
[1]2017
這樣子寫雖然精簡,但是可讀性就變得比較低,尤其是小括號很多,我們都不喜歡去檢查哪個左括號應該對應哪個右括號。
在這種呼叫多次函數,並且需要將前一次函數的輸出作為後一次函數的輸入時,我們就應該想到%>%:
>library(tidyverse)
>
>sys_date_num%
+format(format="%Y")%>%
+as.numeric()
>sys_date_num
[1]2017
這樣子的寫法就兼顧了可讀性高與精簡的兩個優點!
加入運算符號
在使用%>%運算子將多個函數呼叫串連的流程中,也可以加入運算符號,舉例來說,如果我們想要計算香港搖滾樂隊Beyond成立幾週年紀念,可以用系統日期的年份減去成立年份:
>beyond_startbeyond_yr%
+format(format="%Y")%>%
+as.numeric()%>%
+`-`(beyond_start)
>beyond_yr
[1]34
我們將運算符號放入``之中,這個符號叫做tilt,您可以在鍵盤最左上角的按鍵找到它,它在tab的上方,數字1的左方,然後是將要運算的數字放入小括號中。
調整輸入的位置
%>%預設會將輸入放在函數輸入的第一個位置,在某些函數第一個位置不是輸入時,像是lm()函數的第一個位置必須註明方程式(formula),就可以透過.來指定輸入放置的地方:
>#傳統呼叫lm()函數
>cars_lm
>#使用.指定cars放的位置
>cars_lm%
+lm(formula=dist~speed,data=.)
長寬表格的轉換
寬轉長表格
透過tidyr套件中的gather()函數能夠將多個數值變數堆積在同一個數值變數(value)中,並且再使用一個類別變數(key)來記錄每一個數值變數的來源。
舉例來說,我們原先用兩個變數wins與losses來記錄1995-96球季芝加哥公牛隊與2015-16球季金州勇士隊的勝場數與敗場數:
>team_namewinslosses
>great_nba_teamsgreat_nba_teams
team_namewinslosses
1ChicagoBulls7210
2GoldenStateWarriors739
這樣外觀的資料框我們稱之為寬表格,在寬表格的結構中,如果我們希望增加一個變數,例如像是勝率,會以增加欄位數的方式來更新:
>great_nba_teamsgreat_nba_teams$winning_percentagegreat_nba_teams
team_namewinslosseswinning_percentage
1ChicagoBulls72100.8780488
2GoldenStateWarriors7390.8902439
透過tidyr套件中的gather()函數可以將這樣外觀的寬表格轉換為長表格:
>library(tidyverse)
>
>team_namewinslosses
>great_nba_teamsgather(great_nba_teams,key=variable_names,value=values,wins,losses)
team_namevariable_namesvalues
1ChicagoBullswins72
2GoldenStateWarriorswins73
3ChicagoBullslosses10
4GoldenStateWarriorslosses9
其中參數key是指定記錄數值來源的類別變數名稱為何,我們命名作variable_names;參數value是指定堆積數值的變數名稱為何,指定好這兩個參數以後,則是再指定有哪些變數要堆積起來
長轉寬表格
透過tidyr套件中的spread()函數能夠將前述的長表格再轉換為原本的寬表格:
>library(tidyverse)
>
>team_namewinslosses
>great_nba_teamslong_formatspread(long_format,key=variable_names,value=values)
team_namelosseswins
1ChicagoBulls1072
2GoldenStateWarriors973
dplyr套件
接著我們要介紹的是dplyr()套件,相較於原生的資料處理語法,dplyr()套件中融入很多概念與結構化查詢語言(StructuredQueryLanguage,SQL)相仿的函數。
搭配%>%運算子一起使用,能夠讓我們整理資料的能力獲得一個檔次的提升!我們將dplyr()套件提供的常用函數整理如下表:
函數
用途
filter()
篩選符合條件的觀測值
select()
選擇變數
mutate()
新增變數
arrange()
依照變數排序觀測值
summarise()
聚合變數
group_by()
依照類別變數分組,搭配
使用filter()函數
在filter()函數中我們輸入要篩選的資料框,以及依據什麼條件進行篩選,舉例來說我們可以將straw_hat_df中的女性篩選出來:
>library(tidyverse)
>
>namegenderagestraw_hat_dffilter(straw_hat_df,gender=="女")
namegenderage
1娜美女20
2妮可·羅賓女30
我們也藉此機會對照之前學習的R語言原生寫法:
>namegenderagestraw_hat_dfstraw_hat_df[straw_hat_df$gender=="女",]
namegenderage
3娜美女20
7妮可·羅賓女30
在filter()函數中我們可以利用&以及|連結多個篩選條件,舉例來說我們可以將straw_hat_df中超過30歲的男性篩選出來:
>library(tidyverse)
>
>namegenderagestraw_hat_dffilter(straw_hat_df,gender=="男"&age>30)
namegenderage
1佛朗基男36
2布魯克男90
我們也藉此機會對照之前學習的R語言原生寫法:
>namegenderagestraw_hat_dfstraw_hat_df[(straw_hat_df$gender=="男")&(straw_hat_df$age>30),]
namegenderage
8佛朗基男36
9布魯克男90
使用select()函數
在select()函數中我們輸入資料框的名稱,以及想要選取的變數名稱,舉例來說我們可以將straw_hat_df中的name篩選出來:
>library(tidyverse)
>
>namegenderagestraw_hat_dfselect(straw_hat_df,name)
name
1蒙其·D·魯夫
2羅羅亞·索隆
3娜美
4騙人布
5賓什莫克·香吉士
6多尼多尼·喬巴
7妮可·羅賓
8佛朗基
9布魯克
select()函數選擇單一變數時並不會像R語言原生語法預設轉換為向量資料結構,而是維持原本的資料框資料結構,在原生語法中我們可以在中括號中多指定drop=FALSE達到這個效果:
>namegenderagestraw_hat_dfstraw_hat_df[,"name",drop=FALSE]
name
1蒙其·D·魯夫
2羅羅亞·索隆
3娜美
4騙人布
5賓什莫克·香吉士
6多尼多尼·喬巴
7妮可·羅賓
8佛朗基
9布魯克
select()函數也可以在選擇變數的同時對變數重新進行命名:
>library(tidyverse)
>
>namegenderagestraw_hat_dfselect(straw_hat_df,crew_name=name,crew_age=age)
crew_namecrew_age
1蒙其·D·魯夫19
2羅羅亞·索隆21
3娜美20
4騙人布19
5賓什莫克·香吉士21
6多尼多尼·喬巴17
7妮可·羅賓30
8佛朗基36
9布魯克90
使用mutate()函數
利用mutate()函數新增衍生變數或者非衍生變數相當簡潔,舉例來說,我們要在great_nba_teams資料框中新增衍生變數winning_percentage與非衍生變數season:
>library(tidyverse)
>
>team_namewinslossesseasongreat_nba_teamsmutate(great_nba_teams,
+winning_percentage=wins/(wins+losses),
+season=season
+)
team_namewinslosseswinning_percentageseason
1ChicagoBulls72100.87804881995-96
2GoldenStateWarriors7390.89024392015-16
使用arrange()函數
利用指定的變數來排序觀測值,舉例來說以age變數來排序straw_hat_df:
>library(tidyverse)
>
>namegenderagestraw_hat_dfarrange(straw_hat_df,age)
namegenderage
1多尼多尼·喬巴男17
2蒙其·D·魯夫男19
3騙人布男19
4娜美女20
5羅羅亞·索隆男21
6賓什莫克·香吉士男21
7妮可·羅賓女30
8佛朗基男36
9布魯克男90
如果想改為遞減排序,就在變數外面增加desc()函數:
>library(tidyverse)
>
>namegenderagestraw_hat_dfarrange(straw_hat_df,desc(age))
namegenderage
1布魯克男90
2佛朗基男36
3妮可·羅賓女30
4羅羅亞·索隆男21
5賓什莫克·香吉士男21
6娜美女20
7蒙其·D·魯夫男19
8騙人布男19
9多尼多尼·喬巴男17
使用summarise()函數
在summarise()函數中我們輸入資料框的名稱,以及想要聚合的變數名稱,聚合運算的結果通常是一個數字,代表某個數列的運算結果,像是總和、平均數或標準差都是聚合運算的結果,舉例來說,我們可以運算straw_hat_df所有成員的平均年齡:
>library(tidyverse)
>
>namegenderagestraw_hat_dfsummarise(straw_hat_df,mean(age))
mean(age)
130.33333
使用group_by()函數
聚合函數的運算常常會搭配group_by()函數一起使用,這時我們就可以整合%>%運算子,舉例來說,我們可以計算straw_hat_df中男性與女性的平均年齡:
>library(tidyverse)
>
>namegenderagestraw_hat_dfgroup_by(straw_hat_df,gender)%>%
+summarise(mean(age))%>%
+as.data.frame()
gendermean(age)
1女25.00000
2男31.85714
這裡我們必須記得先呼叫group_by()函數然後再呼叫summarise()函數,這個順序與撰寫SQL語法相反,熟練SQL語法的使用者需要特別留意這個差異。
在連結group_by()與summarise()兩個函數之後的輸出是一種叫做tibble的改良式資料框,為了不要增添初學者的負擔,我們不說明它跟原生資料框的差異,tibble可以利用as.data.frame()函數轉換為原生的資料框。
應用函數於資料框
還記得我們在迴圈與流程控制探討了for與while迴圈嗎?資料量小的時候使用迴圈語法不太會感受到它們較低的運行效率,但是在資料量大的時候會感受到它較慢的運行效率,舉例來說我們先來隨機產生50萬人的身高體重:
>heightsweightsh_w_dfheightsweightsh_w_dfbmifor(iin1:nrow(h_w_df)){
+bmi[i]heightsweightsh_w_dfbmisystem.time(
+for(iin1:nrow(h_w_df)){
+bmi[i]heightsweightsh_w_dfbmiheightsweightsh_w_dfsystem.time(
+bmidistinct_countsapply(iris,MARGIN=2,distinct_counts)
Sepal.LengthSepal.WidthPetal.LengthPetal.WidthSpecies
352343223
其中MARGIN=2參數是指定我們自訂的distinct_counts()函數是應用在變數(Column)的方向上,如果想要應用在觀測值(Row)的方向上,我們必須指定MARGIN=1。
除了最基本的apply()函數以外,這一系列函數還有很多種的形式,例如lapply()函數會將輸出儲存為清單這樣的資料結構:
>distinct_countslapply(iris,FUN=distinct_counts)
$Sepal.Length
[1]35
$Sepal.Width
[1]23
$Petal.Length
[1]43
$Petal.Width
[1]22
$Species
[1]3
值得注意的是,不同形式的apply()函數有著自己的屬性,像是lapply()函數只能針對變數(Column)方向應用函數,不像apply()函數可以指定MARGIN參數。
sapply()函數屬於簡化的lapply()函數,它回傳的結果是向量而非清單,在我們範例中使用的效果就像apply()函數指定MARGIN=2一樣:
>distinct_countssapply(iris,FUN=distinct_counts)
Sepal.LengthSepal.WidthPetal.LengthPetal.WidthSpecies
352343223
tapply()函數是融入table()函數功能的形式,舉例來說我們可以拆分不同品種的Species來計算Petal.Length變數的相異個值:
>distinct_countstapply(iris$Petal.Length,INDEX=iris$Species,FUN=distinct_counts)
setosaversicolorvirginica
91920
這裡我們講的apply()、lapply()、sapply()與tapply()函數是比較常見的apply()函數成員,未來當您有更特殊的需求時,還會再碰到它們的親朋好友。
小結
好啦!第十五天的內容就是這麼多,我們今天介紹了三個很實用的套件,並且用tidyverse一次就全部載入,%>%運算子可以在不降低可讀性的前提下簡化多層的函數呼叫;tidyr套件中提供兩個函數讓我們可以彈性地轉換長寬表格;dplyr套件讓我們處理資料的效率更高。
最後我們簡單討論了關於程式執行的效率,當您之後編寫R語言的經驗愈來愈多,開始注重程式執行效率時,記得優先考慮向量運算,然後是apply()形式的各種函數,最後才是撰寫迴圈。
練習題
延續今天所舉的50萬筆的身高體重資料,請分別用向量運算、mapply()函數與迴圈來計算BMI,並且都以system.time()函數觀察執行時間。
heights
延伸文章資訊
- 16 資料處理與清洗| 資料科學與R語言
- 2[R]如何篩選出特定子集數據? subset() - CSDN博客
在R語言中,篩選出特定子集數據的函數為subset(),經由查詢Help的結果, ... R语言选取子集从一个大的数据集中选取、删除部分子集,或者从原有的集合 ...
- 3第15 天:資料處理技巧(2) · 輕鬆學習R 語言
運算子稱作Pipeline,它是進階的R 語言使用者在需要呼叫多次函數時候會採用的運算 ... 在 select() 函數中我們輸入資料框的名稱,以及想要選取的變數名稱,舉例來說 ...
- 4R環境下的大數據運算
我們將介紹一套系統,不僅能夠將龐大的資料在極短時間內讀取並觀看,還能夠將數億筆 ... 購買總數、特定產品購買數量與產品購買總金額之新資料框。
- 56 資料處理利器:dplyr - 認識R 的美好
接著我們要介紹的是 dplyr() 套件,相較於原生的資料處理語法, dplyr() 套件中融入 ... 在 select() 函數中我們輸入資料框的名稱,以及想要選取的變數名稱,舉例來說 ...