问题
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