美文网首页
R语言揭秘 | $符鲜为人知的秘密,避坑预警

R语言揭秘 | $符鲜为人知的秘密,避坑预警

作者: 生信云笔记 | 来源:发表于2023-11-03 15:54 被阅读0次

    序 言

      神奇,这样操作也可以,瞬间再一次击中内心的好奇心。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_idvalue。通常,使用$符号从数据框里面取数据都是用完整的列名,比如从df1里面提取gene_id列数据的操作为df1$gene_id。可没想到的是用列名的前几个字符也是可以的,比如df1$genedf1$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_idgene_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 集群批处理,实用且优雅

    相关文章

      网友评论

          本文标题:R语言揭秘 | $符鲜为人知的秘密,避坑预警

          本文链接:https://www.haomeiwen.com/subject/zefvidtx.html