package: data.table

個人覺得data.table是資料整理上很強大的一個package,尤其當你面對的是一兩百萬筆甚至是更大量的巨量資料。
在對資料進行修改或運算時,由於data.table是採直接對資料本身做運算,不創建複本,因此相較於基本的data.frame格式,data.table在面對巨量的資料時,運算速度依舊是非常的快速!

簡單講一下data.frame跟data.table資料運算時的結構差異

data.frame運算的結構為 DF[ i, j ]
其中i為列的條件,j為欄的條件

data.table運算的結構為 DT[ i, j, by ]
i為列的篩選條件,j為欄的篩選條件或運算,除此之外,還多了by這個部分,可以依據不同的分類群來整合資料,常用excel樞紐分析做的事情,data.table也都可以輕易做到!

廢話不多說,直接進入操作的部分吧~

資料下載

以下教學使用的資料為GBIF上的The Taiwan Breeding Bird Survey Data資料集 可以點選這裡下載
進到網頁中,點選紅色框框內的DOWNLOAD就可以了,下載資料需要登入GBIF噢! 下載頁面

環境設定

安裝以及呼叫data.table等需要用到的套件進來
如果已經有安裝過套件了,可以略過安裝的步驟,直接用library()叫進來就可以了,
這邊也一併介紹另一個在寫程式碼的時候很常用的管線(pipe),屬於magrittr這個套件

安裝套件

list.of.packages <- c("data.table", "magrittr", "tidyr")
new.packages <- list.of.packages[!(list.of.packages %in% installed.packages()[,"Package"])]
if(length(new.packages)) install.packages(new.packages)

讀入套件

library(data.table)
library(magrittr)
library(tidyr)

資料匯入

data.table::fread()函數來讀入資料,fread()可以讀csv或txt,這邊以從GBIF下載的BBS Taiwan資料來做示範

BBS <- fread("F:/R markdown/BBS2009-2015/occurrence.txt",
      sep = "\t", encoding = "UTF-8")

fread()裡面sep =用來設定檔案的分隔符號,這邊的"\t"代表tab鍵,如果是讀csv的話通常用","做分隔
encoding =則用來設定讀入檔案的編碼
練習的時候記得要把fread()內檔案路徑的部分改成自己放置檔案的路徑喔!

資料清理

先用str()看一下原始資料長什麼樣子…
在檢視資料的時候除了str()summary()names()也都是很常用指令

