如何创建数据帧列表?

265

我怎样可以创建一个数据框列表,并从中访问每个数据框?

例如,我怎样可以将这些数据框放入一个列表中?

d1 <- data.frame(y1 = c(1, 2, 3),
                 y2 = c(4, 5, 6))
d2 <- data.frame(y1 = c(3, 2, 1),
                 y2 = c(6, 5, 4))

18
这段话存在于几个回答中,但是在这里也值得有一个可见的评论:在 data.frame() 中使用 = 而不是 <-。使用 <- 会在全局环境中创建 y1y2 变量,导致生成的数据框不符合预期。 - Gregor Thomas
48
看看那一堆没有空格、在data.frame()里还有"<-"的代码,我当时真是太菜了。 - Ben
10
没问题。我刚刚编辑了你的问题以修复代码格式。如果你感到怀旧,随时可以还原。 - Claus Wilke
10个回答

469

其他答案展示了如何在你已经有一堆数据框的情况下创建数据框列表,例如 d1d2 等。顺序命名的数据框是一个问题,将它们放入列表中是一个好的解决方法,但最好的做法是避免一开始就有一堆不在列表中的数据框

其他答案详细介绍了如何将数据框分配给列表元素,访问它们等等。我们也会在这里简要介绍一下,但主要观点是说不要等到你有一堆 data.frames 才将它们添加到列表中。从列表开始。

