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

How to Correctly Use Lists in R?

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

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

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

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

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

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

首先,在我看来,R的list数据类型是map adt的直接实现(python中的dictionary、objective c中的NSMutableDictionary、perl和ruby中的hash、javascript中的object literal等等)。

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

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

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

1
2
3
4
5
6
7
8
9
10
11
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 list与其他广泛使用的语言(如python、perl、javascript):

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

其次,即使在调用函数时从未传入过list,也可以从函数返回list,即使返回list的函数不包含(显式)list构造函数(当然,在实践中可以通过将返回的结果包装为对EDOCX1的调用来处理这一问题)。〔19〕:

1
2
3
4
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's lists的第三个特点是:它们似乎不可能是另一个ADT的成员,如果您尝试这样做,那么主容器就被强制为list。例如。,

1
2
3
4
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/a调用unlist。)

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

    江户十一〔31〕号

    两个表达式都返回"1":

    埃多克斯1〔32〕

    埃多克斯1〔33〕

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

    江户十一〔31〕号

    埃多克斯1〔35〕

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

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


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

Why do these two expressions not return the same result?

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

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

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

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

r列表非常类似于散列映射数据结构,因为每个索引值都可以与任何对象相关联。下面是包含3个不同类(包括函数)的列表的简单示例:

1
2
3
4
5
6
7
8
9
10
> 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"

最后一个元素是搜索函数,我可以这样调用它:

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

最后一点意见是:应注意,data.frame实际上是一个列表(来自data.frame文档):

A data frame is a list of variables of the same number of rows with unique row names, given class ‘"data.frame"’

这就是为什么data.frame中的列可以有不同的数据类型,而矩阵中的列则不能。举个例子,我试着用数字和字符创建一个矩阵:

1
2
3
4
5
6
7
8
9
10
11
12
13
> 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"

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

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


关于你的问题,让我按顺序回答并举例说明:

1)当RETURN语句添加一个列表时,将返回一个列表。考虑

1
2
3
4
5
 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)不设置名称:

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

3)它们不返回相同的内容。您的示例给出

1
2
3
4
5
6
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]只返回一个值的列表。

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

> x[[1]]
[1] 1


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

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

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

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

1
x3 = as.list(1:4)

将载体1:4强制到一个列表中。


只需再加一点:

r的数据结构与hash包中的python dict相当。您可以从开放数据组阅读此博客文章中的相关内容。下面是一个简单的例子:

1
2
3
4
5
6
> 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类与列表非常相似。但是对于大型数据集来说,性能更好。


你说:

For another, lists can be returned
from functions even though you never
passed in a List when you called the
function, and even though the function
doesn't contain a List constructor,
e.g.,

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

我想你认为这是个问题(?)我是来告诉你为什么这不是问题的。您的示例有点简单,因为当您进行字符串拆分时,您有一个元素长度为1个元素的列表,因此您知道x[[1]]unlist(x)[1]相同。但是如果strsplit的结果返回每个箱子中不同长度的结果会怎样呢?简单地返回一个向量(而不是一个列表)根本不行。

例如:

1
2
3
4
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]。既然结果已经"解开"(unlisted),你怎么能用xx做同样的事情呢?


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

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

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


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

所以我自己开始使用下面的拐杖,这有助于我找到用于什么数据的对象,并基本上想象真实世界的使用情况。

虽然我没有给出问题的确切答案,下面的短文可能有助于读者谁刚刚开始与R,并提出西米拉尔问题。

  • 原子矢量…我自己称之为"序列",没有方向,只是相同类型的序列。[子集。
  • 矢量。。。从2d到[子集的单向序列。
  • 矩阵。。。一组长度相同的向量,按行和列或按序列组成[子集。
  • 数组…形成三维分层矩阵
  • 数据帧…类似于Excel的二维表格,我可以在其中对行或列进行排序、添加或删除,或者生成算术。使用它们进行操作后,我才真正认识到,数据帧是list的巧妙实现,在这里我可以使用[按行和列进行子集,甚至使用[[
  • 列表…为了方便自己,我考虑了一个清单,比如tree structure,其中[i]选择并返回整个分支机构,[[i]]从分支机构返回项目。因为它是tree like structure,你甚至可以用index sequence来处理非常复杂的list上的每一片叶子,使用[[index_vector]]。列表可以是简单的,也可以是非常复杂的,可以将各种类型的对象混合在一起。

因此,对于lists,您可以根据以下示例中的情况,选择更多的方法来选择leaf

1
2
3
4
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
x = list(1, 2, 3, 4)
  • [ ]提供子设置操作。任何对象的一般子集将具有与原始对象相同的类型。因此,x[1]。提供列表。同样,x[1:2]是原始列表的一个子集,因此,它是一个列表。前任。

    1
    2
    3
    4
    5
    x[1:2]

    [[1]] [1] 1

    [[2]] [1] 2

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

    1
    2
    3
     x[[2]] [1] 2

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


  • 如果有帮助,我倾向于将r中的"list"想象成其他面向对象前语言中的"records":

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

    名称"record"将与数据库术语中"records"(即行)的标准含义相冲突,这可能就是为什么它们的名称建议自己是列表(字段)。


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

  • 矢量是r的原子,例如,rpois(1e4,5)(5个随机数),numeric(55)(长度-55零矢量对双),character(12)(12个空字符串)都是"基本"的。

  • 列表或向量都可以有names

    1
    2
    3
    4
    5
    6
    7
    8
    9
    > 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
  • 向量要求所有内容都是相同的数据类型。注意:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    > 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

  • 列表可以包含不同的数据类型,如其他答案和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))是没有意义的,没有保证类型是双的。

    关于第二个问题:

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

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

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