str(BBS)
## Classes 'data.table' and 'data.frame':   325938 obs. of  235 variables:
##  $ gbifID                             : int  1658885392 1658885393 1658885394 1658885395 1658885396 1658885397 1658885398 1658885399 1658885400 1658885401 ...
##  $ abstract                           : logi  NA NA NA NA NA NA ...
##  $ accessRights                       : logi  NA NA NA NA NA NA ...
##  $ accrualMethod                      : logi  NA NA NA NA NA NA ...
##  $ accrualPeriodicity                 : logi  NA NA NA NA NA NA ...
##  $ accrualPolicy                      : logi  NA NA NA NA NA NA ...
##  $ alternative                        : logi  NA NA NA NA NA NA ...
##  $ audience                           : logi  NA NA NA NA NA NA ...
##  $ available                          : logi  NA NA NA NA NA NA ...
##  $ bibliographicCitation              : logi  NA NA NA NA NA NA ...
##  $ conformsTo                         : logi  NA NA NA NA NA NA ...
##  $ contributor                        : logi  NA NA NA NA NA NA ...
##  $ coverage                           : logi  NA NA NA NA NA NA ...
##  $ created                            : logi  NA NA NA NA NA NA ...
##  $ creator                            : logi  NA NA NA NA NA NA ...
##  $ date                               : logi  NA NA NA NA NA NA ...
##  $ dateAccepted                       : logi  NA NA NA NA NA NA ...
##  $ dateCopyrighted                    : logi  NA NA NA NA NA NA ...
##  $ dateSubmitted                      : logi  NA NA NA NA NA NA ...
##  $ description                        : logi  NA NA NA NA NA NA ...
##  $ educationLevel                     : logi  NA NA NA NA NA NA ...
##  $ extent                             : logi  NA NA NA NA NA NA ...
##  $ format                             : logi  NA NA NA NA NA NA ...
##  $ hasFormat                          : logi  NA NA NA NA NA NA ...
##  $ hasPart                            : logi  NA NA NA NA NA NA ...
##  $ hasVersion                         : logi  NA NA NA NA NA NA ...
##  $ identifier                         : chr  "TWBBS_2015_A33-13_05_02" "TWBBS_2015_A33-13_06_02" "TWBBS_2015_A33-13_03_02" "TWBBS_2015_A33-13_04_02" ...
##  $ instructionalMethod                : logi  NA NA NA NA NA NA ...
##  $ isFormatOf                         : logi  NA NA NA NA NA NA ...
##  $ isPartOf                           : logi  NA NA NA NA NA NA ...
##  $ isReferencedBy                     : logi  NA NA NA NA NA NA ...
##  $ isReplacedBy                       : logi  NA NA NA NA NA NA ...
##  $ isRequiredBy                       : logi  NA NA NA NA NA NA ...
##  $ isVersionOf                        : logi  NA NA NA NA NA NA ...
##  $ issued                             : logi  NA NA NA NA NA NA ...
##  $ language                           : logi  NA NA NA NA NA NA ...
##  $ license                            : chr  "CC_BY_NC_4_0" "CC_BY_NC_4_0" "CC_BY_NC_4_0" "CC_BY_NC_4_0" ...
##  $ mediator                           : logi  NA NA NA NA NA NA ...
##  $ medium                             : logi  NA NA NA NA NA NA ...
##  $ modified                           : logi  NA NA NA NA NA NA ...
##  $ provenance                         : logi  NA NA NA NA NA NA ...
##  $ publisher                          : logi  NA NA NA NA NA NA ...
##  $ references                         : logi  NA NA NA NA NA NA ...
##  $ relation                           : logi  NA NA NA NA NA NA ...
##  $ replaces                           : logi  NA NA NA NA NA NA ...
##  $ requires                           : logi  NA NA NA NA NA NA ...
##  $ rights                             : logi  NA NA NA NA NA NA ...
##  $ rightsHolder                       : logi  NA NA NA NA NA NA ...
##  $ source                             : logi  NA NA NA NA NA NA ...
##  $ spatial                            : logi  NA NA NA NA NA NA ...
##  $ subject                            : logi  NA NA NA NA NA NA ...
##  $ tableOfContents                    : logi  NA NA NA NA NA NA ...
##  $ temporal                           : logi  NA NA NA NA NA NA ...
##  $ title                              : logi  NA NA NA NA NA NA ...
##  $ type                               : chr  "Event" "Event" "Event" "Event" ...
##  $ valid                              : logi  NA NA NA NA NA NA ...
##  $ institutionID                      : logi  NA NA NA NA NA NA ...
##  $ collectionID                       : logi  NA NA NA NA NA NA ...
##  $ datasetID                          : logi  NA NA NA NA NA NA ...
##  $ institutionCode                    : logi  NA NA NA NA NA NA ...
##  $ collectionCode                     : logi  NA NA NA NA NA NA ...
##  $ datasetName                        : logi  NA NA NA NA NA NA ...
##  $ ownerInstitutionCode               : logi  NA NA NA NA NA NA ...
##  $ basisOfRecord                      : chr  "HUMAN_OBSERVATION" "HUMAN_OBSERVATION" "HUMAN_OBSERVATION" "HUMAN_OBSERVATION" ...
##  $ informationWithheld                : logi  NA NA NA NA NA NA ...
##  $ dataGeneralizations                : logi  NA NA NA NA NA NA ...
##  $ dynamicProperties                  : logi  NA NA NA NA NA NA ...
##  $ occurrenceID                       : chr  "TWBBS_2015_A33-13_05_02:008737" "TWBBS_2015_A33-13_06_02:008756" "TWBBS_2015_A33-13_03_02:008717" "TWBBS_2015_A33-13_04_02:008731" ...
##  $ catalogNumber                      : logi  NA NA NA NA NA NA ...
##  $ recordNumber                       : logi  NA NA NA NA NA NA ...
##  $ recordedBy                         : chr  "廖自強" "廖自強" "廖自強" "廖自強" ...
##  $ individualCount                    : int  12 8 2 1 1 1 1 1 1 2 ...
##  $ organismQuantity                   : logi  NA NA NA NA NA NA ...
##  $ organismQuantityType               : logi  NA NA NA NA NA NA ...
##  $ sex                                : logi  NA NA NA NA NA NA ...
##  $ lifeStage                          : logi  NA NA NA NA NA NA ...
##  $ reproductiveCondition              : logi  NA NA NA NA NA NA ...
##  $ behavior                           : logi  NA NA NA NA NA NA ...
##  $ establishmentMeans                 : logi  NA NA NA NA NA NA ...
##  $ occurrenceStatus                   : logi  NA NA NA NA NA NA ...
##  $ preparations                       : logi  NA NA NA NA NA NA ...
##  $ disposition                        : logi  NA NA NA NA NA NA ...
##  $ associatedReferences               : logi  NA NA NA NA NA NA ...
##  $ associatedSequences                : logi  NA NA NA NA NA NA ...
##  $ associatedTaxa                     : logi  NA NA NA NA NA NA ...
##  $ otherCatalogNumbers                : logi  NA NA NA NA NA NA ...
##  $ occurrenceRemarks                  : chr  "" "" "" "" ...
##  $ organismID                         : logi  NA NA NA NA NA NA ...
##  $ organismName                       : logi  NA NA NA NA NA NA ...
##  $ organismScope                      : logi  NA NA NA NA NA NA ...
##  $ associatedOccurrences              : logi  NA NA NA NA NA NA ...
##  $ associatedOrganisms                : logi  NA NA NA NA NA NA ...
##  $ previousIdentifications            : logi  NA NA NA NA NA NA ...
##  $ organismRemarks                    : logi  NA NA NA NA NA NA ...
##  $ materialSampleID                   : logi  NA NA NA NA NA NA ...
##  $ eventID                            : chr  "TWBBS_2015_A33-13_05_02" "TWBBS_2015_A33-13_06_02" "TWBBS_2015_A33-13_03_02" "TWBBS_2015_A33-13_04_02" ...
##  $ parentEventID                      : chr  "TWBBS_2015_A33-13" "TWBBS_2015_A33-13" "TWBBS_2015_A33-13" "TWBBS_2015_A33-13" ...
##  $ fieldNumber                        : logi  NA NA NA NA NA NA ...
##  $ eventDate                          : chr  "2015-05-23T02:00Z" "2015-05-23T02:00Z" "2015-05-23T02:00Z" "2015-05-23T02:00Z" ...
##   [list output truncated]
##  - attr(*, ".internal.selfref")=<externalptr>

