如何正确使用R中的列表?


320

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

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

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

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

在R编程语言中,前两个分别作为character和实现vector

当我开始学习R时,几乎从一开始就显而易见两件事:list是R中最重要的数据类型(因为它是R的父类data.frame),第二,我至少不了解它们的工作方式,至少不够好,无法在我的代码中正确使用它们。

一方面,在我看来,R的list数据类型是对映射ADT的简单实现(dictionary在Python,NSMutableDictionaryObjective C,hashPerl和Ruby,object literalJavascript等中)。

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

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

然后您就可以像访问Python词典那样访问R列表中的项目,例如x['ev1']。同样,您可以通过以下方式仅检索“键”“值”

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 list不同于其他映射类型的ADT(无论如何,从我所学的语言中)。我的猜测是,这是S最初规格的结果,即从头开始设计数据/统计DSL(特定领域语言)的意图。

R list和其他广泛使用的语言(例如Python,Perl,JavaScript)的映射类型之间的三个重要区别:

首先listR中的s是向量的有序集合,即使这些值是键控的(即,键可以是任何可散列的值,而不仅仅是顺序整数)。几乎总是其他语言的映射数据类型是无序的

第二listS可从即使你在一个从来没有通过函数返回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开头的连续整数?(我想,但我不确定答案是否定的,否则我们将无法强制将这种类型的list向量强制为w或对的调用unlist。)

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

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

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

    x[1]

    x[[1]]

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

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

    x2 = list(1:4)

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

(最后,我最近了解并开始使用一个R包(在CRAN上可用)hash,该包通过S4类实现常规的映射类型行为;我当然可以推荐此包。)


3
使用时x = list(1, 2, 3, 4),这两个返回的结果都不相同:x[1]x[[1]]。第一个返回列表,第二个返回数字向量。在下面滚动,对我来说似乎德克是唯一正确回答此问题的人。
IRTFM'1

2
我没有注意到有人在您的列表中扩展list了R中不像哈希的方式。我还有一个我认为值得注意的地方。 listR中的R可以具有两个具有相同引用名称的成员。认为这obj <- c(list(a=1),list(a=2))是有效的,并返回带有两个名为“ a”的值的列表。在这种情况下,对的调用obj["a"]将仅返回第一个匹配列表元素。使用R中的环境,您可以获得与每个引用名称仅包含一项的哈希类似(也许相同)的行为。例如x <- new.env(); x[["a"]] <- 1; x[["a"]] <- 2; x[["a"]]
russellpierce

1
在过去的6个月中,我已经三遍重新阅读了带有答案的帖子,每次都发现了更多启示。好问题和几个好答案。谢谢。
Rich Lysakowski博士

Answers:


150

仅解决您的问题的最后一部分,因为这实际上指出了R listvectorR 之间的区别:

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

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

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

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

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

R列表非常类似于哈希映射数据结构,因为每个索引值都可以与任何对象关联。这是一个包含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" ...

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

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

因此,a中的列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"

4
这会有所帮助,谢谢。(顺便说一句,您可能已经知道,示例中的“复杂列表”是在C ++,Java等语言中以不包含该语言的语言复制“ switch”语句的标准方法;这可能是一个好方法在需要时在R中执行此操作)。+1

8
是的,尽管switchR中有一个有用的功能可用于此目的(请参阅参考资料help(switch))。
Shane 2010年

63

关于您的问题,让我依次解决并举例:

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

 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 2010年

2
@doug关于项目#1,我认为唯一的方法是检查特定功能的帮助Value。类似于?strsplit:“与x长度相同的列表”。但是您应该考虑到有一个函数可以根据参数返回不同的值(例如sapply可以返回list或vector)。
Marek 2010年

34

仅考虑部分问题:

这篇有关索引的文章解决了[]和之间的区别的问题[[]]

简而言之,[[]]从列表中选择一个项目,然后[]返回所选项目的列表。在您的示例中,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 吗?
denis

13

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

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

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

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

x3 = as.list(1:4)

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


11

只需添加一点:

R确实具有hash包中的Python dict等效的数据结构。您可以在此博客文章中从开放数据组中阅读有关它的信息。这是一个简单的例子:

> 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该类与列表非常相似。但是对于大型数据集,性能更好。


1
我知道哈希包-在我最初的问题中提到它是传统哈希类型的合适代理。
doug 2010年

还要注意,相对于哈希环境rpubs.com/rpierce/hashBenchmarks,hash :: hash的使用具有可疑的实用性。
russellpierce

9

你说:

另外,即使您在调用函数时从未传递过List,并且该函数不包含List构造函数,例如,

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

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

例如:

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]xx既然结果已经被“解开”(unlist-ed),您如何使用相同的方法呢?


5
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)

4

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

尽管已接受的答案表明了什么,但listR中的对象不是哈希映射。如果您想与python并行,list则更像是python list(或tuple实际上是s)。

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

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

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

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

对于x

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

对于y

  • 标头会说类型是listVECSXP在C端),长度是2,等等。
  • 数据部分将是一个包含2个指向两种SEXP类型的指针的数组,分别指向runif(10)和获得的值runif(3)
  • 属性NULLx

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

名称会怎样?好吧,名称只是您可以分配给对象的某些属性。让我们看看下面的对象:

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

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

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

z[["a"]]

这是发生了什么:

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

键值搜索相当间接,并且始终处于位置。另外,请记住以下有用的信息:

  • 在哈希映射的唯一限制的关键必须是,它必须是可哈希namesR中的必须是字符串(character向量);
  • 在哈希图中,您不能有两个相同的键。在R中,您可以分配names给具有重复值的对象。例如:

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

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

总之,为对象赋予任意属性的能力使您从外观上看起来有所不同。但是R list绝不是哈希映射。


2

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

  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本身的问题所示。

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

保证类型时,某些标准R操作也更有意义。例如,在没有保证类型为double的情况下cumsum(1:9)有意义却cumsum(list(1,2,3,4,5,'a',6,7,8,9))在没有意义的情况下。


关于第二个问题:

即使您从未调用过列表,也可以从函数中返回列表

函数返回的数据类型与始终输入的数据类型不同。plot即使不将绘图作为输入也返回绘图。即使接受,也会Arg返回numeric一个complex。等等。

(至于strsplit:源代码在这里。)


2

尽管这是一个非常老的问题,但我必须说,它确实触动了我在R的第一步中所缺少的知识,即如何将手中的数据表示为R中的对象或如何从现有对象中进行选择。对于R新手来说,从一开始就想“在R盒子中”并不容易。

因此,我本人开始使用拐杖,这在很大程度上帮助了我找出要用于什么数据的对象,并且基本上可以想象实际使用情况。

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

  • 原子向量……我自己称其为“序列”,没有方向,只是相同类型的序列。[子集。
  • 向量...具有2D [子集的一个方向的序列。
  • 矩阵...一束等长的向量,形成行或列,[按行和列,或按顺序形成子集。
  • 阵列...形成3D的分层矩阵
  • 数据框...像excel中一样的2D表,我可以在其中排序,添加或删除行或列或进行算术运算。仅在一段时间之后,我才真正意识到数据帧是一种聪明的实现,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

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


1

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

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

名称“记录”将与数据库中的“记录”(又称行)的标准含义相冲突,这可能就是为什么它们的名称会自己提示的原因:作为字段列表。


1

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

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.