Как построить несколько элементов ggplot2 и отображать / отражать метки? - PullRequest
0 голосов
/ 16 июня 2020

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

library(ggplot2)
library(ggrepel)
theme_set(theme_minimal())

chart_df <- structure(list(date = structure(c(18295, 18302, 18309, 18316, 
18323, 18330, 18337, 18344, 18351, 18358, 18365, 18372, 18379, 
18386, 18393, 18400, 18407), class = "Date"), cum_sum_cases = c(0L, 
0L, 0L, 0L, 0L, 0L, 0L, 6L, 71L, 273L, 517L, 929L, 1333L, NA, 
NA, NA, NA), peak = c(NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 
NA, NA, NA, NA, NA, 3745L, NA), peak_label = c(NA, NA, NA, NA, 
NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "Actual peak: 3745", 
NA), forecast_cum_sum_cases = c(NA, NA, NA, NA, NA, NA, NA, NA, 
NA, NA, NA, NA, NA, 1854, 2363, 3528, 4173), forecast_peak = c(NA, 
NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 3528, 
NA), forecast_peak_label = c(NA, NA, NA, NA, NA, NA, NA, NA, 
NA, NA, NA, NA, NA, NA, NA, "Forecasted peak: 3528", NA), true_cum_sum_cases = c(NA, 
NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 2019L, 2624L, 
3745L, 4559L)), row.names = c(NA, -17L), class = "data.frame")

#Plot
ggplot(data = chart_df, aes(x = date)) +
  geom_line(aes(y=cum_sum_cases, color = "Actual cases"), size = 1.5) +
  geom_line(aes(y=true_cum_sum_cases, color = "Ground truth cases"), size = 1.5) +
  geom_line(aes(y=forecast_cum_sum_cases, color = "Forecasted cases"), linetype = "twodash", size = 2) +
  scale_color_manual("", values = c("Actual cases" = "darkred", "Ground truth cases" = "green", "Forecasted cases" = "steelblue")) +
  geom_point(aes(y=peak), color = "darkred", size = 2) +
  geom_label_repel(aes(y= peak, label=peak_label)) +
  geom_point(aes(y=forecast_peak), colour = "steelblue", size = 2) +
  geom_label_repel(aes(y= peak, label=forecast_peak_label)) +
  ylab("Total confirmed cases") +
  xlab("Date")

Однако в этом случае я могу видеть только вторую метку точки даже при использовании пакета ggrepel. Поскольку две отображаемые точки расположены близко друг к другу, мне нужно, чтобы R автоматически разделял их, если это возможно. Кроме того, есть ли способ, чтобы легенда действительно соответствовала аргументу linetype в вызове geom_line()? enter image description here

Ответы [ 2 ]

1 голос
/ 16 июня 2020

@ chemdork123 был быстрее меня! Тем не менее, вот моя версия аккуратного способа, который также соответствует типу линии в легенде (для которого вам нужно, чтобы тип линии определялся переменной).

library(tidyverse)

# tidy data frame for the lines
mydf <-
  chart_df %>%
  pivot_longer(cols = ends_with("_cases"),
              names_to = "mytype",
              values_to = "myval",
              values_drop_na = TRUE) %>%
  mutate(mytype = case_when(mytype == "cum_sum_cases" ~ "Actual cases",
                            mytype == "forecast_cum_sum_cases" ~ "Forecasted cases",
                            mytype == "true_cum_sum_cases" ~ "Ground truth cases"))


# tidy data frame for the labels
label_df <-
  chart_df  %>%
  select(date = date, label1 = peak_label, val1 = peak, 
         label2 = forecast_peak_label, val2 = forecast_peak)  %>%
  pivot_longer(-date,
                names_to = c(".value", NA),
                names_pattern = "(.)(.)",
                values_drop_na = TRUE) %>%
  rename(mylabel = l, peak = v)

# and the plot
mydf   %>%
  ggplot(aes(x = date, y = myval, color = mytype, linetype = mytype)) +
  geom_line() +
  geom_point(data = label_df, aes(x = date, y = peak), inherit.aes = FALSE) +
  geom_label_repel(aes(x = date, y = peak, label = mylabel),
                  data = label_df,
                  inherit.aes = FALSE,
                  force = 1,
                  min.segment.length = 5,
                  point.padding = 1) +
  ylab("Total confirmed cases") +
  xlab("Date")
1 голос
/ 16 июня 2020

Чтобы решить вашу непосредственную проблему, проблема с отсутствием метки для второй точки связана с опечаткой в ​​вашем y= aestheti c, который должен читать y=forecast_peak, а не y=peak. Это решает вашу непосредственную проблему:

p_ptfix <- ggplot(data = chart_df, aes(x = date)) +
  geom_line(aes(y=cum_sum_cases, color = "Actual cases"), size = 1.5) +
  geom_line(aes(y=true_cum_sum_cases, color = "Ground truth cases"), size = 1.5) +
  geom_line(aes(y=forecast_cum_sum_cases, color = "Forecasted cases"), linetype = "twodash", size = 2) +
  scale_color_manual("", values = c("Actual cases" = "darkred", "Ground truth cases" = "green", "Forecasted cases" = "steelblue")) +
  geom_point(aes(y=peak), color = "darkred", size = 2) +
  geom_label_repel(aes(y= peak, label=peak_label)) +
  geom_point(aes(y=forecast_peak), colour = "steelblue", size = 2) +
  geom_label_repel(aes(y= forecast_peak, label=forecast_peak_label)) +
  ylab("Total confirmed cases") +
  xlab("Date")