經過上面str(),會看到BBS的原始資料共有235個欄位,但其中有許多欄位全都是空白的NA值,這時候可以先利用Filter()一次把這些沒有內容的欄位先刪除掉,讓資料乾淨一些,方便後續的操作。

BBS %<>% Filter(function(x)!all(is.na(x)), .)

上面的%<>%magrittr的管線運算子之一,會將式子左邊的資料傳遞到右邊做運算 (在右邊會用 . 來表示),運算完後再將新的資料儲存回原本的變數。 處理完後會看到BBS資料更新了,剩下58個欄位

data.table 練習

利用上面處理完的BBS資料來玩玩看data.table吧!

[ i, j, by ]

EX1 篩出2012年的資料

EX1 <- BBS[year == 2012]

EX2 篩出紀錄數量大於5的資料

Ex2 <- BBS[individualCount > 5]

EX3 篩出都市三俠,白頭翁、綠繡眼、麻雀的資料

EX3 <- BBS[vernacularName %in% c("白頭翁", "綠繡眼", "麻雀")]

EX4 篩出樣區編號開頭為B的資料

EX4 <- BBS[locationID %like% "B"]

[ i, j, by ]

EX1 保留物種名稱以及紀錄數量欄位

EX1 <- BBS[, 
           list(vernacularName, 
                individualCount)]

