【问题标题】:How to Correctly Use Lists in R?如何正确使用 R 中的列表?
【发布时间】:2011-01-04 06:43:57
【问题描述】:

背景简介:许多(大多数?)广泛使用的当代编程语言至少有少数共同的 ADT [抽象数据类型],特别是,

  • 字符串(由字符组成的序列)

  • list(值的有序集合),以及

  • 基于映射的类型(将键映射到值的无序数组)

在 R 编程语言中,前两个分别实现为 charactervector

当我开始学习 R 时,有两件事几乎从一开始就很明显:list 是 R 中最重要的数据类型(因为它是 R 的父类data.frame),第二,我不能'不了解它们是如何工作的,至少还不足以在我的代码中正确使用它们。

一方面,在我看来,R 的 list 数据类型是映射 ADT 的简单实现(Python 中的 dictionary,Objective C 中的 NSMutableDictionary,Perl 和 Ruby 中的 hash,@987654332 @ 在 JavaScript 中,等等)。

例如,您可以像创建 Python 字典一样创建它们,通过将键值对传递给构造函数(在 Python 中是 dict 而不是 list):

x = list("ev1"=10, "ev2"=15, "rv"="Group 1")

您可以像访问 Python 字典一样访问 R 列表中的项目,例如,x['ev1']。同样,您可以通过以下方式仅检索 'keys' 或仅检索 'values'

names(x)    # fetch just the 'keys' of an R list
# [1] "ev1" "ev2" "rv"

unlist(x)   # fetch just the 'values' of an R list
#   ev1       ev2        rv 
#  "10"      "15" "Group 1" 

x = list("a"=6, "b"=9, "c"=3)  

sum(unlist(x))
# [1] 18

但 R lists 也 不同于 其他地图类型 ADT(无论如何都是我学过的语言之一)。我的猜测是,这是 S 初始规范的结果,即从头开始设计数据/统计 DSL [领域特定语言] 的意图。

三个 R lists 与其他广泛使用的语言(例如,Python、Perl、JavaScript)中的映射类型之间的显着差异:

first, R 中的lists 是一个有序 集合,就像向量一样,即使值是键控的(即键可以是任何可散列的值)不仅仅是顺序整数)。几乎总是,其他语言中的映射数据类型是无序的

secondlists 可以从函数返回,即使您在调用函数时从未传入 list,并且 即使函数返回的list 不包含(显式)list 构造函数(当然,您可以通过将返回的结果包装在对unlist 的调用中来处理这个问题):

x = strsplit(LETTERS[1:10], "")     # passing in an object of type 'character'

class(x)                            # returns 'list', not a vector of length 2
# [1] list

R 的lists 的一个 第三个​​ 特殊功能:它们似乎不能成为另一个 ADT 的成员,如果您尝试这样做,那么主容器将被强制为list。例如,

x = c(0.5, 0.8, 0.23, list(0.5, 0.2, 0.9), recursive=TRUE)

class(x)
# [1] list

我在这里的目的不是批评语言或它是如何记录的;同样,我并不是说list 数据结构或其行为方式有什么问题。我所追求的只是纠正我对它们如何工作的理解,以便我可以在我的代码中正确使用它们。

以下是我想更好地理解的一些事情:

  • 确定函数调用何时返回list(例如,上面提到的strsplit 表达式)的规则是什么?

  • 如果我没有为list(例如list(10,20,30,40))明确指定名称,那么默认名称是否只是以1 开头的连续整数? (我假设,但我不能确定答案是否定的,否则我们将无法通过调用 unlist 将这种类型的 list 强制转换为向量。)

    李>
  • 为什么[][[]] 这两个不同的运算符返回相同的结果?

    x = list(1, 2, 3, 4)

    两个表达式都返回“1”:

    x[1]

    x[[1]]

  • 为什么这两个表达式返回相同的结果?

    x = list(1, 2, 3, 4)

    x2 = list(1:4)

请不要将我指向 R 文档(?listR-intro)--我已仔细阅读过它并不能帮助我回答我刚才提到的问题类型。

(最后,我最近了解到并开始使用一个名为 hash 的 R 包(可在 CRAN 上获得),它通过 S4 类实现 传统 地图类型的行为;我当然可以推荐这个包.)