p_ptfix

enter image description here

Чтобы решить проблему с не отображаемым типом линии, я покажу вам два способа. Один из способов - просто внести изменения в код, который вы используете, а другой - подумать о «аккуратных данных» и сделать ваш код немного более масштабируемым и в соответствии с лучшими практиками построения графиков в tidyverse и всех связанных пакетах анализа данных.

Изменение кода для объединения цвета и типа линии в легенде

Чтобы добавить тип линии в легенду, вы можете использовать тот же метод, который вы использовали для цвета. Просто знайте, что все, что добавлено внутри aes(), кроме элементов позиционирования, таких как x= и y=, по умолчанию используется для создания легенды в ggplot2. ggplot2 также попытается объединить легенды, где это возможно. При изменении таких аспектов легенды, как заголовок и значения, очень важно знать, что для поддержания «связи» между двумя легендами (в данном случае color= и linetype=) ваши изменения в одной легенде должны соответствовать другой. . Итак, если вы измените заголовок одной легенды .. вы должны сделать идентичные изменения для другой, et c.

p_legendfix <- ggplot(data = chart_df, aes(x = date)) +
  geom_line(aes(y=cum_sum_cases, color = "Actual cases", linetype="Actual cases"), size = 1.5) +
  geom_line(aes(y=true_cum_sum_cases, color = "Ground truth cases", linetype="Ground truth cases"), size = 1.5) +
  geom_line(aes(y=forecast_cum_sum_cases, color = "Forecasted cases", linetype="Forecasted cases"), size = 2) +
  scale_color_manual(NULL, values = c("Actual cases" = "darkred", "Ground truth cases" = "green", "Forecasted cases" = "steelblue")) +
  scale_linetype_manual(NULL, values=c("Actual cases" = 1, "Ground truth cases" = 1, "Forecasted cases" = 3)) +
  geom_point(aes(y=peak), color = "darkred", size = 2) +
  geom_label_repel(aes(y= peak, label=peak_label)) +
  geom_point(aes(y=forecast_peak), colour = "steelblue", size = 2) +
  geom_label_repel(aes(y= forecast_peak, label=forecast_peak_label)) +
  ylab("Total confirmed cases") + xlab("Date")
p_legendfix

enter image description here

Tidy Data Way

Хотя ваша установка работает, она слишком сложна и ее довольно сложно масштабировать или включать дополнительные функции. Что, если бы вам нужно было построить 5 линий? А как насчет того, чтобы обозначить 10 точек? Что, если бы вы захотели изменить название одной из строк? Менять код опасно, и гораздо проще, если эта информация может поступать непосредственно из данных. Я настоятельно рекомендую вам прочитать о принципах Tidy Data , чтобы понять, почему и как. Тем не менее, я постараюсь дать вам ответ "Чистые данные", чтобы распространить знания :). Для этого идея состоит в том, чтобы переупорядочить ваш набор данных так, чтобы у нас были следующие столбцы:

  • дата - ваш x= aestheti c
  • случаях - ваш y= aestheti c
  • тип - ваш color= и linetype= aestheti c, который будет указывать тип данных («прогнозируемые», «фактические» или «наземные истины»)
  • peak_df - новый набор данных, содержащий информацию о нанесении пиковых точек и интересующих меток

Я собираюсь много использовать gather() из dplyr, что очень похоже на функцию pivot_longer() из tidyr. Мы рассмотрим здесь столбец за столбцом. Поскольку я собираюсь собирать и использовать имена столбцов, я хочу сначала изменить их, чтобы нам было проще. Здесь мы go:

library(dplyr)
new_df <- chart_df
names(new_df) <- c('date','Actual Cases','peak','peak_label','Forecasted Cases','forecast_peak','forecasted_peak_label','Ground Truth Cases')

new_df <- new_df %>%
  select('date', 'Actual Cases', 'Forecasted Cases', 'Ground Truth Cases') %>%
  gather(key='type', value='cases', -date)

Этот фрагмент кода теперь исправляет это, так что у нас есть new_df все данные, необходимые для построения наших линий. Для peak_df ему также нужны те же столбцы, и мы закодируем фактический текст метки в вызове графика. Я вручную создаю столбец peak_df$type ниже, потому что это проще всего с двумя значениями.

# pull the one key observation
peak_df <- chart_df %>%
  dplyr::filter(peak!='NA') %>%  #pull the one observation
  select(date, peak, peak_label, forecast_peak, forecast_peak_label) %>%
  gather(key='lab_type', value='cases', -c(peak_label, forecast_peak_label, date))

# it gets me the two lines for peak_df$date and peak_df$cases
# manually entering in peak_df$type now
peak_df$type <- c('Actual Cases', 'Forecasted Cases')

Для сюжета теперь намного проще комбинировать легенды и контролировать построение.

p1 <- ggplot(new_df, aes(x=date, y=cases)) +
  geom_line(aes(color=type, linetype=type), size=1.5) +
  geom_point(data=peak_df, aes(color=type), size=3, show.legend = FALSE) +
  geom_text_repel(data=peak_df, aes(label=paste0(type, ":",cases))) +
  scale_color_discrete(name=NULL) +
  scale_linetype_manual(name=NULL, values=c(1,2,1))
p1

enter image description here

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