第4講:複雜數據處理和分析聽課及實踐筆記

距離上一講已經將近一個多月,這次@猴子 老師憋了很多內容,光直播就進行了兩個多小時,布置的作業也非常有分量,在完成作業之前,很有必要對講課內容作一個筆記整理,算是在完成作業之前的一個複習。

本次課程主要講了四塊內容,解決了一個問題,四塊內容如下:

  1. 如何編寫函數;

  2. 用dpylr,ggplot2包進行數據處理和展示;

  3. 如何編寫業務模塊;

  4. 如何調試代碼。

解決的一個問題是用nycflight13包中的航班數據分析航班航行距離與延誤時間之間的關係。

1 如何編寫函數

在編寫函數之前,首先要知道什麼是函數,按照數學上的定義,

如果M是一個實數集,而對於每一個xin M都對應一個確定的值y,則程變數y=f(x)為定義在實數集M上的變數x的函數。

我們可以更加抽象一點的理解,x是輸入,y是輸出,而f則是x與y之間的一種映射,從編程的角度f就是你對輸入x進行一系列的處理使得最終輸出你想要的y,我們可以稱之為功能。

於是函數可以這樣來定義:

函數是實現一定功能的模塊,通常接受一定的輸入,並將相應的功能作用在輸入上面,形成相關結果輸出。

在R語言中按照一定的格式來編寫函數:

my_fun <- function(arg1, arg2){bodyreturn(data)}

其中:

  • my_fun 為函數名稱;

  • function()為R中定義函數的關鍵字;

  • arg1和arg2是需要輸入的參數;

  • body裡面代表需要實現的具體功能,比如對輸入參數的處理分析,或者數據可視化等等;

  • return是R中的關鍵字,表示有數據需要返回;

  • data是返回的數據,可以理解為狹義的輸出,有時候函數並不一定返回數據。

函數定義好以後需要編譯,在命令好窗口輸入以後可以直接回車編譯,如果編譯通過在當前項目的運行環境(Global Environment)下會產生相應的函數。

編譯完成以後便可以在當前環境中使用:

> d <- subtract(16,1)> d[1] 15>

接下來是關於R語言的流程式控制制,這部分內容在之前的《R語言實戰》第一部分第五章複習筆記 - 知乎專欄已經講過,不過考慮到課程的完整性,這裡再多說幾句。

R語言作為一種編程語言,和其它編程語言(Java,C/C++,Python等)一樣,也對流程式控制制有著很好的支持。通常,在計算機編程當中存在以下兩種流程式控制制:

  1. 循環

  2. 條件選擇

都比較好理解,循環就是讓計算機重複做某一件事情,這是計算機最擅長的,舉一個簡單的例子,如果我們要列印「Hello World」十次,不採用循環語句的做法是:

> print("Hello World")[1] "Hello World"> print("Hello World")[1] "Hello World"> print("Hello World")[1] "Hello World"> print("Hello World")[1] "Hello World"> print("Hello World")[1] "Hello World"> print("Hello World")[1] "Hello World"> print("Hello World")[1] "Hello World"> print("Hello World")[1] "Hello World"> print("Hello World")[1] "Hello World"> print("Hello World")[1] "Hello World">

顯然,這樣重複的輸入比較麻煩,即便每次都可以複製粘貼,但是如果重複的次數是100次、1000次,那麼就沒有可操作性了。採用循環語句可以這麼寫:

> for(i in 1:10){+ print("Hello World")+ }[1] "Hello World"[1] "Hello World"[1] "Hello World"[1] "Hello World"[1] "Hello World"[1] "Hello World"[1] "Hello World"[1] "Hello World"[1] "Hello World"[1] "Hello World">

上面的10表示循環次數,你可以簡單修改一個數字便可以獲得不同的循環次數。以上是for語句的循環實現,此外還有一種while語句的實現如下:

> i <- 10> while(i > 0){+ print("Hello World")+ i <- i - 1+ }[1] "Hello World"[1] "Hello World"[1] "Hello World"[1] "Hello World"[1] "Hello World"[1] "Hello World"[1] "Hello World"[1] "Hello World"[1] "Hello World"[1] "Hello World">

