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

2021-12-17 09:22:37 浏览数 (2)

tidyr

基础用法

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。

separate&&unite

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

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

代码语言: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
引号 yes or not?

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

处理缺失值

创建一个存在NA 的数据框。

代码语言:javascript复制
X<-data.frame(X1 = LETTERS[1:5],X2 = 1:5)
X[2,2] <- NA
X[4,1] <- NA

> X
    X1 X2
1    A  1
2    B NA
3    C  3
4 <NA>  4
5    E  5
直接去除 drop_na

如果直接对数据框进行 drop_na 其效果和基础包中的 na.omit() 是一样的,会将存在缺失值的行直接删除。如果其后加上参数(列名),则会针对该列进行去除缺失值。

代码语言:javascript复制
drop_na(X,X2)

> drop_na(X,X2)
    X1 X2
1    A  1
2    C  3
3 <NA>  4
4    E  5
替换 replace_na&&fill

通过replace_na,可以将 replace_na(col, value) ,将col 中的NAs 替换为指定的value。

代码语言:javascript复制
> X$X2 <- replace_na(X$X2,0);X
    X1 X2
1    A  1
2    B  0
3    C  3
4 <NA>  4
5    E  5

# 还可以写成
X$X2 <- replace_na(list(X2=0))

通过fill,可以将指定列中的缺失值替换为该缺失值所在行的上一行中的数据。

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

dplyr

是用于处理数据框的关键的一个包。

代码语言:javascript复制
library(dplyr)
test <- iris[c(1:2,51:52,101:102),]
rownames(test) =NULL

必备dplyr技巧

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

select

按列筛选。

按列号
代码语言: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))
其他专属函数

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个。忽略最后一个即表示选择倒数第二个。

everything 可以实现对列的自定义排序。其语法逻辑为,去掉指定的列后,筛选其他的列。因此我们可以对select 与everything 处理,先筛选某列,接着去掉该列后,对其他列取everything,便可以将先筛选的列顺序提到最前。

代码语言:javascript复制
select(test,Species,everything())

filter

使用逻辑条件对行筛选。

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

arrange

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

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

summarize

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

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

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

pull

相当于专门取数据框列的函数:

代码语言:javascript复制
> pull(g, sample1)
[1] 4.498195 3.871712 9.152436 3.468464

> identical(pull(g, sample1), g$sample1)
[1] TRUE

小进阶

count

计算向量或数据框中某列的重复值,并返回不同信息及它们重复的次数。

代码语言:javascript复制
> count(test,Species)
     Species n
1     setosa 2
2 versicolor 2
3  virginica 2

%>%

存在于tidyverse每一个包里。快捷键为 ctrl shift M 。管道操作,类似linux 中的 | ,即将上一步内容的结果重定向作为下一步内容输入的值。

代码语言:javascript复制
library(dplyr)
x1 = filter(iris,Sepal.Width>3)
x2 = select(x1,c("Sepal.Length","Sepal.Width" ))
x3 = arrange(x2,Sepal.Length)

如果依靠变量的传递,每一步都需要将结果指定若干个中间变量,再将指定的这些中间变量,作为输入值传递给下一个值。但从结果来看,它们并没有必要。

代码语言:javascript复制
iris %>% 
  filter(Sepal.Width>3) %>% 
  select(c("Sepal.Length","Sepal.Width" ))%>%
  arrange(Sepal.Length)

dplyr 处理关系数据

即通过dplyr 包将表格进行连接。在基础包中, mergebind 可以帮助我们连接表格。参见:https://www.yuque.com/mugpeng/rr/nhhess#AoSx0但它们缺陷也很明显, rbind (bind_rows)或 cbind (bind_cols)只能非常生硬地将相同列或相同行的表格“压”在一起;而 merge 也只能按照共有部分相连接,两个表格中均不存在的行的内容会被删去。

而dplyr 也提供了更为全面的表格连接的函数—— join 系列。

便于测试,首先生成三个数据集

代码语言:javascript复制
test1 <- data.frame(name = c('jimmy','nicker','doodle'), 
                    blood_type = c("A","B","O"))
> test1
    name blood_type
1  jimmy          A
2 nicker          B
3 doodle          O
test2 <- data.frame(name = c('doodle','jimmy','nicker','tony'),
                    group = c("group1","group1","group2","group2"),
                    vision = c(4.2,4.3,4.9,4.5))
> test2 
    name  group vision