【问题讨论】:

  • 使用x = list(1, 2, 3, 4),这两个都不会返回相同的结果:x[1]x[[1]]。第一个返回一个列表,第二个返回一个数值向量。在我看来,Dirk 是唯一正确回答这个问题的受访者。
  • 我没有注意到有人在您的列表中扩展 R 中的 list 不像哈希。我还有一个我认为值得注意的。 R 中的list 可以有两个具有相同引用名称的成员。考虑obj <- c(list(a=1),list(a=2)) 是有效的并返回一个包含两个命名值“a”的列表。在这种情况下,调用obj["a"] 将只返回第一个匹配的列表元素。您可以使用 R 中的环境获得与哈希类似(可能相同)的行为,每个引用名称只有一个项目。 x <- new.env(); x[["a"]] <- 1; x[["a"]] <- 2; x[["a"]]
  • 在过去的 6 个月里,我已将这篇帖子连同答案重新阅读了 3 次,每次都获得了更多的启发。很好的问题和一些很好的答案。谢谢。

标签: r list data-structures language-features abstract-data-type


【解决方案1】:

关于你的问题,让我按顺序回答并举几个例子:

1) 如果返回语句加一,则返回一个列表。考虑

 R> retList <- function() return(list(1,2,3,4)); class(retList())
 [1] "list"
 R> notList <- function() return(c(1,2,3,4)); class(notList())
 [1] "numeric"
 R> 

2) 名称根本没有设置:

R> retList <- function() return(list(1,2,3,4)); names(retList())
NULL
R> 

3) 他们不会返回相同的东西。你的例子给出了

R> x <- list(1,2,3,4)
R> x[1]
[[1]]
[1] 1
R> x[[1]]
[1] 1

其中x[1] 返回x 的第一个元素——与x 相同。每个标量都是长度为 1 的向量。另一方面,x[[1]] 返回列表的第一个元素。

4) 最后,两者不同,它们分别创建一个包含四个标量的列表和一个包含单个元素的列表(恰好是一个包含四个元素的向量)。

【讨论】:

  • 非常有帮助,谢谢。 (关于您回答中的第 1 项——我同意,但我想到的是像“strsplit”这样的内置函数,而不是用户创建的函数)。无论如何,请向我 +1。
  • @doug 关于#1 我认为唯一的方法是查看特定功能的帮助,Value 部分。就像在 ?strsplit 中一样:“与 x 长度相同的列表”。但是您应该考虑到函数可能会根据参数返回不同的值(例如,sapply 可以返回列表或向量)。
【解决方案2】:

仅回答您的部分问题:

This article 索引解决了[][[]] 之间的区别问题。

简而言之,[[]] 从列表中选择单个项目,[] 返回所选项目的列表。在您的示例中,x = list(1, 2, 3, 4)' 项目 1 是一个整数,但 x[[1]] 返回一个 1,x[1] 返回一个只有一个值的列表。

> x = list(1, 2, 3, 4)
> x[1]
[[1]]
[1] 1

> x[[1]]
[1] 1

【讨论】:

  • 顺便说一下,A = array( 11:16, c(2,3) ); A[5] 是 15,在 flat 数组中?!
【解决方案3】:

只是为了解决您问题的最后一部分,因为这确实指出了 R 中 listvector 之间的区别:

为什么这两个表达式返回的结果不一样?

x = 列表(1, 2, 3, 4); x2 = 列表(1:4)

列表可以包含任何其他类作为每个元素。因此,您可以有一个列表,其中第一个元素是字符向量,第二个元素是数据框,等等。在这种情况下,您创建了两个不同的列表。 x 有四个向量,每个向量长度为​​ 1。x2 有 1 个向量长度为​​ 4:

> length(x[[1]])
[1] 1
> length(x2[[1]])
[1] 4

所以这些是完全不同的列表。

R 列表与a hash map 数据结构非常相似,因为每个索引值都可以与任何对象相关联。这是一个包含 3 个不同类(包括一个函数)的列表的简单示例:

> complicated.list <- list("a"=1:4, "b"=1:3, "c"=matrix(1:4, nrow=2), "d"=search)
> lapply(complicated.list, class)
$a
[1] "integer"
$b
[1] "integer"
$c
[1] "matrix"
$d
[1] "function"

鉴于最后一个元素是搜索功能,我可以这样称呼它:

> complicated.list[["d"]]()
[1] ".GlobalEnv" ...

作为对此的最后评论:应该注意data.frame 实际上是一个列表(来自data.frame 文档):

数据框是具有唯一行名称的相同行数的变量列表,给定类'“data.frame”'

这就是为什么data.frame 中的列可以具有不同的数据类型,而矩阵中的列则不能。例如,这里我尝试创建一个包含数字和字符的矩阵:

> a <- 1:4
> class(a)
[1] "integer"
> b <- c("a","b","c","d")
> d <- cbind(a, b)
> d
 a   b  
[1,] "1" "a"
[2,] "2" "b"
[3,] "3" "c"
[4,] "4" "d"
> class(d[,1])
[1] "character"

请注意我无法将第一列中的数据类型更改为数字,因为第二列有字符:

> d[,1] <- as.numeric(d[,1])
> class(d[,1])
[1] "character"

【讨论】:

  • 这有帮助,谢谢。 (顺便说一句,您可能已经知道,您的示例重新“复杂列表”是在没有 C++、Java 等的语言中复制“switch”语句的标准方法;可能是一个好方法当我需要时在 R 中执行此操作)。 +1
  • 对,尽管 R 中有一个有用的 switch 函数可用于此目的(请参阅 help(switch))。
【解决方案4】:

列表按原样工作(有序)的一个原因是为了满足对有序容器的需求,该容器可以在任何节点包含任何类型,而向量不这样做。列表在 R 中用于多种用途,包括形成 data.frame 的基础,它是任意类型(但长度相同)的向量列表。

为什么这两个表达式返回的结果不一样?

x = list(1, 2, 3, 4); x2 = list(1:4)

要添加到@Shane 的答案,如果您想获得相同的结果,请尝试:

x3 = as.list(1:4)

这会将向量1:4 强制转换为列表。

