【问题标题】:duplication of terra rasters and side effectsterra raster 的重复和副作用
【发布时间】:2021-03-30 13:04:06
【问题描述】:

在修改重复的SpatRaster的部分属性时,原来的也会被修改:

library(terra)
r <- rast(ncol=2, nrow=2, vals=c(5.3, 7.1, 3, 1.2))
#class       : SpatRaster 
#dimensions  : 2, 2, 1  (nrow, ncol, nlyr)
#resolution  : 180, 90  (x, y)
#extent      : -180, 180, -90, 90  (xmin, xmax, ymin, ymax)
#coord. ref. : +proj=longlat +datum=WGS84 +no_defs 
#source      : memory 
#name        : lyr.1 
#min value   :   1.2 
#max value   :   7.1 
xmin(r)
#[1] -180
t <- r          # duplication
xmin(t) <- -300 # xmin modification of the duplicated SpatRaster
xmin(r)         # the original SpatRaster has also been modified
#[1] -300

这是错误还是选择?它只发生在某些属性上,而不是全部。如果是选择,创建“独立”副本的方法是什么,或者如何断开链接?

【问题讨论】:

    标签: duplicates raster side-effects terra


    【解决方案1】:

    这是因为 SpatRaster 只是 C++ 对象的包装器。这使得x(下)成为浅拷贝(即指向内存中的同一个对象)

    library(terra)
    r <- rast()
    x <- r 
    

    只有在某些情况下,使用替换方法时才重要(您的示例不再受当前terra 的影响)。我还添加了一个 copy 方法,该方法返回一个深拷贝,即指向不同(深拷贝)C++ 对象的 SpatRaster。

    【讨论】:

    • 谢谢,很清楚。你很聪明,保持这样的副作用可能令人费解。
    【解决方案2】:

    有关信息,函数add 有同样的问题(在terra 1.0.11 中):

    logo <- rast(system.file("ex/logo.tif", package="terra"))
    nlyr(logo)
    [1] 3
     
    r <- logo
     
    add(r) <- r[[1]]
    
    nlyr(logo)
    #[1] 4
    

    【讨论】:

    • 这是故意的 ---- 和一个完全不同的情况。
    • 谢谢。对于函数add,这种行为的意图是什么?
    • 用户无法提前知道哪些功能会导致这种副作用,哪些不会。应该在某处解释。否则,无法编写可靠的脚本。或者应该说,一个人永远不应该进行简单的光栅分配,而应该总是硬拷贝?
    • function(x) &lt;- value 总是改变x --- 所以add 的行为应该和预期的一样。你还能期待发生什么? .
    【解决方案3】:

    这里可能存在误解,尤其是我自己。正如您所指出的,add(r) &lt;- r[[1]] 更改 r 的事实是绝对正常的。但它也改变logo 的事实根本不是常规(根据文档,add 函数应该向r 添加一个层,而不是向与当前脚本行无关的任何其他对象)。

    如果我理解正确,正如你之前解释的那样,那是因为 r &lt;- logo 不会复制 logo(深拷贝),而只会创建指向 logo 的指针(浅拷贝)。此选择对terra 对象的使用具有重要影响,因为以后对r 的修改也会修改logo 对象(从用户的角度来看,这是一种“副作用”)。我在这里看到至少 3 点:

    1. 异常行为。也许应该警告用户。

    “在 R 语义中,对象是按值复制的。这意味着修改副本会保持原始对象不变。”:深度或惰性副本是 R 的基本规则(https://rlang.r-lib.org/reference/duplicate.html)。因此用户必须清楚terra (r &lt;- logo) 中的简单赋值不是按值复制,而是按引用复制,否则脚本出错的风险很高。但是,terra 的文档中目前没有解释这种异常行为。

    1. 浅拷贝更多是面向程序员的工具。

    当用户明白terra 副本是浅副本时,他们可能最喜欢制作深副本,因为terra 用户使用浅副本可能受到限制且不常见。我没有看到太多这种行为有用的情况。对于用户来说,深度副本或惰性副本更为常见。

    1. 根据函数的异构行为

    但这不是主要问题。主要问题在于,目前,某些操作会同时影响原始对象和复制对象,而其他操作则不会。 add 修改了原始对象(副作用),但 res 没有改变它:

    logo <- rast(system.file("ex/logo.tif", package="terra"))
    nlyr(logo)
    # [1] 3
    res(logo)
    # [1] 1 1
    
    r <- logo
    
    add(r) <- r[[1]]
    nlyr(logo)
    # side effect of add function on the number of layers
    # [1] 4
    res(r) <- c(10,10)
    res(logo)
    # no side effect of res function on the resolution
    # [1] 1 1
    

    如果所有这些猜想都是正确的,那么解释在赋值之后哪些函数会影响或不影响原始对象会很有用,否则就无法可靠地编程。

    【讨论】:

    • 感谢您的出色澄清 --- 尽管编辑您的问题可能更有意义,以便我可以正确回答。但我会在这里尝试:res 返回一个新对象并避免浅拷贝问题。它可以很容易地做到这一点。 add 不会进行深度复制,因为那非常昂贵,而添加的原因是为了允许有效地添加层。但是,我可能会更改默认设置,以便它进行深层复制,除非被告知不要这样做。
    • 我明白了。由于这是提高terra 效率的有意选择,因此在我看来,编程可靠性有必要在terra:copy 帮助页面中列出通过引用工作的函数和terra 运算符的确切列表。做出此选择的其他(不常见的)软件包之一是data.tabledata.table::copy 的帮助页面清楚地表明,函数data.table::set:= 运算符是唯一没有进行硬拷贝的情况。没有这些信息就很难编写可靠的terra 脚本。
    猜你喜欢
    • 2022-12-28
    • 1970-01-01
    • 2021-10-31
    • 2022-10-15
    • 2011-04-07
    • 1970-01-01
    • 1970-01-01
    • 2021-09-19
    • 2015-05-08
    相关资源
    最近更新 更多