前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >35. R 数据整理(七:使用tidyr和dplyr处理数据框 2.0)

35. R 数据整理(七:使用tidyr和dplyr处理数据框 2.0)

作者头像
北野茶缸子
发布2021-12-17 09:22:59
10.8K0
发布2021-12-17 09:22:59
举报
文章被收录于专栏:北野茶缸子的专栏

参考:李东风老师的R 语言实战

1. tidyverse 系统简介

假设数据以 tibble 格式保存。数据集如果用于统计与绘图,需要满足一定的格式要求,(Wickham, 2014) 称之为 整洁数据 (tidy data),基本要求是每行一个观测,每列一个变量,每个单元格恰好有一个数据值。这些变量应该是真正的属性,而不是同一属性在不同年、月等时间的值分别放到单独的列。

我们可以使用tidyverse 系统来操作,其中包括了magrittr 包,readr 包,dplyr 包和 tidyr 包等。

数据集使用:

代码语言:javascript
复制
> set.seed(1234)
> d.class <- data.frame(sex = sample(c("M", "F"), 10, replace=T), age = runif(10,10,19))

> knitr::kable(d.class)


|sex |      age|
|:---|--------:|
|F   | 16.24232|
|F   | 14.90477|
|F   | 12.54460|
|F   | 18.31090|
|M   | 12.63084|
|F   | 17.53566|
|M   | 12.57601|
|M   | 12.40139|
|M   | 11.68051|
|F   | 12.09003|

2. dplyr/tidyr 数据管理

2.1 filter

使用逻辑条件对行筛选。

代码语言:javascript
复制
filter(test, Species == "setosa")
filter(test, Species == "setosa"&Sepal.Length > 5 )
filter(test, Species %in% c("setosa","versicolor"))

filter() 函数第一个参数是要选择的数据框,后续的参数是条件,这些条件是需要同时满足的,另外,条件中取 缺失值的观测自动放弃,这一点与直接在数据框的行下标中用逻辑下标有所不同,逻辑下标中有缺失值会在结果中 产生缺失值。filter() 会自动舍弃行名,如果需要行名只能将其转换成数据框的一列。

2.2 sample_n

dplyr 包的 sample_n(tbl, size) 函数可以从数据集 tbl 中随机无放回抽取 size 行,如:

代码语言:javascript
复制
> d.class %>% sample_n(size = 3) %>% knitr::kable()


|sex |      age|
|:---|--------:|
|F   | 11.40572|
|F   | 16.50834|
|M   | 16.90388|

ps: 可以用 weight 选项指定数据框中的一列作为抽样 权重,进行不等概抽样。感觉还挺有意思,有机会研究研究。

2.3 distinct

用来去除重复行,有时我们希望得到一个或若干个变量组合的所有不同值。dplyr 包的 distinct() 函数可以对数据框指定若干变 量,然后筛选出所有不同值,每组不同值仅保留一行。指定变量名时不是写成字符串形式而是直接写变量名:

代码语言:javascript
复制
d.class %>% distinct(sex, age) %>% knitr::kable()

如果希望保留数据框中其它列,可以加选项 keep_all=TRUE,默认distinct 后只会返回选定的列。

2.4 drop_na

效果和na.omit 一样,但是高级之处在于,其可以指定列,对数据框某列存在NA 的行直接删除:

代码语言:javascript
复制
> library(tidyr)
> drop_na(X,X1)
  X1 X2
1  A  1
2  B NA
3  C  3
4  D  4
5  E  5

如果不特定指定列,则和na.omit 效果一样。

2.5 select

对行或列筛选,比较有用的是其一些专属函数:

代码语言:javascript
复制
select(test, starts_with("Petal")) #选中..开头的列
select(test, ends_with("Width")) #选中..结尾的列
select(test, contains("etal")) #选中包含..的列
select(test, matches(".t.")) #选中符合某正则表达的列
select(test, everything()) #选中所有列,可以使指定的列先提前
select(test, last_col()) #选中最后一列
select(test, last_col(offset = 1)) #选中倒数第二列。offset 表示忽略n个。忽略最后一个即表示选择倒数第二个。

2.6 arrange

按照数据框里的某列或某几列,对所有行进行排序。可以使用 desc 产生倒序,或写入多个列使其按照多个列进行排序。

代码语言:javascript
复制
arrange(test, Sepal.Length)#默认从小到大排序
arrange(test, desc(Sepal.Length))#用desc从大到小
arrange(test,  desc(Sepal.Width),Sepal.Length) #先按照一列排,再按照一列排

排序时不论升序还是降序,所有的缺失值都自动排到末尾。

2.7 rename

修改变量名。在 dplyr 包的 rename() 中用 “新名字 = 旧名字” 格式修改变量名,如:

代码语言:javascript
复制
 d2.class <- d.class %>% dplyr::rename(h=height, w=weight)

rename() 这个函数可能出现在其它包中,保险起见写成 dplyr::rename()。

2.8 mutate

可以为数据框计算新变量,返回含有新变量以及原变量的新数据框:

代码语言:javascript
复制
mutate(test, new = Sepal.Length * Sepal.Width)
> head(test,3)
  Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
1          5.1         3.5          1.4         0.2     setosa
2          4.9         3.0          1.4         0.2     setosa
3          7.0         3.2          4.7         1.4 versicolor
    new
1 17.85
2 14.70
3 22.40

另外,mutate 也可以用来添加新列,结合我先前说过的新增列的种种方法,并且支持多个语句组成的复合语句:

代码语言:javascript
复制
> d.class %>% mutate(sexc = {
+ x <- rep(" 男", length(sex))
+ x[sex == "F"] <- " 女"
+ x
+ })

其中复合语句中也可以简化的调用数据框的列。

按列号

代码语言:javascript
复制
select(test,1)
select(test,c(1,5))

按列名

如果想要用向量来存放希望筛选的列名,需要使用函数 one_of 来存放该向量。

代码语言:javascript
复制
select(test,Sepal.Length)
select(test, Petal.Length, Petal.Width)
vars <- c("Petal.Length", "Petal.Width")
select(test, one_of(vars))

可以使用,冒号表示列的范围,向列号一样的切片操作:

代码语言:javascript
复制
d.class %>% select(age:weight) %>% head(n=3) %>% knitr::kable()

参数中前面写负号表示扣除,如:

代码语言:javascript
复制
d.class %>% select(-name, -age) %>% head(n=3) %>% knitr::kable()

2.9 表格的长宽转换

gather/spread

可以将本来扁平的数据框变为宽长的数据框。扁平(两个维度对应一个数据)。

代码语言:javascript
复制
# 先生成一个原始的数据
> test <- data.frame(geneid = paste0("gene",1:4),
+                  sample1 = c(1,4,7,10),
+                  sample2 = c(2,5,0.8,11),
+                  sample3 = c(0.3,6,9,12))
> test
  geneid sample1 sample2 sample3
1  gene1       1     2.0     0.3
2  gene2       4     5.0     6.0
3  gene3       7     0.8     9.0
4  gene4      10    11.0    12.0

很显然, data.frame 生成的是“扁平”的数据框。

代码语言:javascript
复制
> test_gather <- gather(data = test,
+                     key = sample_nm,
+                     value = exp,
+                     - geneid)
> head(test_gather)
  geneid sample_nm exp
1  gene1   sample1   1
2  gene2   sample1   4
3  gene3   sample1   7
4  gene4   sample1  10
5  gene1   sample2   2
6  gene2   sample2   5

通过 gather ,并设定key(原先的列),与value(原先的数据),并通过 - (原先的行),对数据框进行转换。

宽长(一个维度对应一个数据)。

代码语言:javascript
复制
> test_re <- spread(data = test_gather,
+                 key = sample_nm,
+                 value = exp)
> test_re
  geneid sample1 sample2 sample3
1  gene1       1     2.0     0.3
2  gene2       4     5.0     6.0
3  gene3       7     0.8     9.0
4  gene4      10    11.0    12.0

通过spread ,设定key(希望转换的列),value(希望转换的数据)。也就回到了开始创建的数据框test。

pivot_longer/pivot_wider

大部分功能是类似的,这里主要说下pivot_longer 针对下面情况的功能:

我们需要 指定切分变量名和随访号的模式,以解决一行中有多个属性的多次观测的情形,在对应的 names_to 中用特殊的".value" 名字表示切分出来的那一部分实际是变量名,这 时不需要 values_to 选项:

代码语言:javascript
复制
dwide4 %>% pivot_longer(
-id,
names_pattern = "(x|y)([[:digit:]])", names_to = c(".value", "time")
) %>% knitr::kable()

对应的长变宽的函数有pivot_wider:

代码语言:javascript
复制
dlong4 %>% pivot_wider(
names_from = c("sex", "type"),
values_from = "count" ) %>%
knitr::kable()

  • 长宽混合转换

有时候,需要将数据框先转换为宽列表,再转换回长列表,比如:

这个数据的问题是 x, y 应该放在两列中却合并成一个了,2018 和 2019 应该放在一列中却分成了两列。先合并 2018 和 2019 这两列,然后再拆分 x 和 y:

代码语言:javascript
复制
dlong6 %>% pivot_longer(
`2018`:`2019`, names_to = "year", values_to = "value" ) %>%
pivot_wider(
names_from = "variable", values_from = "value"
) %>%
knitr::kable()

