gpt4 book ai didi

r - `vec_arith` 未按预期调用

转载 作者:行者123 更新时间:2023-12-03 13:40:24 25 4
gpt4 key购买 nike

我在下面放置了一个简单的案例,我在双对象上定义了一个类“foo”,我希望任何涉及此类对象的算术运算都将其从其“foo”类中剥离并正常进行。
我可以部分让它工作,但不是很强大。见下文 :

library(vctrs)

x <- new_vctr(42, class = "foo")

# then this won't work (expected)
x * 2
#> Error: <foo> * <double> is not permitted

# define vec_arith method
vec_arith.foo <- function(op, x, y, ...) {
print("we went there")
# wrap x in vec_data to strip off the class, and forward to `vec_arith_base`
vec_arith_base(op, vec_data(x), y)
}

# now this works
x * 2
#> [1] "we went there"
#> [1] 84

# but this doesn't, and doesn't go through vec_arith.foo
x * data.frame(a=1)
#> Warning: Incompatible methods ("*.vctrs_vctr", "Ops.data.frame") for "*"
#> Error in x * data.frame(a = 1): non-numeric argument to binary operator

# while this works
42 * data.frame(a=1)
#> a
#> 1 42
如何制作 x * data.frame(a=1)返回与 42 * data.frame(a=1) 相同 traceback()不返回任何东西,所以我不确定如何调试它。

最佳答案

这是一个有趣的问题,引起了我的兴趣。我不是这个问题的专家,但我找到了一种让它工作的方法。这是一个相当肮脏的解决方法,没有真正的解决方案。使用 {vctrs} 包应该有更好的方法来解决这个问题。
问题很复杂,因为我们正在处理一个内部泛型 *它使用双重调度(见 here )。重要的part就是它:

Generics in the Ops group, which includes the two-argument arithmeticand Boolean operators like - and &, implement a special type of methoddispatch. They dispatch on the type of both of the arguments, which iscalled double dispatch.


事实证明,对于像 x * y 这样的电话R 查找此调用和 y * x .那么有三种可能的结果:

The methods are the same, so it doesn’t matter which method is used.

The methods are different, and R falls back to the internal methodwith a warning.

One method is internal, in which case R calls the other method.


让我们在查看问题时牢记这一点。我首先避免使用 {vctrs} 包并尝试以两种方式重建问题。首先,我尝试将新类的对象与列表相乘。这重现了原始示例中的错误:

# lets create a new object
x1 <- 10
class(x1) <- "myclass"

# and multiply it with a list
l <- list(1)
x1 * l

# same error as in orignal example, but without warning
#> Error in x1 * l: non-numeric argument to binary operator

sloop::s3_dispatch(x1 * l)
#> *.myclass
#> *.default
#> Ops.myclass
#> Ops.default
#> => * (internal)

sloop::s3_dispatch(l * x1)
#> *.list
#> *.default
#> Ops.list
#> Ops.default
#> => * (internal)
我们可以通过 {sloop} 包看到调用了内部泛型。对于这个泛型,没有办法使用 *在名单上。因此,让我们尝试是否可以覆盖此方法:

`*.myclass` <- function(x, y) {
print("myclass")
if (is.list(y)) {
print("if clause")
y <- unlist(y)
} else {
print("didn't use if clause")
}

x + y # to see if it's working the operation is changed
}

x1 * l # now working
#> [1] "myclass"
#> [1] "if clause"
#> [1] 11
#> attr(,"class")
#> [1] "myclass"

sloop::s3_dispatch(x1 * l)
#> => *.myclass
#> *.default
#> Ops.myclass
#> Ops.default
#> * * (internal)

sloop::s3_dispatch(l * x1)
#> *.list
#> *.default
#> Ops.list
#> Ops.default
#> => * (internal)
这很有效(尽管我们真的不应该改变方法调用中的对象)。这里我们现在有上面描述的第三种情况:方法不同,一种是内部的,所以调用了非内部的方法。不像 data.frame的, list ' 没有现有的算术运算方法。所以我们需要一个例子,其中两个具有不同方法的不同类的对象相乘。

# another object
y1 <- 20
class(y1) <- "another_class"

# here we still only have one method `*.myclass`:
x1 * y1 # working
#> [1] "myclass"
#> [1] "didn't use if clause"
#> [1] 30
#> attr(,"class")
#> [1] "myclass"

sloop::s3_dispatch(x1 * y1)
#> => *.myclass
#> *.default
#> Ops.myclass
#> Ops.default
#> * * (internal)

sloop::s3_dispatch(y1 * x1)
#> *.another_class
#> *.default
#> Ops.another_class
#> Ops.default
#> => * (internal)

