序 言
神奇,这样操作也可以,瞬间再一次击中内心的好奇心。R语言做为用户友好型的高级语言,使用多了时不时就能像彩蛋一样蹦出个隐藏技能
。这不,小伙伴无意中就发现了$
符的一个隐藏技能。
说到$
符,熟悉的人都知道可以用来从data.frame
里面提取列的数据,很普通的功能。做为R语言的老用户,首次遇到这个隐藏特性时本人也表示一脸疑惑,不会吧?啥情况?出错了?R本版?
带着这些疑问,咱们一起看看到底是怎么回事?$
是如何做到不用完整的列名,也可以提取出正确的列数据。
回 顾
先用实际例子展示一下事情的真实面貌,下面创建一个数据框来演示:
df1 <- data.frame(gene_id=paste0('gene',1:5), value=1:5, stringsAsFactors=F)
df1
gene_id value
1 gene1 1
2 gene2 2
3 gene3 3
4 gene4 4
5 gene5 5
df1$gene_id
[1] gene1 gene2 gene3 gene4 gene5
df1$gene
[1] "gene1" "gene2" "gene3" "gene4" "gene5"
df1$g
[1] "gene1" "gene2" "gene3" "gene4" "gene5"
df1$v
[1] 1 2 3 4 5
数据框df1
有两列,列名分别为gene_id
、value
。通常,使用$
符号从数据框里面取数据都是用完整的列名,比如从df1
里面提取gene_id
列数据的操作为df1$gene_id
。可没想到的是用列名的前几个字符也是可以的,比如df1$gene
、df1$g
。瞬间内心的情绪变得有点复杂,好奇中透漏着点疑惑,疑惑中夹杂着些不安
。
不明真相时,这些奇奇怪怪的操作着实带有一种琢磨不透的神秘感与诱惑力。好奇心果然是不错的驱动力,既然遇到了这个问题,带着对代码的小偏执,咱来一探究竟:
df1$count
NULL
df1[,'count']
Error in `[.data.frame`(df1, , "count") : undefined columns selected
上面的两行代码,都可以从df1
里面提取count
列的数据,执行后的结果却完全不同。由于df1
里面根本没有这一列,[
的方式直接报错,这很容易理解,而$
方式却毫无波澜的正常执行了。虽然从$
方式返回的NULL
可以推测df1
里面没有count
列,但还是不免有些担心的情绪,这要是在复杂的代码里藏着这么个小失误,那多少得折腾一下了。
发 现
在脑海里闪过不少联想,难道真的是偷懒机制,方便用户少敲些代码?这答案有点道理,可却没法说服自己呀。
脑海里继续浮想联翩,不由地想起不久前写过的帖子[R编程技巧 | 学习高手实现函数多功能化的两种方法],match.arg
可以校验函数参数,$
这个特性是不是跟这个有关。回过头一想,$
符就是一个函数,内心似乎得到了答案 -- 参数的校验机制。看看下面的代码示例:
df2 <- data.frame(gene_id=paste0('gene',1:5), gene_name=paste0('GENE',1:5), value=1:5, stringsAsFactors=F)
df2
gene_id gene_name value
1 gene1 GENE1 1
2 gene2 GENE2 2
3 gene3 GENE3 3
4 gene4 GENE4 4
5 gene5 GENE5 5
df2$gene_id
[1] "gene1" "gene2" "gene3" "gene4" "gene5"
df2$gene
NULL
df2
里面有两列gene_id
、gene_name
都含有gene
,由于受到干扰此时用gene
就无法正确取出数据了,这更有match.arg
的感觉了。
答 案
也许上面的方式并不直观,有些小伙伴不太明白,下面换个直白的方式,用函数的形式来演示一下,保证看完立马觉悟,一起来见证:
# $方式
df2$gene_id
[1] "gene1" "gene2" "gene3" "gene4" "gene5"
df2$gene
NULL
# 正常函数形式
'$'(df2, gene_id)
[1] "gene1" "gene2" "gene3" "gene4" "gene5"
'$'(df2, gene)
NULL
真相开始浮出水面,$
函数会校验参数是不是数据框的列名,如果存在与之匹配的唯一列名即返回数据,否则返回NULL
。最后,再来看看$
函数的源码:
# 源码
function (x, name)
{
a <- x[[name]]
if (!is.null(a))
return(a)
a <- x[[name, exact = FALSE]]
if (!is.null(a) && getOption("warnPartialMatchDollar", default = FALSE)) {
names <- names(x)
warning(gettextf("Partial match of '%s' to '%s' in data frame",
name, names[pmatch(name, names)]))
}
return(a)
}
# [[方式
df2[['value']]
df2[['va']]
NULL
df2[['va', exact=F]]
[1] 1 2 3 4 5
可以看出$
函数内部利用了[[
方式提取数据,而[[
可以通过参数exact
参数控制字符是否需要完全匹配。至此,一切好像都明朗了,满足了内心的好奇心。奇怪的知识又增加了。。。
结 语
R语言易使用的特性,很多时候也是通过语法糖的方式,比如$
函数的调用方式。易学实用固然是好的,但其中包含的兼容性还需格外留心,比如,$
这种方式失误躲在代码堆里,需要多长时间发现这个BUG
?
在R语言的世界里,有时候自己写得代码能够运行下来不出错,得到的结果也未必正确。可见,虽然R用起来简单,但还是需要了解一些语法特性,唯避坑耳。
往期回顾
scRNA-seq稀疏矩阵图解,格式转换的核心
scRNAseq | h5文件转化为matrix表达矩阵
venn | 多样本间peak重叠韦恩图的解决方案
R编程技巧 | 学习高手实现函数多功能化的两种方法
linux | while + read 实现本地 or 集群批处理,实用且优雅
网友评论