16

Let's say I have a vector where I've set a few attributes:

vec <- sample(50:100,1000, replace=TRUE)
attr(vec, "someattr") <- "Hello World"

When I subset the vector, the attributes are dropped. For example:

tmp.vec <- vec[which(vec > 80)]
attributes(tmp.vec) # Now NULL

Is there a way to, subset and persist attributes without having to save them to another temporary object?

Bonus: Where would one find documentation of this behaviour?

smci
  • 32,567
  • 20
  • 113
  • 146
Brandon Bertelsen
  • 43,807
  • 34
  • 160
  • 255

2 Answers2

18

I would write a method for [ or subset() (depending on how you are subsetting) and arrange for that to preserve the attributes. That would need a "class" attribute also adding to your vector so that dispatch occurs.

vec <- 1:10
attr(vec, "someattr") <- "Hello World"
class(vec) <- "foo"

At this point, subsetting removes attributes:

> vec[1:5]
[1] 1 2 3 4 5

If we add a method [.foo we can preserve the attributes:

`[.foo` <- function(x, i, ...) {
    attrs <- attributes(x)
    out <- unclass(x)
    out <- out[i]
    attributes(out) <- attrs
    out
}

Now the desired behaviour is preserved

> vec[1:5]
[1] 1 2 3 4 5
attr(,"someattr")
[1] "Hello World"
attr(,"class")
[1] "foo"

And the answer to the bonus question:

From ?"[" in the details section:

Subsetting (except by an empty index) will drop all attributes except names, dim and dimnames.

Andrie
  • 176,377
  • 47
  • 447
  • 496
Gavin Simpson
  • 170,508
  • 25
  • 396
  • 453
  • Thx for the answer! btw for subsetting lists one may include before transferring attributes to out, `if(!is.null(attrs$names)) attrs$names = names(x)[i] ` to subset list names also. Otherwise it will likely cause an error. – Soren Havelund Welling Aug 05 '17 at 21:16
  • There's now a package [sticky](https://github.com/decisionpatterns/sticky) which does this behind the scenes, though I'm unsure how active the repo is and there are some limitations. – maxheld Aug 08 '18 at 12:24
  • @Hadley Wickham also has [a section](https://adv-r.hadley.nz/s3.html#methods) explaining how this is done in more detail, as well as a helper generic `sloop::reconstruct()` from the yet-unreleased [sloop](https://github.com/hadley/sloop) package. – maxheld Aug 08 '18 at 12:27
  • and lastly, I just kicked off a discussion on how to best do this in general over at [community.rstudio.com](https://community.rstudio.com/t/how-can-i-retain-attributes-from-an-s3-class-on-subsetting-and-should-i) – maxheld Aug 08 '18 at 12:27
0

Thanks to a similar answer to my question @G. Grothendieck, you can use collapse::fsubset see here.

library(collapse)
#tmp_vec <- fsubset(vec, vec > 80)
tmp_vec <- sbt(vec, vec > 80)     # Shortcut for fsubset
attributes(tmp_vec) 
# $someattr
# [1] "Hello World" 
user63230
  • 4,095
  • 21
  • 43