5

I've been baffled by this a few times already, so here's a made-up question for others who might stumble upon the same problem.

Consider this grid unit vector,

a = unit(1:3, c("cm", "in", "npc"))

I want to replace some elements with new values. The natural approach would be,

a[1] = unit(2,"pt")
a
# [1] 2cm  2in  3npc

Something went wrong: only the numeric value was changed, not the unit. Why? What to do?

Edit: As pointed out in one answer below, such units are just numeric vectors with attributes. However, their offsprings unit.arithmetic and unit.list should also be considered solution to be fully general (e.g to use in adjusting panel sizes of ggplot objects). Consider this unit vector,

(b = a + unit(1, "npc"))
# [1] 1cm+1npc  2in+1npc  3npc+1npc
# [1] "unit.arithmetic" "unit"   

Now replacing a specific element is more tricky, since they're not atomic anymore.

baptiste
  • 75,767
  • 19
  • 198
  • 294

3 Answers3

3

Following a discussion with Paul Murrell (and, amusingly, re-inventing what I'd figured out before), the problem lies in the absence of a [<- method for grid units. The long-term fix would be to implement those methods, but it's not trivial since grid units come with siblings such as unit.arithmetic and unit.list, and their interaction can become hard to comprehend.

The easier, user-oriented fix, is to convert such unit vectors to unit.list objects, which will inherit an accessor method more like regular R lists. This promotion to unit.list object can be done with the unexported function grid:::unit.list().

a = unit(1:3, c("cm", "in", "npc"))
b = grid:::unit.list(a)
is.list(b) # check that indeed this is a list object, thanks @Josh O'Brien
# [1] TRUE
# so now we can use standard list methods
b[[1]] = unit(2,"pt")
b
#[1] 2pt  2in  3npc
Community
  • 1
  • 1
baptiste
  • 75,767
  • 19
  • 198
  • 294
  • please explain to me why the `[<-` primitive now does the right thing: I [can't find a method for unit.list either :S](https://github.com/wch/r-source/blob/b156e3a711967f58131e23c1b1dc1ea90e2f0c43/src/library/grid/R/unit.R) – baptiste Dec 02 '15 at 00:26
  • It doesn't need a special method. That's just the default behavior `[<-` when applied to a list (and a `unit.list` is a list). – Josh O'Brien Dec 02 '15 at 00:31
  • but why is it a list? (or how do you see that it is one). I guess it's something to do with is.atomic (?) – baptiste Dec 02 '15 at 00:32
  • Well, that's what `is.list(b)` and `str(b)` would seem to indicate, isn't it? Also try something like `b[1] <- list(mtcars)`, which works just like it would for any list. – Josh O'Brien Dec 02 '15 at 00:34
  • oops, thanks! I was looking at `is(b)` and `class(b)` and was a bit perplexed – baptiste Dec 02 '15 at 00:37
3

grid now has a [<-.unit method.

baptiste
  • 75,767
  • 19
  • 198
  • 294
2

Looks like a is just an atomic vector with some attributes attached to it. So, when you use a[1] = unit(2,"pt") , the new unit function creates another atomic vector of length one which replaces the value of a[1]. The attributes stay untouched.

So, something like this seems to be working:

a[1] <- 2
attr(a, 'unit')[1] <- 'pt'

> a
[1] 2pt  2in  3npc
LyzandeR
  • 37,047
  • 12
  • 77
  • 87
  • I was in the middle of writing an answer, but got stuck at one point. Your approach is correct, but I fear not entirely general since unit vectors can also contain more complex elements, such as unit.arithmetic: `b = a + unit(1, "npc")`. I'll edit the question. – baptiste Dec 02 '15 at 00:23
  • I can see where it can go wrong with `unit.arithmetics`. If you say that the `[<-` method will be difficult to implement I have no reason to not believe you. `unit.list` seems like a good enough solution tbh. – LyzandeR Dec 02 '15 at 00:29
  • 1
    to be fair, I'm just summarising a discussion I had with Paul Murrell, he seems to think this will be difficult. In the meantime, I figured I could post a workaround here. – baptiste Dec 02 '15 at 00:35