文章和代碼已經歸檔至【Github倉庫:https://github.com/timerring/dive-into-AI 】或者公眾號【AIShareLab】回覆 R語言 也可獲取。
這個包以一種統一的規範更高效地處理數據框。dplyr 包裏處理數據框的所有函數的第一個參數都是數據框名。
下面以 MASS 包裏的 birthwt 數據集為例,介紹 dplyr 包裏常用函數的用法。該數據集來自一項關於新生兒低體重危險因素的病例對照研究。首先加載該數據集並查看其相關信息。
library(dplyr)
data(birthwt, package = "MASS")
# ??birthwt
數據集 birthwt 裏一共包含 189 個研究對象、10 個變量。其中結果變量 bwt 是新生兒的體重(單位:g),變量 low 是將 bwt 的取值以 2500g 為分點轉換成的一個二分類變量。其餘 8 個變量均為預測變量,包括孕婦的年齡(age)、種族(race)、吸煙狀況(smoke)、高血壓史(ht)等。
1.使用 filter( ) 和 slice( ) 篩選行
函數 filter() 可以基於觀測值篩選數據框的一個子集。第一個參數是數據框名,第二個參數以及隨後的參數是用來篩選數據框的表達式。
例如,篩選數據框裏年齡大於 35 歲的對象的所有記錄:
filter(birthwt, age > 35)
函數 filter ( ) 裏可以用逗號分隔多個條件。使用下面的命令將會選擇選擇年齡大於 35 歲,並且出生體重小於 2500g 或者大於 4000g 的所有記錄,因為記錄較多,這裏只顯示了前 10 行。
head(filter(birthwt, age > 35, bwt < 2500 | bwt > 4000),10)
函數 slice( ) 可以按照行號選擇指定的行。例如,下面的命令選擇數據集裏面的第 2 行到第 5 行。
slice(birthwt, 2:5)
2.使用 arrange( ) 排列行
有時候我們想要將數據框的記錄按照某個變量進行排序,函數 arrange() 可以實現這個功能。下面的命令將數據框按照變量 bwt 的值從小到大進行排序後顯示:
arrange(birthwt, bwt) # 默認升序
在上面的輸出中,第 6 行和第 7 行的變量 bwt 的值都是 1588,在這種情況下如果還想將數據框按照第二個變量排序,只需要在函數 arrange( ) 里加上第二個變量即可。例如,下面的命令將數據框按照變量 bwt 的值從小到大排序,在 bwt 取值相等的情況下再按照第二個變量 age 的值從小到大排序。
arrange(birthwt, bwt, age)
如果想把數據框按照某個變量的值從大到小進行排序,可以藉助函數 desc( ) 實現。
arrange(birthwt, desc(bwt))
# 等價於
arrange(birthwt, - bwt)
3. 使用 select( ) 選擇列
函數 select( ) 用於選擇數據框中的列(變量)。
# 下面的命令選擇數據框裏面的 bwt、age、race 和 smoke 這 4 個變量組成新的數據框。
select(birthwt, bwt, age, race, smoke)
請注意,MASS 包裏有一個同名函數 select( ),如果同時加載了 dplyr 包和 MASS 包,R 會默認使用較後加載的包裏的函數。為了避免混淆,我們可以使用符號 :: 特別指明使用某一個包裏的函數,例如 dplyr::select( )。之後我們將會對函數 select( ) 作進一步介紹。
4.使用 mutate( ) 添加新變量
函數 mutate( ) 用於在數據框中創建新的變量。下面的命令將數據集 birthwt 裏的變量 lwt(單位:lb)乘以係數 0.4536 後生成新的變量 lwt.kg(1lb ≈ 0.4536kg)。
# 當然如果想要用新變量替換原來的變量,只需把新變量命名為原來的變量名:
mutate(birthwt, lwt.kg = lwt*0.4536)
5.使用 summarise( ) 計算統計量
函數 summarise( ) 可以用於計算數據框中某個變量的指定統計量。
例如,計算變量 bwt 的樣本均值和樣本標準差:
summarise(birthwt, Mean.bwt = mean(bwt), Sd.bwt = sd(bwt))
6. 使用 group\_by( ) 拆分數據框
函數 group_by( ) 可以將數據框按照某一個或某幾個分類變量拆分成多個數據框。例如:
group_by(birthwt, race)
str(group_by(birthwt, race))
# ============ 輸出 =============
grouped_df [189 × 10] (S3: grouped_df/tbl_df/tbl/data.frame)
$ low : int [1:189] 0 0 0 0 0 0 0 0 0 0 ...
$ age : int [1:189] 19 33 20 21 18 21 22 17 29 26 ...
$ lwt : int [1:189] 182 155 105 108 107 124 118 103 123 113 ...
$ race : int [1:189] 2 3 1 1 1 3 1 3 1 1 ...
$ smoke: int [1:189] 0 0 1 1 1 0 0 0 1 1 ...
$ ptl : int [1:189] 0 0 0 0 0 0 0 0 0 0 ...
$ ht : int [1:189] 0 0 0 0 0 0 0 0 0 0 ...
$ ui : int [1:189] 1 0 0 1 1 0 0 0 0 0 ...
$ ftv : int [1:189] 0 3 1 2 0 0 1 1 1 0 ...
$ bwt : int [1:189] 2523 2551 2557 2594 2600 2622 2637 2637 2663 2665 ...
- attr(*, "groups")= tibble [3 × 2] (S3: tbl_df/tbl/data.frame)
..$ race : int [1:3] 1 2 3
..$ .rows: list<int> [1:3]
.. ..$ : int [1:96] 3 4 5 7 9 10 15 16 18 20 ...
.. ..$ : int [1:26] 1 17 29 30 31 33 35 41 43 70 ...
.. ..$ : int [1:67] 2 6 8 11 12 13 14 19 21 24 ...
.. ..@ ptype: int(0)
..- attr(*, ".drop")= logi TRUE
函數 group\_by( ) 不會改變數據框的外觀,而會改變它與其他 dplyr 動詞函數的作用方式 。因此,上面的輸出結果看上去和原來的數據框沒有什麼差別,但實質上是不同的。最本質的差別是多了一個分組屬性(Groups),即上面的結果包含了 3 個數據框,分別對應於變量 race 的 3 個類別。
你還可能注意到上面輸出對象的格式(grouped_df [189 × 10] (S3: grouped_df/tbl_df/tbl/data.frame))。與 R/Rstudio 上不同,notebook 這裏把它顯示成了 A grouped_df: 189 × 10(而非 # A tibble: 189 x 10),實際它仍然包含 tibble(注意其中的 - attr(*, "groups")= tibble [3 × 2] (S3: tbl_df/tbl/data.frame))。另外,它沒有顯示 Groups 屬性信息,實際應為 # Groups: race [3]。
tibble 是 tidyverse 系列包(包括 dplyr 包)提供的一種類似數據框的格式。相對於傳統的數據框,tibble 在很多方面具有優勢,感興趣的讀者可以參閲函數 tibble( ) 的幫助文檔。我們可以用函數 as_tibble( ) 將傳統的數據框轉換為 tibble,也可以用函數 as.data.frame( ) 將 tibble 轉換成傳統的數據框。
as_tibble(birthwt)
下面我們將會看到,把函數 group\_by( ) 和 summarise( ) 聯合使用能方便地對變量進行分組統計。
7. 使用傳遞符 %>% 組合多個操作
我們經常需要對一個數據框做一系列的操作,後面一個操作的輸入需要用前一個操作的輸出結果。
# 第一步把數據框 birthwt 裏面的變量 race 轉換成因子並給各個水平添加標籤,把新的數據框命名為 birthwt1
birthwt1 <- mutate(birthwt,
race = factor(race, labels = c("white", "black", "other")))
# 第二步把數據框 birthwt1 按照變量 race 分組,把分組後的對象命名為 birthwt.group;
birthwt.group <- group_by(birthwt1, race)
# 第三步對於分組對象 birthwt.group 計算各組中變量 bwt 的平均值。
summarise(birthwt.group, mean(bwt))
這種方法的最大缺點是需要為每個中間結果建立一個變量。在很多情況下,比如在上面的示例中,這些中間變量其實是沒有什麼實際意義的。我們需要給這些中間變量命名,而且這些中間變量會保存在工作空間中佔用內存。傳遞操作符 %>% 將該符號之前的對象傳遞給符號後面的函數並作為函數的第一個參數值。例如:
c(2, 4, 6, 8) %>% matrix(nrow = 2)
因為 dplyr 包裏面的函數第一個參數總是數據框,所以這些函數配合傳遞操作符處理數據框非常方便。下面用傳遞操作符改寫上面的命令:
birthwt %>%
mutate(race = factor(race, labels = c("white", "black", "other"))) %>%
group_by(race) %>%
summarise(mean(bwt))
上述代碼的重點在於動詞函數,而不是函數中的參數。在閲讀這一串代碼組合時,可以將它們當成一系列的規定動作。
項目實戰
epiDisplay 包裏的數據集 Planning 來自 20 世紀 80 年代中期泰國的一項計劃生育調查研究,請通過其幫助文件查看數據信息並整理該數據集。
library(epiDisplay)
data(Planning)
print(des(Planning))
names(Planning) <- tolower(names(Planning)) # 把變量名變為小寫字母
summary(Planning)
table(duplicated(Planning$id)) # 查看是否有重複id;
# FALSE TRUE
# 250 1
which(duplicated(Planning$id)) # 找出重複id的行號;把 XXXXXX 替換成正確的代碼
# 216
Planning$id # 驗證下
Planning$id[216] <- 216 # 修正重複id;
library(dplyr)
Planning <- mutate(
Planning,
relig = ifelse(relig == 9, NA, relig), # 將變量relig中的9變成NA
ped = ifelse(ped == 0 | ped == 9, NA, ped), # 將變量ped中的0和9變成NA
income = ifelse(income == 9, NA, income), # 將變量income中的9變成NA
am = ifelse(am == 99, NA, am), # 將變量am中的99變成NA
reason = ifelse(reason == 9, NA, reason), # 將變量reason中的9變成NA
bps = ifelse(bps == 0 | bps == 999, NA, bps), # 將變量bps中的0和999變成NA
bpd = ifelse(bpd == 0 | bpd == 999, NA, bpd), # 將變量bpd中的0和999變成NA
wt = ifelse(wt == 0 | wt > 99, NA, wt), # 將變量wt中的0和大於99的值變成NA
ht = ifelse(ht == 0 | ht > 300, NA, ht) # 將變量ht中的0和大於300的值變成NA;
)