使用purrr package學functional programming的觀念(五):一行同時讀取多個檔案,彙整成單一個data.frame且新增欄位

這邊嘗試要解決一個很棘手的問題,手上有一個下面這樣的資料清單,而每個資料夾下面有多個有固定編號的檔案。

screenshot.png

而每筆資料大概長的如下面這樣:

screenshot.png

而每個檔名是那筆資料的編號和資訊,最後希望可以把單個檔案讀取進來後,合併在一起,且根據每個檔案的檔名,新增4個欄位和其型態,最後理想上希望變成下面的樣子:

screenshot.png

根據這個問題的話,可以使用read_delim, pmap_chr, map, pmap, reduce這四個函數來使用,要完成這個小工作的話,能簡單可以分成幾個概念:
第一個是要彙整多個檔案路徑、第二個讀取多個檔案路徑的函數、第三個則是根據不同資料的檔名,來為其所讀取的data.frame新增欄位、第四個則是將所有放在list中的data.frame合併。

當概念解離清楚之後,便可以針對每一步挑選適用的函數來處理,比如
第一個
小步驟想要彙整多個檔案路徑,這步要準備的便是把所有路徑以list的方式來儲存,然後用paste0函數來將每個路徑合起來,使用pmap_chr來完成這件事。
pmap_chr函數可以用函數來處理list中的每個element,接者在使用map搭配read_delim來完成第二個小步驟,如下面這個代碼

#將所有的變數,使用list來儲存
list(as.list(rep(Pathway.path, pathway.number)),as.list(rep("/",pathway.number)),as.list(list.pathway.json)) %>%
#將這些變數導入到pmap_chr函數,因為pmap_chr是當list內的element為chr時所用的,這一部是為了把每個檔案的路徑組合起來
pmap_chr(paste0) %>%
#接者在使用map函數來針對上面處理完的一串路徑list,使用read_delim函數,分別讀取,並且在用list來吐出
map(read_delim,delim="\t")

第三個則是根據不同的資料檔名,來為其所讀取的data.frame新增欄位
此時我們已取得一個list,裡面的element都是一個檔案讀入的data.frame,而我們希望再把data.frame合併前,將每個data.frame使用其檔案名稱來增添其資料欄位,如下面的代碼:

#這邊的.代表從剛剛上面的代碼pipe進來的位置,後面三個則是我們希望增加的欄位,和每個欄位的名稱
list(.,pharmGKB_ID,pathwayName,Pharmacokinetics,Pharmacodynamics) %>%
#同樣的這邊可以使用pmap,此時不同使用pmap_chr,因為我們list中,一組是data.frame的格式
pmap(function(x,pharmGKB_ID,pathwayName,pharmacokinetics,pharmacodynamics){
                x %>%
mutate(`PharmGKB ID`=pharmGKB_ID,
       `PathwayName`=pathwayName,
        Pharmacokinetics=pharmacokinetics,
        Pharmacodynamics=pharmacodynamics )
                 })
#這邊比較值得一提的是pmap裡面可以使用函數作為參數,而這個函數可以直接在裡面定義,有點套用lambda的寫法,來做這件事情,這邊是為了把欄位的名稱確立

第四個則是將所有放在list中的data.frame合併,這邊使用reduce來解決這件事情,reduce可以將放入的list,把每個element用某個函數處理,一個接一個。

CleanPathway <-list(as.list(rep(Pathway.path, pathway.number)),                 as.list(rep("/", pathway.number)),as.list(list.pathway.json)) %>%
pmap_chr(paste0) %>%
map(read_delim,delim="\t") %>%
list(.,pharmGKB_ID,pathwayName,Pharmacokinetics,Pharmacodynamics) %>%
pmap(function(x,pharmGKB_ID,pathwayName,pharmacokinetics,pharmacodynamics){
                x %>% mutate(`PharmGKB ID`=pharmGKB_ID,
                             `PathwayName`=pathwayName,
                              Pharmacokinetics=pharmacokinetics,
                              Pharmacodynamics=pharmacodynamics )
                 }) %>%
                reduce(rbind)

