Trouble with scale_shape_manual()

1,396 views
Skip to first unread message

Robert Latest

unread,
Oct 29, 2012, 5:08:41 AM10/29/12
to ggp...@googlegroups.com
Hello all,

I'm trying to manually manipulate a shape scale using a "palette"
function. The documentation says:

discrete_scale {ggplot2}

palette
a palette function that when called with a single integer argument
(the number of levels in the scale) returns the values that they
should take

so I built a primitive function that IMO does just that: return a
numeric vector (starting at 21, see definition of p.test below).

When run the script below, I get an error message that fails to tell
me anything: "Error in as.character(x$label) : cannot coerce type
'closure' to vector of type 'character'"

What's going on here?
Thanks, robert

library(ggplot2)

df <- data.frame(x=1:4, y=1:4, s=factor(1:4))
plt <- ggplot(df, aes(x=x, y=y, shape=s)) + geom_point()

# print(plt) # works

p.test <- function(n) { 21:(20+n) }
print(plt + scale_shape_manual(palette=p.test)) # gives this error:

# Error in as.character(x$label) :
# cannot coerce type 'closure' to vector of type 'character'

Brandon Hurr

unread,
Oct 29, 2012, 6:23:04 AM10/29/12
to Robert Latest, ggp...@googlegroups.com
You appear to have two problems. 

You are not giving p.test() an 'n'. 
> p.test
function(n) { 21:(20+n) }
> p.test(4)
[1] 21 22 23 24

And instead of using palette, use values. Then it all works. 

p.test <- function(n) { 21:(20+n) }
print(plt + scale_shape_manual(values=p.test(4)))

Robert Latest

unread,
Oct 29, 2012, 10:00:10 AM10/29/12
to ggp...@googlegroups.com
On Mon, Oct 29, 2012 at 11:23 AM, Brandon Hurr <brando...@gmail.com> wrote:
> You appear to have two problems.
>
> You are not giving p.test() an 'n'.

I'm not supposed to. "palette" takes a function as argument.

> And instead of using palette, use values. Then it all works.

Hm... yes it does, but then, what's the purpose of the "palette"
function I wonder...

Thanks, anyway!
robert

Brandon Hurr

unread,
Oct 29, 2012, 10:27:19 AM10/29/12
to Robert Latest, ggp...@googlegroups.com
Robert,

Sorry, I failed to read your request thoroughly. 

I've never messed with custom palettes like this before, but it looks as though there is no reason this doesn't work. 

The code in question is in the scales package. 

At the very bottom.

map_discrete <- function(palette, x, limits, na.value = NA) {
  n <- length(limits)
  pal <- palette(n)[match(as.character(x), limits)]
  ifelse(!is.na(x), pal, na.value)
}

limits is levels(factor(as.character(df$s))), which is:
> p.test(4)[match(as.character(df$s), levels(factor(as.character(df$s))))]
[1] 21 22 23 24

I don't follow where the error is hitting so I'm not sure how to help. Perhaps someone else will know more or know where to file a bug if it is one. 

B


--
You received this message because you are subscribed to the ggplot2 mailing list.
Please provide a reproducible example: https://github.com/hadley/devtools/wiki/Reproducibility

To post: email ggp...@googlegroups.com
To unsubscribe: email ggplot2+u...@googlegroups.com
More options: http://groups.google.com/group/ggplot2

Brian Diggs

unread,
Oct 29, 2012, 6:04:22 PM10/29/12
to Robert Latest, ggplot2
[As an aside, only part of this is directed at you, Robert; much of it
is meant for the developers, so don't worry if it gets obscure and
technical. I've summarized bits throughout and at the end.]

The manual scales do not have a palette argument. From the
scale_shape_manual page, ... is said to be able to take "common discrete
scale parameters: name, breaks, labels, na.value, limits and guide."
That is an inclusive list, not an example list. In particular, palette
is not a argument that (is meant to be) passed through to a discrete
scale. Now here is where it gets tricky.

By passing a palette, you do override the palette that the manual scale
internally creates from the values argument (which you don't provide).
Because of the argument matching based on named arguments and positional
matching, your passed palette becomes the one used and the internally
determined palette gets kicked to the next argument which happens to be
the name of the guide. (Which explains why when I was debugging, the
error was coming from a text grob.) In particular note:

> dput(scale_shape_manual(palette=p.test))
structure(list(call = discrete_scale(aesthetics = aesthetic,
scale_name = "manual", palette = ..1, name = pal), aesthetics =
"shape",
scale_name = "manual", palette = function (n)
{
21:(20 + n)
}, range = <S4 object of class structure("DiscreteRange", package =
"scales")>,
limits = NULL, na.value = NA, expand = structure(list(), class =
"waiver"),
name = function (n)
{
if (n > length(values)) {
stop("Insufficient values in manual scale. ", n,
" needed but only ", length(values), " provided.",
call. = FALSE)
}
values
}, breaks = structure(list(), class = "waiver"), labels =
structure(list(), class = "waiver"),
legend = NULL, drop = TRUE, guide = "legend"), .Names = c("call",
"aesthetics", "scale_name", "palette", "range", "limits", "na.value",
"expand", "name", "breaks", "labels", "legend", "drop", "guide"
), class = c("manual", "discrete", "scale"))

So, part 1, you should not give a palette argument to a scale_*_manual
function, but ggplot is not doing a good job of protecting its arguments
when you do.

The code you should have used is

plt + scale_shape_discrete(palette=p.test)

... except that doesn't work either do to a similar bug in a different
function. scale_shape_discrete also defines its own palette and passes
that along positionally and thus kicks some other function into the name
which gives the same (effective) error as it tries to write the name of
the legend which is a function instead of a string.

So, part 2, if you do specify it as you are supposed to, it still won't
work because of a bug.

Apparently, despite this palette functionality being there for awhile,
no one has actually attempted to use it before to realize it was broken.

So follow Brandon's advice and specify values instead. At least until
this functionality is working.

--
Brian S. Diggs, PhD
Senior Research Associate, Department of Surgery
Oregon Health & Science University

Brian Diggs

unread,
Oct 29, 2012, 6:39:32 PM10/29/12
to Robert Latest, ggplot2
Actually, looking at the documentation for scale_shape, it is not stated
that it can take a palette argument either. Apparently you can only
specify palette if you create your own scale.

plt + discrete_scale("shape", "shape", palette=p.test)
Reply all
Reply to author
Forward
0 new messages