Ggplot: Как объединить два поля легенды и придать ключам легенды любой желаемый порядок?

Я пытаюсь построить график с несколькими сложенными столбиками и точкой (соответствующей их сумме). Однако у меня возникли проблемы с размещением ключей легенды в нужных мне позициях. В частности у меня два вопроса:

  1. Как сделать так, чтобы легенда для точек находилась в том же поле, что и легенда для столбцов?
  2. Как изменить порядок клавиш легенд для полосок в желаемом порядке (не в алфавитном порядке)?

Вот воспроизводимый пример:

plot_data <- structure(list(unit = c("AA", "AA", "AA", "AA", "AA", "AA", "AA"
), group = c("A", "B", "C", "D", "E", "F", "G"), varZ = c(0.315095655620098, 
8.47993358969688, 1.07286661490798, 0.451091277599335, -3.06214834600687, 
0.356725811958313, 2.84848970770836), varA = c(0.0098613808164373, 
0.641465996578336, 0.361015032231808, 0.213144088536501, 0.389915533736348, 
0.0736596005037427, 0.110731973079965), varC = c(0.341579372435808, 
0.330122645944357, 0.45065661072731, 0.092761187441647, 0.368695167452097, 
0.542871625348926, 0.563651240617037), varD = c(-0.00427791306283325, 
0.323448045179248, 0.017125289858086, 0.0177044845186174, 0.0418251685332507, 
0.0367124438518658, 0.0115989629644901), varB = c(-0.101550728082657, 
1.68335139006376, 0.36021358249709, -0.529840487614274, -0.199755599349737, 
0.260609032586217, 0.358311578631401), varE = c(0.0694835543632507, 
5.50154550075531, -0.116143900901079, 0.657321977615356, -3.66282860934734, 
-0.5571269325912, 1.80419590175152)), row.names = c(NA, -7L), class = c("data.table", 
"data.frame"))

## Split in two melted data tables. One with data for the stacked bars, one for the dots
bar_data <- plot_data[][, list(unit, group, varA, varB, varC, varD, varE)]
bar_data <- melt(bar_data, id.vars= c("unit", "group"))

line_data <- plot_data[][, list(unit, group, varZ)]
line_data <- melt(line_data, id.vars=c("unit", "group"))

p <-
  ggplot() +
  # Stacked bars
  geom_col( data = bar_data, 
            aes( y = value,
                 x = group,
                 fill = variable
            )
  ) +
  # Point
  geom_point( data = line_data,
              aes( y = value,
                   x = group,
                   colour = "Z"
              )
  ) +
  theme(
    legend.key.height = unit(.15,"cm"),
    legend.key.width = unit(.8,"cm"),
    legend.position = "bottom",
    legend.title = element_blank()
  ) +
  scale_fill_manual(values = c("#005390", "#B6C5DE", "#082A4A", "#7AACF6", "#00B0F0"),
                    labels = c("A", "B", "C", "D", "E")
  ) +
  scale_color_manual(values = c("Z" = "black")) +
  guides(fill = guide_legend(ncol = 2))

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
73
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Просто измените colour на fill в geom_point и измените shape.

Пара замечаний:

  • Я добавил show.legend = FALSE в geom_point, чтобы не показывать точки на каждой метке легенды. Если вы попытаетесь удалить его, вы поймете, что я имею в виду.
  • Я отредактировал scale_fill_manual, добавив Z и 'black'.

Так:

ggplot() +
  # Stacked bars
  geom_col( data = bar_data, 
            aes( y = value,
                 x = group,
                 fill = variable
            )
  ) +
  # Point
  geom_point( data = line_data,
              aes( y = value,
                   x = group,
                   fill = "Z"
              ), shape = 20, size = 3, show.legend = FALSE
  ) +
  theme(
    legend.key.height = unit(.15,"cm"),
    legend.key.width = unit(.8,"cm"),
    legend.position = "bottom",
    legend.title = element_blank()
  ) +
  scale_fill_manual(values = c("#005390", "#B6C5DE", "#082A4A", "#7AACF6", "#00B0F0", "black"),
                    labels = c("A", "B", "C", "D", "E", "Z")
  ) +
  guides(fill = guide_legend(ncol = 2))