关于更详细用法,参见李东风的R 教程。

2.10 表格的拆分与合并

将同一列中的内容分为两列内容。或将两列内容合并为同一列内容。

首先还是可以创建一个数据框。

代码语言:javascript
复制
> test <- data.frame(x = c( "a,b", "a,d", "b,c"));test
    x
1 a,b
2 a,d
3 b,c

使用separate,便可以对一列中的数据达到“分离”的效果。对于待分离的对象(col),不必加上引号;但对于即将创建的新列(into),需要使用引号,由于是两列,这里使用向量创建。sep参数设定读取表格信息时以何符号作为分隔符。

代码语言:javascript
复制
> test_seprate <- separate(test,col = x, into = c("X", "Y"),sep = ",");test_seprate
  X Y
1 a b
2 a d
3 b c

使用unite,可以将两列“合并”为一列。对于即将合并的新列,需要使用引号;但对于想要合并的多个列名,可以不用使用引号。sep 参数设定多列合并后不同数据分隔使用的分割符。

代码语言:javascript
复制
> test_re <- unite(test_seprate,col = "x",X,Y,sep = ",");test_re
    x
1 a,b
2 a,d
3 b,c

其实也可以用paste 替代。

引号 yes or not?

到底需不需要引号,对于要处理的列(无论分离还是合并)不用;对于待生成的列则需要。

extract

除了seperate 外,函数 extract() 可以按照某种正则表达式表示的模式从指定列拆分出对应于正则表达式中捕获组的一列或多列内容。

2.11 处理关系数据

参见:中的join 函数介绍部分

2.12 数据框的列拆分与合并

参见:34. R 数据整理(六:根据分类新增列的种种方法 1.0)

其他函数

  • slice

dplyr 包的函数 slice(.data, ...) 可以用来选择指定序号的行子集,正的序号表示保留,负的序号表示排除。如:

代码语言:javascript
复制
> d.class %>% slice(3:5) %>% knitr::kable()


|sex |      age|
|:---|--------:|
|F   | 17.63344|
|F   | 11.40572|
|F   | 16.50834|

  • tranmute

函数 transmute() 用法与 mutate() 类似,但是仅保留新定义的变量,不保留原来的所有变量。如:

代码语言:javascript
复制
d.class %>% transmute(
height_cm = round(height*2.54),
weight_kg = round(weight*0.4535924),
bmi = weight_kg / (height_cm / 100)^2) %>%
head(n=3) %>% knitr::kable()

3. dplyr/tidyr 数据汇总

3.1 summarize

汇总。使用统计相关参数计算列表内相关内容。如sum, mean, median, min, max。

代码语言:javascript
复制
summarize(test, mean(Sepal.Length), sd(Sepal.Length))

在有多个变量需要汇总时,summarise 的格式就会比较罗嗦。比如,需要对 cancer 数据集中 v0 和 v1 两个变量同时计算平均值和标准差:

显然,如果有许多变量要计算不止一个统计量,就需要人为地将每一个变量的每一个统计量单独命名。dplyr 包的 summarse_at() 函数可以指定一批变量名与一批统计函数,自动命名结果变量,如:

代码语言:javascript
复制
d.cancer %>% summarise_at(
c("v0", "v1"),
list(avg = ~mean(.), std = ~sd(.)), na.rm=TRUE) %>%
knitr::kable()

其中的变量子集也可以用序号范围表示,或者用 vars() 函数写成不加撇号的格式,比如vars(v0, v1)。

其他还有几个变形:

  • summarize_if

对列筛选,进行汇总:

代码语言:javascript
复制
d.cancer %>% summarise_if( is.numeric,
list(avg = ~mean(.), std = ~sd(.)),
na.rm=TRUE) %>% knitr::kable()

  • summarise_all

直接对所有变量进行计算:

代码语言:javascript
复制
d.cancer %>% select(v0, v1) %>% summarise_all(
list(avg = ~mean(.), std = ~sd(.)),
na.rm=TRUE) %>% knitr::kable()

结合的好用函数

image.png

  • n()

进行计数:

代码语言:javascript
复制
> CO2 %>% group_by(Type) %>% summarise(
+   count=dplyr::n(),
+   mean.uptake=mean(uptake, na.rm=TRUE)) %>% knitr::kable()


|Type        | count| mean.uptake|
|:-----------|-----:|-----------:|
|Quebec      |    42|    33.54286|
|Mississippi |    42|    20.88333|

3.2 group_by

group_by 按照某列对数据框进行分组,非常适合联合summarize 使用,获取指定组别不同类型内容的统计数值。