本答案的其余部分将涵盖一些常见情况,你可能会被诱惑创建顺序变量,并向你展示如何直接转到列表。如果你是 R 中的新手,请阅读 如何访问列表元素中的[[[之间的区别?


从头开始的列表

永远不要先创建 d1, d2, d3, ..., dn。应该创建一个具有 n 个元素的列表 d

将多个文件读入数据框列表

当读取文件时,这很容易完成。也许您在目录中有文件 data1.csv,data2.csv,...。您的目标是一个名为 mydata 的数据框列表。您需要的第一件事是一个包含所有文件名的向量。您可以使用 paste 构造此向量(例如,my_files = paste0("data", 1:5, ".csv")),但是使用 list.files 抓取所有适当的文件可能更容易: my_files <- list.files(pattern = "\\.csv$")。您可以使用正则表达式来匹配文件,请在其他问题中阅读有关正则表达式的更多信息,如果需要帮助。这样,即使 CSV 文件不遵循良好的命名方案,您也可以获取所有 CSV 文件。或者,如果需要从其中挑选某些 CSV 文件,则可以使用更复杂的正则表达式模式。

目前为止,大多数R语言初学者会使用for循环,这样做没有问题,它可以正常工作。

my_data <- list()
for (i in seq_along(my_files)) {
    my_data[[i]] <- read.csv(file = my_files[i])
}

更像R语言的方法是使用lapply,这是上述方法的一种快捷方式

my_data <- lapply(my_files, read.csv)

当然,根据需要可以使用其他数据导入函数替换read.csvreadr :: read_csv data.table :: fread 速度更快,或者您可能需要针对不同的文件类型使用不同的函数。
无论哪种方式,将列表元素命名为与文件匹配非常方便。
names(my_data) <- gsub("\\.csv$", "", my_files)
# or, if you prefer the consistent syntax of stringr
names(my_data) <- stringr::str_replace(my_files, pattern = ".csv", replacement = "")

将数据框拆分为数据框列表

这非常简单,基本函数split()可以帮您完成。您可以按数据的一列(或多列)进行拆分,也可以按您想要的任何其他方式进行拆分。

mt_list = split(mtcars, f = mtcars$cyl)
# This gives a list of three data frames, one for each value of cyl

这也是一种将数据框分成多个部分进行交叉验证的好方法。也许您想将mtcars分成训练、测试和验证部分。

groups = sample(c("train", "test", "validate"),
                size = nrow(mtcars), replace = TRUE)
mt_split = split(mtcars, f = groups)
# and mt_split has appropriate names already!

模拟数据帧列表

也许您正在模拟数据,类似于这样:

my_sim_data = data.frame(x = rnorm(50), y = rnorm(50))

但是谁只做一次模拟呢?你想要做100次、1000次甚至更多!但是你不想在你的工作空间中有10000个数据框。使用replicate并将它们放入列表中:

sim_list = replicate(n = 10,
                     expr = {data.frame(x = rnorm(50), y = rnorm(50))},
                     simplify = F)

在这种情况下,你应该考虑是否真的需要单独的数据框,或者一个带有“组”列的单一数据框是否能够同样起作用?使用data.tabledplyr,对数据框进行“按组”操作非常容易。

我没有把我的数据放在列表中:( 下次我会这样做,但现在我该怎么办?

如果它们是奇怪的混合(这很少见),你可以简单地赋值:

mylist <- list()
mylist[[1]] <- mtcars
mylist[[2]] <- data.frame(a = rnorm(50), b = runif(50))
...

如果您有按照某种模式命名的数据框,例如df1df2df3,并且您想将它们放入列表中,您可以使用正则表达式来匹配名称,然后使用get函数获取它们。类似于:
df_list = mget(ls(pattern = "df[0-9]"))
# this would match any object with "df" followed by a digit in its name
# you can test what objects will be got by just running the
ls(pattern = "df[0-9]")
# part and adjusting the pattern until it gets the right objects.

通常情况下,mget 用于获取多个对象并将它们以命名列表的形式返回。它的对应函数 get 则用于获取单个对象并将其返回(不以列表形式返回)。

将数据框列表合并为一个数据框

常见的任务是将数据框列表合并为一个大的数据框。如果您想将它们叠在一起,则可以使用 rbind 来处理一对数据框,但对于一个数据框列表,有三种好的选择:

# base option - slower but not extra dependencies
big_data = do.call(what = rbind, args = df_list)

# data table and dplyr have nice functions for this that
#  - are much faster
#  - add id columns to identify the source
#  - fill in missing values if some data frames have more columns than others
# see their help pages for details
big_data = data.table::rbindlist(df_list)
big_data = dplyr::bind_rows(df_list)

(同样地,对于列可以使用cbinddplyr::bind_cols。)

要合并(联接)一组数据框,您可以查看这些答案。通常,想法是使用Reducemerge(或其他某些联接函数)将它们组合在一起。

但我真的需要按顺序命名的变量

它们可能很难处理,而且几乎总是不需要,但如果确实需要,请尽可能在list中完成所有操作,然后可以使用list2env()将所有列表项放入环境中,例如您的.GlobalEnv

为什么要将数据放在列表中?

将类似的数据放在列表中,因为您想对每个数据框执行类似的操作,而像 lapply , sapply do.call ,the purrr 包和旧的 plyr l * ply 函数使其易于实现。人们轻松使用列表处理数据的示例遍布SO。
即使您使用低级的for循环,循环遍历列表元素比使用 paste 构造变量名并使用 get 访问对象要容易得多。调试也更容易。
请看 可扩展性。如果你真的只需要三个变量,使用d1d2d3就可以了。但是如果你发现实际上需要6个,那就需要打很多字。下一次,当你需要10或20个时,你会发现自己在复制和粘贴代码行,也许使用查找/替换来改变d14d15,你会想到编程不应该是这样的。如果你使用列表,3种情况、30种情况和300种情况之间的区别最多只有一行代码——如果你的用例数量是通过例如你目录中有多少个.csv文件自动检测到的话,根本不需要任何更改。
你可以为列表的元素命名,以便在访问数据框时使用其他内容而不是数字索引(你可以同时使用两者,这不是一个异或选择)。
总的来说,使用列表将使你编写更清晰、易于阅读的代码,从而减少错误和混乱。

5
您推荐哪本介绍列表操作的书籍? - derelict
29
我建议阅读在 Stack Overflow 上标记为“r”和“list”的问题和答案。 - Gregor Thomas
10
可以,但是使用my_data <- list()可以清楚地表明你正在创建一个列表,这很好!清晰的代码是一件好事。我看不出使用my_data <- NULL的任何优点。 - Gregor Thomas
1
@Daniel 除非你能证明将变量初始化为 NULL 而不是 list() 有什么优势,否则我们可以通过删除这些注释来清理代码。它们似乎没有必要保留。 - Gregor Thomas
1
@Matteo 如果 dfs 是一个数据框列表,那么 dfs[i] 将是一个包含一个数据框的列表。你需要使用 [[ 来提取单个列表元素,dfs[[i]] 将是一个数据框。请参阅此 FAQ 获取更多上下文和解释 - Gregor Thomas
显示剩余2条评论

154

这与你的问题无关,但是你应该在函数调用中使用 = 而不是 <-。如果你使用 <-,你将在任何工作环境中创建变量 y1y2

d1 <- data.frame(y1 <- c(1, 2, 3), y2 <- c(4, 5, 6))
y1
# [1] 1 2 3
y2
# [1] 4 5 6

这并不会如所期望的那样,在数据框中创建列名:

d1
#   y1....c.1..2..3. y2....c.4..5..6.
# 1                1                4
# 2                2                5
# 3                3                6
< p >与此相反,=运算符将将您的向量与data.frame的参数相关联。

至于您的问题,制作数据框列表很容易:

d1 <- data.frame(y1 = c(1, 2, 3), y2 = c(4, 5, 6))
d2 <- data.frame(y1 = c(3, 2, 1), y2 = c(6, 5, 4))
my.list <- list(d1, d2)

您可以像访问其他列表元素一样访问数据框:

my.list[[1]]
#   y1 y2
# 1  1  4
# 2  2  5
# 3  3  6

26

您还可以使用[[[访问每个列表元素中的特定列和值。以下是一些示例。首先,我们可以使用lapply(ldf, "[", 1)仅访问列表中每个数据框的第一列,其中1表示列号。

ldf <- list(d1 = d1, d2 = d2)  ## create a named list of your data frames
lapply(ldf, "[", 1)
# $d1
#   y1
# 1  1
# 2  2
# 3  3
#
# $d2
#   y1
# 1  3
# 2  2
# 3  1

同样地,我们可以使用以下方式访问第二列中的第一个值:

lapply(ldf, "[", 1, 2)
# $d1
# [1] 4
# 
# $d2
# [1] 6

然后,我们也可以直接访问列值,作为向量,使用[[

lapply(ldf, "[[", 1)
# $d1
# [1] 1 2 3
#
# $d2
# [1] 3 2 1

这种东西让我不想再用 R 了。它看起来很笨拙。 - gghuffer

13

如果您有大量按顺序命名的数据框,您可以像这样创建所需子集的数据框列表:

d1 <- data.frame(y1=c(1,2,3), y2=c(4,5,6))
d2 <- data.frame(y1=c(3,2,1), y2=c(6,5,4))
d3 <- data.frame(y1=c(6,5,4), y2=c(3,2,1))
d4 <- data.frame(y1=c(9,9,9), y2=c(8,8,8))

my.list <- list(d1, d2, d3, d4)
my.list

my.list2 <- lapply(paste('d', seq(2,4,1), sep=''), get)
my.list2

my.list2 返回一个包含第二、第三和第四个数据框的列表。

[[1]]
  y1 y2
1  3  6
2  2  5
3  1  4

[[2]]
  y1 y2
1  6  3
2  5  2
3  4  1

[[3]]
  y1 y2
1  9  8
2  9  8
3  9  8

请注意,上述列表中的数据框已不再具有名称。如果您想创建一个包含数据框子集并想保留它们名称的列表,您可以尝试以下方法:

list.function <-  function() { 

     d1 <- data.frame(y1=c(1,2,3), y2=c(4,5,6))
     d2 <- data.frame(y1=c(3,2,1), y2=c(6,5,4))
     d3 <- data.frame(y1=c(6,5,4), y2=c(3,2,1))
     d4 <- data.frame(y1=c(9,9,9), y2=c(8,8,8))

     sapply(paste('d', seq(2,4,1), sep=''), get, environment(), simplify = FALSE) 
} 

my.list3 <- list.function()
my.list3

返回结果如下:

> my.list3
$d2
  y1 y2
1  3  6
2  2  5
3  1  4

$d3
  y1 y2
1  6  3
2  5  2
3  4  1

$d4
  y1 y2
1  9  8
2  9  8
3  9  8

> str(my.list3)
List of 3
 $ d2:'data.frame':     3 obs. of  2 variables:
  ..$ y1: num [1:3] 3 2 1
  ..$ y2: num [1:3] 6 5 4
 $ d3:'data.frame':     3 obs. of  2 variables:
  ..$ y1: num [1:3] 6 5 4
  ..$ y2: num [1:3] 3 2 1
 $ d4:'data.frame':     3 obs. of  2 variables:
  ..$ y1: num [1:3] 9 9 9
  ..$ y2: num [1:3] 8 8 8

> my.list3[[1]]
  y1 y2
1  3  6
2  2  5
3  1  4

> my.list3$d4
  y1 y2
1  9  8
2  9  8
3  9  8

2
不要使用lapply(foo, get),而是直接使用mget(foo) - Gregor Thomas

11

假设您拥有许多名称类似的数据框(这里是d#,其中#是某个正整数),以下是对@mark-miller方法的轻微改进。 它更加简洁,并返回一个命名列表的数据框,其中列表中的每个名称都是相应原始数据框的名称。

关键是结合mgetls使用。 如果提供问题中的数据框d1和d2是环境中名称为d#的唯一对象,则:

my.list <- mget(ls(pattern="^d[0-9]+"))

将返回

my.list
$d1
  y1 y2
1  1  4
2  2  5
3  3  6

$d2
  y1 y2
1  3  6
2  2  5
3  1  4

这种方法利用ls中的模式参数,允许我们使用正则表达式对环境中对象的名称进行更精细的解析。与正则表达式"^d[0-9]+$"相比,另一种选择是"^d\\d+$"

如@gregor 所指出的,更好的做法是在开始时将数据框放入命名列表中,以设置数据构造过程。

data

d1 <- data.frame(y1 = c(1,2,3),y2 = c(4,5,6))
d2 <- data.frame(y1 = c(3,2,1),y2 = c(6,5,4))

7

我认为自己是一个完全的新手,但我认为我有一个非常简单的答案来回答一个原始子问题,这个问题还没有在这里提到:访问数据框或其部分。

让我们从按上述方式创建包含数据框的列表开始:

d1 <- data.frame(y1 = c(1, 2, 3), y2 = c(4, 5, 6))

d2 <- data.frame(y1 = c(3, 2, 1), y2 = c(6, 5, 4))

my.list <- list(d1, d2)

然后,如果您想访问其中一个数据框中的特定值,可以使用连续的双括号进行操作。第一组将带您进入数据框,第二组将带您到达特定坐标:

my.list[[1]][3, 2]

[1] 6

5

for 循环模拟

如果我有一个生成数据框的 for 循环,我会先创建一个空的 list(),然后在生成每个数据框时将其添加到列表中。

# Empty list
dat_list <- list()

for(i in 1:5){
    # Generate dataframe
    dat <- data.frame(x=rnorm(10), y=rnorm(10))
    # Add to list
    dat_list <- append(dat_list, list(dat))
}

请注意,在我们的append()调用中,需要使用list(dat).

访问数据

要从列表中获取第n个数据框,我们使用dat_list[[n]]。您可以以正常方式访问此数据框中的数据,例如dat_list[[2]]$x

或者,如果您想要从所有数据框中获得相同的部分,则可以使用sapply(dat_list, "[", "x")

请参见@Gregor Thomas的答案,了解如何在不使用for循环的情况下执行此操作。


3
这可能有点晚了,但是回到你的例子上,我想稍微扩展一下答案。
 D1 <- data.frame(Y1=c(1,2,3), Y2=c(4,5,6))
 D2 <- data.frame(Y1=c(3,2,1), Y2=c(6,5,4))
 D3 <- data.frame(Y1=c(6,5,4), Y2=c(3,2,1))
 D4 <- data.frame(Y1=c(9,9,9), Y2=c(8,8,8))

然后你可以轻松地创建你的列表:
mylist <- list(D1,D2,D3,D4)

现在你有一个列表,但是不要像以前那样访问列表,比如说:
mylist[[1]] # to access 'd1'

您可以使用此函数获取并分配您选择的数据框。
GETDF_FROMLIST <- function(DF_LIST, ITEM_LOC){
   DF_SELECTED <- DF_LIST[[ITEM_LOC]]
   return(DF_SELECTED)
}

现在获取您想要的那一个。
D1 <- GETDF_FROMLIST(mylist, 1)
D2 <- GETDF_FROMLIST(mylist, 2)
D3 <- GETDF_FROMLIST(mylist, 3)
D4 <- GETDF_FROMLIST(mylist, 4)

希望这些额外的细节能有所帮助。
祝好!

2
是的,我知道,但由于某种原因,当我复制和粘贴时,所有内容都变成了大写字母。:( 无论如何,小写字母的代码可以正常工作。 - ML_for_now
5
你为什么更喜欢使用GETDF_FROMLIST(mylist, 1)而不是mylist[[1]]呢?如果你更喜欢函数语法,甚至可以使用"[["(mylist, 1)而不需要定义一个自定义函数。 - Gregor Thomas
4
你可以简化函数定义,整个函数体只需要是 return(DF_LIST[[ITEM_LOC]]),不需要分配一个中间变量。 - Gregor Thomas

2
非常简单!这是我的建议:
如果你想在你的工作空间中选择数据框,试试这个:
Filter(function(x) is.data.frame(get(x)) , ls())

或者
ls()[sapply(ls(), function(x) is.data.frame(get(x)))]

所有这些都会得到相同的结果。
您可以更改is.data.frame以检查其他类型的变量,如is.function

1
在tidyverse中,您可以使用函数lst()根据对象自动命名列表。
library(tibble)

d1 <- data.frame(y1 = c(1, 2, 3),
                 y2 = c(4, 5, 6))
d2 <- data.frame(y1 = c(3, 2, 1),
                 y2 = c(6, 5, 4))

lst(d1, d2)
# $d1
# y1 y2
# 1  1  4
# 2  2  5
# 3  3  6
# 
# $d2
# y1 y2
# 1  3  6
# 2  2  5
# 3  1  4

这在编译后需要按名称引用列表时很有帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接