1 doodle group1    4.2
2  jimmy group1    4.3
3 nicker group2    4.9
4   tony group2    4.5
test3 <- data.frame(NAME = c('doodle','jimmy','lucy','nicker'),
                    weight = c(140,145,110,138))
> test3
    NAME weight
1 doodle    140
2  jimmy    145
3   lucy    110
4 nicker    138

inner_join 取交集

inner_join 和merge 的功能很像,都是取交集。使用merge:

代码语言:javascript复制
> merge(test1,test3,by.x = "name",by.y = "NAME")
    name blood_type weight
1 doodle          O    140
2  jimmy          A    145
3 nicker          B    138

使用inner_join:

代码语言:javascript复制
> inner_join(test1,test3,by = c("name"="NAME"))
    name blood_type weight
1  jimmy          A    145
2 nicker          B    138
3 doodle          O    140

共同列列名不一致时,inner_join 的语法似乎“有点奇怪”。

left_join&&right_join

左连(按照左边,保留所有左边数据),右连(按照右边,保留所有右边数据)。其中另外一边中缺失的数据用NA 填充。

代码语言:javascript复制
> left_join(test2, test1, by = 'name')
    name  group vision blood_type
1 doodle group1    4.2          O
2  jimmy group1    4.3          A
3 nicker group2    4.9          B
4   tony group2    4.5       <NA>

full_join 取全集

不管左边右边,有的全连上,缺失用NA 补充。

代码语言:javascript复制
> full_join(test2, test3, by = c("name"="NAME"))
    name  group vision weight
1 doodle group1    4.2    140
2  jimmy group1    4.3    145
3 nicker group2    4.9    138
4   tony group2    4.5     NA
5   lucy   <NA>     NA    110

semi_join 半连接&&anti_join 反连接

半连接返回的是x所有的在y中存在的记录。

代码语言:javascript复制
semi_join(x = test1, y = test2, by = 'name')

反连接与半连接相反,返回的是x中所有的在y中不存在的记录。

代码语言:javascript复制
anti_join(x = test2, y = test1, by = 'name')

易错点

  1. 处理“宽长”型数据框时(gather处理生成的),该数据库需要存在某个“索引列”,可以保证其对应唯一的某行内容的信息。(或通过group_by与mutate 自行添加索引)
  2. 进行separate 时,要注意特殊符号的用法,其可能存在正则用法,需要进行转义。
  3. 如果分隔出的结果存在0的话,会自动识别为NA。

练习题

6-1

代码语言:javascript复制
# 练习6-1
library(tidyverse)
# 1.将iris数据框的前4列gather,然后还原
test <- iris[,1:4]
x_gather <- gather(data=test,key=var, value=num)
head(x_gather)

# 还原
# 错误答案,由于建立的gather 缺乏有效索引,因此会报错。
# 缺乏一个唯一确定该数据的变量。
# x_spread <- spread(test, key=var, value=num)

# 通过mutate 会表格添加一列索引列。
x_spread <- x_gather %>% 
    group_by(var) %>% 
    mutate(id=1:n()) %>% 
    spread(var,num)
x_spread

# 2.将第三列分成两列(以小数点为分隔符)然后合并
head(iris)
# 错误答案由于sep 会识别正则(. 表示任意字符),因此需要对. 转义。
# iris_sep <- separate(iris, Petal.Length, c('Petal.Length_1','Petal.Length_2'),
                     # sep='.')

# 这里还是会存在一个bug,如果数字. 后面部分为0,分割后会识别为NA。
iris_sep <- iris %>% 
  separate(Petal.Length, into = c('Petal.Length_1','Petal.Length_2'),
                     sep='[.]') 
iris_sep$Petal.Length_2 <- replace_na(iris_sep$Petal.Length_2,0)
tail(iris_sep)
# NA 都已经消除了。

iris_unite <- unite(iris_sep, col='Petal.Length',Petal.Length_1,Petal.Length_2, 
                    sep='.')
tail(iris_unite)

str(iris_unite)
# 这里发现Petal.Length 格式为chr。
iris_unite['Petal.Length'] = as.numeric(iris_unite['Petal.Length'])

# 3.加载test1.Rdata,将deg数据框按照pvalue从小到大排序
load('test1.Rdata')
str(deg)
deg_sort <- arrange(deg, P.Value)
# 4. 将两个数据框按照probe_id列连接在一起
deg_join <- inner_join(deg, ids, by = 'probe_id')
head(deg_join)

0 人点赞