【讨论】:

    【解决方案5】:

    你说:

    另外,可以返回列表 来自函数,即使你从来没有 当您调用时传入一个列表 函数,即使函数 不包含 List 构造函数, 例如,

    x = strsplit(LETTERS[1:10], "") # passing in an object of type 'character'
    class(x)
    # => 'list'
    

    我猜你认为这是一个问题(?)。我在这里告诉你为什么这不是问题:-)。您的示例有点简单,因为当您进行字符串拆分时,您有一个包含 1 个元素长的元素的列表,因此您知道 x[[1]]unlist(x)[1] 相同。但是,如果strsplit 的结果在每个 bin 中返回不同长度的结果怎么办。简单地返回一个向量(相对于一个列表)根本行不通。

    例如:

    stuff <- c("You, me, and dupree",  "You me, and dupree",
               "He ran away, but not very far, and not very fast")
    x <- strsplit(stuff, ",")
    xx <- unlist(strsplit(stuff, ","))
    

    在第一种情况下(x:返回一个列表),您可以知道第三个字符串的第二个“部分”是什么,例如:x[[3]][2]。既然结果已经“解开”(unlist-ed),你怎么能用xx做同样的事情?

    【讨论】:

      【解决方案6】:

      再补充一点:

      R 确实具有与the hash package 中的 Python dict 等效的数据结构。您可以在this blog post from the Open Data Group 中了解它。这是一个简单的例子:

      > library(hash)
      > h <- hash( keys=c('foo','bar','baz'), values=1:3 )
      > h[c('foo','bar')]
      <hash> containing 2 key-value pairs.
        bar : 2
        foo : 1
      

      就可用性而言,hash 类与列表非常相似。但是对于大型数据集,性能会更好。

      【讨论】:

      • 我知道 hash 包——在我最初的问题中提到它是传统哈希类型的合适代理。
      • 另请注意,hash::hash 的使用相对于散列环境的效用存在问题,rpubs.com/rpierce/hashBenchmarks
      【解决方案7】:
      x = list(1, 2, 3, 4)
      x2 = list(1:4)
      all.equal(x,x2)
      

      不一样,因为 1:4 与 c(1,2,3,4) 相同。 如果您希望它们相同,那么:

      x = list(c(1,2,3,4))
      x2 = list(1:4)
      all.equal(x,x2)
      

      【讨论】:

        【解决方案8】:

        关于其他语言的向量和哈希/数组概念:

        1. 向量是 R 的原子。例如,rpois(1e4,5)(5 个随机数)、numeric(55)(长度为 55 的双精度零向量)和 character(12)(12 个空字符串)都是 "基本”。

        2. 列表或向量都可以有names

          > n = numeric(10)
          > n
           [1] 0 0 0 0 0 0 0 0 0 0
          > names(n)
          NULL
          > names(n) = LETTERS[1:10]
          > n
          A B C D E F G H I J 
          0 0 0 0 0 0 0 0 0 0
          
        3. 向量要求所有内容都是相同的数据类型。观看:

          > i = integer(5)
          > v = c(n,i)
          > v
          A B C D E F G H I J           
          0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
          > class(v)
          [1] "numeric"
          > i = complex(5)
          > v = c(n,i)
          > class(v)
          [1] "complex"
          > v
             A    B    C    D    E    F    G    H    I    J                          
          0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i
          
        4. 列表可以包含不同的数据类型,如其他答案和 OP 的问题本身所示。

        我见过“数组”可能包含可变数据类型的语言(ruby、javascript),但例如在 C++ 中,“数组”必须都是相同的数据类型。我相信这是一个速度/效率的事情:如果你有一个 numeric(1e6) 你知道它的大小和每个元素的位置先验;如果该事物可能在某个未知切片中包含"Flying Purple People Eaters",那么您必须实际解析事物以了解有关它的基本事实。

        当类型得到保证时,某些标准 R 操作也更有意义。例如,cumsum(1:9) 有意义,而 cumsum(list(1,2,3,4,5,'a',6,7,8,9)) 没有意义,但类型不能保证为 double。


        关于你的第二个问题:

        即使您在调用函数时从未传入列表,也可以从函数返回列表

        函数返回的数据类型与它们一直输入的不同。 plot 会返回一个绘图,即使它没有将绘图作为输入。 Arg 返回 numeric,即使它接受了 complex。等等。

        (至于strsplit:源代码为here。)

        【讨论】:

          【解决方案9】:

          如果有帮助,我倾向于将 R 中的“列表”视为其他预 OO 语言中的“记录”:

          • 他们不对总体类型做出任何假设(或者更确切地说,任何参数和字段名称的所有可能记录的类型都是可用的)。
          • 它们的字段可以是匿名的(然后您可以按照严格的定义顺序访问它们)。

          “记录”这个名称会与数据库用语中“记录”(又名行)的标准含义发生冲突,这可能就是为什么它们的名称本身就是这样的:作为(字段的)列表。

          【讨论】:

            【解决方案10】:

            为什么[ ][[ ]] 这两个不同的运算符返回相同的结果?

            x = list(1, 2, 3, 4)
            
            1. [ ]提供子设置操作。通常任何对象的子集 将具有与原始对象相同的类型。因此,x[1] 提供一个列表。同样x[1:2] 是原始列表的子集, 因此它是一个列表。例如。

              x[1:2]
              
              [[1]] [1] 1
              
              [[2]] [1] 2
              
            2. [[ ]] 用于从列表中提取元素。 x[[1]] 有效 并从列表中提取第一个元素。 x[[1:2]][[ ]] 无效 不提供像[ ] 这样的子设置。

               x[[2]] [1] 2 
              
              > x[[2:3]] Error in x[[2:3]] : subscript out of bounds
              

            【讨论】:

              【解决方案11】:

              虽然这是一个相当古老的问题,但我必须说它恰好触及了我在 R 的第一步中所缺少的知识——即如何将我手中的数据表达为 R 中的对象或如何从现有对象中进行选择。对于 R 新手来说,从一开始就“在 R 盒子里”思考并不容易。

              所以我自己开始使用下面的拐杖,这极大地帮助了我找出要使用什么对象来处理什么数据,并且基本上可以想象现实世界的使用情况。

              虽然我没有对这个问题给出确切的答案,但下面的简短文字可能会对刚开始使用 R 并提出类似问题的读者有所帮助。

              • 原子向量...我自己称它为“序列”,没有方向,只是相同类型的序列。 [ 子集。
              • 向量...从二维到一个方向的序列,[ 子集。
              • 矩阵 ... 一组具有相同长度的向量形成行或列,[ 按行和列或按顺序的子集。
              • 阵列 ... 形成 3D 的分层矩阵
              • Dataframe ... 像 excel 中的 2D 表,我可以在其中对行或列进行排序、添加或删除或制作 arit。对它们进行操作,直到一段时间后我才真正认识到数据框是 list 的巧妙实现,我可以使用 [ 按行和列进行子集化,甚至使用 [[
              • 列表...为了帮助自己,我想到了tree structure 的列表,其中[i] 选择并返回整个分支,[[i]] 从分支返回项目。因为它是tree like structure,您甚至可以使用index sequence 来处理非常复杂的list 上的每一片叶子,使用它的[[index_vector]]。列表可以很简单,也可以很复杂,可以将各种类型的对象混合为一个。

              因此,对于lists,您最终可以通过更多方法来选择leaf,具体取决于以下示例中的情况。

              l <- list("aaa",5,list(1:3),LETTERS[1:4],matrix(1:9,3,3))
              l[[c(5,4)]] # selects 4 from matrix using [[index_vector]] in list
              l[[5]][4] # selects 4 from matrix using sequential index in matrix
              l[[5]][1,2] # selects 4 from matrix using row and column in matrix
              

              这种思维方式对我帮助很大。

              【讨论】:

                【解决方案12】:

                这是一个非常古老的问题,但我认为新的答案可能会增加一些价值,因为在我看来,没有人直接解决 OP 中的一些问题。

                尽管接受的答案表明,R 中的list 对象是不是哈希映射。如果你想与 python 平行,list 更像是 python lists(或者实际上是tuples)。

                最好描述一下大多数 R 对象是如何在内部存储的(R 对象的 C 类型是SEXP)。它们基本上由三部分组成:

                • 标头,声明对象的R类型、长度和一些其他元数据;
                • 数据部分,它是标准的 C 堆分配数组(连续的内存块);
                • 属性,它们是指向其他 R 对象的指针的命名链表(如果对象没有属性,则为 NULL)。

                例如,从内部角度来看,listnumeric 向量之间几乎没有区别。他们存储的值只是不同的。让我们将两个对象分解为我们之前描述的范式:

                x <- runif(10)
                y <- list(runif(10), runif(3))
                

                对于x

                • 头部会说类型为numeric(C端为REALSXP),长度为10等。
                • 数据部分将是一个包含 10 个 double 值的数组。
                • 属性是NULL,因为该对象没有任何属性。

                对于y

                • 头部会说类型为list(C端为VECSXP),长度为2等。
                • 数据部分将是一个数组,其中包含两个指向两种 SEXP 类型的指针,分别指向runif(10)runif(3) 获得的值。
                • 属性为NULL,至于x

                所以numeric 向量和list 之间的唯一区别是numeric 数据部分由double 值组成,而list 的数据部分是指向其他指针的数组R 对象。

                名字会发生什么?好吧,名称只是您可以分配给对象的一些属性。让我们看看下面的对象:

                z <- list(a=1:3, b=LETTERS)
                
                • 头部会说类型为list(C端为VECSXP),长度为2等。
                • 数据部分将是一个数组,包含两个指向两种 SEXP 类型的指针,分别指向1:3LETTERS 获得的值。
                • 属性现在存在并且是一个names 组件,它是一个character R 对象,其值为c("a","b")

                在 R 级别,您可以使用 attributes 函数检索对象的属性。

                R 中哈希映射的典型键值对只是一种错觉。当你说:

                z[["a"]]
                

                这就是发生的事情:

                • [[ 子集函数被调用;
                • 函数的参数("a")的类型为character,因此指示该方法从对象names的属性(如果存在)中搜索该值z;
                • 如果names 属性不存在,则返回NULL
                • 如果存在,则在其中搜索"a" 值。如果"a"不是对象名,则返回NULL
                • 如果存在,则确定 第一次出现的位置(示例中为 1)。所以返回列表的第一个元素,即相当于z[[1]]

                键值搜索是相当间接的,并且总是定位的。另外,请记住:

                • 在散列映射中,键必须具有的唯一限制是它必须是可散列的。 R 中的names 必须是字符串(character 向量);

                • 在哈希映射中,您不能有两个相同的键。在 R 中,您可以将 names 分配给具有重复值的对象。例如:

                    names(y) <- c("same", "same")
                  

                在 R 中完全有效。当您尝试 y[["same"]] 时,将检索第一个值。此时你应该知道为什么了。

                总之,为对象赋予任意属性的能力使您看起来与外部观点不同。但是 R lists 无论如何都不是哈希映射。

                【讨论】:

                • "在 R 中,您可以将 names 分配给具有重复值的对象"。我很确定环境是唯一的例外。没有加载包,它们是 R 最接近哈希图的东西。
                【解决方案13】:

                你可以试试,

                set.seed(123)
                l <- replicate(20, runif(sample(1:10,1)), simplify = FALSE)
                
                out <- vector("list", length(l))
                for (i in seq_along(l)) {
                  out[[i]] <- length(unique(l[[i]])) #length(l[[i]])
                }
                unlist(out)
                
                unlist(lapply(l,length))
                unlist(lapply(l, class))
                unlist(lapply(l, mean))
                unlist(lapply(l, max))
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2016-08-08
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多