打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
12.4 使用dplyr管道操作处理数据框
userphoto

2022.08.16 安徽

关注
关于数据操作的另一个流行的包是dplyr,它发明了一种数据操作语法。dplyr扩展包并没有使用构建子集函数([ ]),而是定义了一系列基础的变形函数作为数据操作模块,并且引入了一个管道操作符,利用管道操作符将这些变形函数串联起来,进而完成复杂的多步任务。
如果还没有安装dplyr,请运行以下代码以从CRAN中安装:
install.packages("dplyr")
首先,我们重新加载产品表格,将它们重置为原始形式:
library(readr)product_info <- read_csv("data/product-info.csv") product_stats <- read_csv("data/product-stats.csv") product_tests <- read_csv("data/product-tests.csv") toy_tests <- read_csv("data/product-toy-tests.csv")
然后,载入dplyr包:
library(dplyr)#### Attaching package: 'dplyr'## The following objects are masked from 'package:data.table':### between, last## The following objects are masked from 'package:stats':## ## filter, lag ## The following objects are masked from 'package:base': ## ## intersect, setdiff, setequal, union
以上输出信息说明dplyr泛化了很多内置函数。加载这个包之后,这些内置函数便被屏蔽了。
现在,我们可以使用dplyr包提供的变形函数了。先使用select( ) 函数从数据框中提取列,并将这些列存储在新创建的表中:
select(product_info, id, name, type, class) ## Source:local data frame[6x4] ## ## id name type class ## <chr> <chr> <chr> <chr> ## 1 T01 SupCar toy vehicle ## 2 T02 SupPlane toy vehicle ## 3 M01 JeepX model vehicle ## 4 M02 AircraftX model vehicle ## 5 M03 Runner model people ## 6 M04 Dancer model people
打印出来的表格与data.frame和data.table都不太一样。它不仅显示了表格本身,也包括一个表头,用于说明数据框的大小和每一列的数据类型。
显然,select( ) 使用了非标准计算,所以我们可以直接将数据框的列名作为参数。它和subset( )、transform( ) 以及with( ) 的工作方式类似。
其次,我们可以使用filter( ) 函数,通过逻辑条件筛选数据框。同样地,这个函数也是在数据框的语义中被计算:
filter(product_info, released == "yes")## Source:local data frame[4x5]#### id name type class released## <chr> <chr> <chr> <chr> <chr>## 1 T01 SupCar toy vehicle yes## 2 M01 JeepX model vehicle yes## 3 M02 AircraftX model vehicle yes## 4 M03 Runner model people yes
如果想要根据多个条件筛选记录,只需要把每个条件都作为filter() 的参数:
filter(product_info,released == "yes", type == "model")## Source:local data frame[3x5]#### id name type class released## <chr> <chr> <chr> <chr> <chr>## 1 M01 JeepX model vehicle yes## 2 M02 AircraftX model vehicle yes## 3 M03 Runner model people yes
mutate( )函数可以创建一个新的数据框,这个数据框包含新列,或者替换原数据框的列。它与transform( )类似,不同的是,如果数据是data.table,它也能支持原地赋值:=:
mutate(product_stats, density = size / weight)## Source: local data frame [6 x 5]#### id material size weight density## <chr> <chr> <int> <dbl> <dbl>## 1 T01 Metal 120 10.0 12.000000## 2 T02 Metal 350 45.0 7.777778## 3 M01 Plastics 50 NA NA## 4 M02 Plastics 85 3.0 28.333333## 5 M03 Wood 15 NA NA## 6 M04 Wood 16 0.6 26.666667
arrange( ) 函数也是用于创建一个新的数据框,这个数据框是按一个或多个列排序后的。desc( ) 函数表示降序排列:
arrange(product_stats, material, desc(size), desc(weight))## Source: local data frame [6 x 4]#### id material size weight## <chr> <chr> <int> <dbl>## 1 T02 Metal 350 45.0## 2 T01 Metal 120 10.0## 3 M02 Plastics 85 3.0## 4 M01 Plastics 50 NA## 5 M04 Wood 16 0.6## 6 M03 Wood 15 NA
dplyr包提供了丰富的连接函数,包括inner_join( )、left_join( )、right_join( )、full_join( )、semi_join( ) 和anti_join( )。如果要连接的两个表存在无法匹配的记录,这些连接操作的行为会有很大差别。对于product_info和product_tests,它们的记录可以完全匹配,所以left_join( ) 的返回结果和merge( ) 相同:
product_info_tests <- left_join(product_info, product_tests, by = "id")product_info_tests## Source: local data frame [6 x 8]#### id name type class released quality durability## <chr> <chr> <chr> <chr> <chr> <int> <int>## 1 T01 SupCar toy vehicle yes NA 10## 2 T02 SupPlane toy vehicle no 10 9## 3 M01 JeepX model vehicle yes 6 4## 4 M02 AircraftX model vehicle yes 6 5## 5 M03 Runner model people yes 5 NA## 6 M04 Dancer model people no 6 6## Variables not shown: waterproof (chr)
运行?dplyr::join了解这些连接操作的更多差异。
为了对数据进行分组汇总,我们需要先利用group_by( ) 创建一个分组后的表格。然后使用summarize( ) 汇总数据。例如,我们想把product_info_tests按照type和class分割开,然后对每一组计算quality和durability的平均值:
summarize(group_by(product_info_tests, type, class), mean_quality = mean(quality, na.rm = TRUE), mean_durability = mean(durability, na.rm = TRUE))## Source: local data frame [3 x 4]## Groups: type [? ]#### type class mean_quality mean_durability## <chr> <chr> <dbl> <dbl>## 1 model people 5.5 6.0## 2 model vehicle 6.0 4.5## 3 toy vehicle 10.0 9.5
通过前面的代码示例,我们掌握了这些变形函数: select( )、filter( )、mutate( )、arrange( )、group_by( ) 和summarize( )。这些函数的设计初衷都是对数据进行一个小操作,但是将它们合理地组合到一起,就可以完成复杂的数据处理操作。除了这些函数,dplyr包还从magrittr包中引入了管道操作符 %>%,利用 %>% 将函数连接起来,组合使用。
假设现在有product_info和product_tests。我们需要对已发布的产品进行分析,对于每种类型和类对应的组,计算该组产品的质量和耐久性的平均值,并将结果数据按照质量均值降序排列。通过使用管道操作符将dplyr变形函数连接起来,可以漂亮地完成这个任务:
product_info %>% filter(released == "yes") %>% inner_join(product_tests, by = "id") %>% group_by(type, class) %>% summarize( mean_quality = mean(quality, na.rm = TRUE), mean_durability = mean(durability, na.rm = TRUE)) %>% arrange(desc(mean_quality))## Source: local data frame [3 x 4]## Groups: type [2]#### type class mean_quality mean_durability## <chr> <chr> <dbl> <dbl>## 1 model vehicle 6 4.5## 2 model people 5 NaN## 3 toy vehicle NaN 10.0
但是 %>% 是如何工作的呢?其实,管道操作符基本上只负责一件事情:把符号左侧返回的结果,作为符号右侧调用函数的第1个参数。也就是说,x %>% f(...) 等价于f(x, ...)。因为 %>% 是一个由包定义的二元操作符,所以允许我们将函数调用连接起来,一方面避免存储多余的中间值,另一方面将嵌套调用分解,使每一步操作流程清晰地展现出来。
假设将d0转化为d3需要3个步骤。在每一步的函数调用中,需要将前面一步的结果作为参数。如果像这样操作数据,可能会有很多中间结果,当数据量很大的时候,会消耗很多内存:
d1 <- f1(d0, arg1)d2 <- f2(d1, arg2)d3 <- f3(d2, arg3)
想要避免中间结果,就不得不写嵌套调用。这个任务看起来一点都不友好,特别是在每个函数调用都有多个参数的时候:
f3(f2(f1(d0, arg1), arg2), arg3)
使用管道操作符,工作流便可以像下面这样重新组织:
d0 %>% f1(arg1) %>% f2(arg2) %>% f3(arg3)
这样的代码看起来更加简洁和直观。整个表达式不止看起来像一个管道,其工作方式也像一个管道。d0 %>% f1(arg1) 等价于f1(d0, arg1),并会被送往f2(., arg2),紧接着又会被送往f3(., arg3)。每一步的输出结果都会成为下一步的输入。
而且,管道操作符不止在dplyr的函数中生效,对其他所有的函数也都是适用的。假设我们想要对钻石价格画一个密度图,如图所示。
data(diamonds, package = "ggplot2")plot(density(diamonds$price, from = 0), main = "Density plot of diamond prices")
image
使用管道操作符,我们可以像这样重写代码:
diamonds$price %>% density(from = 0) %>% plot(main = "Density plot of diamonds prices")
与data.table类似,dplyr也提供了do( ) 函数来对每组数据进行任意操作。例如,将diamonds按cut分组,每组都按log(price) ~ carat拟合一个线性模型。和data.table不同的是,我们需要为操作指定一个名称,以便将结果储存到列中。而且,do( ) 中的表达式不能直接在分组数据的语义下计算,我们需要使用.来表示数据:
models <- diamonds %>% group_by(cut) %>% do(lmod = lm(log(price) ~ carat, data = .))models## Source: local data frame [5 x 2]## Groups: <by row>#### cut lmod## <fctr> <chr>## 1 Fair <S3: lm>## 2 Good <S3: lm>## 3 Very Good <S3: lm>## 4 Premium <S3: lm>## 5 Ideal <S3: lm>
注意到一个新列lmod被创建了。这不是一个典型的原子向量列,而是一个包含了线性回归对象的列表,也就是说,每一个cut的值对应的模型会以列表的形式储存在lmod列的对应位置中。我们可以使用索引来获得每个模型:
models$lmod[[1]]#### Call:## lm(formula = log(price) ~ carat, data = .)#### Coefficients:## (Intercept) carat## 6.785 1.251
在需要完成高度定制的操作时,do( ) 函数的优势就更加明显了。举个例子,假如我们需要分析toy_tests数据,要对每种产品的质量和耐久性进行汇总。如果只需要样本数最多的3个测试记录,并且每个产品的质量和耐久性是经样本数加权的平均数,考虑下我们应该做什么。
使用dplyr包的函数和管道操作符,上述任务可以通过以下代码轻松完成:
toy_tests %>% group_by(id) %>% arrange(desc(sample)) %>% do(head(., 3)) %>% summarize( quality = sum(quality * sample) / sum(sample), durability = sum(durability * sample) / sum(sample))## Source:local data frame[2x3]#### id quality durability## <chr> <dbl> <dbl>## 1 T01 9.319149 9.382979## 2 T02 9.040000 8.340000
注意到,当数据分组后,所有的后续操作都是按组进行的。为了查看中间结果,我们可以运行do(head(., 3)) 之前的代码,如下所示:
toy_tests %>% group_by(id) %>% arrange(desc(sample))## Source: local data frame [8 x 5]## Groups: id [2]#### id date sample quality durability## <chr> <int> <int> <int> <int>## 1 T0120160405 180 9 10## 2 T0120160302 150 10 9## 3 T0120160502 140 9 9## 4 T0120160201 100 9 9## 5 T0220160403 90 9 8## 6 T0220160502 85 10 9## 7 T0220160303 75 8 8## 8 T0220160201 70 7 9
这样我们就得到了按样本数降序排列的所有记录。然后,do(head(., 3)) 将会对每一个组计算head(. 3),其中,.表示每组数据:
toy_tests %>% group_by(id) %>% arrange(desc(sample)) %>% do(head(., 3))## Source: local data frame [6 x 5]## Groups: id [2]#### id date sample quality durability## <chr> <int> <int> <int> <int>## 1 T0120160405 180 9 10## 2 T0120160302 150 10 9## 3 T0120160502 140 9 9## 4 T0220160403 90 9 8## 5 T0220160502 85 10 9## 6 T0220160303 75 8 8
现在,我们得到了每一组的样本数最多的3条记录,如此汇总数据是很方便的。
dplyr函数定义了一种非常直观的数据操作语法,并且提供了便于使用管道操作符的高性能变形函数。更多内容,请阅读包的指南(https://cran.rstudio.com/web/packages/dplyr/vignettes/introduction.html),并且访问DataCamp上的交互式教程(https://www.datacamp.com/courses/dplyr-data-manipulation-r-tutorial)。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
R语言dplyr包实操
R学习:R for Data Science(四)
从一行代码里面学点 JavaScript
Rxjs 里 Observable 对象的 tap 操作
收藏|零基础学R,进阶数据框-双表操作
dplyr和tidyr简介|数据处理
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服