代码语言:javascript
复制
group_by(test, Species)
tmp = summarise(group_by(test, Species),mean(Sepal.Length), sd(Sepal.Length))
> tmp
# A tibble: 3 x 3
  Species    `mean(Sepal.Length)` `sd(Sepal.Length)`
  <fct>                     <dbl>              <dbl>
1 setosa                     5                 0.141
2 versicolor                 6.7               0.424
3 virginica                  6.05              0.354

3.3 交叉分类

我们可以输入多行给group_by,并且传递给summarise 进行统计:

代码语言:javascript
复制
> CO2 %>% group_by(Type, Plant) %>% summarise(
+   count=dplyr::n(),
+   mean.uptake=mean(uptake, na.rm=TRUE)) %>% knitr::kable()
`summarise()` has grouped output by 'Type'. You can override using the `.groups` argument.


|Type        |Plant | count| mean.uptake|
|:-----------|:-----|-----:|-----------:|
|Quebec      |Qn1   |     7|    33.22857|
|Quebec      |Qn2   |     7|    35.15714|
|Quebec      |Qn3   |     7|    37.61429|
|Quebec      |Qc1   |     7|    29.97143|
|Quebec      |Qc3   |     7|    32.58571|
|Quebec      |Qc2   |     7|    32.70000|
|Mississippi |Mn3   |     7|    24.11429|
|Mississippi |Mn2   |     7|    27.34286|
|Mississippi |Mn1   |     7|    26.40000|
|Mississippi |Mc2   |     7|    12.14286|
|Mississippi |Mc3   |     7|    17.30000|
|Mississippi |Mc1   |     7|    18.00000|

如果只是想获得计数的统计量,可以直接用count 函数:

代码语言:javascript
复制
> head(CO2 %>% count(Type, Plant))
    Type Plant n
1 Quebec   Qn1 7
2 Quebec   Qn2 7
3 Quebec   Qn3 7
4 Quebec   Qc1 7
5 Quebec   Qc3 7
6 Quebec   Qc2 7

这里有个小问题,交叉分组计算频数后的结果仍按照外层分类变量 Type 分组。比如:

代码语言:javascript
复制
CO2 %>% group_by(Type, Plant) %>% summarise(freq=n()) %>% 
  summarise(ntotal=sum(freq))
  
`summarise()` has grouped output by 'Type'. You can override using the `.groups` argument.
# A tibble: 2 x 2
  Type        ntotal
  <fct>        <int>
1 Quebec          42
2 Mississippi     42  

如果想要摆脱分组限制,计算总共的和,加入ungroup 即可:

代码语言:javascript
复制
CO2 %>% group_by(Type, Plant) %>% summarise(freq=n()) %>% ungroup() %>% 
  summarise(ntotal=sum(freq))
  
  ntotal
   <int>
1     84  

3.4 tibble 中的列表列

nest 与unnest

对于数据框,我们可以使用split 将数据框按某列拆分为多个数据框,并储存在列表中。nest 和 unnest 函数,可以将子数据框保存在 tibble 中,可以将保存在 tibble 中的子数据框合并为一个大数据 框。实际上,tibble 允许存在数据类型是列表 (list) 的列,子数据框就是以列表数据类型保存在 tibble 的一列中的。

  • group_by 与nest 配合
代码语言:javascript
复制
tmp <- CO2 %>% group_by(Type) %>% nest()

> tmp
# A tibble: 2 x 2
# Groups:   Type [2]
  Type        data                 
  <fct>       <list>               
1 Quebec      <tibble[,4] [42 × 4]>
2 Mississippi <tibble[,4] [42 × 4]>

由于tibble 类型数据相较数据框来说其元素类型可以是列表,因此相比split 拆分为列表来说,其保存后数据更加直观。

我们还可以借助unlist 将tibble 元素提取出来:

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-09-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 北野茶缸子 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. tidyverse 系统简介
  • 2. dplyr/tidyr 数据管理
    • 2.1 filter
      • 2.2 sample_n
        • 2.3 distinct
          • 2.4 drop_na
            • 2.5 select
              • 2.6 arrange
                • 2.7 rename
                  • 2.8 mutate
                    • 按列号
                    • 按列名
                  • 2.9 表格的长宽转换
                    • gather/spread
                    • pivot_longer/pivot_wider
                  • 2.10 表格的拆分与合并
                    • 引号 yes or not?
                    • extract
                  • 2.11 处理关系数据
                    • 2.12 数据框的列拆分与合并
                      • 其他函数
                      • 3. dplyr/tidyr 数据汇总
                        • 3.1 summarize
                          • 结合的好用函数
                        • 3.2 group_by
                          • 3.3 交叉分类
                            • 3.4 tibble 中的列表列
                              • nest 与unnest
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档