# lets introduce another method:
`*.another_class` <- function(x, y) {
x - y # again, to see if it is working we change the operation
}

# now we get (only) a warning, but with a different result!
x1 * y1
#> Warning: Incompatible methods ("*.myclass", "*.another_class") for "*"
#> [1] 200
#> attr(,"class")
#> [1] "myclass"

sloop::s3_dispatch(x1 * y1)
#> => *.myclass
#> *.default
#> Ops.myclass
#> Ops.default
#> * * (internal)

sloop::s3_dispatch(y1 * x1)
#> => *.another_class
#> *.default
#> Ops.another_class
#> Ops.default
#> * * (internal)
这里我们现在有上面描述的第二种情况:两种方法不同,R 回退到内部方法并发出警告。这会产生“未改变”的结果 20 * 10 = 200 .
所以关于原来的问题,我的理解是我们有两个相互冲突的方法“*.vctrs_vctr”和“Ops.data.frame”。为此,内部方法 * (internal)被调用,并且此内部方法不允许 list s 或 data.frame s (这通常在 Ops.data.frame 内完成,由于方法冲突,未使用它)。

library(vctrs)

z <- new_vctr(42, class = "foo")
a <- data.frame(a = 1)

z * a
#> Warning: Incompatible methods ("*.vctrs_vctr", "Ops.data.frame") for "*"
#> Error in z * a: non-numeric argument to binary operator

sloop::s3_dispatch(z * a)
#> *.foo
#> => *.vctrs_vctr
#> *.default
#> Ops.foo
#> Ops.vctrs_vctr
#> Ops.default
#> * * (internal)

sloop::s3_dispatch(a * z)
#> *.data.frame
#> *.default
#> => Ops.data.frame
#> Ops.default
#> * * (internal)
在这里,我们可以再次看到存在两种不同的方法,因此使用了内部方法。
我想出的肮脏的解决方法是:
  • 创建一个非内部泛型 *
  • 明确定义 *.foo
  • 明确定义 *.numeric一旦使用 vec_data() 将对象“分类”,就会调用它.

  • `*` <- function(x, y) {
    UseMethod("*")
    }

    `*.foo` <- function(x, y) {
    op_fn <- getExportedValue("base", "*")
    op_fn(vec_data(x),vec_data(y))
    }

    `*.numeric` <- function(x, y) {
    print("numeric")
    fn <- getExportedValue("base", "*")
    fn(x, y)
    }

    z * a
    #> [1] "numeric"
    #> a
    #> 1 42

    sloop::s3_dispatch(z * a)
    #> => *.foo
    #> * *.vctrs_vctr
    #> *.default
    #> Ops.foo
    #> Ops.vctrs_vctr
    #> Ops.default
    #> * * (internal)

    sloop::s3_dispatch(a * z)
    #> *.data.frame
    #> *.default
    #> => Ops.data.frame
    #> Ops.default
    #> * * (internal)
    创建于 2021-01-13 由 reprex package (v0.3.0)
    不幸的是,我不是 100% 确定发生了什么。似乎覆盖了 *泛型,还覆盖了 R 处理此泛型的双重分派(dispatch)的方式。让我们重温一下两种不同类型对象的乘法 x1 * y1多于。早些时候,这两种方法都被调用了,由于它们不同,因此发出了警告并选择了内部方法。现在我们观察以下内容:
    x1 * y1 # working without warning
    #> [1] "myclass"
    #> [1] "didn't use if clause"
    #> [1] 30
    #> attr(,"class")
    #> [1] "myclass"

    sloop::s3_dispatch(x1 * y1)
    #> => *.myclass
    #> *.default
    #> Ops.myclass
    #> Ops.default
    #> * * (internal)

    sloop::s3_dispatch(y1 * x1)
    #> => *.another_class
    #> *.default
    #> Ops.another_class
    #> Ops.default
    #> * * (internal)
    我们有两个冲突的方法,但 R 仍然选择了第一个对象的方法,没有发出警告。
    这当然不是问题的真正解决方案,原因有很多:
  • 覆盖算术运算的泛型似乎不是一个好主意,因为它可能会破坏代码。
  • 我们还需要处理 data.frame(a = 1) * z这仍然不起作用(这里我们需要覆盖 Ops.data.frame 的现有代码。
  • 我们不需要为每个算术运算编写方法。

  • {vctrs} 包应该可以帮助我们找到更简单、更安全的解决方案,而且它可能已经存在。在 Github 上打开一个问题可能是值得的。

    关于r - `vec_arith` 未按预期调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65656622/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com