【问题标题】:Why function `load` not works in `lapply` but works in `for` loops?为什么函数 `load` 不能在 `lapply` 中工作,但可以在 `for` 循环中工作?
【发布时间】:2021-11-02 15:57:13
【问题描述】:

我正在尝试将一系列文件加载到 R 中的列表中。下面是示例和我使用的代码。

## data
val <- c(1:5)
save(val, file='test1.rda')
val <- c(6:10)
save(val, file='test2.rda')

## file names
files = paste0('test',c(1:2), '.rda')
# "test1.rda" "test2.rda"

## use apply to load data into a list 
res <- lapply(files, function(x) load(x))
res
# [[1]]
# [1] "val" # ??? supposed to be 1,2,3,4,5
# 
# [[2]]
# [1] "val" # ??? supposed to be 6,7,8,9,10


## use for loops to load data
for (i in c(1:2)){
  load(files[i])
}
# data sets are loaded as expected

我不明白为什么apply + load 函数没有返回正确的列表。如果有人能指出我正确的方向,我将不胜感激。

【问题讨论】:

  • load() 返回它创建的对象的名称。您的两个文件都创建了一个名为 val 的对象。

标签: r load apply


【解决方案1】:

前面的底线:load 将数据加载到调用环境中,这在从 for 循环和从 lapply 运行时非常不同。您可以覆盖它以强制将数据加载到 哪个 环境中。

如果您阅读?load,您会看到envir= 参数:

Usage:

     load(file, envir = parent.frame(), verbose = FALSE)
     
Arguments:

    file: a (readable binary-mode) connection or a character string
          giving the name of the file to load (when tilde expansion is
          done).

   envir: the environment where the data should be loaded.

 verbose: should item names be printed during loading?

由于默认值为parent.frame(),这意味着它被加载到lapply 中定义的环境中,而不是全局环境中。

演示:

for (i in 1:2) { print(environment()); }
# <environment: R_GlobalEnv>
# <environment: R_GlobalEnv>
ign <- lapply(1:2, function(ign) print(environment()))
# [[1]]
# <environment: 0x000000006f54b838>                # not R_GlobalEnv, aka .GlobalEnv
# [[2]]
# <environment: 0x000000006f54de58>

还有,因为

Value:

     A character vector of the names of objects created, invisibly.

这意味着res &lt;- lapply(files, load) 将始终只返回一个character 向量,而不是值本身。

虽然我同意 Samet Sökel 的前提,即 readRDS 提供了一个更功能 接口(意思是:它返回一些东西,它不仅仅在副作用上运行),但解决方法不是太困难:

  1. 加载到全局环境中:

    res <- lapply(files, load, envir = .GlobalEnv)
    

    这将返回加载到res 中的所有变量的名称,以及出现在全局环境中的所有数据。

  2. 加载到用户定义的环境中:

    e <- new.env(parent = emptyenv())
    res <- lapply(files, load, envir = e)
    # all data is now in 'e'
    

    res 也将只包含名称,但这更接近于功能接口,因为数据将进入您定义的非常具体的位置。

    不要急于解决这个问题:如果您选择“生产”加载所有.rda 文件的代码,最好将数据加载到.GlobalEnv 以外的环境中。一方面,在函数内部加载并将数据放在全局中是非常糟糕的做法,并且它可能并不总是对您的函数顺利运行。好吧,它只是“一个”,生产型函数/包中的副作用是一件坏事(imo):它经常破坏可重复性,它真的会惹恼那些碰巧在 中有同名变量的用户他们的环境......覆盖它们是一种不可逆转的操作,会很快导致愤怒和生产力损失。当出现问题时,副作用也很难排除。

【讨论】:

  • 在提出问题后仅 16 分钟。我是你的忠实粉丝。
【解决方案2】:

load 函数不是分配保存的 R 对象的好方法,因为它直接在您的环境中加载对象(就像您在 for 循环中所做的那样,没有分配新的命名对象)

saveRDSreadRDS 可以帮助您将保存的文件分配给环境中的新对象;

val <- c(1:5)
saveRDS(val, file='test1.rds')
val <- c(6:10)
saveRDS(val, file='test2.rds')

files = paste0('test',c(1:2), '.rds')

res <- lapply(files, function(x) readRDS(x))
res

输出;

1. 1 2 3 4 5
2. 6 7 8 9 10

【讨论】:

  • 你的建议很好,但是load() 比你想象的还要糟糕:它不是进入工作环境,而是进入调用环境,当lapply() 完成时它就消失了。
猜你喜欢
  • 1970-01-01
  • 2019-07-03
  • 1970-01-01
  • 1970-01-01
  • 2016-11-02
  • 2014-10-15
  • 2013-04-09
  • 2020-09-06
  • 1970-01-01
相关资源
最近更新 更多