這邊給個小練習,試看看以下兩個取欄位的寫法結果跟上面有沒有不同 EX1.1 <- BBS[, c(vernacularName, individualCount)] EX1.2 <- BBS[, c("vernacularName", "individualCount")]

EX2 增加新的欄位elevation,並且填入locationID的第一個字

EX2 <- BBS[, 
           elevation := substr(locationID, 1, 1)]  # substr(x, start, stop)

[ i, j, by ]

EX1 計算每年每個物種的被記錄到的調查樣點數

EX1 <- BBS[, 
           .(樣點數 = uniqueN(locationID)), 
           by = list(year, 
                     vernacularName)]

EX2 計算每年記錄到的總鳥隻數

EX2 <- BBS[, 
           .(鳥隻數 = sum(individualCount)),
           by = year]

DT[DT2, on = “a”]

利用相同的欄位a來合併兩個data.table,DTDT2

EX 將鳥類名錄中的遷留屬性(Bird.list$Taiwan)對應到BBS資料(BBS)當中

#取BBS資料當中的物種中文名稱跟數量欄位
BBS.species <-
  BBS[, list(vernacularName, individualCount)]

#看一下資料
head(BBS.species, 3)
##    vernacularName individualCount
## 1:           麻雀              12
## 2:           麻雀               8
## 3:         白頭翁               2
#接著把要對應的鳥類名錄叫進來
Bird.list <- 
  fread("F:/R markdown/Bird_List.txt",
        sep = "\t", encoding = "UTF-8") %>%
  # 取資料裡面的鳥類中文俗名(C.CommonName)跟在台灣的遷留屬性(Taiwan)
  # 把C.CommonName重新取名為與BBS資料當中對應的vernacularName
  .[, list(vernacularName = C.CommonName,
           Taiwan)]
#確認一下欄位名稱有修改對
head(Bird.list, 3)
##    vernacularName Taiwan
## 1:           樹鴨     迷
## 2:           鴻雁 冬、稀
## 3:       寒林豆雁 冬、稀
#把Bird.list與BBS資料合併
# 注意on後面一定要用""來包住欄位名稱,可以同時有多個欄位作為索引
BBS.species %<>% Bird.list[., on = "vernacularName"]

#確認合併完後的新資料
head(BBS.species)
##    vernacularName        Taiwan individualCount
## 1:           麻雀        留、普              12
## 2:           麻雀        留、普               8
## 3:         白頭翁        留、普               2
## 4:       珠頸斑鳩        留、普               1
## 5:       灰頭鷦鶯        留、普               1
## 6:       紅尾伯勞 冬、普/過、普               1

data.table 應用

範例一

目標:摘要2010年的調查中,每個樣區的物種資訊(物種數、個體數、平均個體數)

BBS擷取2010年的調查物種清單,包含調查樣區、樣點、調查物種、紀錄數量
BBS的資料中,樣區跟樣點資訊被合併在locationID欄位裡面,用separate()來分開,並且給予新的欄位名稱SitePoint

BBS2010 <- BBS[year == 2010, list(locationID, 
                                  species = vernacularName, 
                                  count = individualCount)] %>%
  separate(locationID, c("Site", "Point"), "_")

這邊的%>%也是常見的管線運算子,他的功能是將前面運算完的結果傳遞到後面的運算式,傳遞過去的資料一樣用.表示

計算每個樣區在2010年總共記錄到多少個物種、共多少隻個體,以及各樣區的平均個體數

BBS2010.site <- 
  BBS2010[, .(物種數 = uniqueN(species),
              總個體數 = .N,
              平均個體數 = mean(count)),
          by = list(Site)]

最後看一下計算完的結果

