如何正确使用列表?

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

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

  • list(值的有序集合),和

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

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

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

首先,在我看来R的list数据类型是映射ADT的直接实现(Python中的dictionary, Objective C中的NSMutableDictionary, Perl和Ruby中的hash, Javascript中的object literal,等等)。

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

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

访问R List中的项就像访问Python字典中的项一样,例如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__abc0也是< em >与< / em >其他映射类型的adt(从我学过的语言中)。我猜这是S的初始规范的结果,也就是说,打算从头开始设计一种数据/统计DSL[领域特定语言]。

在广泛使用的其他语言中,R lists和映射类型之间存在显著差异(例如,Python, Perl, JavaScript):

第一个, R中的__abc0是一个命令集合,就像向量一样,尽管值是键控的(即键可以是任何可哈希值,而不仅仅是连续的整数)。在其他语言中,映射数据类型几乎总是无序

第二个list可以从函数返回,即使你在调用函数时从未传入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的list的一个第三特殊特征:它们似乎不能成为另一个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]] < / p >

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

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

    x2 = list(1:4) < / p >

请不要指给我R文档(?listR-intro)——我已经仔细阅读了它,它不能帮助我回答我刚才背诵的问题类型。

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

214697 次浏览

关于大家提出的问题,我愿按顺序回答并举例说明:

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

就拿你们的一部分问题来说

索引上的这篇文章解决了[][[]]之间的差异问题。

简而言之,[[]]从列表中选择单个项,[]返回所选项的列表。在你的例子中,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

只是为了解决你问题的最后一部分,因为这确实指出了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列表非常像哈希映射数据结构,每个索引值可以与任何对象相关联。下面是一个简单的列表示例,它包含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"

列表之所以能够(有序地)工作,原因之一是为了满足对有序容器的需求,该容器可以在任何节点上包含任何类型,而向量不能做到这一点。在R中,列表被重用用于各种目的,包括形成data.frame的基,它是任意类型的向量的列表(但长度相同)。

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

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

给@Shane的答案加上一点,如果你想得到同样的结果,试试:

x3 = as.list(1:4)

它将vector 1:4强制转换为一个列表。

你说:

另一方面,列表可以返回 从函数中 调用时传入一个List 函数,即使函数 不包含List构造函数, 例如,< / p >
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做同样的事情呢?

再补充一点:

R确实有一个与hash中的Python字典相当的数据结构。你可以在这篇来自开放数据小组的博客文章中读到它。这里有一个简单的例子:

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

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)

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

  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. Vectors require everything to be the same data type. Watch this:

    > 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. Lists can contain varying data types, as seen in other answers and the OP's question itself.

I've seen languages (ruby, javascript) in which "arrays" may contain variable datatypes, but for example in C++ "arrays" must be all the same datatype. I believe this is a speed/efficiency thing: if you have a numeric(1e6) you know its size and the location of every element a priori; if the thing might contain "Flying Purple People Eaters" in some unknown slice, then you have to actually parse stuff to know basic facts about it.

Certain standard R operations also make more sense when the type is guaranteed. For example cumsum(1:9) makes sense whereas cumsum(list(1,2,3,4,5,'a',6,7,8,9)) does not, without the type being guaranteed to be double.


As to your second question:

Lists can be returned from functions even though you never passed in a List when you called the function

Functions return different data types than they're input all the time. plot returns a plot even though it doesn't take a plot as an input. Arg returns a numeric even though it accepted a complex. Etc.

(And as for strsplit: the source code is here.)

如果有帮助的话,我倾向于把R中的“列表”想象成其他前oo语言中的“记录”:

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

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

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

x = list(1, 2, 3, 4)
  1. [ ]提供子设置操作。一般来说,任何对象的子集 将具有与原始对象相同的类型。因此,x[1] 提供一个列表。类似地,x[1:2]是原始列表的子集, 因此它是一个列表。交货。< / p >

    x[1:2]
    
    
    [[1]] [1] 1
    
    
    [[2]] [1] 2
    
  2. [[ ]] is for extracting an element from the list. x[[1]] is valid and extract the first element from the list. x[[1:2]] is not valid as [[ ]] does not provide sub setting like [ ].

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

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

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

虽然我没有给出问题的确切答案,但下面的简短文本可能会帮助那些刚刚以R开头并提出类似问题的读者。

  • 原子矢量……我称之为“序列”。对我来说,没有方向,只是相同类型的序列。[子集。
  • 向量……从2D, [子集一个方向的序列。
  • 矩阵……一串具有相同长度的向量,形成行或列,[子集按行和列,或按序列。
  • 数组…层状矩阵形成三维
  • Dataframe……一个像excel一样的2D表格,我可以排序,添加或删除行或列,或者创建arit。只有在一段时间后,我才真正意识到数据帧是list的一个聪明的实现,在那里我可以使用[按行和列进行子集,但甚至使用[[
  • 列表…为了帮助自己,我考虑了tree structure的列表,其中[i]选择并返回整个分支,而[[i]]返回来自分支的项目。因为它是tree like structure,你甚至可以使用index sequence来寻址非常复杂的list中的每一个叶子,使用它的[[index_vector]]。列表可以很简单,也可以非常复杂,并且可以将各种类型的对象混合在一起。

因此,对于lists,你可以根据情况选择更多的方法,如下面的例子所示。

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

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

这是一个非常老的问题,但我认为一个新的答案可能会增加一些价值,因为在我看来,没有人直接解决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等等。
  • 数据部分将是一个数组,包含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组件,它是一个值为c("a","b")character R对象。

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

R中典型哈希映射的键-值只是一种假象。当你说:

z[["a"]]

事情是这样的:

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

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

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

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

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

在r中完全有效。当您尝试y[["same"]]时,将检索第一个值。现在你应该知道原因了。

总之,给对象赋予任意属性的能力会让您从外部角度看到不同的外观。但是R __abc0在任何方面都不是哈希映射。

你可以试试,

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