1 Star 7 Fork 1

zhongyf/Rbook

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
07-Data-manipulation.Rmd 39.98 KB
一键复制 编辑 原始数据 按行查看 历史
zhongyf 提交于 2021-06-15 11:23 . commit
# 数据处理之-dplyr {#Data:Manipulation-dplyr} 本章节主要目的是通过阐述dplyr动词用法,实现与`Excel透视表`或`sql`相同功能,从而达到不同的数据整理、聚合需求。 本章主要从以下方面阐述: 1. 行条件筛选 2. 列筛选 3. 字段重命名 4. 列位置排序 5. 行排序 6. 新增计算字段 7. 分组聚合 8. 表关联 9. 行列操作 10. 使用dplyr编写自定义函数 其中9,10行列操作和自定义函数有一定难度,大家可以先熟悉dplyr基本用法后再了解其用法。 与`sql`相比,用dplyr的优势: - 代码量极大减少 - 逻辑复杂时,dplyr动词可以按照顺序一步步实现,无需嵌套,实现过程简单 - 代码可读性好 - 配合`dbplyr`包使用,大部分情况下可以扔掉`sql`语法,从而实现不同数据库间语法并不完全一致时,代码可重复使用 > 本章节中部分案例照搬dplyr包的官方案例, dplyr动词从数据库相关操作中抽象而来,从sql迁移成本低 ## 前言 {#dplyr:description} `dplyr`包是`tidyverse`系列的核心包之一。dplyr是**A Grammar of Data Manipulation **,即dplyr是数据处理的语法。数据操作在数据库中往往被增、改、删、查四字描述,加上表连接查询基本涵盖了大部分的数据操作。 `dplyr`包通过提供一组动词来解决最常见的数据处理问题: - `mutate()` 添加新变量,现有变量的函数 - `select()` 筛选列,根据现有变量名称选择变量 - `filter()` 筛选行,根据条件筛选 - `summarise()` 按照一定条件汇总聚合 - `arrange()` 行排序 以上动词都可以和`group_by()`结合,使我们可以按组执行以上任何操作。除了以上单个表操作的动词,dplyr中还有操作两表(表关联)的动词,可以通过`vignette("two-table")`查看学习。 ### 安装 {#dplyr:install-package} dplyr包可以直接安装。 ```{r eval=FALSE} ## 最简单是的方式就是安装tidyverse install.packages('tidyverse') ## 或者仅仅安装 tidyr: install.packages('dplyr') ## 或者从github 安装开发版本 ## install.packages("devtools") devtools::install_github("tidyverse/dplyr") ``` > 在开始使用前,请确保自己dplyr版本是较新版本,因为1.0.0版本有较大更新。 ```{r} packageVersion('dplyr') ``` ### Excel and Sql 类比{#dplyr:difference-of-sql} 与Excel相比,dplyr使用`filter`实现筛选,`mutate`实现列新增计算,`summarise`配合`group_by`实现数据透视表,`arrange`实现排序功能。 `dplyr::left_join()`等表连接功能,实现Excel中的`vlookup`,`xlookup`等函数效果。 请看案例: >案例中使用的数据集是R包`nycflights13`带的flights数据集。 Excel实现 ![透视表截图](./picture/data-table/01picture.png) R实现: ```{r message=FALSE,warning=FALSE} library(tidyverse,warn.conflicts = FALSE) data % filter(year==2014,month==6) %>% group_by(origin,dest) %>% summarise(distance求和项 = sum(distance)) ``` Sql实现: ```{r include=FALSE} library(DBI) data ### 常见问题 1. 筛选订单表中的1-5月订单数据,按照城市汇总,求每个城市的销售额和门店数(去重)? ```{r eval=FALSE} data %>% filter(between(月,1,5)) %>% group_by(城市) %>% summarise(金额 = sum(金额),门店数 = n_distinct(门店编码)) ``` 2. 近30天商品销量排名? ```{r eval=FALSE} data %>% filter(订单日期 >= Sys.Date()-30) %>% group_by(分析大类,商品编码) %>% summarise(商品销量 = sum(数量)) %>% group_by(分析大类) %>% mutate(商品排名 = dense_rank(desc(商品销量))) # 注意用desc倒序,销量高排第一 ``` 3. 销售和库存形成笛卡尔积表,计算商品有货率、动销率? **Cheat Sheet** 手册搬运于dplyr[官方介绍](https://dplyr.tidyverse.org/),方便下载查阅。 ![dplyr-sheet](./picture/dplyr/data-transformation.pdf){width=100% height=400} Rstudio提供的其它手册: ## 基础用法 {#dplyr:usage} 基础用法部分,我们将从行筛选,重命名、列位置调整、新增计算列、排序、分组聚合几个方面阐述`dplyr`动词功能。 首先加载包,加载包时可能会有一些重名函数的提示,可以通过warn.conflict参数禁掉提示。如下所示: ```{r} # 禁掉提示 library(dplyr,warn.conflicts = FALSE) ``` ### filter{#dplyr-filter} `filter`动词顾名思义即筛选功能,按照一定条件筛选data.frame;与Excel中的筛选功能和`SQL`中`where`条件一致。 filter条件筛选中可以分为单条件筛选和多条件筛选;多条件中间用`,`分隔。 - 单条件 条件为` species == "Droid" `时,如下所示: ```{r} starwars %>% filter(species == "Droid") ``` ```{sql eval = FALSE} select * from starwars where species = "Droid" -- 注意=与==的区别 ``` - 多条件 多条件筛选时,用英文逗号隔开多个条件。sql中用“and”连接多个条件,与 R 中用逗号隔开效果相同,“and” 在 R 中用 & 表示。 ```{r} starwars %>% filter(species == "Droid",skin_color == "gold") # 同上 # starwars %>% # filter(species == "Droid" & skin_color == "gold") ``` ```{sql eval =FALSE} select * from starwars where species = "Droid" and skin_color = "gold" ``` - 多情况筛选 `%in%`类似`SQL`中 `in` 的用法,与Excel中筛选条件时"或"条件相当。 ```{r} starwars %>% filter(species %in% c("Droid",'Clawdite')) ``` ```{sql eval = FALSE} select * from starwars where species in ("Droid","Clawdite") --sql查询 ``` - 逻辑关系筛选 条件运算分为逻辑运算、关系运算。 关系运算符 >、=、<=分别代表大于、小于、等于、不等于、大于等于、小于等于。 逻辑运算符 &、|、!。 `|`为 或, `&` 为并、且条件,`!`为非。 ```{r} library(nycflights13) filter(flights, !(arr_delay > 120 | dep_delay > 120)) filter(flights, arr_delay <= 120, dep_delay <= 120) # same above filter(flights, arr_delay <= 120 & dep_delay <= 120) # %in% 的反面 starwars %>% filter(!species %in% c("Droid",'Clawdite')) ``` > !的运算级别相比 %in% 更高 ### select {#dplyr-select} 当完整数据集列较多时,我们某次分析可能并不需要那么多列,通过动词`select()`筛选列,剔除不需要的列。 - 基础用法 通过指定列名称筛选,并指定列之间顺序 ```{r} starwars %>% select(name,height,mass,hair_color,skin_color,eye_color) ``` - 列索引 通过列名或数字向量索引,但是不建议用数字索引,避免原始数据列顺序变化后导致报错。 ```{r} starwars %>% select(name : eye_color) # 同上 starwars %>% select(1:6) # starwars %>% select(c(1,2,4,5,7)) ``` - 新增列筛选方式 ```{r} # starwars %>% select(!(name:mass)) # iris %>% select(!ends_with("Width")) # iris %>% select(starts_with("Petal") & ends_with("Width")) # iris %>% select(starts_with("Petal") | ends_with("Width")) ``` ### rename {#dplyr-rename} 列重命名使用`rename()`函数,新名称写前面,如下所示: ```{r rename} starwars %>% rename(home_world = homeworld) # 多列同换 starwars %>% rename(home_world = homeworld,skincolor = skin_color) ``` ```{sql eval = FALSE} select * ,homeworld as home_word from starwars select * ,homeworld home_word from starwars ``` > as 可以省略,但中间有一个以上空格。与R的差异是新增home_word列,原始列继续存在,R中是替换列名。 有时我们需要批量修改列名,改如何实现? ```{r eval=FALSE} names(starwars) % relocate(sex:homeworld, .before = height) ``` ### mutate {#dplyr-mutate} 动词`mutate`可以新增计算列,删除列,更新已有列,列之间的计算都可以通过mutate实现。 - 新增计算列 ```{r} starwars %>% mutate(bmi = mass / ((height / 100) ^ 2)) %>% select(name:mass,bmi) ``` - 新增计算列基础上新增列 ```{r} starwars %>% mutate(bmi = mass / ((height / 100) ^ 2),newbmi = bmi *2) %>% select(name:mass,bmi,newbmi) ``` - 删除列 ```{r} starwars %>% mutate(height = NULL) ``` ### arrange {#dplyr-arrange} - 单列排序,默认升序,通过`desc()`降序排列 ```{r} starwars %>% arrange(desc(mass)) ``` - 多列排序 ```{r} starwars %>% arrange(height,desc(mass)) ``` ```{sql eval=FALSE} select * from starwars order by height,mass desc ``` ### summarise {#dplyr-summarise} `summarise`常与`group_by`结合使用。 ```{r} mtcars %>% summarise(mean = mean(disp), n = n()) ``` > n()是dplyr包中的计算当前组大小的函数,用在summarise()和mutate()中。通常用来组计算。 ### group_by {#dplyr-groupby} 聚合前一般都需要分组,`group_by()`动词实现该功能,与`SQL`中`group by ···`类似。 ```{r} starwars %>% group_by(species) %>% summarise( n = n(), mass = mean(mass, na.rm = TRUE) ) ``` ```{sql eval=FALSE} SELECT species, count(species) n, AVG(mass) mass FROM [spb].[dbo].[starwars] GROUP BY species ``` ## 常用函数 {#dplyr-functions} 本小节介绍dplyr处理数据时常用的函数,类似sql中的case_when、count、distinct、row_number、between等函数。 ### 条件判断 dplyr::`if_else()`相比于`base::ifelse`,`if_else`输出类型更为严格,无论`TRUE`或`FALSE`输出类型一致,这样计算速度更快。 dplyr::`if_else()`参数: ```{r dplyr-ifelse ,eval=FALSE} if_else(condition, true, false, missing = NULL) ``` ```{r} x 5,"大于五","小于等于五",missing = "空值") ``` 与`ifelse`不同的是,`if_else`保留类型 ```{r dplyr-ifelse-1} x 与`data.table::fifelse()`功能相似 ### case_when 当条件嵌套较多时,使用`case_when`,使代码可读并且不易出错。与sql 中的case when 等价。 ```{r} x % mutate(性别 = case_when( sex == "male" ~ "雄性", sex == "female" ~ "雌性", sex == "hermaphroditic" ~ "雌雄同体", TRUE ~ "无" )) %>% pull(性别) %>% table() ``` > pull()功能与 data$.类似,为了在管道中使用设计 ### 计数函数 - 计数 `count()`函数用来计数。下面两种表达方式等价。 ```{r dplyr-count,eval =FALSE} df %>% count(a, b) # same above df %>% group_by(a, b) %>% summarise(n = n()) ``` ```{r dplyr-count-1} starwars %>% count(species) # same above 等价 starwars %>% group_by(species) %>% summarise(n = n()) ``` - 非重复计数 `n_distinct()`与` length(unique(x))`等价,但是更快更简洁。当我们需要给门店或订单之类数据去重计算时采用该函数。 ```{r} x % group_by(商品大类) %>% slice_head(n = 5) ``` #### 函数用法 ```{r eval=FALSE} slice(.data, ..., .preserve = FALSE) slice_head(.data, ..., n, prop) slice_tail(.data, ..., n, prop) slice_min(.data, order_by, ..., n, prop, with_ties = TRUE) slice_max(.data, order_by, ..., n, prop, with_ties = TRUE) slice_sample(.data, ..., n, prop, weight_by = NULL, replace = FALSE) ``` #### 参数解释 .data: 一个 data.frame , data frame extension (tibble) ...: 必须是整数,如果是正数将保留,负值将删除。提供的值超过行数的将会被忽略 .preserve :默认为FALSE,根据结果数据重新计算分组结构,否则保持原样分组 n,p: 提供要选择的行数 n,或行的比列比如 prop = 0.4,如果都未提供则默认n = 1 order_by: 要排序的变量或者是被函数作用的变量 with_ties: 针对 _min 或 _max 函数,相等时候是否强制输出指定行数。 weight_by: 抽样的权重 replace: 是否允许重复抽样,默认为FALSE #### slice 案例 - slice() ```{r} # 筛选第一行 mtcars %>% slice(1L) # 筛选最后一行 mtcars %>% slice(n()) # 筛选5至最后一行 mtcars %>% slice(5:n()) # 删除前面四行 slice(mtcars, -(1:4)) ``` - slice_head slice_tail ```{r} # 基于现有顺序筛选前面行或最后行 mtcars %>% slice_head(n = 5) mtcars %>% slice_tail(n = 5) ``` - slice_min slice_max ```{r} # 基于变量筛选 mtcars %>% slice_min(mpg, n = 5) #最小的五行 mtcars %>% slice_max(mpg, n = 5) #最大的五行 # slice_min()可能返回更多行,通过with_ties参数控制 mtcars %>% slice_min(cyl, n = 1) mtcars %>% slice_min(cyl, n = 1, with_ties = FALSE) ``` - slice_sample 在数据集中抽样,通过 replace 参数控制是否可以重复。 ```{r} mtcars %>% slice_sample(n = 5) mtcars %>% slice_sample(n = 5, replace = TRUE) ``` weight_by 参数调整抽样权重 ```{r} # 重量大的会更容易抽到 mtcars %>% slice_sample(weight_by = wt, n = 5) ``` - 分组操作 ```{r} df % group_by(group) %>% slice_head(n = 2) # 注意体会使用prop参数时的差异 df %>% group_by(group) %>% slice_head(prop = 0.4) #仅c组返回一个,因为c组3个数字的 40%的前面还有数 df %>% group_by(group) %>% slice_head(prop = 0.5) # 因为a组只有一个数字,前50%位没有数字 ``` ### group 系列 group 系列函数包含 group_by(),group_map(), group_nest(), group_split(), group_trim()等。 其中我常用group_by(),group_split()两个函数。group_by()是我们熟悉的动词,大部分数据操作中的分组操作由它完成。 - group_by() ```{r} #group_by()不会改变数据框 by_cyl % group_by(cyl) by_cyl # It changes how it acts with the other dplyr verbs: by_cyl %>% summarise( disp = mean(disp), hp = mean(hp) ) # group_by中可以添加计算字段 即mutate操作 mtcars %>% group_by(vsam = vs + am) %>% group_vars() ``` - group_split() 目前该函数是实验性的,group_split() 功能与 base::split()相似,按照数据集的分组变量切割数据集。如下,将 iris 数据集按照 Species 分组并切割成长度为3的列表,每个子元素是 Species 变量的子数据集。Species 变量包含`setosa`、`versicolor`、` virginica`三种情况。 ```{r} ir % group_by(Species) group_split(ir) group_keys(ir) ``` - group_map() group_map,group_modify,group_walk等三个函数是purrr类具有迭代风格的函数。简单关系数据的数据清洗一般不涉及,常用在建模等方面。 但是目前函数是实验性的,未来可能会发生变化。 ```{r} # return a list # 返回列表 mtcars %>% group_by(cyl) %>% group_map(~ head(.x, 2L)) ``` ```{r} iris %>% group_by(Species) %>% group_modify(~ { .x %>% purrr::map_dfc(fivenum) %>% mutate(nms = c("min", "Q1", "median", "Q3", "max")) }) ``` 分组后批量输出 ```{r eval=FALSE} # group_walk dir.create(temp % group_by(Species) %>% group_walk(~ write.csv(.x, file = file.path(temp, paste0(.y$Species, ".csv")))) list.files(temp, pattern = "csv$") unlink(temp, recursive = TRUE) ``` - group_cols() 选择分组变量 ```{r} gdf % group_by(Species) gdf %>% select(group_cols()) ``` ### 其它函数 有针对性学习函数可以有效提高学习效率,先浏览一遍 dplyr 中函数,再挑选觉得对自己有用的函数学习即可。 - between ```{r} between(1:12, 7, 9) ``` - pull ```{r} mtcars %>% pull(-1) mtcars %>% pull(cyl) ``` - distinct ```{r eval=FALSE} df 该函数在我们计算下单间隔天数时比较简洁方便 如需了解更多,可以通过学习[`slider`](https://github.com/DavisVaughan/slider)包知道更多"滑动窗口函数"。 - cummean cumsum cumall cumany 累计系列函数 ```{r} x % mutate(a = coalesce(a,0)) ``` ## 表关联 {#dplyr:merge-two-table} 在我们做数据分析项目时,大多数时候数据源不止一个,我们需要通过表间共有信息字段将表关联,让表信息更加全面,从而方便做各种信息分类汇总。 dplyr 中表关联是通过一组函数实现,像`sql`中的`left join`,`inner join`等表格之间的操作,或者是Excel中`Power Piovt`建模的建立关系,从而实现不同表格间的关联。 ### 两表关联 {#dplyr:merge-table-usage} dplyr 中`left_join()`,`full_join`,`inner_join()`等动词关联两个表,详情请查看:`vignette("two-table")`。 `left_join()`,`right_join()`,`full_join()`,`inner_join`(),第一个以左表为主,第二个右表为主,第三个全连接,第四个内连接(只返回两表中都有的记录),和数据库中用法一致。 > `left_join()`实现类似 Excel中`VLOOKUP`函数功能,将匹配到的"右表"字段依据对应关系关联到“左表”上。 #### 基础用法 dplyr中的关联函数的关联条件可以分为: - 同名列关联 - 不同名称列关联 ##### 同名列关联 默认同名列关联,也可指定关联条件列,类似数据库中`on a.column = b.column `,但 R 代码看起来更加"优雅"。 1. 单个关联条件 ```{r dplyr-32} library("nycflights13") flights2 % select(year:day, hour, origin, dest, tailnum, carrier) # 默认同名 flights2 %>% left_join(airlines) ``` 在某些时候,不同的表间可能出现同名的字段,但是并不能作为关联字段,这是就需要人为指定关联字段,并且建议在写代码的时候明确指定关联字段,能避免一些可能不必要的错误。 ```{r} # 或指定列名 flights2 %>% left_join(planes, by = "tailnum") ``` 像 Sql 中指定关联字段,以下 Sql 代码和上面 R 代码 等效。 ```{sql dplyr-33, eval=FALSE} select * from flights2 a left join planes b on a.tailnum = b.tailnum ``` 2. 多个关联条件 ```{r} dta % left_join(airports, by = c("dest" = "faa")) # flights2 %>% left_join(airports, c("origin" = "faa")) # 多条件关联时用逗号隔开 dta % select(-id.x,-id.y) ``` ##### 不唯一关联 如果关联表中值不是唯一的,连接将添加匹配表的所有可能组合(笛卡尔积): 右表重复 ```{r} df1 % left_join(df2) df1 %>% right_join(df2) ``` 两表重复 ```{r} df1 % left_join(df2) ``` 在我们工作中可能需要求两个表之间的笛卡尔乘积[^笛卡尔乘积],并且没有可关联字段,该如何求? [^笛卡尔乘积]:笛卡尔乘积是指在数学中,两个集合X和Y的笛卡尔积(Cartesian product),又称直积,表示为X × Y,第一个对象是X的成员而第二个对象是Y的所有可能有序对的其中一个成员 ```{r} dta % glimpse() ``` 以上是简单的构造笛卡尔积表的办法,我们还可以通过构建相同列达到效果,如下所示: ```{r} dta %>% mutate(col = 1) %>% left_join(dtb %>% mutate(col=1)) %>% mutate(col = NULL) %>% glimpse() ``` #### 筛选关联 在某些时候筛选关联会比较方便,但是我个人比较少用。 `anti_join()`:删除所有左表中在右表中匹配到的行 `semi_join()`:保留所有左表在右表中匹配到的行 ```{r dplyr-34} df1 % anti_join(df1) df1 %>% semi_join(df2) ``` #### 集合操作 以下动词要求x,y具有相同的变量,也就是x,y的格式一样。 1. `intersect(x,y)`返回x,y交集 2. `union(x,y)`返回x,y中唯一的值 3. `setdiff(x,y)`返回存在x中但是不存在y中的记录 4. `union_all(x,y)`返回全部值,不删除重复值 ```{r dplyr-35} (df1 % left_join(dt2) %>% left_join(dt3) %>% left_join(dt4) %>% left_join(dt5) ``` - 改进写法 当需要合并多个表时,可用`purrr::reduce()`实现 ```{r} dtlist ### 添加分组 ```{r dplyr-groupby-1} by_species % group_by(species) by_sex_gender % group_by(sex, gender) ``` 在添加分组后,print()打印时可以看到分组: ```{r} by_species ``` 计算每个分组的行数,可以通过`sort`参数控制排序方式。 ```{r} by_species %>% tally() by_sex_gender %>% tally(sort = TRUE) ``` > 在数据探索时比较有用。 除了按照现有变量分组外,还可以按照函数处理后的变量分组,等效在`mutate()`之后执行`group_by`: ```{r dplyr-groupby-2} bmi_breaks % group_by(bmi_cat = cut(mass/(height/100)^2, breaks=bmi_breaks)) %>% tally() ``` > group_by()可通过在group_by()计算产生的新字段分组 ### 查看分组 使用`group_keys()`查看数据的分组,每个组一行,每个分组变量占一列: ```{r} by_species %>% group_keys() by_sex_gender %>% group_keys() ``` 也可以使用命令`group_indices()`查看每行属于哪个组: ```{r} by_species %>% group_indices() ``` 该特性方便增加组别列。 ```{r} df % group_by(a) %>% mutate(组别列 = group_indices() ) ``` 上述用法在dplyr 1.0.0 中弃用,用`cur_group_id()`代替,如下所示: ```{r} df %>% group_by(a) %>% mutate(组别列 = cur_group_id()) ``` > dplyr 在我看来 API 变化较快,所以我个人习惯使用 data.table 包处理数据。所以在学习的时候用最新的版本学习。 `group_rows()`每个组包含哪些行: ```{r} by_species %>% group_rows() %>% head() ``` `group_vars()`返回分组变量的名称,请使用: ```{r} by_species %>% group_vars() by_sex_gender %>% group_vars() ``` ### 更改和添加分组变量 如果将`group_by()`应用已经分组的数据集,将覆盖现有的分组变量。例如,下面的分组变量是`homeworld`而不是之前的`species`。 ```{r} by_species %>% group_by(homeworld) %>% tally() ``` 要增加分组变量,使用`.add=TRUE`参数即可。例如: ```{r} by_species %>% group_by(homeworld,.add = TRUE) %>% tally() ``` ### 删除分组变量 要删除所有分组变量,使用`ungroup()`: ```{r dplyr-groupby-3} by_species %>% ungroup() %>% tally() ``` 还可以通过列出要删除的变量来有选择的删除分组变量: ```{r} by_sex_gender %>% ungroup(sex) %>% tally() ``` ### 动词影响 `group_by()`是如何影响 dplyr 的主要动词。 - summarise `summarise()` 计算每个组的汇总,表示从`group_keys()`开始,在右侧添加`summarise()`的汇总变量。 ```{r dplyr-groupby-4} by_species %>% summarise( n = n(), height = mean(height, na.rm = TRUE) ) ``` 该`.groups=`参数控制输出的分组结构。删除右侧分组变量的历史行为对应于`.groups =` "drop_last"没有提示消息,或.groups = NULL有消息(默认值)。 ```{r} by_sex_gender %>% summarise(n = n()) %>% group_vars() by_sex_gender %>% summarise(n = n(),.groups = 'drop_last') %>% group_vars() ``` 从1.0.0版开始,分组信息可以保留`(.groups = "keep")`或删除 `(.groups = 'drop)` ```{r dplyr-groupby-5} a % summarise( n = n(), height = mean(height, na.rm = TRUE),.groups='drop') b % summarise( n = n(), height = mean(height, na.rm = TRUE),.groups='keep') object.size(a) object.size(b) ``` 以上,可以看到保留分组信息的比没保留的对象大了两倍多,在实际使用中,当数据较大时会占据更多内存,所以我们需要根据实际情况决定是否保留分组信息,我在大部分时候都会删除分组信息。 - arrange 默认情况下,分组和不分组的数据集应用在在`arrange()`效果相同。除非设置`.by_group = TRUE`,这时首先按照分组变量排序。 ```{r} df % group_by(a) df %>% arrange(desc(b)) df %>% arrange(desc(b),.by_group = TRUE) ``` 实际上就是,默认情况下`arrange()`会忽略分组变量,除非通过设置参数`.by_group`参数。 - mutate and transmute 根据 mutate 中函数不同,返回值视情况而定。 ```{r} df % mutate( d = mean(b)) res2 % group_by(a) %>% mutate(d = mean(b)) # 分组后返回不同值,此时的 mutate 相当于汇总函数 identical(res1,res2) ``` - filter filter 受 group_by 影响。 ```{r} df %>% group_by(a) %>% filter(b == max(b)) ``` 以上代码本质是先执行 `mutate(max(b))`,再filter,最后只保留 TRUE 所在行。如下所示: ```{r} df %>% group_by(a) %>% mutate(max_num = max(b)) %>% filter(b==max_num) %>% select(-max_num) ``` 想想以下代码的含义? ```{r} df %>% group_by(a) %>% filter(n()!=1) ``` ~~删除只有一行记录的数据行~~ - slice 系列 选择每个分组变量的第一个观测值。 ```{r} by_species %>% relocate(species) %>% slice(1) ``` 同样,我同样可以使用`slice_min()`来选择 变量的最大值。 ```{r} by_species %>% filter(!is.na(height)) %>% slice_max(height,n=3) ``` ## 列操作 {#dplyr-column-manipulation} 在多列上执行相同的函数是常有的操作,但是通过复制和粘贴代码,麻烦并且容易错,如下所示: ```{r eval=FALSE} df %>% group_by(g1, g2) %>% summarise(a = mean(a), b = mean(b), c = mean(c), d = mean(d)) ``` 通过`across()`函数可以更简洁地重写上面代码: ```{r eval=FALSE} df %>% group_by(g1, g2) %>% summarise(across(a:d, mean)) ``` 假设我们要将表格中多列向上取整,代码如下: ```{r} dt % mutate(across(a:d,ceiling)) ``` 函数`across()`通过与`summarise()`和`mutate()`结合,很容易将某函数运用到多列上。函数`across()`取代了`summarise_all()`,`summarise_at()`,`summarise_if()`函数。 ```{r} starwars %>% summarise_at(c("height", "mass"), mean, na.rm = TRUE) starwars %>% summarise(across(c("height", "mass"), ~ mean(.x, na.rm = TRUE))) # purrr风格函数 ``` ### 基础用法 {#dplyr-column-manipulation-basic-usage} across() 有两个主要参数: - 第一个参数,.cols 选择要操作的列。它使用`tidyr`的方式选择(例如select()),因此您可以按位置,名称和类型选择变量。 - 第二个参数,.fns是要应用于每一列的一个函数或函数列表。也可以是 purrr 样式的公式(或公式列表),例如~ .x / 2。 ```{r} starwars %>% summarise(across(where(is.character), ~ length(unique(.x)))) starwars %>% group_by(species) %>% filter(n() > 1) %>% summarise(across(c(sex, gender, homeworld), ~ length(unique(.x)))) starwars %>% group_by(homeworld) %>% filter(n() > 1) %>% summarise(across(where(is.numeric), ~ mean(.x, na.rm = TRUE))) ``` > ~ .x / 2是purrr包提供的函数式编程风格,等效于 function(x) (x/2) `across()` 不会选择分组变量,如下所示:`group_by()`中的变量`g`不会被选中执行`sum()`函数。 ```{r} df % group_by(g) %>% summarise(across(where(is.numeric), sum)) ``` ### 多种函数功能 通过在第二个参数提供函数或 lambda 函数的命名列表,可是使用多个函数转换每个变量: ```{r} min_max % summarise(across(where(is.numeric), min_max)) ``` 通过`.names`参数控制名称: Note: 该参数的机制没有特别理解,需多练习体会,主要是运用到匿名函数时 以下是官方案例,但是报错(目前已修复): ```{r } starwars %>% summarise(across(where(is.numeric), min_max, .names = "{.fn}.{.col}")) ``` 修改后正常运行: ```{r} starwars %>% summarise(across(where(is.numeric), min_max, .names = "{fn}.{col}")) ``` 区别主要是`.names`参数的使用方式问题,`.`加不加的问题。 ```{r } starwars %>% summarise(across(where(is.numeric), min_max, .names = "{fn}——{col}")) ``` ### 当前列 如果需要,可以通过调用访问内部的“当前”列的名称`cur_column()`,仅在`across()`使用。和 Excel 中power pivot的"上下文"概念类似。 该函数不是特别容易理解,需要多尝试使用加深认识。 ```{r} df % mutate(across(all_of(names(mult)), ~ .x * mult[[cur_column()]])) ``` 代码解释:代码实现的是数据框 df 中列和 mult 中同名元素相乘得到新列。`mult[[cur_column()]]`依次返回mult[["x"]],mult[["y"]],mult[["z"]]。 以上部分是关于列操作的内容,详情查看`vignette("colwise")`。 ## 行操作{#dplyr-row-manipulation} 行操作指不同字段间的计算,如`Excle`的列与列之间计算,`Excle`中的函数对行列不敏感,没有明显区别,但是`R`中`tidyverse`里列计算简单,行间计算依赖`rowwise()`函数实现。 ![Excel-sum](picture/dplyr/dplyr-row-manipulation.png) ```{r} df % rowwise() df %>% rowwise() %>% mutate(total = sum(c(x, y, z))) #返回结果与Excel一致 df %>% mutate(total = sum(c(x, y, z))) # 返回结果不符合预期 ``` 通过自己定义函数实现: ```{r} fun1 % mutate(total = fun1(x,y,z)) ``` ### 比较差异 像`group_by()`,`rowwise()`并没有做任何事情,它的作用是改变其他动词的工作方式。 注意以下代码返回结果不同: ```{r} df %>% mutate(m = mean(c(x, y, z))) df %>% rowwise() %>% mutate(m = mean(c(x, y, z))) ``` `df %>% mutate(m = mean(c(x, y, z)))`返回的结果是x,y,z散列全部数据的均值;`df %>% rowwise() %>% mutate(m = mean(c(x, y, z)))`通过rowwise改变了mean的作为范围,返回的某行x,y,z列3个数字的均值。两种动词的作用的范围因为rowwise完全改变。 可以选择在调用中提供“标识符”变量`rowwise()`。这些变量在您调用时被保留`summarise()`,因此它们的行为与传递给的分组变量有些相似`group_by()`: ```{r} df % rowwise() %>% summarise(m = mean(c(x, y, z))) df %>% rowwise(name) %>% summarise(m = mean(c(x, y, z))) ``` ### 常用案例 ```{r} df % rowwise() %>% mutate(m = mean(c(x, y, z))) # Compute the minimum of x and y in each row df %>% rowwise() %>% mutate(m = min(c(x, y, z))) # In this case you can use an existing vectorised function: df %>% mutate(m = pmin(x, y, z)) ``` 键入每个变量名称很繁琐,通过`c_across()`使更简单。 ```{r eval=FALSE} df % rowwise(id) rf %>% mutate(total = sum(c_across(w:z))) rf %>% mutate(total = sum(c_across(where(is.numeric)))) rf %>% mutate(total = sum(c_across(w:z))) %>% ungroup() %>% mutate(across(w:z, ~ . / total)) ``` 有关更多信息请查看 vignette("rowwise")。 ## `dplyr`编程{#dplyr-programming} [Programming with dplyr](https://cloud.r-project.org/web/packages/dplyr/vignettes/programming.html) 本小节概念性东西较多且复杂不易理解,先尝试会使用,概念再慢慢消化理解。虽然复杂,但是比较实用,尤其是当我们需要定义一些通用功能函数时。以下是对原文引用。 两种情况: - When you have the data-variable in a function argument (i.e. an env-variable that holds a promise2), you need to ** embrace ** the argument by surrounding it in doubled braces, like `filter(df, {{ var }})`. The following function uses embracing to create a wrapper around `summarise()` that computes the minimum and maximum values of a variable, as well as the number of observations that were summarised: ```{r eval=FALSE} var_summary % summarise(n = n(), min = min({{ var }}), max = max({{ var }})) } mtcars %>% group_by(cyl) %>% var_summary(mpg) ``` - When you have an env-variable that is a character vector, you need to index into the .data pronoun with [[, like summarise(df, mean = mean(.data[[var]])). The following example uses .data to count the number of unique values in each variable of mtcars: ```{r eval=FALSE} for (var in names(mtcars)) { mtcars %>% count(.data[[var]]) %>% print() } ``` Note that .data is not a data frame; it’s a special construct, a pronoun, that allows you to access the current variables either directly, with `.data$x` or indirectly with ` .data[[var]]`. Don’t expect other functions to work with it. ### 案例 当我们不知道接下来会用哪个变量汇总时: ```{r} my_summarise % group_by({{ group_var }}) %>% summarise(mean = mean(mass)) } ``` 如果在多个位置使用: ```{r} my_summarise2 % summarise( mean = mean({{ expr }}), sum = sum({{ expr }}), n = n() ) } ``` 当多个表达式时: ```{r} my_summarise3 % summarise(mean = mean({{ mean_var }}), sd = mean({{ sd_var }})) } ``` 如果要输出变量名时: ```{r} my_summarise4 % summarise( "mean_{{expr}}" := mean({{ expr }}), "sum_{{expr}}" := sum({{ expr }}), "n_{{expr}}" := n() ) } my_summarise5 % summarise( "mean_{{mean_var}}" := mean({{ mean_var }}), "sd_{{sd_var}}" := mean({{ sd_var }}) ) } ``` 任意个表达式,这种使用场景更多。 ```{r} my_summarise % group_by(...) %>% summarise(mass = mean(mass, na.rm = TRUE), height = mean(height, na.rm = TRUE)) } starwars %>% my_summarise(homeworld) starwars %>% my_summarise(sex, gender) ``` 本小节做为拓展学习部分,建议完全掌握基础动词的用法后再学习,尤其是在有相关需求的时候再研究效果更好,甚至大部分的商业数据分析师并不需要掌握使用"dplyr 编程",可以直接跳过不学习本小节。 ## 参考资料 {#dplyr:reference-material} 关于使用 dbplyr 包转换 dplyr 动词为 SQL 语句实现数据查询,请参考[R 包 dbplyr 学习](https://www.yuque.com/docs/share/2d5a9224-4be2-4043-9645-924892246bb2?# 《R包 dbplyr 学习》)。 1. dplyr 项目地址 2. dplyr programming 3. dplyr programming 4. dbplyr 包学习
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
R
1
https://gitee.com/zhongyufei/Rbook.git
git@gitee.com:zhongyufei/Rbook.git
zhongyufei
Rbook
Rbook
master

搜索帮助

23e8dbc6 1850385 7e0993f3 1850385