0%

R语言中的子集问题深入(二)

前面对R语言中的子集做了一个简单的概述,阐述了三个常用的操作符号以及排序的简单应用,接下来会进行更加深入的探讨。另外请注意:之前在文章中插入的 **rdrr.io*框框可以自己输入代码进行试验。ヾ(≧▽≦)o此外,因为我是一个临床医生,很多统计学的术语,没有那么精确,敬请斧正。

PS:奉上除了我女朋友之外最喜欢的女神…新垣结衣。

PPS:首页访问量插件坏掉了,不修了,浪费时间,能看就行😕

逻辑操作与取子集

索引

和所有的语言一样,R语言中自然也存在逻辑操作,如果你熟悉C、Python或者是其他相关语言,哪怕是shell script,基础的“与”、“或”、“非”,肯定也是耳熟能详、烂熟于心的。此外就是比较符号,各家语言或多或少有些区别,但无非就几种,如下罗列下:

含义 R Python C shell script
大于 > > > gt
小于 < < < lt
等于 == == == eq
不等于 != !=   <>
(后者已经弃用)
!= ne
大于等于 >= >= >= ge
小于等于 <= <= <= le

有了这些基础以后,我们就可以进行一些简单的操作了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
head(iris)
#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 4.7 3.2 1.3 0.2 setosa
#4 4.6 3.1 1.5 0.2 setosa
#5 5.0 3.6 1.4 0.2 setosa
iris[iris$Sepal.Length == 5.1,]
# Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#1 5.1 3.5 1.4 0.2 setosa
#18 5.1 3.5 1.4 0.3 setosa
#20 5.1 3.8 1.5 0.3 setosa
#22 5.1 3.7 1.5 0.4 setosa
#24 5.1 3.3 1.7 0.5 setosa
#40 5.1 3.4 1.5 0.2 setosa
#45 5.1 3.8 1.9 0.4 setosa
#47 5.1 3.8 1.6 0.2 setosa
#99 5.1 2.5 3.0 1.1 versicolor

#此时上式返回结果同 iris[which(iris$Sepal.Length == 5.1),]
#如果你想返回符合标准的下标,可以如下
which(iris$Sepal.Length == 5.1)
[1] 1 18 20 22 24 40 45 47 99

#这种情况下使用attach函数以后会更加方便
attach(iris)
iris[Sepal.Length == 5.1,]

#对结果进一步选取
iris[Sepal.Length == 5.1,]$Species
[1] setosa setosa setosa setosa setosa setosa
setosa setosa versicolor
iris$Species[Sepal.Length == 5.1]
[1] setosa setosa setosa setosa setosa setosa
setosa setosa versicolor

注意:iris[Sepal.Length == 5.1,]$Species 的结果不等同于iris$Species[Sepal.Length == 5.1,],后者的结果为空,因为操作顺序是从左向右。前者先取出Sepal.Length == 5.1的子集,之后取出其中Species的值;后者先取出Species的值,然后再取Sepal.Length == 5.1,但是由于之前结果已经是特定值了,里面没有Sepal.Length的结果,所以取不出来。我是这么理解的。

但是iris$Species[Sepal.Length == 5.1]确等于iris[Sepal.Length == 5.1,]$Species。这点还不是很能从理论上解释,我估计是[x]的优先级别高于"$",所以先取出Sepal.Length == 5.1的数据框,再取子集。不过没有求证过。

其他比较符号的操作大致同上。

Subset

在逻辑操作的情况下,subset具有更强的优势,书写起来更加简洁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#同等效果
subset(iris,Sepal.Length == 5.1,select = Species, drop = F)
# Species
#1 setosa
#18 setosa
#20 setosa
#22 setosa
#24 setosa
#40 setosa
#45 setosa
#47 setosa
#99 versicolor
subset(iris,Sepal.Length == 5.1,select = Species, drop = T)
[1] setosa setosa setosa setosa setosa setosa
setosa setosa versicolor

subset函数基本用法很简单, subset(data,expression,select,drop)这要是这四部分(参考官方文档,按照我自己的理解写的)。

data:数据来源

expression:逻辑表达式,这里需要注意的是多条逻辑表达要用&或者"与"、“或”(即“&”或“|”)联合subset(data,expression1 && expression2,select,drop);如果直接分开写,如subset(data,expression1,expression2,select,drop)那么只有expression1生效

select:主要是输出,可以用c()创建向量或者“:”、“-”等符号进行操作。

drop:效果如上,drop如果是T的他将会把结果传递给"["操作符号,也就是直接输出值,F的时候视原始数据类型输出。

SQL查询

如果你和我一样,经常使用mysql、mariadb之类的数据库,那么使用sql语言进行数据查询提取也是极好的。

但是这时候我们需要使用一个包:sqldf。

1
2
3
4
5
6
7
8
9
10
11
12
GEM <- data.frame(ID = seq(1,4,1),
song = c("泡沫","睡公主","新的心跳","摩天动物园"),
year = c("2012","2018","2015","2019") )
# ID song year
#1 1 泡沫 2012
#2 2 睡公主 2018
#3 3 新的心跳 2015
#4 4 摩天动物园 2019