這四個步驟便可以完成這件事情,這過程很重要的便是搞清楚map, pmap, reduce,他們要怎麼接受參數和處理,可以因此更清楚在R裡面list和data.frame的特性。不過在處理的過程也會使用到stringr的函數,來處理字串的問題。

閱讀參考:

How can I read in multiple files
Reading and combining many tidy data files into R

使用rprojroot改善麻煩的檔案路徑問題,以子資料夾結構取而代之

screenshot.png

在Jenny Bryan登高一呼,希望有人解決這種資料夾路徑總是要重設的問題時,這個rprojroot 便誕生了,他主要解決了路近依賴的問題,而使用sub-directory結構取而代之,本身是個簡單的package,但算是為培養好的reproducible coding踏出一步。

rprojroot要發揮作用的話,要建立在兩個使用者的好習慣:
第一個每個專案有固定的子資料夾結構,這我個人覺得很重要,專業素養便是從檔案管理做起,可以看下面的文章,對於如何管理好專案,裡面的建議頗受用

Managing a statistical analysis project – guidelines and best practices
R Workflow: Slides from a Talk at Melbourne R Users
Dynamic documents with R and LATEX as an important part of reproducible research
How to efficiently manage a statistical analysis project?

第二個願意系統性的寫好代碼(可能30秒能搞定的代碼,必須多花兩倍時間,為了代碼的嚴謹)

     假如使用者有上面這兩個習慣,這個包便可以幫助你的R代碼進步一點點,此包主要使用一個概念,便是為專案root資料夾設立“criteria”,藉由檢查當前的資料架內結構,是否為特定的形式如r package、r project,或是包含特定檔名的檔案。藉由這個criteria,來確定root路徑,而剩下的檔案便是用相對路徑來表示

這邊有示範的代碼:

library(rprojroot
#Make Criteria
root <- has_file("README.md")

#Fine the file
dir <- find_root(root)
data.path <-find_root_file("Data", criterion = root)

#True or False criteria
file.exists(find_root_file("Data", criterion = has_file("README.md")))

這邊則是我的放代碼的資料夾結構:
screenshot.png

所以使用這個包來設定檔案路徑,未來只要整個資料夾給要合作的對象,基本上,重新執行就不會發生問題。

在dplyr的pipe中,在mutate裡使用cut來將連續變數依據區間分塊,形成新的變數

     本來在dplyr的函數中是不支持cut函數的(cut為R base函數),但將連續變數依照特定區間分塊貼標籤這使用場景太常見了,要把班上成績,依據80,85,90分別給予C,B,A等操作,且在stackover可以看到非常多類似的問題如下:
Create column with grouped values based on another colum

R dplyr – categorize numeric variable with mutate

Is cut() style binning available in dplyr?

cut function not working in dplyr, but works outside

applying cut within dplyr

screenshot.png

cars %>% mutate(type = cut(speed,c(0,15,30),c("slow","fast")))

screenshot.png

使用purrr package學functional programming的觀念(ㄧ):簡介

purrr package 是Hadley Wickham和Lionel Henry所開發的,補強R base中原本的fucntional programming tool,帶入其他語言的特性,整體的風格是使用underscore.js, lodashlazy.js的語法。

R base語法中本身就有很多函數帶有部分Functional Programming意味的風格,如any,which,do.call,apply, sapply, vapply,lapply等等,只是沒有好好的統一,Hadley Wickham恰好把這部分補足了, 且裡頭的函數都是所謂的type-stable pure function導向,正如其package的名稱purrrr。

functional programming 其實是奠基在lambda calulus的觀念上,專注在四個基石上:資料(numbers, strings)、變數(函數的參數)、函數、函數應用(如何用函數修飾函數),概念上有點像是把Object-oriented programming中,function變成一個object,且專注於function功能的實現,用function去處理另一個function,去減少loop傳統寫法的使用,和重複性argument使用的麻煩。

基本上,purrr package中的函數便是依照許多其他functional programming導向的語言如scala、erlang、lisp等等特性來做的,簡單有這幾類的函數:

Category    Related Function    
Map      map(), map_chr(), map_int(), map_dbl(), map_df(), walk()
Reduce      reduce(), reduce_right()
Search      contains(), detect(), detect_index()
Filter   keep(), discrad(), every(), some()

而觀念上的一些使用如recursion寫法、expression的應用等,都可以簡化和提高代碼的效果。

這整個項目的特性:
1. 將函數輸入輸出的特性統一且分開,相對於原生的函數,常常因為輸入的資料格式不同,也會輸出不同的結果,這某些方面的“聰明”,在purrr中都簡潔化,但相對的就會產生很多相同功能,但對應不同資料格式的函數如map_chr(), map_dbl()等。
2. 將pipe %>% 的適用性導入purrr中
3.  改善function error handling的機制(使用safely())

這邊收集相關的發佈資料,能更理解這purrr項目相關進展的脈絡!(把purrr package稱為項目,是覺得這package的開發很有野心,想要把R語言在往可以適應於分散式運算系統,且整個代碼也是在一整個tidyverse下的邏輯開發的,很有一貫性)
purrr 0.1.0 2015/9/29

purrr 0.2.0 2016/1/06

畫3D scatter plot的工具:plotly, scatterplot3d

3D scatterplot的使用可以用來顯示如Principle Component Analysis的結果,目前在R中可以畫3D圖形的package其實不多,scatterplot3dplotly是唯二可以做這件事情的,scatterplot3d可以做出傳統使用R lattice所展現出來的繪圖功能,基本功能完善,但無法即時動態的去旋轉圖,這其實是畫3D Scatterplot最想做的事情,而這部分plotly提供非常好的R接口可以完成,plotly畫出來的圖形是可以即時去調整他的角度和一些顯示的細節,在探索一些三維下有無cluster現象時,真的很方便!

這邊附上官網的範例,非常簡單上手

library(plotly)

mtcars$am[which(mtcars$am == 0)] <- 'Automatic'
mtcars$am[which(mtcars$am == 1)] <- 'Manual'
mtcars$am <- as.factor(mtcars$am)

p <- plot_ly(mtcars, x = ~wt, y = ~hp, z = ~qsec, color = ~am, colors = c('#BF382A', '#0C4B8E')) %>%
  add_markers() %>%
  layout(scene = list(xaxis = list(title = 'Weight'),
                     yaxis = list(title = 'Gross horsepower'),
                     zaxis = list(title = '1/4 mile time')))

幾乎語法上跟ggplot2類似,只是在做輸出時候的layout會嚴謹一點
screenshot.png

經典閱讀:Computer Age Statistical Inference: Algorithm, Evidence, And Data Science

screenshot.png

越來越發現複雜的學科,其實很難單純有選修一兩門課來建立清晰明白的思路,反而是必須依靠自己的廣泛閱讀,而像統計學這門“很接地氣”的學科,常常會因所解決領域的問題有些許不同,更是容易在自我學習中迷失方向,所以後來找一兩本非常經典的書來閱讀,才會是系統性建立知識的好習慣,這本書Computer Age Statistical Inference: Algorithms, Evidence, And Data Science熱騰疼剛完成,由劍橋出版社出版,將會是我這半年希望可以努力啃完的好書,從第一面開始就能感受到那種字裡行間的智慧,很令人享受,尤其是讀這種天才教授們所精雕的作品,細膩的科學真的不是文鄒鄒的,讀起來不好理解其實是作者的問題,希望可以有這種功力將複雜的事物細細撥開的能力,並且講述給別人理解!

這是一本由史丹佛統計學家Bradley EfronTrevor Hastie所寫的,兩位統計學家都是如神級般的存在,很多目前常用的統計方法是由他們所提出的,看這兩位大師的履歷,佩服他們努力地把統計方法一步步應用到各式生活裡的資料,並且不斷改善。在這本書中,他們回朔一百年來統計上各種觀念的改變,然後點出電腦計算將如何的改變統計方法,且開啟新的紀元(機器學習、人工智能等等)
“…the role of electronic computation is central to our story. This doesn’t mean that every advance was computer-related. A land bridge had opened up to a new continent but not all were eager to cross.”
這段話很棒,資訊科技的不斷進步,某種新大陸似乎可以藉由搭者這座“計算機”之橋通往,但不是所有人都願意往前走!
書中的內容很系統性的(不是傳統教科書那種系統性,是很“簡潔”且有歷史意義的方式,並且想要傳達出各種方法是屬於何種脈絡下的產物)整理前半世紀所發展的方法:Empirical Bayes, James-Stein estimation. bootsrap, poportional hazrd model, large-scale hypothesis testing, machine learning algorithms (the result of “cross bridge”),另外書中也展開一場很細膩的探索,在frequentist , Bayesian, fisherian三個不同系統中的inference概念,比較其間各自的優點和缺點。
本書主要分成三個大方向:
  1. 傳統經典的統計檢定
  2. 早期電腦時代所衍生的方法
  3. 新時代大資料量下的檢定方式
本書大部分內容皆涵蓋非常高知識密度的東西,蠻期待閱讀的!
話說感覺史丹佛大學統計系非常可怕,擠滿者大師,最近R世界裡面開疆闢土出很多經典package的Hadley Wickham 也要去史丹佛大學教書了,很令人興奮的一個地方。
screenshot.png

Rstudio IDE 1.0可以提升效率的功能

這個月Rstudio剛發布其1.0版本,除了在一些大家正視的熱點如專案管理、封包搭建和notebook互動式coding等等外,其實Rstudio IDE還有做了許多貼心可以改善寫代碼或是分析時候的功能

Tearable Panes

可以拖移的代碼控制板,之前不能的時候,就無法發揮雙螢幕的功效,但現在可以多個代碼穿就顯得非常無痛和舒服了!要是有可以上下或是左右split的功能,就太棒了!tip_tearable.gif

Command History

代碼歷史記錄回朔功能,這部分之前必須用鍵盤上鍵來實現,而Cmd + ⇑ 就可以一次閱覽多次的歷史輸入

tip_console.gif

 

History Panel

代碼歷史控制板,提供對於歷史代碼輸入的搜尋功能tip_history.gif

 

Rename in Scope

提供一次更改整個代碼中某一變數的能力(Rename in Scope),這功能非常強大,也是….一直很痛的問題,還在想說有什麼方法可以改善
tip_rename_in_scope.gif

Jump to Function Definition

F2快捷鍵直接跳進function中的代碼,這功能很棒,很多時候就是…不停的在修各種統計函數的細節
tip_goto_func.gif

Code Snippets

代碼的snippets設定,這功能在大部分其他語言IDE也有,算是可以大大提升速度的功能tip_code_snippet.gif

這邊有 Rstudio release note!其實他們做的gif真的蠻清楚的,一眼就知道可以幹麻!

tidy data- 怎麼樣的資料才是“整齊乾淨的”

在一開始使用ggplot2時,一定會被其要的data input格式所困擾,其實那是養成好習慣和觀念的開始,在Hadley Wickham 最近剛完成的R for Data Science  裡頭很重要的一件事就是使用tidy的data來“往返”整個資料分析的過程,讓各個package之間的I/O能完美的銜接,且從根源解決常因資料格式不同而要從新處理的問題。

關於怎樣的資料才算是“tidy”的呢?這篇論文中Hadley Wickham已經非常仔細地探討了,有興趣可以進一步點開。

三個重點決定了一筆資料的乾淨與否

  1. 每行就代表一個變數(量測的性質)
  2. 每一列為一個觀察點(樣本、或是病人、檢體..)
  3. 每一個table就是在講一類事情

 

 

髒的資料,無法直接filter,需要進一步整理
screenshot.png
乾淨的資料,可直接filter,甚至在excel中就可以處理
screenshot.png
相關跟處理tidy data的包,有下面這些:
  • magrittr: 主要將pipe的概念帶入R中,使用%>%符號
  • dplyr: 一整套處理data.frame的包
  • tidyr: 將資料整理成tidy form的包,其中gather, spread是最關鍵的函數
  • broom: 將R內建常見的統計輸出整理成tidy form

關於R function的幾點小事

寫好function一點都不簡單,這邊分享和整理在撰寫function要思考的事情。而這邊主要是講R語言裡的function,但其實很多觀念是可以供通的,可以分別為:一個function的組成什麼是好的function各種function的類型何時要寫function撰寫function的流程。

function是一連串expression包裝(encapsulate)起來而成,expression的多寡決定一個function可以處理事情的多寡,恰當好處的功能,是一個好function的關鍵,能解決問題的function就是有用的,而function裡面細節的設計則決定會不會讓使用者愛用。

首先,理解在R裡面,一個 function基本由什麼組成:

  • 參數arguments
    • 可以使用formals()檢視
  • 主體的代碼body:
    • 可以使用body()檢視
  • 函數中使用變數對應的“變數空間”environment:
    • 可以使用environment()檢視

再來理解,怎麼樣是一個好的function:

  1. 能解決當下問題(最重要)
  2. 易讀性
  3. 輸入輸出高穩定性(stable / pure function )
  4. 對於錯誤訊息的處理(error handling)
  5. 減少對於hidden argument的依賴(比如環境變數等等)

特殊的Function:

  • infix function:(或可以稱為binary operator)
    • 函數出現在兩個參數之間,像是%%, %*%, %/%, %in%, %o%, %x%.
    • 可以這樣來定義:

[ code language="r" ]

%g%

paste(left, right)

}