while語句的循環邏輯是首先判斷條件是否成立,比如? i > 0,如果i大於零則執行後面的語句,如果i不大於零,則循環退出。在使用while語句的時候需要注意的是在執行主體裡面需要對前面的判斷條件所涉及的參數進行修改,否則有可能導致死循環,條件判斷條件永遠為真。

兩種循環的模板如下:

如果能夠理解while語句,其實條件語句就更好理解了。條件語句主要實現程序執行流程的選擇,就好比你站在一個丁字路口,向左還是向右,如果向左的條件達成了就向左,否則就向右。在R語言當中主要通過if-else語句、ifelse語句以及switch語句來實現條件選擇的功能。

2 數據處理:dpylr,ggplot2

以下跟著課件的順序重點講講如果用dpylr和ggplot2處理和分析並展示數據。

2.1 理解數據

理解數據就是先了解一下數據的大致情況,數據的觀測數,觀測變數名,數據類型等等,對所要分析的數據有一個大致的了解,並初步判斷在哪些角度著手可以獲得較高的分析價值。本講的分析對象是航班數據,觀測數總計336, 776條,17個變數,數據類型設計時間、字元串等。

> install.packages("dplyr")trying URL "https://mirrors.tuna.tsinghua.edu.cn/CRAN/bin/windows/contrib/3.3/dplyr_0.5.0.zip"Content type "application/zip" length 2557208 bytes (2.4 MB)downloaded 2.4 MBpackage 『dplyr』 successfully unpacked and MD5 sums checkedThe downloaded binary packages are in C:UserspanhuayinAppDataLocalTempRtmp6feVludownloaded_packages> library(nycflights13)> library(dplyr)載入程輯包:『dplyr』The following objects are masked from 『package:stats』: filter, lagThe following objects are masked from 『package:base』: intersect, setdiff, setequal, union> flights# A tibble: 336,776 × 19 year month day dep_time sched_dep_time dep_delay arr_time <int> <int> <int> <int> <int> <dbl> <int>1 2013 1 1 517 515 2 8302 2013 1 1 533 529 4 8503 2013 1 1 542 540 2 9234 2013 1 1 544 545 -1 10045 2013 1 1 554 600 -6 8126 2013 1 1 554 558 -4 7407 2013 1 1 555 600 -5 9138 2013 1 1 557 600 -3 7099 2013 1 1 557 600 -3 83810 2013 1 1 558 600 -2 753# ... with 336,766 more rows, and 12 more variables: sched_arr_time <int>,# arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,# origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,# minute <dbl>, time_hour <dttm>>

2.2 數據導入

事實上,在本講當中,安裝完nycflights13包並載入已經完成了數據導入的過程,在其它情況下可能有更複雜的數據導入過程,可以具體參考《R語言實戰》第二章的內容。

2.3 數據預處理

數據預處理的主要步驟如下:

子集的選擇主要基於分析需求所確定的目標,在本講當中主要分析航班航行距離與延誤時間的關係,與此無關的變數可以剔除。與此相關的主要有以下幾個欄位:

> myFlights <- select(flights, year,month, day,+ dep_delay,arr_delay,distance,dest)> myFlights# A tibble: 336,776 × 7 year month day dep_delay arr_delay distance dest <int> <int> <int> <dbl> <dbl> <dbl> <chr>1 2013 1 1 2 11 1400 IAH2 2013 1 1 4 20 1416 IAH3 2013 1 1 2 33 1089 MIA4 2013 1 1 -1 -18 1576 BQN5 2013 1 1 -6 -25 762 ATL6 2013 1 1 -4 12 719 ORD7 2013 1 1 -5 19 1065 FLL8 2013 1 1 -3 -14 229 IAD9 2013 1 1 -3 -8 944 MCO10 2013 1 1 -2 8 733 ORD# ... with 336,766 more rows>

這裡補充以下dplyr包中select()函數的用法,在Rstudio中查閱help(select),dplyr包的文檔對它說明如下:

這個說明還涵蓋了rename()函數,一會兒還會用到,這裡順便一次帶過。首先select()函數傳入兩組參數,首先.data指定一個待數據表格,後面「...」表示用逗號分離的無引用的表達式列表,可以是數據表中的變數名,如果是正的表示你要選擇的,如果前面加一個負號表示你要捨棄的。比如:

此外,表達式還支持模糊查詢,比如:

help文檔也給出了很多的例子:

rename()函數與select()函數主要的區別在於前者保留所有變數,後者只保留你提到的變數。下面將dest變數重命名為destination。

> myFlights <- rename(myFlights, destination = dest)> myFlights# A tibble: 336,776 × 7 year month day dep_delay arr_delay distance <int> <int> <int> <dbl> <dbl> <dbl>1 2013 1 1 2 11 14002 2013 1 1 4 20 14163 2013 1 1 2 33 10894 2013 1 1 -1 -18 15765 2013 1 1 -6 -25 7626 2013 1 1 -4 12 7197 2013 1 1 -5 19 10658 2013 1 1 -3 -14 2299 2013 1 1 -3 -8 94410 2013 1 1 -2 8 733# ... with 336,766 more rows, and 1 more variables:# destination <chr>

接下來刪除缺失數據,這次採用的是dplyr包中的filter()函數。老規矩,先看看文檔裡面是怎麼說的。

簡單的描述就是返回滿足條件的數據行,對於不滿足條件的數據會進行整行的刪除。傳入的數據與select()函數相似,首先是需要處理的數據表,後面的一組參數有所不同,是一個邏輯判斷表達式。R的邏輯運算符可以參考《R語言實戰》第四章的內容,詳見下表:

值得一提的說,當有多個邏輯表達式時,可以用逗號隔開,不過R將多個表達式按照和(&)來對待,即只有滿足所有邏輯表達式時才會被保留。

> myFlights <- filter(myFlights,+ !is.na(dep_delay),+ !is.na(arr_delay))> myFlights# A tibble: 327,346 × 7 year month day dep_delay arr_delay distance destination <int> <int> <int> <dbl> <dbl> <dbl> <chr>1 2013 1 1 2 11 1400 IAH2 2013 1 1 4 20 1416 IAH3 2013 1 1 2 33 1089 MIA4 2013 1 1 -1 -18 1576 BQN5 2013 1 1 -6 -25 762 ATL6 2013 1 1 -4 12 719 ORD7 2013 1 1 -5 19 1065 FLL8 2013 1 1 -3 -14 229 IAD9 2013 1 1 -3 -8 944 MCO10 2013 1 1 -2 8 733 ORD# ... with 327,336 more rows

filter()函數一共剔除了8255行數據。關於查找數據的例子,除了help文檔中的,還可以再舉其它幾個例子,比如查找12月30日的航班:

> filter(myFlights, month == 12, day == 30)# A tibble: 953 × 7 year month day dep_delay arr_delay distance destination <int> <int> <int> <dbl> <dbl> <dbl> <chr>1 2013 12 30 2 4 1576 BQN2 2013 12 30 24 17 1617 PSE3 2013 12 30 28 16 1598 SJU4 2013 12 30 118 112 187 BOS5 2013 12 30 210 214 1028 PBI6 2013 12 30 -6 -12 529 CLT7 2013 12 30 -1 20 1400 IAH8 2013 12 30 -1 25 997 TPA9 2013 12 30 -8 -13 96 PHL10 2013 12 30 -7 -2 1028 PBI# ... with 943 more rows

延誤和出發均大於2小時的航班:

> filter(myFlights, arr_delay > 120 & dep_delay > 120)# A tibble: 8,335 × 7 year month day dep_delay arr_delay distance destination <int> <int> <int> <dbl> <dbl> <dbl> <chr>1 2013 1 1 853 851 184 BWI2 2013 1 1 144 123 200 BOS3 2013 1 1 134 145 1416 IAH4 2013 1 1 290 338 1134 OMA5 2013 1 1 260 263 266 BTV6 2013 1 1 131 127 2475 LAX7 2013 1 1 129 151 765 BNA8 2013 1 1 155 166 277 RIC9 2013 1 1 157 174 213 DCA10 2013 1 1 216 222 708 SAV# ... with 8,325 more rows

最後是數據預處理的最後一步,給即將接入下一道工序的數據排個序。dplyr包的arrange()函數用於排序也非常簡單,默認的是升序,如果要降序的話在變數名前面加上desc函數。

本講暫按降序考慮,代碼如下:

> myFlights <- arrange(myFlights, desc(dep_delay))> myFlights# A tibble: 327,346 × 7 year month day dep_delay arr_delay distance destination <int> <int> <int> <dbl> <dbl> <dbl> <chr>1 2013 1 9 1301 1272 4983 HNL2 2013 6 15 1137 1127 483 CMH3 2013 1 10 1126 1109 719 ORD4 2013 9 20 1014 1007 2586 SFO5 2013 7 22 1005 989 589 CVG6 2013 4 10 960 931 1005 TPA7 2013 3 17 911 915 1020 MSP8 2013 6 27 899 850 2454 PDX9 2013 7 22 898 895 762 ATL10 2013 12 5 896 878 1085 MIA# ... with 327,336 more rows

2.4 數據計算

這一步才是真正發揮dplyr包強大功能的時候。而且不得不提的是數據計算的一種常規套路:

一共分三步走:

  1. 數據分組:可以以某一個變數將數據進行分組,在本例中,可以以不同目的地對數據進行分組,假設有10個不同的目的地,此時有十組不同的數據;

  2. 應用函數:對不同組的數據應用函數取得需要的統計指標,比如求各分組的行數、平均值、請標準差等等;

  3. 組合結果:將第二步當中計算後的返回結果進行按第一步當中的分組進行組合。

以下結合講義舉一個北京、上海航班延誤時間和距離的例子。原始預處理後的表格如下:

第一步,按不同的目的地進行分組,分組後數據如下:

第二步,對分組後的數據應用函數,求取平均延誤時間和平均航行距離,計算過程如下:

第三步,將分組後的數據進行組合,形成新的數據表:

以上三個步驟在dplyr包中有相應的函數進行處理。首先分組由group_by()函數進行處理,help文檔如下:

group_by()函數按照第二次參數的變數名對第一個參數的數據表進行分組,本講當中首先對數據按不同destination進行分組,代碼如下:

> by_dest <- group_by(myFlights, destination)> class(by_dest)[1] "grouped_df" "tbl_df" "tbl" "data.frame"> by_destSource: local data frame [327,346 x 7]Groups: destination [104] year month day dep_delay arr_delay distance destination <int> <int> <int> <dbl> <dbl> <dbl> <chr>1 2013 1 9 1301 1272 4983 HNL2 2013 6 15 1137 1127 483 CMH3 2013 1 10 1126 1109 719 ORD4 2013 9 20 1014 1007 2586 SFO5 2013 7 22 1005 989 589 CVG6 2013 4 10 960 931 1005 TPA7 2013 3 17 911 915 1020 MSP8 2013 6 27 899 850 2454 PDX9 2013 7 22 898 895 762 ATL10 2013 12 5 896 878 1085 MIA# ... with 327,336 more rows

分組後一共有104組數據。

dplyr包中的summarize()函數完成剩下應用函數和組合結果兩步。summarize()函數的help文檔如下:

summarize()函數傳入的第一個參數是分組後的數據表,同時是group_by()函數返回的grouped_df類型的數據對象,在上一步當中特地用class()函數查看了下by_dest的類型。

後面的參數是各種需要計算的表達式,表達式的通用格式如下:

variable = appliedFunction(variables in the original grouped_df object)

等號左邊的是summarize()所返回對象數據表中的變數名,appliedFunction是你需要計算的統計量,比如求平均就是mean(),標準差就是sd(),appliedFunction()中的參數就是在分組後的數據表中你要了解的變數名稱,比如本例我們隊飛行距離感興趣就是distance, 對到達延誤時間感興趣就是arr_delay。具體代碼如下:

> delay_Tab <- summarise(by_dest,+ count = n(),#統計各分組目的地的航班數+ dist = mean(distance, na.rm = TRUE),+ delay = mean(arr_delay, na.rm = TRUE))>

原課件中summarize()函數是賦值給delay,這容易和summarize()函數當中定義的delay向混淆,其實函數中的delay只是函數返回數據表中的一個變數,為了避免混淆,這裡取了delay_Tab以示區分,具體可以看下面的代碼:

> head(delay_Tab)# A tibble: 6 × 4 destination count dist delay <chr> <int> <dbl> <dbl>1 ABQ 254 1826.0000 4.3818902 ACK 264 199.0000 4.8522733 ALB 418 143.0000 14.3971294 ANC 8 3370.0000 -2.5000005 ATL 16837 757.1383 11.3001136 AUS 2411 1514.2522 6.019909> class(delay_Tab)[1] "tbl_df" "tbl" "data.frame">

最後再利用filter()函數移除一些噪音數據,即數據量太少的組,這裡設置為20。

delay_Tab <- filter(delay_Tab, count > 20)

至此,數據計算的步驟完成,為了使得代碼更加的整潔,我們嘗試使用一個叫做管道的工具。在介紹管道之前,我們試著用一行代碼完成上述數據計算過程:

> delay_Tab2 <- filter(summarise(group_by(myFlights,destination), count = n(), dist = mean(distance, na.rm = TRUE), delay = mean(arr_delay, na.rm = TRUE)),count > 20)

以上是多層函數的嵌套,即後面一個函數的輸出為前面一個函數的輸入,這樣可以減少不必要中間變數的引入,為了使得層次更加分明一些,表達更加簡潔一些,可以採用管道,格式如下:

%>%

管道的思路很簡單,將左邊的值管道輸出為右邊調用的函數的第一個參數。舉個例子:

> set.seed(1234)> rnorm(3) %>% sum()[1] 0.1548047> set.seed(1234)> rnorm(3) [1] -1.2070657 0.2774292 1.0844412

此處第二行就是講rnorm(3)生成的三個隨機數作為sum()函數的輸入進行求和,下面我設定相同的隨機數種子進行了驗證。

如果採用管道,本例的數據計算可以用以下代碼實現:

delay_Tab <- myFlights %>% group_by(destination) %>% summarise( count = n(), dist = mean(distance, na.rm = TRUE), delay = mean(arr_delay, na.rm = TRUE) ) %>% filter(count > 20)

2.5 數據顯示

本次的數據顯示主要採用ggplot2包。ggplot2包功能強大,內容包羅萬象,本文主要結合第四講的內容簡單講講如何用ggplot2包中的函數進行畫圖,向具體了解的可以進一步閱讀該包作者Hadley Wichham的專著:Library Genesis: Hadley Wickham (auth.)

或者關注github ggplot2的代碼庫(tidyverse/ggplot2),了解最新的開發情況。

首先,需要安裝並導入ggplot2包:

install.packages("ggplot2")library(ggplot2)

調用ggplot()函數進行繪圖,ggplot()函數主要調用結構如下:

首先用 data 參數設定好數據源,然後根據需要用ggplot2()包中的其他函數往圖像當中添加圖層。本講中首先繪製了航程和延誤時間的散點圖,然後擬合了一條平滑曲線,代碼如下:

view <- ggplot(data = delay_Tab) + geom_point(mapping = aes(x = dist, y = delay))+ geom_smooth(mapping = aes(x = dist, y = delay))

3 編寫業務模塊

本質上這是一個文件管理的問題,包括數據文件、代碼文件、輸出文件、日誌文件等等,甚至還可以再細分,比如是實現業務邏輯的文件、實現數據輸入輸出的文件、實現視圖輸出的文件等等,每個人有每個人的風格,但是模塊化的方法論是一通百通的。總之,大家多實踐,多敲代碼,如果一開始不知道怎麼弄的話,按照@猴子的方法應該是個不錯的選項。

4 代碼調試

這也是一個與實踐緊密結合的話題,也是一個很大的話題,當下,首先應該熟悉一下Rstudio的一些調試手段。

最後,本講內容還有兩個作業,首先關於翻譯,我覺得翻譯下面這篇文章:

bin-summarise-smooth: A framework for visualising large data: 合併-分組應用-平滑:一種大數據可視化的框架

然後關於數據,我想以IMDB 5000 Movie Dataset選定一些角度進行分析,具體問題還沒有想好,如果有好的建議歡迎留言。


推薦閱讀:

LightGBM調參指南(帶貝葉斯優化代碼)
R|ggplot2(一)|一個完整的繪圖流程
【譯文】R語言不平衡數據分類指南
【機器學習】確定最佳聚類數目的10種方法

TAG:R编程语言 | 数据分析 | ggplot2 |