第15 天:資料處理技巧(2) · 輕鬆學習R 語言

文章推薦指數: 80 %
投票人數:10人

運算子稱作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



請為這篇文章評分?