sqldf("select year from GEM where song='泡沫'")
# year
#1 2012

不足:在sql语言中我们用“.”dot符号表示从属关系(emmm,我没有相处更好的表达方式,但应该是有专业的表达的)。这就意味着,如果你输入GEM.song,你表达的是GEM这个表里面的song这一栏,而非是ABC这个表里面的song一栏。所以这就意味着如果你要使用sql语言的话,你的变量名称不应该写成song.year这种形式。这种形式,会导致程序访问名称位song的表下面的year一栏,但显然不存在,所以不会返回有意义的结果。试验如下:

1
2
sqldf("select Species from iris where Sepal.Length = 5.1")
#Error: no such column: Sepal.Length

管道与取子集

管道使得R的操作简洁性更进一步,同时也使得操作深度能够更进一步。

Pipe这个词很有意思,我们从本质上理解就会很简单。下面用一张图来表示:

简单理解,事实上pipe就是把pipe符号前的操作结果作为后一个操作的参数导入继续进行运算。(虽然事实可能并不完全如此,但是对于使用掌握到这个程度我觉得就可以了。)

当使用pipe的时候,操作A的结果默认作为操作B的第一个参数传入给操作B。也就是例如head(x,n=20)取x的前20行这样的操作写成 commandA %>% head(n=20)就可以,当然,如果你想让他作为其他参数传入的话,需要把剩余参数的位置明确占用。

得出了这样一个结论以后,我们可以在之前的技巧上更进一步。

pipe操作事实上使得我们能够在子集的基础上再取子集,虽然很多情况下我们可以通过详细写入限定条件达到相同的目的,但还是没有那么方便。

使用pipe需要使用dplyr包,如果没有安装可以安装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
library(dplyr)
set.seed(2020)
attach(iris)
iris[order(Sepal.Length)[120],] %>%
subset(Sepal.Width >= 3 ,select = names(iris)) %>%
group_by(Petal.Length) %>%
summarise(avg=mean(Petal.Width))

Petal.Length avg
# <dbl> <dbl>
#1 1 0.2
#2 1.1 0.1
#3 1.3 0.2
#4 1.4 0.2
#5 1.5 0.167
#6 1.6 0.2
#7 1.9 0.2

上述代码,我们先通过order讲iris数据集以Sepal.Length进行排序并取出前20行,然后取了其中Sepal.width大于等于3的所有观测值,之后按照Petal.Length进行归类,并计算每一类的Petal.Width平均值。

正则与取子集

如果你学过一些bash shell,或者是生信方面的知识,那么正则在字符串中的应用就肯定给你留下很深的印象。这里我不将展开讲正则怎么用,如果有兴趣可以查看我的Telegram频道(在首页社交标签有链接,科学上网方可访问)查看相关资料,量饱管够。

在真实的临床数据当中,很多数据的采集事实上是很糟糕的,这将涉及到数据清洗的概念,专门有一门课程,同样不在这里展开,后续有精力可能会讨论。这些糟糕的情况中,存在一种极度恶劣的情况,我简单捏造一组数据:

真实的临床数据列数*N、观测数*N。

这种东西,的特点就是,一个单元格有很多元素,大量字符串充斥,并且很多用语不规范或者说不统一。

通常,你的领导会和你说:哎呀,小王,你把今年做L3-L4手术用的施耐德厂家的患者给统计一下,然后你就瞬间想…

如果是你自己的坑,需要提取数据做研究的时候你就开始找小伙伴了:

反正不管哪种,这种任务都令人头秃。

通常情况下,我会这么做(终端录屏载入慢,请耐心等待):

使用正则来匹配字符串,这样子不管多少条件都可以叠加,此外,如果用语不规范还可以使用sed先行替换统一。

但是当你把这样的数据导入到R里面的时候。。。直接拿来分析是不现实的。如果在外部先行处理又显得很业余,那就用R里面的正则进行处理吧。

1
2
3
4
5
6
7
cases_full[grep('L3-4',cases_full$手术方式),] %>%
subset(厂家 == "施耐德",select = names(cases_full))


# ID 姓名 既往病史 吸烟史 手术方式 厂家
#1 100010 张三 糖尿病,高血压 是 L3-4椎管减压术+椎体融合 施耐德
#4 100040 麻子 克罗恩病,高血压 是 L3-4椎间盘摘除术+植骨融合术 施耐德

Now, you get what you want!

但是注意的一点:正则在R中的应用并不像在bash 里面那么方便,就我个人而言我还是会先把数据用数据流工具处理完再导入进行分析。

在R中使用正则的不方便之处:

  • 匹配条件不能跨列,局限在一列,不是很方便。

  • 如果直接使用数据框进行匹配,会返回匹配的列数,而非详细下标。

综上,在R里面用正则就是麻烦点。多搞几次。

上面的文件你都可以在这里下载

结语

基本上到这里我觉得R中的子集提取就我目前的水平,已经总结的七七八八了,如果后续有更有意思的想法,我也会再加上去。

我生也有涯而学无涯,望君共勉之。

如果有想法请邮件联系; 如果有收获,请我喝杯咖啡也是可以的。

欢迎关注我的其它发布渠道