How do I build an object with the R vctrs package that can combine with c()

廉价感情. 提交于 2021-02-19 04:30:10

问题


I'm trying to understand how to build objects with vectors. I thought this was straightforwards, but then had trouble when I used c() on my object.

Our object has two attributes, x and descriptor, both strings in this case (my object will have attributes with differing types). We've built a constructor, new_toy_vector. I haven't built a convenience function in this example yet.

new_toy_vector <- function(
  x = character(),
  descriptor = character()) {

  vctrs::vec_assert(x,character())
  vctrs::vec_assert(descriptor, character())

  vctrs::new_vctr(x,
                  descriptor = descriptor,
                  class = "toy_vector")
}



format.toy_vector <- function(x, ...) {
  paste0(vctrs::vec_data(x)," is ", attr(x, "descriptor"))
}

obj_print_data.toy_vector <- function(x) {
  cat(format(x), sep = "\n")
}

c(new_toy_vector("Hello", "Foo"), new_toy_vector("World", "Bar"))
#> Error: No common type for `..1` <toy_vector> and `..2` <toy_vector>.

Created on 2020-04-26 by the reprex package (v0.3.0)

I then tried to create a coercion with itself unless the default method wasn't defined for some reason:

> vec_ptype2.toy_vector.toy_vector <- function(x, y, ...) new_toy_vector()
> c(new_toy_vector("Hello", "Foo"), new_toy_vector("World", "Bar"))
Error: Can't convert <toy_vector> to <toy_vector>.

Any ideas what I'm missing or misunderstanding? Why can't I combine the two objects in the example?


回答1:


Add an explicit `[.toy_vector` which subsets the descriptor attribute.

Like this:

`[.toy_vector` <- function(x,i){
      new_toy_vector(vec_data(NextMethod()),
                     descriptor = attr(NextMethod(), "descriptor")[i])
    }

I'm not sure how to get attributes to 'subset' in this way using vctrs, or even if it's possible. But using this method we can basically do what vctrs does, and then some.

Bear in mind that subsetting generic will no longer call the `[.vctrs_vctr` method, so you'll lose other vctrs functionallity (such as subsetting sub-classes with vec_restore()) and may need to implement further fixes in the `[.toy_vector` method.

library(vctrs)

new_toy_vector <- function(
  x = character(),
  descriptor = character()) {
  
  vec_assert(x,character())
  vec_assert(descriptor, character())
  
  new_vctr(x,
           descriptor = descriptor,
           class = "toy_vector")
}



format.toy_vector <- function(x, ...) {
  paste0(vec_data(x)," is ", attr(x, "descriptor"))
}

obj_print_data.toy_vector <- function(x) {
  cat(format(x), sep = "\n")
}


vec_ptype2.toy_vector.toy_vector <- function(x, y, ...) {
  new <- c(attr(x, "descriptor"), attr(y, "descriptor"))
  new_toy_vector(descriptor = new)
}

vec_cast.toy_vector.toy_vector <- function(x, to, ...) {
  new_toy_vector(vec_data(x),
                 attr(to, "descriptor"))
}

`[.toy_vector` <- function(x,i){
  new_toy_vector(vec_data(NextMethod()),
                 descriptor = attr(NextMethod(), "descriptor")[i])
}

c(new_toy_vector("Hello", "Foo"), new_toy_vector("World", "Bar")) -> tmp

tmp
#> <toy_vector[2]>
#> Hello is Foo
#> World is Bar

tmp[1]
#> <toy_vector[1]>
#> Hello is Foo

tmp[2]
#> <toy_vector[1]>
#> World is Bar

Created on 2021-01-19 by the reprex package (v0.3.0)




回答2:


Generally attributes are not subsetted when an object is subsetted, this is not a rule and the "names" attribute is a prominent example which doesn't follow this practice. To create an attribute that behaves like "names" you'd have to jump through hoops, and {vctrs} was designed to simplify this kind of tasks for you.

The way we do this with {vctrs} is by using records, and we won't need attributes :

Record-style objects use a list of equal-length vectors to represent individual components of the object. The best example of this is POSIXlt, which underneath the hood is a list of 11 fields like year, month, and day. Record-style classes override length() and subsetting methods to conceal this implementation detail.

Using the example in the link above as a template we can implement your case :

new_toy_vector <- function(
  value = character(),
  descriptor = character()) {
  vctrs::vec_assert(value,character())
  vctrs::vec_assert(descriptor, character())
  vctrs::new_rcrd(list(value = value, descriptor = descriptor), class = "toy_vector")
}


format.toy_vector <- function(x, ...) {
  value <- vctrs::field(x, "value")
  descriptor <- vctrs::field(x, "descriptor")
  paste0('"', value," is ", descriptor, '"')
}

v1 <- new_toy_vector(
  c("Hello", "World"), 
  c("Foo", "Bar"))

v2 <- c(
  new_toy_vector("Hello", "Foo"), 
  new_toy_vector("World", "Bar"))

v1
#> <toy_vector[2]>
#> [1] "Hello is Foo" "World is Bar"

identical(v1, v2)
#> [1] TRUE

v2[2]
#> <toy_vector[1]>
#> [1] "World is Bar"

Created on 2021-01-23 by the reprex package (v0.3.0)

Note that we didn't need to create a coercion method, in this case the default coercion method for records is good enough.




回答3:


I tried your code and I got a more informative error message:

Error: Can't combine `..1` <toy_vector> and `..2` <toy_vector>.
x Some attributes are incompatible.
ℹ The author of the class should implement vctrs methods.
ℹ See <https://vctrs.r-lib.org/reference/faq-error-incompatible-attributes.html>.
Run `rlang::last_error()` to see where the error occurred.

https://vctrs.r-lib.org/reference/faq-error-incompatible-attributes.html

If you go to the page about the error, the answer is there: vctrs does not know by default how to combine custom attributes. Your vectors have different attributes: Foo and Bar.

If you try

a <- new_toy_vector("Hello", "Foo")
b <- new_toy_vector("World", "Foo")
c(a, b)

this will work.




回答4:


To provide some context why I put a bounty on this question (and to give a bad answer to the question); I can get concatenation to work, but this causes trouble in other areas. So obviously something isn't right, but what?

library(vctrs)

new_toy_vector <- function(
  x = character(),
  descriptor = character()) {
  
  vec_assert(x,character())
  vec_assert(descriptor, character())
  
  new_vctr(x,
           descriptor = descriptor,
           class = "toy_vector")
}



format.toy_vector <- function(x, ...) {
  paste0(vec_data(x)," is ", attr(x, "descriptor"))
}

obj_print_data.toy_vector <- function(x) {
  cat(format(x), sep = "\n")
}


vec_ptype2.toy_vector.toy_vector <- function(x, y, ...) {
  new <- c(attr(x, "descriptor"), attr(y, "descriptor"))
  new_toy_vector(descriptor = new)
}

vec_cast.toy_vector.toy_vector <- function(x, to, ...) {
  new_toy_vector(vec_data(x),
                 attr(to, "descriptor"))
}

z <- c(new_toy_vector("Hello", "Foo"), new_toy_vector("World", "Bar"))
print(z)
#> <toy_vector[2]>
#> Hello is Foo
#> World is Bar

# Subsetting doesn't work properly
z[2]
#> <toy_vector[1]>
#> World is Foo
#> World is Bar

Created on 2021-01-18 by the reprex package (v0.3.0)



来源:https://stackoverflow.com/questions/61740647/how-do-i-build-an-object-with-the-r-vctrs-package-that-can-combine-with-c

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!