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

dou*_*oug 308 language-features r list abstract-data-type data-structures

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

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

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

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

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

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

首先,在我看来,R的list数据类型是地图ADT的简单实现(dictionary在Python中,NSMutableDictionary在Objective C中,hash在Perl和Ruby中,object literal在Javascript中,等等).

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

x = list("ev1"=10, "ev2"=15, "rv"="Group 1")
Run Code Online (Sandbox Code Playgroud)

并且您可以像访问Python字典那样访问R List的项目,例如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
Run Code Online (Sandbox Code Playgroud)

但是R lists也不同于其他地图类型的ADT(从我学到的语言中来看).我的猜测是,这是S的初始规范的结果,即打算从头开始设计数据/统计DSL [特定于域的语言].

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

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

第二,list即使您在list调用函数时从未传入过函数,也可以从函数返回s ,即使返回的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
Run Code Online (Sandbox Code Playgroud)

一个第三讨论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
Run Code Online (Sandbox Code Playgroud)

我的意图不是批评语言或如何记录; 同样,我并不是说list数据结构或它的行为有什么问题.我所要做的就是纠正我对它们如何工作的理解,这样我才能在我的代码中正确使用它们.

以下是我想要更好理解的各种事情:

  • 确定函数调用何时返回a list(例如,strsplit上述表达式)的规则是什么?

  • 如果我没有为a list(例如list(10,20,30,40))明确指定名称,那么默认名称只是从1开始的连续整数?(我假设,但我很难确定答案是肯定的,否则我们将无法将这种类型强制list转换为带有调用的向量unlist.)

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

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

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

    x[1]

    x[[1]]

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

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

    x2 = list(1:4)

请不要指向R文档(?list,R-intro) - 我仔细阅读了它并没有帮助我回答上面我刚才所述的问题类型.

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

Sha*_*ane 143

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

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

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

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

> length(x[[1]])
[1] 1
> length(x2[[1]])
[1] 4
Run Code Online (Sandbox Code Playgroud)

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

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"
Run Code Online (Sandbox Code Playgroud)

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

> complicated.list[["d"]]()
[1] ".GlobalEnv" ...
Run Code Online (Sandbox Code Playgroud)

作为对此的最后评论:应该注意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"
Run Code Online (Sandbox Code Playgroud)

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

> d[,1] <- as.numeric(d[,1])
> class(d[,1])
[1] "character"
Run Code Online (Sandbox Code Playgroud)

  • 没错,虽然在R中有一个有用的`switch`函数可以用于那个目的(参见`help(switch)`). (8认同)
  • 这有帮助,谢谢.(顺便说一句,你的例子是'复杂列表',正如你可能已经知道的那样,是用C++,Java等在没有语言的语言中复制'switch'语句的标准方法;可能是一种好方法当我需要时,在R中执行此操作).+1 (4认同)

Dir*_*tel 62

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

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> 
Run Code Online (Sandbox Code Playgroud)

2)名称根本没有设置:

R> retList <- function() return(list(1,2,3,4)); names(retList())
NULL
R> 
Run Code Online (Sandbox Code Playgroud)

3)他们不会返回相同的东西.你的例子给出了

R> x <- list(1,2,3,4)
R> x[1]
[[1]]
[1] 1
R> x[[1]]
[1] 1
Run Code Online (Sandbox Code Playgroud)

其中x[1]返回的第一个元素x-这是一样的x.每个标量都是长度为1的向量.另一方面,x[[1]]返回列表的第一个元素.

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

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

JD *_*ong 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
Run Code Online (Sandbox Code Playgroud)


Ale*_*own 13

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

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

x = list(1, 2, 3, 4); x2 = list(1:4)
Run Code Online (Sandbox Code Playgroud)

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

x3 = as.list(1:4)
Run Code Online (Sandbox Code Playgroud)

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


Sha*_*ane 11

只是再补充一点:

R确实具有hash包中的Python dict相同的数据结构.您可以在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
Run Code Online (Sandbox Code Playgroud)

在可用性方面,hash该类与列表非常相似.但是大型数据集的性能更好.


Ste*_*lou 9

你说:

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

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

我猜你认为这是一个问题(?).我在这里告诉你为什么这不是问题:-).您的示例有点简单,因为当您执行字符串拆分时,您有一个列表,其中包含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, ","))
Run Code Online (Sandbox Code Playgroud)

在第一种情况下(x:返回一个列表),你可以知道第三个字符串的第二个"部分"是什么,例如:x[[3]][2].xx既然结果已被"解开"(unlist-ed),你怎么能这样做呢?


nic*_*ola 6

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

尽管接受的答案暗示了什么list,R 中的对象不是哈希映射。如果你想与 python 平行list,你猜更像是 python lists(或tuple实际上是 s)。

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

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

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

x <- runif(10)
y <- list(runif(10), runif(3))
Run Code Online (Sandbox Code Playgroud)

对于x

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

对于y

  • 标题会说类型是listVECSXP在 C 端),长度是 2 和其他东西。
  • 数据部分将是一个包含 2 个指向两个 SEXP 类型的指针的数组,分别指向runif(10)和获得的值runif(3)
  • 属性是NULL,至于x

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

名字会发生什么?好吧,名称只是您可以分配给对象的一些属性。让我们看看下面的对象:

z <- list(a=1:3, b=LETTERS)
Run Code Online (Sandbox Code Playgroud)
  • 标题会说类型是listVECSXP在 C 端),长度是 2 和其他东西。
  • 数据部分将是一个包含 2 个指向两个 SEXP 类型的指针的数组,分别指向1:3和获得的值LETTERS
  • 属性现在存在并且是一个names组件,它是一个character具有 value的R 对象c("a","b")

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

R 中散列映射的典型键值只是一种错觉。当你说:

z[["a"]]
Run Code Online (Sandbox Code Playgroud)

这是发生的事情:

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

键值搜索是相当间接的,并且始终是定位的。此外,请记住:

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

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

      names(y) <- c("same", "same")
    
    Run Code Online (Sandbox Code Playgroud)

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

总而言之,赋予对象任意属性的能力使您从外部的角度来看具有不同的外观。但是 Rlist无论如何都不是哈希映射。


Jer*_*myS 5

x = list(1, 2, 3, 4)
x2 = list(1:4)
all.equal(x,x2)
Run Code Online (Sandbox Code Playgroud)

是不一样的,因为1:4与c(1,2,3,4)相同.如果你想要它们是相同的那么:

x = list(c(1,2,3,4))
x2 = list(1:4)
all.equal(x,x2)
Run Code Online (Sandbox Code Playgroud)


Pet*_*usu 5

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

所以我自己开始使用下面的拐杖,这对我找出使用什么对象来处理什么数据有很大帮助,并且基本上可以想象现实世界的用法。

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

  • 原子向量......我自己称之为“序列”,没有方向,只是相同类型的序列。[子集。
  • 向量……来自 2D 的一个方向的序列,[子集。
  • 矩阵...一组具有相同长度的向量,形成行或列、[按行和列或按序列的子集。
  • 数组...形成 3D 的分层矩阵
  • Dataframe ...类似于 Excel 中的 2D 表,我可以在其中排序、添加或删除行或列或创建 arit。与它们进行操作,只有一段时间后,我才真正认识到数据框架是一种巧妙的实现list,我可以使用[行和列进行子集化,但甚至使用[[.
  • 列表...为了帮助自己,我考虑了tree structure选择[i]并返回整个分支并[[i]]从分支返回项目的列表。因为它是tree like structure,您甚至可以使用 an来使用其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
Run Code Online (Sandbox Code Playgroud)

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