Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
640 views
in Technique[技术] by (71.8m points)

r - How to implement a 2-d (hue x luminance) color scale?

Here's a toy data.frame that illustrates the problem (the most basic version thereof, that is; there will be an additional wrinkle later on):

df <- read.table(textConnection(
"toxin  dose    x   y
A   1   0.851   0.312
A   10  0.268   0.443
A   100 0.272   0.648
B   1   0.981   0.015
B   10  0.304   0.658
B   100 0.704   0.821
C   1   0.330   0.265
C   10  0.803   0.167
C   100 0.433   0.003
D   1   0.154   0.611
D   10  0.769   0.616
D   100 0.643   0.541
"), header = TRUE)

I want to make a scatterplot of these data in which the toxin is indicated by the hue of the points, and the dose is indicated by their luminance (to a first approximation, a low dose should correspond to a high luminance).

The particularly challenging aspect of this visualization problem is that the legend would have to be a 2-dimensional color grid (rather than a 1-dimensional color bar), with the rows corresponding to the toxin variable and the columns corresponding to dose (or a transform thereof).

The extra wrinkle I alluded to above is that the data actually includes one control observation, where the dose is different from all the other ones (note the row with toxin = "Z", below):

df <- read.table(textConnection(
"toxin  dose    x   y
A   1   0.851   0.312
A   10  0.268   0.443
A   100 0.272   0.648
B   1   0.981   0.015
B   10  0.304   0.658
B   100 0.704   0.821
C   1   0.330   0.265
C   10  0.803   0.167
C   100 0.433   0.003
D   1   0.154   0.611
D   10  0.769   0.616
D   100 0.643   0.541
Z   0.001   0.309   0.183
"), header = TRUE)

The point for the control ("Z") toxin should be a single gray dot. (It's OK if the 2-d color grid legend does not include the control value, but in this case there should be at least one legend that identifies its point appropriately.)

In summary, the problem has three parts:

  1. Represent toxin and dose by hue and luminance, respectively.
  2. Make a 2-d color grid legend.
  3. Legends should identify the control point.

Below is what I've managed so far.

The only way I can think of to solve the first aspect of the problem would be to devote a different layer to each toxin, and use a color gradient based on the dose.

Unfortunately, there does not seem to be a way to specify a different gradient scale for each layer.

More specifically, I first define the following:

library(ggplot2)

hues <- RColorBrewer::brewer.pal(4, "Set1")

gradient <- function (hue_index) {
  scale_color_gradient(high = hues[hue_index],
                       low = "white",
                       trans = "log",
                       limits = c(0.1, 100),
                       breaks = c(1, 10, 100))
}

baseplot <- ggplot(mapping = aes(x = x, y = y, color = dose))

The first layer, by itself, looks promising:

(
 baseplot
          + geom_point(data = subset(df, toxin == "A"), size = 4)
          + gradient(1)
)

enter image description here

But when I add the second layer...

(
 baseplot
          + geom_point(data = subset(df, toxin == "A"), size = 4)
          + gradient(1)
          + geom_point(data = subset(df, toxin == "B"), size = 4)
          + gradient(2)
)

...I get the following warning:

Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.

And, sure enough, this is the plot I get:

enter image description here

I have not been able to find a way to define different layers each with its own color scale.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Does it have to be a grid for the legend? If you are willing to have one legend for toxin (color) and a second legend for dose (alpha), you could use this (and set your colors/fills to what makes sense for your data)

df$dose <- factor(df$dose)

ggplot(
  df
  , aes(x = x, y = y
        , col = toxin
        , alpha = dose)
) +
  geom_point(size = 4)

enter image description here

If it really must be a matrix for the legend, you could do make the matrix yourself, then combine them on the plot. You will lose some flexibility, and will need to carefully set things, but this should work in general (note that I am using the minimal theme as it seems best for the legend -- obviously personal preference):

theme_set(theme_minimal())

mainPlot <-
  ggplot(
    df
    , aes(x = x, y = y
          , col = toxin
          , alpha = dose)
  ) +
  geom_point(size = 4)

mainPlot


allLevels <-
  expand.grid(toxin = levels(df$toxin)
              , dose = levels(df$dose))

legendPlot <-
  ggplot(
    allLevels
    , aes(x = toxin, y = dose
          , col = toxin
          , alpha = dose)
  ) +
  geom_point(size = 4)

legendPlot



library(gridExtra)

grid.arrange(
  mainPlot +
    theme(legend.position = "none")
  , legendPlot +
    theme(legend.position = "none") +
    ggtitle("Legend")
  , layout_matrix =
    matrix(c(1,1,1,NA,2,NA)
           , ncol = 2)
  , widths=c(2,1)
  , heights = c(1,2,1)
  )

enter image description here


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...