Обновлено: основываясь на принятом решении, я оставлю здесь очень похожий код, но только на основе data.table. Кроме того, я написал другой ввод guide_legend, поскольку тот, который указан в принятом решении, не работал на моей машине.

library(data.table)
library(ggplot2)

data <- melt(plot_data, id.vars = c("unit", "group"))

ggplot() +
  # Stacked bars
  geom_col( 
    data = data[variable != "varZ"], 
    aes(y = value,
        x = group,
        fill = variable)
  ) +
  # Point
  geom_point(
    data = data[variable == "varZ"],
    aes(y = value,
        x = group,
        colour = variable)
  ) +
  theme(
    legend.key.height = unit(.15,"cm"),
    legend.key.width = unit(.8,"cm"),
    legend.position = "bottom",
    legend.title = element_blank()
  ) +
  scale_fill_manual(
    values = c("#005390", "#B6C5DE", "#082A4A", "#7AACF6", "#00B0F0", "black"),
    labels = c("A", "B", "C", "D", "E", "Z"),
    aesthetics = c("colour", "fill"),
    guide = guide_legend(ncol = 2, byrow = TRUE)
  )

Большое спасибо! Однако у меня все еще есть три проблемы: 1) с вашим кодом Z теперь появляется вместе с другими клавишами, но с белым (невидимым) полем, а не с черным, как на вашем рисунке. 2) Есть ли способ показать Z точкой, как на графике, а не столбиком? 3) Почему легенда расположена вертикально, а не горизонтально? (т.е. B должен располагаться справа от A, а не под ним и т. д.)

gitcanzo 24.07.2024 11:19

Я не понимаю первый вопрос. Вы скопировали мой код? конкретно часть Scale_fill_manual? Обратите внимание, что я удаляю файл Scale_colour_manual. Вторая проблема, я не уверен, разрешима. Мне нужно проверить. Для третьего я оставил заказ, как вы на своей картинке. Какой заказ вы хотите?

Edo 24.07.2024 12:36

Я точно скопировал ваши данные, но Z отображается белым прямоугольником вместо черного. Говоря о порядке, я имею в виду, что он должен идти слева направо, а не сверху вниз.

gitcanzo 24.07.2024 12:38

Чтобы переупорядочить элементы легенды, проще просто изменить порядок факторов, прежде чем создавать график. В вашем примере вы можете сделать это с помощью простой строки кода, например:

bar_data$variable <-factor(bar_data$variable,levels=c('varE', 'varC', 'varA','varB', 'varD'))

С точки зрения объединения точки и цветов для вашего графика, это может быть сложно при использовании двух типов переменных (но, как правило, это можно обойти). Если Z представляет собой сумму обработок (и, следовательно, отличается от переменных A-E), у меня возникнет соблазн вообще не включать ее в легенду (поскольку она технически не эквивалентна другим буквам), а вместо этого объяснить, что означают черные точки. подпись под рисунком.

Если я ошибаюсь, и Z эквивалентен другим буквам, то, возможно, рассмотрите возможность добавления его в тот же фрейм данных bar_data и построения его единым способом.

Вы правы (Z — сумма других переменных). То, что вы предлагаете, может быть вариантом. Тем не менее, меня все равно очень заинтересовало бы хитрое решение — добавить его в то же поле легенды, что и столбцы. Как это можно сделать?

gitcanzo 24.07.2024 10:58
Ответ принят как подходящий

Вы можете легко добиться желаемого результата, работая с одним набором данных, включающим все категории, и преобразуя variable в factor. Затем используйте отфильтрованный набор данных для каждого из geom, сопоставьте variable с color aes также в geom_point и, наконец, примените один и тот же масштаб как к color, так и к fill aes, чего можно добиться с помощью аргумента aesthetics=.

Для порядка категорий в легенде. Чтобы упорядочить категории по строкам, вы можете установить legend.byrow=TRUE, а для достижения любого желаемого порядка вы можете использовать аргумент limits. В последнем случае (но я бы предложил это в целом) используйте именованный вектор цветов и меток, чтобы они были назначены правильным категориям при изменении ограничений.

library(ggplot2)
library(tidyr)
library(dplyr, warn = FALSE)

plot_data <- plot_data |>
  tidyr::pivot_longer(!c(unit, group), names_to = "variable") |>
  dplyr::mutate(variable = factor(variable))

