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

时间:2010-01-12 17:08:50

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

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

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

  • 列表(有序的值集合)和

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

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

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

首先,在我看来,R的list数据类型是地图ADT的直接实现(Python中为dictionary,目标C中为NSMutableDictionary,{{1}在Perl和Ruby中,在Javascript中hash,等等。)

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

list

您可以像访问Python词典一样访问R列表中的项目,例如x = list("ev1"=10, "ev2"=15, "rv"="Group 1") 。同样,您可以通过以下方式仅检索'键'或仅检索'值'

x['ev1']

但是R 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 也是 ,不像 其他地图类型的ADT(从我学到的语言中)。我的猜测是,这是S初始规范的结果,即打算从头开始设计数据/统计DSL [特定于域的语言]。

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

R中的

list有序集合,就像向量一样,即使值是键控的(即,键可以是任何键)可输出的值不仅仅是连续的整数)。几乎总是,其他语言中的映射数据类型是无序

第二个list可以从函数返回,即使你在调用函数时从未传入list,而即使 >返回list的函数不包含(显式)list构造函数(当然,您可以通过将返回的结果包装到list的调用中来处理此问题。 ):

unlist

R x = strsplit(LETTERS[1:10], "") # passing in an object of type 'character' class(x) # returns 'list', not a vector of length 2 # [1] list s的第三个​​特有的功能:它们似乎不能成为另一个ADT的成员,如果你试图这样做,那么主容器就会被强制执行到list。如,

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),那么默认名称只是以1开头的连续整数? (我假设,但我很难确定答案是肯定的,否则我们无法将这种类型的list(10,20,30,40)强制转换为调用list的向量。)

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

    [[]]

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

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

    x[1]

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

    x[[1]]

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

请不要指向R文档(?listR-intro) - 我仔细阅读了它并没有帮助我回答上面列举的问题类型。< / p>

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

13 个答案:

答案 0 :(得分:137)

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

  

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

     

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

列表可以包含任何其他类作为每个元素。因此,您可以拥有一个列表,其中第一个元素是字符向量,第二个元素是数据框等。在这种情况下,您创建了两个不同的列表。 x有四个向量,每个长度为1. x2有一个长度为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"

答案 1 :(得分:61)

关于你的问题,让我按顺序解决这些问题并举一些例子:

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 )最后,两者分别创建了包含四个标量的列表和一个包含单个元素的列表(恰好是四个元素的向量)。

答案 2 :(得分:34)

只是提出一些问题:

索引上的

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

答案 3 :(得分:12)

列出工作原因(有序)的一个原因是解决了对任何节点可以包含任何类型的有序容器的需求,而这些容器不能执行。列表在R中被重复用于各种目的,包括形成data.frame的基础,x = list(1, 2, 3, 4); x2 = list(1:4) 是任意类型的向量列表(但长度相同)。

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

x3 = as.list(1:4)

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

1:4

将矢量{{1}}强制转换为列表。

答案 4 :(得分:11)

再加上一点:

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

答案 5 :(得分:9)

你说:

  

另一方面,可以返回列表   从功能,即使你从来没有   当你打电话给   功能,即使功能   不包含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]。如果结果已被“解开”(xx - ed),你怎么能使用unlist做同样的事情?

答案 6 :(得分: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)

答案 7 :(得分:3)

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

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

最好描述内部如何存储大多数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,等等。
  • 数据部分将是一个包含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,等等。
  • 数据部分将是一个包含2个指向两种SEXP类型的指针的数组,分别指向1:3LETTERS获得的值。
  • 这些属性现在已经存在,并且是一个names组件,它是一个character R对象,其值为c("a","b")

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

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

z[["a"]]

会发生什么:

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

键值搜索是间接的,并且始终处于位置。另外,请记住以下几点:

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

    names

    在R中完全有效。当您尝试names(y) <- c("same", "same") 时,将检索第一个值。您现在应该知道为什么。

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

答案 8 :(得分:1)

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

  1. 向量是R的原子。例如,rpois(1e4,5)(5个随机数),numeric(55)(长度为-55,零向量超过双倍),character(12)(12为空字符串),都是&#34; basic&#34;。

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

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

    当保证类型时,某些标准R操作也更有意义。例如cumsum(1:9)是有意义的,而cumsum(list(1,2,3,4,5,'a',6,7,8,9))则没有,没有保证类型是双倍的。


    至于你的第二个问题:

      

    即使您在调用函数

    时从未传入List,也可以从函数返回列表

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

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

答案 9 :(得分:1)

如果有帮助,我倾向于设想&#34;列表&#34;在R as&#34;记录&#34;在其他前OO语言中:

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

名称&#34;记录&#34;会与#34;记录&#34;的标准含义发生冲突(也就是行数)用数据库的说法,可能就是这就是为什么他们的名字建议自己:作为列表(字段)。

答案 10 :(得分: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
    

答案 11 :(得分:1)

虽然这是一个非常古老的问题,但我必须说它完全触及了我在R的第一步中所缺少的知识 - 即如何将我手中的数据表示为R中的对象或如何从现有对象中进行选择。对于R新手而言,在R盒中思考#34;并不容易。从一开始就是。

所以我自己开始使用下面的拐杖帮助我找到了什么对象用于什么数据,基本上想象现实世界的使用。

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

  • 原子载体......我称之为&#34;序列&#34;对于我自己,没有方向,只有相同类型的序列。 [子集。
  • 向量...来自2D,[子集的一个方向的序列。
  • 矩阵...具有相同长度的矢量形成行或列,按行和列的[子集,或按顺序。
  • 阵列...形成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 :(得分:0)

你可以试试,

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