1. 程式人生 > >【R語言】必學包之dplyr包

【R語言】必學包之dplyr包

     Rdplyr可用於處理R內部或者外部的結構化資料,相較於plyr包,dplyr專注接受dataframe物件大幅提高了速度,並且提供了更穩健的資料庫介面。同時,dplyr包可用於操作Sparkdataframe。本文只是基礎的dplyr包學習筆記,所以並不會討論一些高階應用,或者與data.table包的效能比較。

1.  資料集型別轉換

    tbl_df()可用於將過長過大的資料集轉換為顯示更友好的 tbl_df 型別。使用dplyr包處理資料前,建議先將資料集轉換為tbl物件。

  語法 : tbl_df(data)

 舉例 1:

#data.frame型別資料集
class(mtcars)
#轉換為tbl_df型別
ds <- tbl_df(mtcars)
#轉換為data.frame型別
df <- as.data.frame(ds)
2.   篩選:  filter

    filter() 和slice()函式可以按給定的邏輯條件篩選出符合要求的子資料集, 類似於 base::subset() 函式,但程式碼更加簡潔, 同時也支援對同一物件的任意個條件組合(表示AND時要使用&或者直接使用逗號),返回與.data相同型別的物件。原資料集行名稱會被過濾掉。

語法 : filter(.data, ...)

  舉例 1:

#過濾出cyl == 8的行
filter(mtcars, cyl == 8)
filter(mtcars, cyl < 6)
#過濾出cyl < 6 並且 vs == 1的行
filter(mtcars, cyl < 6 & vs == 1)
filter(mtcars, cyl < 6, vs == 1)
#過濾出cyl < 6 或者 vs == 1的行
filter(mtcars, cyl < 6 | vs == 1)
#過濾出cyl 為4或6的行
filter(mtcars, cyl %in% c(4, 6))

語法 : slice(.data, ...)

  slice() 函式通過行號選取資料。

 舉例 2:

#選取第一行資料
slice(mtcars, 1L)
filter(mtcars, row_number() == 1L)
#選取最後一行資料
slice(mtcars, n())
filter(mtcars, row_number() == n())
#選取第5行到最後一行所有資料
slice(mtcars, 5:n())
filter(mtcars, between(row_number(), 5, n()))
3. 排列: arrange

  arrange()按給定的列名依次對行進行排序,類似於base::order()函式。預設是按照升序排序,對列名加 desc() 可實現倒序排序。原資料集行名稱會被過濾掉。

語法 : arrange(.data, ...)

  舉例1:

#以cyl和disp聯合升序排序
arrange(mtcars, cyl, disp)
#以disp降序排序
arrange(mtcars, desc(disp))
4. 選擇: select

  select()用列名作引數來選擇子資料集。dplyr包中提供了些特殊功能的函式與select函式結合使用, 用於篩選變數,包括starts_with,ends_with,contains,matches,one_of,num_range和everything等。用於重新命名時,select()只保留引數中給定的列,rename()保留所有的列,只對給定的列重新命名。原資料集行名稱會被過濾掉。

語法 : select(.data, ...)

 舉例 1:

iris <- tbl_df(iris)
#選取變數名字首包含Petal的列
select(iris, starts_with("Petal"))
#選取變數名字首不包含Petal的列
select(iris, -starts_with("Petal"))
#選取變數名字尾包含Width的列
select(iris, ends_with("Width"))
#選取變數名字尾不包含Width的列
select(iris, -ends_with("Width"))
#選取變數名中包含etal的列
select(iris, contains("etal"))
#選取變數名中不包含etal的列
select(iris, -contains("etal"))
#正則表示式匹配,返回變數名中包含t的列
select(iris, matches(".t."))
#正則表示式匹配,返回變數名中不包含t的列
select(iris, -matches(".t."))
#直接選取列
select(iris, Petal.Length, Petal.Width)
#返回除Petal.Length和Petal.Width之外的所有列
select(iris, -Petal.Length, -Petal.Width)
#使用冒號連線列名,選擇多個列
select(iris, Sepal.Length:Petal.Width)
#選擇字元向量中的列,select中不能直接使用字元向量篩選,需要使用one_of函式
vars <- c("Petal.Length", "Petal.Width")
select(iris, one_of(vars))
#返回指定字元向量之外的列
select(iris, -one_of(vars))
#返回所有列,一般調整資料集中變數順序時使用
select(iris, everything())
#調整列順序,把Species列放到最前面
select(iris, Species, everything())
 舉例 2:
df <- as.data.frame(matrix(runif(100), nrow = 10))
df <- tbl_df(df[c(3, 4, 7, 1, 9, 8, 5, 2, 6, 10)])
#選擇V4,V5,V6三列
select(df, V4:V6)
select(df, num_range("V", 4:6))
語法 :rename(.data, ...)

 舉例 3:

#重新命名列Petal.Length,返回子資料集只包含重新命名的列
select(iris, petal_length = Petal.Length)
#重新命名所有以Petal為字首的列,返回子資料集只包含重新命名的列
select(iris, petal = starts_with("Petal"))
#重新命名列Petal.Length,返回全部列
rename(iris, petal_length = Petal.Length)
5.變形: mutate

  mutate()和transmute()函式對已有列進行資料運算並新增為新列,類似於base::transform() 函式, 不同的是可以在同一語句中對剛增新增的列進行操作。mutate()返回的結果集會保留原有變數,transmute()只返回擴充套件的新變數。原資料集行名稱會被過濾掉。

語法 :mutate(.data, ...)

transmute(.data, ...)

 舉例 1:

#新增新列wt_kg和wt_t,在同一語句中可以使用剛新增的列
mutate(mtcars, wt_kg = wt * 453.592, wt_t = wt_kg / 1000)
#計算新列wt_kg和wt_t,返回物件中只包含新列
transmute(mtcars, wt_kg = wt * 453.592, wt_t = wt_kg / 1000)
6. 去重: distinct

  distinct()用於對輸入的tbl進行去重,返回無重複的行,類似於 base::unique() 函式,但是處理速度更快。原資料集行名稱會被過濾掉。

語法 :distinct(.data, ..., .keep_all = FALSE)

 舉例 1:

df <- data.frame(
  x = sample(10, 100, rep = TRUE),
  y = sample(10, 100, rep = TRUE)
)
#以全部兩個變數去重,返回去重後的行數
nrow(distinct(df))
nrow(distinct(df, x, y))
#以變數x去重,只返回去重後的x值
distinct(df, x)
#以變數y去重,只返回去重後的y值
distinct(df, y)
#以變數x去重,返回所有變數
distinct(df, x, .keep_all = TRUE)
#以變數y去重,返回所有變數
distinct(df, y, .keep_all = TRUE)
#對變數運算後的結果去重
distinct(df, diff = abs(x - y))
7. 概括: summarise

  對資料框呼叫函式進行彙總操作, 返回一維的結果。返回多維結果時會報如下錯誤:
  Error: expecting result of length one, got : 2
原資料集行名稱會被過濾掉。

語法 :summarise(.data, ...)

 舉例 1:
#返回資料框中變數disp的均值
summarise(mtcars, mean(disp))
#返回資料框中變數disp的標準差
summarise(mtcars, sd(disp))
#返回資料框中變數disp的最大值及最小值
summarise(mtcars, max(disp), min(disp))
#返回資料框mtcars的行數
summarise(mtcars, n())
#返回unique的gear數
summarise(mtcars, n_distinct(gear))
#返回disp的第一個值
summarise(mtcars, first(disp))
#返回disp的最後個值
summarise(mtcars, last(disp))
8. 抽樣: sample

  抽樣函式,sample_n()隨機抽取指定數目的樣本,sample_frac()隨機抽取指定百分比的樣本,預設都為不放回抽樣,通過設定replacement = TRUE可改為放回抽樣,可以用於實現Bootstrap抽樣。

語法 :sample_n(tbl, size, replace = FALSE, weight = NULL, .env = parent.frame())

 舉例 1:

#隨機無重複的取10行資料
sample_n(mtcars, 10)
#隨機有重複的取50行資料
sample_n(mtcars, 50, replace = TRUE)
#隨機無重複的以mpg值做權重取10行資料
sample_n(mtcars, 10, weight = mpg)
語法 :sample_frac(tbl, size = 1, replace = FALSE, weight = NULL,.env = parent.frame()) 舉例 2:
#預設size=1,相當於對全部資料無重複重新抽樣
sample_frac(mtcars)
#隨機無重複的取10%的資料
sample_frac(mtcars, 0.1)
#隨機有重複的取總行數1.5倍的資料
sample_frac(mtcars, 1.5, replace = TRUE)
#隨機無重複的以1/mpg值做權重取10%的資料
sample_frac(mtcars, 0.1, weight = 1 / mpg)

9. 分組: group

  group_by()用於對資料集按照給定變數分組,返回分組後的資料集。對返回後的資料集使用以上介紹的函式時,會自動的對分組資料操作。

語法 :group_by(.data, ..., add = FALSE)

 舉例 1:

#使用變數cyl對mtcars分組,返回分組後資料集
by_cyl <- group_by(mtcars, cyl)
#返回每個分組中最大disp所在的行
filter(by_cyl, disp == max(disp))
#返回每個分組中變數名包含d的列,始終返回分組列cyl
select(by_cyl, contains("d"))
#使用mpg對每個分組排序
arrange(by_cyl,  mpg)
#對每個分組無重複的取2行記錄
sample_n(by_cyl, 2)
 舉例 2:
#使用變數cyl對mtcars分組,然後對分組後資料集使用聚合函式
by_cyl <- group_by(mtcars, cyl)
#返回每個分組的記錄數
summarise(by_cyl, n())
#求每個分組中disp和hp的均值
summarise(by_cyl, mean(disp), mean(hp))
#返回每個分組中唯一的gear的值
summarise(by_cyl, n_distinct(gear))
#返回每個分組第一個和最後一個disp值
summarise(by_cyl, first(disp))
summarise(by_cyl, last(disp))
#返回每個分組中最小的disp值
summarise(by_cyl, min(disp))
summarise(arrange(by_cyl,  disp), min(disp))
#返回每個分組中最大的disp值
summarise(by_cyl, max(disp))
summarise(arrange(by_cyl,  disp), max(disp))
#返回每個分組中disp第二個值
summarise(by_cyl, nth(disp,2))
 舉例 3:
#使用cyl對資料框分組
grouped <- group_by(mtcars, cyl)
#獲取分組資料集所使用的分組變數
groups(grouped)
#ungroup從資料框中移除組合資訊,因此返回的分組變數為NULL
groups(ungroup(grouped))
語法 :group_indices(.data, ...)
返回分組後,每條記錄的分組id。

 舉例 4:

#返回每條記錄所在分組id組成的向量
group_indices(mtcars, cyl)
語法 :group_size(x)

n_groups(x)

group_size用於返回每個分組的記錄數,n_groups返回分成的組數。

 舉例 5:

by_cyl <- group_by(mtcars, cyl)
#返回每個分組記錄陣列成的向量
group_size(by_cyl)
summarise(by_cyl, n())
table(mtcars$cyl)
#返回所分的組數
n_groups(by_cyl)
length(group_size(by_cyl))

  對資料集的每個分組計數,類似於base:: table()函式。其中count已經過group_by分組,而tally需要對資料集呼叫group_by後對分組資料計數。
語法 :
tally(x, wt, sort = FALSE)count(x, ..., wt =NULL, sort = FALSE)

 舉例 6:

#使用count對分組計數,資料已按變數分組
count(mtcars, cyl)
#設定sort=TRUE,對分組計數按降序排序
count(mtcars, cyl, sort = TRUE)
#使用tally對分組計數,需要使用group_by分組
tally(group_by(mtcars, cyl))
#使用summarise對分組計數
summarise(group_by(mtcars, cyl), n())
 舉例 7:
#按cyl分組,並對分組資料計算變數的gear的和
count(mtcars, cyl, wt = gear)
tally(group_by(mtcars, cyl), wt = gear)
10. 資料關聯:join

  資料框中經常需要將多個表進行連線操作, 如左連線、右連線、內連線等,dplyr包也提供了資料集的連線操作,類似於 base::merge() 函式。語法如下: 

#內連線,合併資料僅保留匹配的記錄

inner_join(x,y, by = NULL, copy = FALSE, suffix = c(".x", ".y"), ...) 

#左連線,向資料集x中加入匹配的資料集y記錄

left_join(x,y, by = NULL, copy = FALSE, suffix = c(".x", ".y"), ...)

#右連線,向資料集y中加入匹配的資料集x記錄

right_join(x,y, by = NULL, copy = FALSE, suffix = c(".x", ".y"), ...) 

#全連線,合併資料保留所有記錄,所有行

full_join(x,y, by = NULL, copy = FALSE, suffix = c(".x", ".y"), ...)

#返回能夠與y表匹配的x表所有記錄

semi_join(x,y, by = NULL, copy = FALSE, ...)

#返回無法與y表匹配的x表的所有記錄

anti_join(x, y, by = NULL, copy = FALSE, ...) 

 by設定兩個資料集用於匹配的欄位名,預設使用全部同名欄位進行匹配,如果兩個資料集需要匹配的欄位名不同,可以直接用等號指定匹配的欄位名,如, by = c("a" = "b"),表示用x.a和y.b進行匹配。如果兩個資料集來自不同的資料來源,copy設定為TRUE時,會把資料集y的資料複製到資料集x中,出於效能上的考慮,需要謹慎設定copy引數為TRUE。合併後的資料集中同名變數,會自動新增suffix中設定的字尾加以區分。

 舉例 1:

df1 = data.frame(CustomerId=c(1:6), sex = c("f", "m", "f", "f", "m", "m"), Product=c(rep("Toaster",3), rep("Radio",3)))
df2 = data.frame(CustomerId=c(2,4,6,7),sex = c( "m", "f", "m", "f"), State=c(rep("Alabama",3), rep("Ohio",1)))
#內連線,預設使用"CustomerId"和"sex"連線
inner_join(df1, df2)
#左連線,預設使用"CustomerId"和"sex"連線
left_join(df1, df2)
#右連線,預設使用"CustomerId"和"sex"連線
right_join(df1, df2)
#全連線,預設使用"CustomerId"和"sex"連線
full_join(df1, df2)
#內連線,使用"CustomerId"連線,同名欄位sex會自動新增字尾
inner_join(df1, df2, by = c("CustomerId" = "CustomerId"))
#以CustomerId連線,返回df1中與df2匹配的記錄
semi_join(df1, df2, by = c("CustomerId" = "CustomerId"))
#以CustomerId和sex連線,返回df1中與df2不匹配的記錄
anti_join(df1, df2)
11. 集合操作: set

  dplyr也提供了集合操作函式,實際上是對base包中的集合操作的重寫,但是對資料框和其它表格形式的資料操作更加高效。語法如下:

#取兩個集合的交集

intersect(x,y, ...)

    #取兩個集合的並集,並進行去重

union(x,y, ...)

#取兩個集合的並集,不去重

union_all(x,y, ...)

#取兩個集合的差集

setdiff(x,y, ...)

#判斷兩個集合是否相等

setequal(x, y, ...)

 舉例 1:

mtcars$model <- rownames(mtcars)
first <- mtcars[1:20, ]
second <- mtcars[10:32, ]
#取兩個集合的交集
intersect(first, second)
#取兩個集合的並集,並去重
union(first, second)
#取兩個集合的差集,返回first中存在但second中不存在的記錄
setdiff(first, second)
#取兩個集合的交集,返回second中存在但first中不存在的記錄
setdiff(second, first)
#取兩個集合的交集, 不去重
union_all(first, second)
#判斷兩個集合是否相等,返回TRUE
setequal(mtcars, mtcars[32:1, ])

12. 資料合併: bind

    dplyr包中也提供了按行/列合併資料集的函式,合併的物件為資料框,也可以是能夠轉換為資料框的列表。按行合併函式bind_rows()通過列名進行匹配,不匹配的值使用NA替代,類似於base:: rbind()函式。按列合併函式bind_cols()通過行號匹配,因此合併的資料框必須有相同的行數,函式類似於base:: cbind()函式。原資料集行名稱會被過濾掉。語法如下:

    #按行合併,.id新增新列用於指明合併後每條資料來自的源資料框

bind_rows(...,.id = NULL)

#按列合併

bind_cols(...)

#合併資料集

combine(...)

 舉例 1:

one <- mtcars[1:4, ]
two <- mtcars[11:14, ]
#按行合併資料框one和two
bind_rows(one, two)
#按行合併元素為資料框的列表
bind_rows(list(one, two))
#按行合併資料框,生成id列指明資料來自的源資料框,id列的值使用數字代替
bind_rows(list(one, two), .id = "id")
#按行合併資料框,生成id列指明資料來自的源資料框,id列的值為資料框名
bind_rows(list(a = one, b = two), .id = "id")
#按列合併資料框one和two
bind_cols(one, two)
bind_cols(list(one, two))
 舉例 2:
#合併資料框,列名不匹配,因此使用NA替代,使用rbind直接報錯
bind_rows(data.frame(x = 1:3), data.frame(y = 1:4))
 舉例 3:
#合併因子
f1 <- factor("a")
f2 <- factor("b")
c(f1, f2)
unlist(list(f1, f2))
#因子level不同,強制轉換為字元型
combine(f1, f2)
combine(list(f1, f2))
13. 條件語句:ifelse

  dplyr包也提供了更加嚴格的條件操作語句,if_else函式類似於base::ifelse(),不同的是true和false對應的值必須要有相同的型別,這樣使得輸出型別更容易預測,因此相對而言執行效率更高。

語法 :if_else(condition,true, false, missing = NULL)

missing值用於替代缺失值。

 舉例 1:

x <- c(-5:5, NA)
#替換所有小於0的元素為NA,為了保持型別一致,因此使用NA_integer_
if_else(x < 0, NA_integer_, x)
#使用字串missing替換原資料中的NA元素
if_else(x < 0, "negative", "positive", "missing")
#if_else不支援型別不一致,但是ifelse可以
ifelse(x < 0, "negative", 1)
 舉例 2:
x <- factor(sample(letters[1:5], 10, replace = TRUE))
#if_else會保留原有資料型別
if_else(x %in% c("a", "b", "c"), x, factor(NA))
ifelse(x %in% c("a", "b", "c"), x, factor(NA))

  case_when語句類似於if/else語句。表示式使用“~”連線,左值LHS為條件語句用於判斷滿足條件的元素,右值為具有相同型別的替換值,用於替換滿足條件的元素。

語法 :case_when(...)

 舉例 3:

#順序執行各語句對原向量進行替換,因此越普遍的條件需放在最後
x <- 1:50
case_when(
  x %% 35 == 0 ~ "fizz buzz",
  x %% 5 == 0 ~ "fizz",
  x %% 7 == 0 ~ "buzz",
  TRUE ~ as.character(x)
)
14. 資料庫操作: database

  dplyr也提供了對資料庫的連線和操作函式,目前僅支援sqlite, mysql,postgresql以及google bigquery。dplyr可把R程式碼自動轉換為SQL語句,然後在資料庫上執行以獲取資料。實際的處理過程中,所有的R程式碼並不是立即執行,而是在實際獲取資料的時候,一次性在資料庫中執行。下面以sqlite資料庫為例。

建立和連線資料庫: src_sqlite(path, create = FALSE)

  當create為FALSE(預設),path必須為已存在的資料庫路徑和全名,為TRUE,會根據設定的path建立sqlite資料庫。

 舉例 1:

#在預設工作路勁下建立sqlite資料庫
my_db <- src_sqlite("dplyrdb.db", create = TRUE)
列出資料來源x中所有的表src_tbls(x)

 舉例 2:

#目前資料庫中還沒有表
src_tbls(my_db)

  匯入資料到建立的資料庫中並建立相應的表,如果未給出表名則使用傳入的data frame名稱,匯入時可以通過indexes引數給建立的表新增索引, copy_to同時會執行ANALYZE命令以保證表具有最新的統計資訊並且執行相應的查詢優化。

匯入資料到遠端資料來源:copy_to(dest, df, name =deparse(substitute(df)), temporary, indexes,...)

 舉例 3:

library(nycflights13)
#匯入flights資料到資料庫中,並建立相應的索引
flights_sqlite <- copy_to(my_db, flights, temporary = FALSE, indexes = list(c("year", "month", "day"), "carrier", "tailnum"))
#已存在表flights
src_tbls(my_db)

  tbl可用於與源資料來源(src)中的資料(from)建立連線,from可以是表名或者是SQL語句返回的資料。

與資料庫建立連線: tbl(src, from, ...)

 舉例 4:

#查詢資料庫中表資料,直接給出表名
tb.flight <- tbl(my_db, 'flights')
#查詢資料庫中表資料,使用SQL語句返回資料
tb.flight2 <- tbl(my_db, sql("SELECT * FROM flights"))
 舉例 5:
#操作資料庫中資料,語句並沒有被實際執行,只有顯式獲取資料時才會執行
c1 <- filter(tb.flight, year == 2013, month == 1, day == 1)
c2 <- select(c1, year, month, day, carrier, dep_delay, air_time, distance)
c3 <- mutate(c2, speed = distance / air_time * 60)
c4 <- arrange(c3, year, month, day, carrier)

  在未顯式獲取資料時,所有的操作只是生成tbl_sql物件,可以通過以下操作獲取返回相應的SQL語句以及執行計劃。

語法: show_query(x)

explain(x, ...)

 舉例 6:

#返回物件c4對應的SQL語句
show_query(c4)
#返回物件c4對應的SQL語句以及執行計劃
explain(c4)

  對於lazy操作的這種機制,資料操作實際並沒有真正的執行查詢,如果需要返回資料結果,可以用以下的函式強制執行查詢並返回結果。

#強制執行查詢,並返回tbl_df物件到R

collect(x, ...)

#強制執行查詢,並在源資料庫中建立臨時表儲存結果

compute(x, name = random_table_name(),temporary = TRUE,

unique_indexes = list(), indexes = list(),...)

#不強制執行查詢,拆分複雜的tbl物件,以便新增額外的約束

collapse(x, ...) 

 舉例 7:

#執行c4查詢,返回物件到R
tbl_dfight <- collect(c4)
#執行查詢並在資料庫中建立臨時表,通過src_tbls可查詢到新建的temp表
compute(c4, name = 'temp_flights')
src_tbls(my_db)
#實際並沒有執行查詢,仍可用show_query返回對應的SQL語句
remote <- collapse(c4)
show_query(remote)