【R语言】必学包之dplyr包
R包dplyr适用于处理嵌入式或外部结构化数据,在相较于 plyr 包的基础上专注于接收 dataframe 对象并显著提升了运行效率的同时也确保了操作过程的稳定性和可靠性。此外,dplyr还支持对 Spark 数据框的操作。本文仅限于基础 dplyr 学习笔记因此不涉及高级功能或与其他库如 data.table 的技术对比。
1. 数据集类型转换
tbl_df()可用于将过长过大的数据集转译为更友好地呈现的tbl_df类型。提醒用户在使用dplyr处理数据之前,在R语言中将数据集转换为tbl对象。
语法 : tbl_df(data)
****** 举例 1:**
#data.frame类型数据集
class(mtcars)
#转换为tbl_df类型
ds <- tbl_df(mtcars)
#转换为data.frame类型
df <- as.data.frame(ds)
2. 筛选: filter
能够根据指定逻辑条件筛选出符合条件的数据子集;其功能类似于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
按照指定的列名依次对每一行进行排列,并与R语言中的base::order()函数功能相似。通常采用升序排列,默认情况下;若在列名前添加desc()则可实现降序排序。原始数据集中的行索引会被过滤掉。
语法 : arrange(.data, ...)
举例1:
#以cyl和disp联合升序排序
arrange(mtcars, cyl, disp)
#以disp降序排序
arrange(mtcars, desc(disp))
4. 选择: select
该函数通过列名作为参数来筛选子数据集,并在dplyr包中提供了多个辅助功能以扩展其操作能力。这些辅助功能包括开始于特定字符(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
在数据处理中存在多种数据清洗方法和技巧以提升数据质量。其中一种常用的技术是利用draw_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)
举例 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之后统计各分组的数据数量。语法如下:
****举例 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 = c("a" = "b")),这样就能实现x表中的a列与y表中的b列之间的对应关系。当参数copy设为TRUE时,则会在结果中将y表的数据复制至x表中(此处需注意该操作会对性能产生一定影响)。需要注意的是,在合并后的结果中若存在同一名称变量(如相同名称但不同来源),系统会自动在变量后缀中添加指定后缀以区别开这些变量。
****举例 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 表示缺失值;而沿列结合则采用 bind_cols() 函数,在这种情况下需要确保两个结合的数据框拥有相同的数量来进行配对运算(即每条记录对应相同位置)。值得注意的是,在执行完这两种操作后,默认会移除原始数据集中所包含的所有记录索引信息。
语法如下:
#按行合并,.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()存在相似之处, 其主要区别在于其对应取值必须保持一致的数据类型, 这种设定使得输出结果的数据特性更为明确, 因此整体运行效率更高.
语法 :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这几种主流数据库系统。它能将R代码自动生成对应的SQL语句,并且在数据库上执行以获取所需的数据。实际上,在处理过程中,并非所有的R代码都是立即执行的,在实际获取数据时一次性生成并执行相应的SQL语句以提高效率。
创建和连接数据库: src_sqlite(path, create = FALSE)
如果create设为FALSE(默认情况下),则应确保指定的path既指明了具体名称又提供了完整的路径信息;若create设为TRUE,则将基于指定的path参数生成一个SQLite数据文件。
****举例 1:
#在默认工作路劲下创建sqlite数据库
my_db <- src_sqlite("dplyrdb.db", create = TRUE)
<span style="font-size: 13.3333px;"><span style="font-family:Courier New;background-color: rgb(255, 255, 255);"> </span><span style="background-color: rgb(204, 204, 204);">列出数据源x中所有的表</span></span><span style="background-color: rgb(204, 204, 204); font-family: "Courier New"; font-size: 13.3333px;">:<span style="font-size: 13.3333px;">src_tbls(x)</span></span>
****举例 2:
#目前数据库中还没有表
src_tbls(my_db)
将数据导入至新建的数据库中,并建立相应的表。若未指定表名,则使用输入的数据框名称作为表名。在导入过程中可通过indexes参数为新建的表添加索引。该过程还会执行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)进行关联操作,并且该关联操作可选地基于表名或由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)