pal_color <- c("#005390", "#B6C5DE", "#082A4A", "#7AACF6", "#00B0F0", "black")
labels_color <- c("A", "B", "C", "D", "E", "Z")

names(pal_color) <- names(labels_color) <- levels(plot_data$variable)

p <- ggplot(mapping = aes(
  y = value,
  x = group
)) +
  geom_col(
    data = dplyr::filter(plot_data, variable != "varZ"),
    aes(
      fill = variable
    )
  ) +
  geom_point(
    data = dplyr::filter(plot_data, variable == "varZ"),
    aes(
      color = variable
    )
  ) +
  theme(
    legend.key.height = unit(.15, "cm"),
    legend.key.width = unit(.8, "cm"),
    legend.position = "bottom",
    legend.title = element_blank()
  ) 

p +
  scale_fill_manual(
    values = pal_color,
    labels = labels_color,
    aesthetics = c("color", "fill"),
    guide = guide_legend(ncol = 2, theme = theme(legend.byrow = TRUE))
  )


p +
  scale_fill_manual(
    values = pal_color,
    labels = labels_color,
    aesthetics = c("color", "fill"),
    guide = guide_legend(ncol = 2),
    limits = c("varD", "varE", "varZ", "varA", "varB", "varC")
  )

Большое спасибо! Это работает очень хорошо, за исключением: как мне убедиться в том, что в строке labels_color <- c("A", "B", "C", "D", "E", "Z") метка «A» соответствует «varA», «B» соответствует «varB» и т. д.? Я понимаю, что это зависит только от того, как сортируются данные, поэтому в зависимости от того, как они сортируются, метка «A» может быть присвоена другой переменной, чем «varA».

gitcanzo 24.07.2024 14:55

@stefan, тебе не нужна вся эта часть с tidyverse. Просто измените форму plot_data с помощью data <- melt(plot_data, id.vars=c("unit", "group")) в начале. Затем накормите geom_col с помощью data[variable != "varZ"] и geom_point с помощью data[variable == "varZ"]. Это делает раствор чище и облегчает его повторное использование. (проголосовал, кстати!)

Edo 24.07.2024 15:05

отвечая на мой собственный вопрос: чтобы убедиться, что маркировка соответствует правильным переменным, я объявляю уровни факторов, предложенные Эллиотом, а затем гарантирую использование того же порядка в маркировке, т. е. plot_data[, variable := factor(variable, levels=c("A", "B", "C", "D", "E", "Z"))], а затем labels_color <- c("A", "B", "C", "D", "E", "Z")

gitcanzo 24.07.2024 15:39

Базовое решение R:

cols <- c("#005390", "#B6C5DE", "#082A4A", "#7AACF6", "#00B0F0")

plot_data.neg <- plot_data.pos <- plot_data[,c(2,4:8)]
plot_data.pos[,2:6][plot_data[,4:8] <0] <- 0
plot_data.neg[,2:6][plot_data[,4:8]>=0] <- 0

ylim <- c(min(colSums(plot_data.neg[,2:6])) * 1.15, 
          max(colSums(plot_data.pos[,2:6])) * 1.15)

par(mar=c(3,2,1,1), las=1)
bp <- barplot(cbind(varE, varD, varC, varB, varA)~group, 
              data=plot_data.pos,
              ylim=ylim,
              legend.text=c("Z", LETTERS[5:1]),
              args.legend=list(x = "topright", nc=2, box.lty=0,
                               fill=c(cols, NA),
                               border=NA, 
                               pch=c(rep(NA, 5), 21),
                               pt.bg = "red", col = "black"),
              col=rev(cols),
              axis.lty = 1
)

barplot(cbind(varE, varD, varC, varB, varA)~group, 
        data=plot_data.neg,
        ylim=rev(ylim),
        col=rev(cols),
        add=TRUE
)

points(bp, plot_data$varZ, pch=21, bg = "red", col = "black")

(у вас потрясающее понимание графиков базы R! поздравляю!) однако что-то выглядит не так: столбцы A D E должны быть частично положительными и отрицательными, но они стоят только с одной стороны. Действительно, Z, которая является суммой, не находится сверху A и D или внизу E.

Edo 24.07.2024 14:47

@Edo Может быть, я сделал это очень быстро. Я еще раз проверю, когда будет больше времени…

Edward 24.07.2024 15:56

Другие вопросы по теме