我如何做一个数据帧的列表,我如何从列表中访问每个这些数据帧?

例如,如何将这些数据帧放入列表中?

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

当前回答

其他答案告诉你如何在你已经有了一堆data.frames的情况下创建一个data.frames的列表,例如,d1, d2, ....拥有按顺序命名的数据帧是个问题,将它们放在一个列表中是一个很好的解决方案,但最佳实践是避免在一开始就有一堆数据帧不在列表中。

其他答案提供了大量关于如何将数据帧分配给列表元素、访问它们等的细节。我们在这里也会讲到一点,但主要的观点是不要等到你有了一堆数据帧才把它们添加到列表中。从清单开始。

这个答案的其余部分将涵盖一些常见的情况,在这些情况下,您可能会忍不住创建顺序变量,并向您展示如何直接进入列表。如果您不熟悉R中的列表,您可能还想阅读在访问列表元素时[[和[]之间的区别是什么?


从一开始就列出

不要创造d1 d2 d3…在第一个地方。创建一个包含n个元素的列表d。

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

This is done pretty easily when reading in files. Maybe you've got files data1.csv, data2.csv, ... in a directory. Your goal is a list of data.frames called mydata. The first thing you need is a vector with all the file names. You can construct this with paste (e.g., my_files = paste0("data", 1:5, ".csv")), but it's probably easier to use list.files to grab all the appropriate files: my_files <- list.files(pattern = "\\.csv$"). You can use regular expressions to match the files, read more about regular expressions in other questions if you need help there. This way you can grab all CSV files even if they don't follow a nice naming scheme. Or you can use a fancier regex pattern if you need to pick certain CSV files out from a bunch of them.

在这一点上,大多数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.csv。Readr::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次,甚至更多!但您不希望工作区中有10,000个数据帧。使用复制并将它们放入列表中:

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

特别是在这种情况下,您还应该考虑是否真的需要单独的数据帧,或者单个具有“group”列的数据帧也能工作吗?使用数据。表或dplyr是很容易做的事情“组”到一个数据帧。

我没有把我的数据放在列表中:(我下次会,但我现在能做什么?

如果它们是一个奇怪的分类(这是不寻常的),你可以简单地分配它们:

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

如果你有一个以模式命名的数据帧,例如,df1, df2, df3,并且你想要它们在一个列表中,你可以通过编写一个正则表达式来匹配这些名称来获得它们。类似的

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)

(类似地,对列使用cbind或dplyr::bind_cols。)

要合并(联接)数据帧列表,您可以看到这些答案。通常,这个想法是使用Reduce和merge(或其他一些连接函数)来将它们组合在一起。

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

使用它们可能很痛苦,而且几乎总是不需要它们,但如果需要,请在列表中尽可能地使用它们,然后可以使用list2env()将所有列表项放入一个环境中,例如. globalenv。

为什么把数据放在列表中?

将相似的数据放在列表中,因为您希望对每个数据帧执行类似的操作,以及像lapply, sapply do这样的函数。Call、purrr包和旧的plyr l*ply函数可以很容易地做到这一点。人们用清单轻松做事的例子比比皆是。

即使使用低级的for循环,遍历列表中的元素也比使用paste构造变量名并使用get访问对象容易得多。调试也更容易。

Think of scalability. If you really only need three variables, it's fine to use d1, d2, d3. But then if it turns out you really need 6, that's a lot more typing. And next time, when you need 10 or 20, you find yourself copying and pasting lines of code, maybe using find/replace to change d14 to d15, and you're thinking this isn't how programming should be. If you use a list, the difference between 3 cases, 30 cases, and 300 cases is at most one line of code---no change at all if your number of cases is automatically detected by, e.g., how many .csv files are in your directory.

你可以命名一个列表的元素,以防你想使用数字索引以外的东西来访问你的数据帧(你可以使用两者,这不是一个异或的选择)。

总的来说,使用列表将使您编写更清晰、更易于阅读的代码,从而减少错误和混乱。

其他回答

假设你有“大量”名称相似的data.frames(这里的d#是一个正整数),下面的方法是对@mark-miller方法的轻微改进。它更简洁,返回data.frames的命名列表,其中列表中的每个名称都是对应的原始data.frame的名称。

关键是使用mget和ls。如果问题中提供的数据帧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中的pattern参数,它允许我们使用正则表达式对环境中对象的名称进行更精细的解析。另一个正则表达式”^ d[0 - 9] + $”是“^ \ \ d +美元”。

正如@gregor指出的那样,从整体上讲,更好的方法是设置数据构建过程,以便在开始时将data.frames放入命名列表中。

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

这可能有点晚了,但回到你的例子,我想我可以稍微扩展一下答案。

 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)

希望这一点能有所帮助。

干杯!

这与您的问题无关,但您希望在函数调用中使用=而不是<-。如果你使用<-,你最终会在你工作的环境中创建变量y1和y2:

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

另一方面,=操作符将你的向量与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

还可以使用[和[[]访问每个列表元素中的特定列和值。这里有几个例子。首先,使用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

对于循环模拟

如果我有一个生成数据框架的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)。

访问数据

然后使用dat_list[[n]]从列表中获取第n个数据帧。你可以通过正常的方式访问这个数据帧中的数据,例如dat_list[[2]]$x。

或者,如果你想从所有的数据框架中获得相同的部分,则使用(dat_list, "[", "x")。

见@Gregor Thomas的回答,没有for循环。