首页 文章

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

提问于
浏览
291

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

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

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

  • map-based type (将键映射到值的无序数组)

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

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

首先,在我看来,R的 list 数据类型是 Map ADT的直接实现(在Python中为 dictionary ,在目标C中为 NSMutableDictionary ,在Perl和Ruby中为 hash ,在Javascript中为 object literal ,依此类推) .

例如,您可以像创建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 list 也是 unlike 其他 Map 类型的ADT(从我学到的语言中来看) . 我的猜测是,这是S初始规范的结果,即打算从头开始设计数据/统计DSL [特定于域的语言] .

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

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

第二, 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开始的连续整数? (我假设,但我很难确定答案是肯定的,否则我们无法将这种类型的 list 强制转换为一个调用 unlist 的向量 . )

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

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

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

x[1]

x[[1]]

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

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

x2 = list(1:4)

请不要指向R文档(?listR-intro) - 我仔细阅读了它,它无法帮助我回答上面列举的问题类型 .

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

11 回答

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

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

    • 他们不对总体类型做出任何假设(或者更确切地说,任何arity和字段名称的所有可能记录的类型都可用) .

    • 他们的字段可以是匿名的(然后您可以通过严格的定义顺序访问它们) .

    “记录”这个名称会与数据库用语中“记录”(又名行)的标准含义冲突,这可能就是他们的名字建议的原因:作为列表(字段) .

  • 12

    只是为了解答你的一小部分问题:

    关于索引的This article解决了 [][[]] 之间差异的问题 .

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

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

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

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

    • 列表或向量可以有 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
    
    • Vectors要求所有内容都是相同的数据类型 . 看这个:
    > 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的问题本身所示 .

    我见过"arrays"可能包含变量的语言(ruby,javascript)数据类型,但例如在C _161726中必须是所有相同的数据类型 . 我相信这是速度/效率的事情:如果你有一个 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 . )

  • 1

    尽管这是一个非常古老的问题,但我必须说它完全触及了我在R的第一步中所缺少的知识 - 即如何将我手中的数据表示为R中的对象或如何从现有对象中进行选择 . R新手从一开始就认为“在R盒子里”是不容易的 .

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

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

    • 原子矢量......我为自己称之为"sequence",没有方向,只是相同类型的序列 . [ 子集 .

    • Vector ...具有来自2D, [ 子集的一个方向的序列 .

    • 矩阵...具有相同长度的向量形成行或列,按行和列或按顺序的子集.161640_子集 .

    • Arrays ...形成3D的分层矩阵

    • Dataframe ...像excel中的2D表,我可以在其中排序,添加或删除行或列或制作arit . 使用它们的操作,只是经过一段时间我才真正认识到数据帧是 list 的一个聪明的实现,我可以按行和列使用 [ 进行子集,但即使使用 [[ .

    • 列表...帮助自己我想到 tree structure 的列表,其中 [i] 选择并返回整个分支, [[i]] 从分支返回项目 . 因为它是 tree like structure ,你甚至可以使用 index sequence 使用 [[index_vector]] 来处理非常复杂的 list 上的每一片叶子 . 列表可以是简单的也可以是非常复杂的,并且可以将各种类型的对象混合在一起 .

    因此,对于 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
    

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

  • 11

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

    x = list(1, 2, 3, 4)
    
    • [ ] 提供子设置操作 . 通常,任何对象的子集都将与原始对象具有相同的类型 . 因此, x[1] 提供了一个列表 . 类似地, x[1:2] 是原始列表的子集,因此它是一个列表 . 防爆 .
    x[1:2]
    
    [[1]] [1] 1
    
    [[2]] [1] 2
    
    • [[ ]] 用于从列表中提取元素 . x[[1]] 有效并从列表中提取第一个元素 . x[[1:2]] 无效,因为 [[ ]] 不提供像 [ ] 这样的子设置 .
    x[[2]] [1] 2 
    
    > x[[2:3]] Error in x[[2:3]] : subscript out of bounds
    
  • 9

    列出工作原因(有序)的一个原因是解决了对任何节点可以包含任何类型的有序容器的需求,这些向量不能执行 . 列表在R中被重用于各种用途,包括形成 data.frame 的基数, data.frame 是任意类型的向量列表(但长度相同) .

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

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

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

    x3 = as.list(1:4)
    

    将矢量 1:4 强制强制转换为列表 .

  • 1

    只是为此添加一点:

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

  • 132

    你说:

    另一方面,即使可以从函数返回列表在调用函数时,你从未传入List,即使函数不包含List构造函数,例如,

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

    我猜你认为这是一个问题(?) . 我'm here to tell you why it'不是问题:-) . 您的示例有点简单,因为当您执行字符串拆分时,您有一个包含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 :返回一个列表),你可以知道第三个字符串的第二个"part"是什么,例如: x[[3]][2] . 既然结果是"unraveled"( unlist -ed),你怎么能用 xx 做同样的事?

  • 1

    只是为了解决你问题的最后一部分,因为这确实指出了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"
    
  • 1

    关于你的问题,让我按顺序解决它们并给出一些例子:

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

相关问题