Пользовательская дискретная цветовая шкала в ggplot не соблюдает порядок - PullRequest
2 голосов
/ 08 мая 2020

Контекст

Я пытаюсь создать настраиваемую цветовую шкалу, которая будет вызываться, используя что-то вроде scale_fill_perso в ggplot. Я выполнил шаги, описанные в этой красивой записи блога . Моя дискретная шкала имеет 7 уровней.

Мне удалось правильно выставить шкалу (см. Ниже). При использовании графика с 7 уровнями я получаю ожидаемые цвета. Однако, когда я использую не так много цветов, я хотел бы, чтобы R соблюдал порядок моей палитры и не интерполировал между значениями (см. Пример). Например, если у меня 3 цвета, я бы хотел, чтобы R использовал первые три значения моего цветового вектора.

Я думаю, что это происходит от my_pal, который сам использует grDevices::colorRampPalette, который при использовании количество классов, меньшее, чем размер цветового вектора, обрезает цветной вектор, используя крайности, а не порядок.

Итак, мой вопрос: Есть ли способ зафиксировать количество классов и, если number classes < length(color vector) не использовать colorRampPalette интерполяцию?

Текущая реализация

Шаги следуют вышеупомянутому сообщению в блоге .

Сначала создайте цветной вектор и способ его назвать:

mycolors <- c(
`red` = "#E2447A",
`green` = "#BCE550",
`blue` = "#708DD3", 
`grey` = "#666666",
`orange` = "#FFBAA8",
`violet` = "#D1A3FF",
`lightgrey` = "#B2B2B2"
)

my_cols <- function(...) {

  cols <- c(...)

  if (is.null(cols))
    return (mycolors)

  mycolors[cols]
}

call_palettes <- function(palette = "main"){
  if (palette == "main"){ return(my_cols()) }
}

На данный момент есть только одна палитра, но это может измениться. Затем создайте функцию палитры, которая интерполирует значения (насколько я понял):

my_pal <- function(palette = "main", reverse = FALSE, ...) {

  args <- list(...)
  #return(args)

  pal <- call_palettes(palette, ...)

  if (reverse) pal <- rev(pal)

  grDevices::colorRampPalette(pal, ...)
}

Затем создайте функцию scale_fill_perso для использования этой палитры.

scale_fill_perso <- function(palette = "main", discrete = TRUE, reverse = FALSE, ...) {
  pal <- my_pal(palette = palette, reverse = reverse)

  if (discrete) {
    ggplot2::discrete_scale("fill", paste0("my_pal_", palette), palette = pal, ...)
  } else {
    ggplot2::scale_fill_gradientn(colours = pal(256), ...)
  }
}

Вывод

При использовании 7 классов проблем нет:

iris$random <- sample(1:7, nrow(iris), replace = TRUE)

ggplot2::ggplot(iris) +
  ggplot2::geom_histogram(ggplot2::aes(x = Sepal.Width, y = ..density..,
                                       fill = factor(random))) +
  scale_fill_perso(palette = "main")

enter image description here

Однако, когда используя меньшее количество цветов, я хотел бы использовать первые три цвета моего вектора (красный-зеленый-синий), что на данный момент не так

ggplot2::ggplot(iris) +
  ggplot2::geom_histogram(ggplot2::aes(x = Sepal.Width, y = ..density..,
                                       fill = factor(Species))) +
  scale_fill_perso(palette = "main")

enter image description here

Я определил, что это происходит из-за того, что my_pal не принимает порядок вектора как информативный. Например, для двух цветов он берет два крайних значения вектора:

my_pal()(2)
# "#E2447A" "#B2B2B2"
mycolors 
#      red     green      blue      grey    orange    violet lightgrey 
# "#E2447A" "#BCE550" "#708DD3" "#666666" "#FFBAA8" "#D1A3FF" "#B2B2B2" 

, а для трех он добавляет среднее значение:

my_pal()(3)
# "#E2447A" "#666666" "#B2B2B2"
mycolors 
#      red     green      blue      grey    orange    violet lightgrey 
# "#E2447A" "#BCE550" "#708DD3" "#666666" "#FFBAA8" "#D1A3FF" "#B2B2B2" 

Как я могу обеспечить соблюдение векторный порядок, когда классы чисел <цвета числа? </p>

1 Ответ

1 голос
/ 09 мая 2020

Немного покопавшись, я нашел решение, немного изменив функции colorRamp и colorRampPalette.

Идея состоит в том, чтобы зафиксировать количество классов внутри colorRampPalette и использовать его как аргумент цветового вектора среза в функции colorRamp:

colorRamp_d <- function (colors, n,
                         bias = 1,
                         space = c("rgb", "Lab"),
                         interpolate = c("linear",
                                         "spline"),
                         alpha = FALSE){

  # PRELIMINARY STEPS ----------------
  if (bias <= 0)
    stop("'bias' must be positive")
  if (!missing(space) && alpha)
    stop("'alpha' must be false if 'space' is specified")
  colors <- t(col2rgb(colors, alpha = alpha)/255)
  space <- match.arg(space)
  interpolate <- match.arg(interpolate)

  # CUT THE COLOR VECTOR ----------------------

  if (space == "Lab")
    colors <- convertColor(colors, from = "sRGB", to = "Lab")
  interpolate <- switch(interpolate, linear = stats::approxfun,
                        spline = stats::splinefun)

  # RESPECT ORDER IF NCLASSES<NCOLORS
  if (n<nrow(colors)) colors <- colors[1:n,]

  if ((nc <- nrow(colors)) == 1L) {
    colors <- colors[c(1L, 1L), ]
    nc <- 2L
  }
  x <- seq.int(0, 1, length.out = nc)^bias
  palette <- c(interpolate(x, colors[, 1L]), interpolate(x,
                                                         colors[, 2L]), interpolate(x, colors[, 3L]), if (alpha) interpolate(x,
                                                                                                                             colors[, 4L]))
  roundcolor <- function(rgb) pmax(pmin(rgb, 1), 0)
  if (space == "Lab")
    function(x) roundcolor(convertColor(cbind(palette[[1L]](x),
                                              palette[[2L]](x), palette[[3L]](x), if (alpha)
                                                palette[[4L]](x)), from = "Lab", to = "sRGB")) *
    255
  else function(x) roundcolor(cbind(palette[[1L]](x), palette[[2L]](x),
                                    palette[[3L]](x), if (alpha)
                                      palette[[4L]](x))) * 255
}


colorRampPalette_d <- function (colors, ...){
  # n: number of classes
  function(n) {
    ramp <- colorRamp_d(colors, n, ...)
    x <- ramp(seq.int(0, 1, length.out = n))
    if (ncol(x) == 4L)
      rgb(x[, 1L], x[, 2L], x[, 3L], x[, 4L], maxColorValue = 255)
    else rgb(x[, 1L], x[, 2L], x[, 3L], maxColorValue = 255)
  }
}

Единственное отличие от функции grDevices::colorRamp - это аргумент n (количество классов) и срез, представленный этой строкой:

if (n<nrow(colors)) colors <- colors[1:n,]

Наконец, вместо вызова Grdevices::colorRampPalette, я вызываю свой собственный colorRampPalette_d:

my_pal <- function(palette = "main", reverse = FALSE, ...) {

  args <- list(...)
  #return(args)

  pal <- call_palettes(palette, ...)

  if (reverse) pal <- rev(pal)

  colorRampPalette_d(pal, ...)
}

Что дает:

ggplot2::ggplot(iris) +
  ggplot2::geom_histogram(ggplot2::aes(x = Sepal.Width, y = ..density..,
                                       fill = factor(Species))) +
  scale_fill_perso(palette = "main")

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...