[/code ]

  • replacement function
    • 函數只接改變其參數的數值。
  • pure function
    • 沒有side effect,輸入跟輸入乾淨
  • function with side effect
    • 會直接改變環境中的設定library()、setwd()、 Sys.setenv()Sys.setlocale()plot()、write()、write.csv()saveRDS()、options()par()

 

何時是寫function的好時機呢?

這邊可以參考這本Executive Data Science  裡面的說法,當你開始寫個code來完成一件事情的時候,這時候就要仔細記錄使用場景,要第次用這個代碼來完成事情的時候,便可以考慮一下撰寫function,當第次依舊需要再做一樣的事情時,就可以規劃來把function包成packages。

 

撰寫function的基本流程

  1. 先定義好自己function要完成的事情
  2. 先用一連串的expression來做快速的prototype
  3. 將其包裝成function
  4. 進行重構(refactoring),盡量讓代碼完成最小單位的事情,過多的複雜會使代碼難以閱讀和管理
  5. 進行vectorize,盡量讓函數的輸入維持成單個數入,避免過度的使function的表現更好預測
  6. 設定default值

使用ggplot2: stat_function 簡化函數畫圖

在R裡面有很多可以用來畫函數的方法,像是curve就是一個簡單懶人函式,其實ggplot2裡頭有一個超好用的函式,不需要像一般ggplot2必須輸入一個data.frame的data.set,只要把想要畫圖的function定義好即可。這邊有documentation的連結

ggplot()+stat_function( aes(x=0:2), fun = 自訂函式,  args = list())

aes內的x設定要帶入函數的範圍,也是圖x軸的邊界,fun後面則放入想要畫圖的函式,args裡面可以把原本function中其他的參數指定好,這樣就可以開始畫圖了,下面用簡單的例子展現一下他的方便!

 

screenshot.png

screenshot.png