View(BBS2010.site)
Site 物種數 總個體數 平均個體數
A01-01 22 68 1.867647
A01-02 22 76 2.250000
A02-01 28 200 2.670000
A02-02 24 202 2.485149
A02-03 25 196 2.341837
A03-01 62 299 6.130435
A04-02 19 82 2.243902
A04-03 17 102 1.980392
A04-04 13 233 1.055794
A04-05 22 179 1.173184
A04-06 19 97 NA
A05-01 29 384 1.059896
A05-02 23 371 1.083558
A07-02 28 177 2.344633
A09-01 25 321 1.105919
A09-02 21 228 1.197368
A09-03 25 276 1.166667
A09-04 35 221 2.013575
A09-05 29 198 1.116162
A09-06 27 303 1.066007
A10-01 24 196 1.352041
A10-02 24 133 3.879699
A10-03 28 298 1.090604
A12-01 16 71 1.591549
A12-02 23 80 1.662500
A16-01 18 77 2.000000
A16-02 16 68 1.470588
A16-03 18 62 5.096774
A16-04 28 188 1.244681
A16-05 35 215 1.269767
A17-01 35 186 2.026882
A17-02 25 117 2.965812
A17-03 21 127 1.559055
A17-04 23 222 2.126126
A18-01 21 117 2.102564
A18-02 43 235 2.348936
A18-03 34 247 1.955466
A19-01 30 200 1.150000
A19-02 47 409 1.166259
A19-03 44 349 1.186246
A19-04 34 136 3.110294
A19-05 35 317 5.413249
A19-06 52 255 1.600000
A20-01 42 284 3.989437
A20-02 32 146 4.657534
A20-03 24 157 1.114650
A20-04 26 304 1.082237
A21-01 30 151 5.403974
A22-01 24 88 6.204546
A22-02 13 47 2.000000
A24-01 29 171 4.491228
A24-02 26 129 4.674419
A26-01 28 211 1.620853
A26-02 27 219 2.273973
A26-03 26 192 1.630208
A26-04 15 119 1.445378
A27-01 17 135 2.207407
A27-02 24 289 2.570934
A27-03 31 315 NA
A27-04 17 264 1.473485
A27-05 27 183 2.994536
A27-06 17 166 1.650602
A28-01 32 100 2.200000
A28-02 28 169 2.426035
A28-03 27 91 1.967033
A28-04 18 59 2.440678
A28-05 43 127 2.181102
A28-06 30 159 2.427673
A29-01 30 146 2.315069
A29-02 20 113 1.345133
A29-03 34 265 1.501887
A29-04 33 193 1.658031
A29-05 21 132 2.242424
A30-03 33 202 1.985148
A32-01 15 69 1.768116
A32-02 34 284 1.707747
A32-03 29 159 1.333333
A32-04 41 169 1.467456
A33-01 26 176 3.130682
A33-02 32 185 2.405405
A33-04 39 247 2.032389
A33-05 19 138 2.086956
A33-06 28 200 2.405000
A33-07 35 273 1.406593
A33-08 32 359 1.779944
A34-01 21 168 4.392857
A34-02 30 290 3.175862
A34-03 24 194 2.427835
A34-04 25 100 2.600000
A34-05 32 193 2.932643
A34-06 24 231 2.666667
A34-07 30 279 3.448029
A34-08 22 240 2.604167
A35-01 22 139 1.820144
A35-02 32 210 1.890476
A35-03 31 218 2.568807
A35-04 31 270 2.266667
A35-05 38 240 3.120833
A35-06 35 253 3.739130
A36-01 29 122 2.229508
A36-02 27 166 2.385542
A36-03 28 349 2.369627
A36-04 27 205 1.707317
A36-05 54 438 1.468036
A37-03 38 276 2.014493
A37-04 16 259 2.795367
A37-05 36 243 1.954732
A39-02 20 210 3.857143
A40-01 34 426 1.969484
A39-08 17 53 1.886793
A40-02 31 412 1.786408
A40-03 34 324 1.851852
A41-01 24 170 1.835294
A41-02 23 164 1.890244
B06-01 25 140 1.307143
B10-01 21 66 2.212121
B10-02 25 175 1.982857
B11-01 36 267 1.711610
B13-01 16 58 1.931034
B13-03 30 145 1.786207
B14-01 33 192 1.489583
B14-03 34 195 1.646154
B14-04 32 156 1.429487
B16-01 31 186 1.268817
B16-02 25 103 1.699029
B21-01 27 96 4.062500
B22-01 18 56 3.107143
B28-01 21 123 2.154472
B30-01 41 206 1.728155
B30-02 40 220 1.954546
B30-04 34 208 1.562500
B32-01 30 269 1.631970
B32-03 26 142 2.140845
B32-04 31 175 2.348571
B37-02 22 209 1.722488
B38-01 22 152 1.519737
C30-01 17 155 1.019355
C37-02 21 245 1.040816
C37-03 15 302 1.069536
C37-05 24 209 1.794258



