我怎样可以创建一个数据框列表,并从中访问每个数据框?
例如,我怎样可以将这些数据框放入一个列表中?
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
、d2
等。顺序命名的数据框是一个问题,将它们放入列表中是一个好的解决方法,但最好的做法是避免一开始就有一堆不在列表中的数据框。
其他答案详细介绍了如何将数据框分配给列表元素,访问它们等等。我们也会在这里简要介绍一下,但主要观点是说不要等到你有一堆 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.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次甚至更多!但是你不想在你的工作空间中有10000个数据框。使用replicate
并将它们放入列表中:
sim_list = replicate(n = 10,
expr = {data.frame(x = rnorm(50), y = rnorm(50))},
simplify = F)
data.table
或dplyr
,对数据框进行“按组”操作非常容易。
如果它们是奇怪的混合(这很少见),你可以简单地赋值:
mylist <- list()
mylist[[1]] <- mtcars
mylist[[2]] <- data.frame(a = rnorm(50), b = runif(50))
...
df1
、df2
、df3
,并且您想将它们放入列表中,您可以使用正则表达式来匹配名称,然后使用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)
(同样地,对于列可以使用cbind
或dplyr::bind_cols
。)
要合并(联接)一组数据框,您可以查看这些答案。通常,想法是使用Reduce
与merge
(或其他某些联接函数)将它们组合在一起。
它们可能很难处理,而且几乎总是不需要,但如果确实需要,请尽可能在list
中完成所有操作,然后可以使用list2env()
将所有列表项放入环境中,例如您的.GlobalEnv
。
lapply , sapply do.call ,the purrr 包
和旧的 plyr l * ply 函数使其易于实现。人们轻松使用列表处理数据的示例遍布SO。
即使您使用低级的for循环,循环遍历列表元素比使用 paste 构造变量名并使用 get 访问对象要容易得多。调试也更容易。
请看 可扩展性。如果你真的只需要三个变量,使用d1
、d2
、d3
就可以了。但是如果你发现实际上需要6个,那就需要打很多字。下一次,当你需要10或20个时,你会发现自己在复制和粘贴代码行,也许使用查找/替换来改变d14
为d15
,你会想到编程不应该是这样的。如果你使用列表,3种情况、30种情况和300种情况之间的区别最多只有一行代码——如果你的用例数量是通过例如你目录中有多少个.csv
文件自动检测到的话,根本不需要任何更改。
你可以为列表的元素命名,以便在访问数据框时使用其他内容而不是数字索引(你可以同时使用两者,这不是一个异或选择)。
总的来说,使用列表将使你编写更清晰、易于阅读的代码,从而减少错误和混乱。
my_data <- list()
可以清楚地表明你正在创建一个列表,这很好!清晰的代码是一件好事。我看不出使用my_data <- NULL
的任何优点。 - Gregor ThomasNULL
而不是 list()
有什么优势,否则我们可以通过删除这些注释来清理代码。它们似乎没有必要保留。 - Gregor Thomasdfs
是一个数据框列表,那么 dfs[i]
将是一个包含一个数据框的列表。你需要使用 [[
来提取单个列表元素,dfs[[i]]
将是一个数据框。请参阅此 FAQ 获取更多上下文和解释。 - Gregor Thomas这与你的问题无关,但是你应该在函数调用中使用 =
而不是 <-
。如果你使用 <-
,你将在任何工作环境中创建变量 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
< 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
您还可以使用[
和[[
访问每个列表元素中的特定列和值。以下是一些示例。首先,我们可以使用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
如果您有大量按顺序命名的数据框,您可以像这样创建所需子集的数据框列表:
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
lapply(foo, get)
,而是直接使用mget(foo)
。 - Gregor Thomas假设您拥有许多名称类似的数据框(这里是d#,其中#是某个正整数),以下是对@mark-miller方法的轻微改进。 它更加简洁,并返回一个命名列表的数据框,其中列表中的每个名称都是相应原始数据框的名称。
关键是结合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
中的模式参数,允许我们使用正则表达式对环境中对象的名称进行更精细的解析。与正则表达式"^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))
我认为自己是一个完全的新手,但我认为我有一个非常简单的答案来回答一个原始子问题,这个问题还没有在这里提到:访问数据框或其部分。
让我们从按上述方式创建包含数据框的列表开始:
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
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
循环的情况下执行此操作。
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)
GETDF_FROMLIST(mylist, 1)
而不是mylist[[1]]
呢?如果你更喜欢函数语法,甚至可以使用"[["(mylist, 1)
而不需要定义一个自定义函数。 - Gregor Thomasreturn(DF_LIST[[ITEM_LOC]])
,不需要分配一个中间变量。 - Gregor ThomasFilter(function(x) is.data.frame(get(x)) , ls())
ls()[sapply(ls(), function(x) is.data.frame(get(x)))]
is.data.frame
以检查其他类型的变量,如is.function
。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
data.frame()
中使用=
而不是<-
。使用<-
会在全局环境中创建y1
和y2
变量,导致生成的数据框不符合预期。 - Gregor Thomas