範例二

目標:找出BBS自2009年以來,低中高三個海拔段每年各做了多少樣區及樣點

在BBS的資料中,樣區代號最前面的英文字母為各樣區所屬的海拔段(A開頭的樣區為低海拔樣區;B開頭的樣區為中海拔樣區;C開頭的樣區為高海拔樣區),因此我們可以利用這樣的資料特性,給定一個新欄位elevation,然後用elevation分類群來計算我們要的結果

BBS中擷取需要的資料並且增加新欄位elevation,依據樣區編號開頭的英文字母填入所屬的海拔段
利用uniqueN()計算每年每個海拔段的樣區與樣點數量

BBS.agg <- 
  BBS[, list(year, locationID)] %>%   # 取出需要的欄位
  separate(locationID, c("Site", "Point"), "_") %>%   # 分開樣區跟樣點資訊
  .[Site %like% "A", elevation := "低海拔"] %>%   #樣區名稱包含A的樣區,elevation填入"低海拔"
  .[Site %like% "B", elevation := "中海拔"] %>%   #樣區名稱包含B的樣區,elevation填入"中海拔"
  .[Site %like% "C", elevation := "高海拔"] %>%   #樣區名稱包含C的樣區,elevation填入"高海拔"
  .[, .(N.site = uniqueN(Site),   #計算樣區數
        N.point = uniqueN(Point)),    #計算樣點數
    by = list(year, elevation)]   #分類群依據year跟elevation

看一下整理完的結果

View(BBS.agg)
year elevation N.site N.point
2015 低海拔 306 14
2015 中海拔 34 10
2015 高海拔 14 10
2009 低海拔 108 10
2009 中海拔 30 10
2009 高海拔 10 10
2010 低海拔 114 11
2010 中海拔 22 10
2010 高海拔 4 10
2011 低海拔 215 10
2011 中海拔 27 10
2011 高海拔 11 10
2012 低海拔 263 11
2012 中海拔 35 10
2012 高海拔 13 10
2013 低海拔 248 13
2013 中海拔 30 10
2013 高海拔 13 10
2014 低海拔 247 14
NA 低海拔 3 5
2014 中海拔 33 10
2014 高海拔 13 10

但是這樣做完後資料是長型式的,我們希望能轉成寬型式的,比較好判讀,
所以接著用dcast()來轉資料型式,dcast(資料, 轉換公式, value.var = "填入的資訊來源")
反之如果今天是要將寬型式的資料轉成長型式的,則要用melt()
value.var =用來選擇填入新表格的資訊,另外也可以用fun =來做運算,這邊我們選擇直接填入樣區數量。
記得要用" “來包住要填入的欄位名稱!

BBS.dcast <- 
  dcast(BBS.agg, elevation ~ year, value.var = "N.site")

看一下轉換完的寬型式資料

View(BBS.dcast)
elevation 2009 2010 2011 2012 2013 2014 2015 NA
中海拔 30 22 27 35 30 33 34 NA
低海拔 108 114 215 263 248 247 306 3
高海拔 10 4 11 13 13 13 14 NA

以上就是這次data.table基本介紹,data.table還有很多的應用是這篇沒有納入的,如果你想知道如何用data.table做某種運算,但是這篇沒有列出來的,歡迎提出來一起來動動腦~~ 另外,要達到相同運算結果的方法有很多種,上面僅只是我個人習慣的寫法,不是唯一,可以多估狗看看別人的寫法,找出自己喜歡的方式。
最後一點小提醒,這一篇沒有花太多篇幅在資料清理的部分,像是沒有去確認物種清單是否有非鳥類的物種(實際上有!),還有在範例二,區分樣區海拔段的時候,沒有特別去檢查有沒有例外的存在…等,所以如果實際上要使用BBS資料來分析的話,記得要多花一點心思在資料清理的部分,不然會得到錯誤的資訊噢!