У меня есть решение, которое может помочь. Мне не удалось получить данные, которыми вы поделились, я создал свой собственный фиктивный набор данных следующим образом:
set.seed(12345)
library(lubridate)
df <- data.frame(
dates=as.Date('2020-03-01')+days(0:9),
y_vals=rnorm(10, 50,7),
n=100
)
Сначала базовый сюжет c:
library(scales)
library(ggrepel)
p.basic <- ggplot(df, aes(dates, y_vals)) +
geom_line() +
geom_point(size=2.5, shape=15) +
geom_text_repel(
aes(label=paste0(round(y_vals, 1), '%')),
size=3, direction='y', force=7) +
ylim(0,100) +
scale_x_date(breaks=date_breaks('day'), labels=date_format('%b %d')) +
theme_bw()
Обратите внимание, что мой код немного отличается от вашего. Текстовые метки отбрасываются с помощью пакета ggrepel
. Я также использую некоторые функции из scales
для исправления и установки форматирования оси даты (обратите внимание также, что lubridate
- это пакет, используемый для создания дат в наборе фиктивных данных выше). В противном случае, довольно стандартные ggplot
вещи там.
Для текста вне оси, лучший способ сделать это - через пользовательскую аннотацию, где вы должны настроить grob. Подход здесь следующий:
Переместите ось «вниз», чтобы освободить место для дополнительного текста. Мы делаем это путем установки поля в верхней части заголовка оси.
Отключение отсечения с помощью coord_cartesian(clip='off')
. Это необходимо для того, чтобы увидеть аннотации за пределами графика, позволяя рисовать объекты вне области графика.
L oop через значения df$n
, чтобы создать отдельный annotation_custom
объект, добавленный на график через for
l oop.
Вот код:
p <- p.basic +
theme(axis.title.x = element_text(margin=margin(50,0,0,0))) +
coord_cartesian(clip='off')
for (i in 1:length(df$n)) {
p <- p + annotation_custom(
textGrob(
label=paste0('n=',df$n[i]), rot=90, gp=gpar(fontsize=9)),
xmin=df$dates[i], xmax=df$dates[i], ymin=-25, ymax=-15
)
}
p
Дополнительные параметры для большего удовольствия
Еще две вещи, которые нужно добавить: аннотации (например, выноски для заданных c точек + текст) и линии под графиком между метка оси.
Для линий ниже оси: Вы можете довольно просто добавить breaks=
к другим осям с помощью scale_...
и параметра breaks=
; однако для оси даты это ... сложно. Вот почему мы просто добавим строки, используя тот же метод, что и выше, для текста под осью. Идея здесь состоит в том, чтобы разбить ось на sub.div
сегментов в приведенном ниже коде, который основан на том, сколько дискретных значений находится на вашей оси x. Я мог бы сделать это in-line несколько раз ... но сначала интересно создать переменную:
sub.div <- 1/length(df$n)
Затем я использую это для создания линий, аннотируя отдельные строки вдоль шага sub.div*i
снова используя for
l oop:
for (i in 1:(length(df$n)-1)) {
p <- p + annotation_custom(
linesGrob(
x=unit(c(sub.div*i,sub.div*i), 'npc'),
y=unit(c(0,-0.2), 'npc') # length of the line below axis
)
)
}
Я понимаю, что у меня нет линий на заканчивается здесь, но вы, вероятно, можете увидеть, как было бы легко добавить это, изменив метод выше.
Аннотации (со стрелками, почему бы и нет?): Есть много способов сделать аннотации. Некоторые из них описаны здесь с использованием функции annotate()
. Как и здесь . Вы можете использовать annotate()
, если у вас sh, но в этом примере я просто собираюсь использовать geom_label
для текстовых меток и geom_curve
для создания пышных стрелок.
Вы можете вручную передать отдельные значения aes()
через вызов обеих функций для каждой аннотации. Например, geom_text(aes(x=as.Date('2020-03-01'), y=55,...
, но если у вас есть несколько в вашем наборе данных, было бы целесообразно установить аннотации на основе информации в самом кадре данных. Сначала я сделаю это здесь, где мы отметим две точки:
df$notes <- c('','','','Wow!','','','OMG!!!','','','')
Вы можете использовать значение df$notes
, чтобы указать, какая из точек помечается, а также воспользоваться преимуществами отображение значений x
и y
в одном и том же наборе данных.
Затем вам просто нужно добавить два geom к вашему графику, изменив их по мере того, как вы sh подгоняетесь под свою эстетику.
p <- p + geom_curve(
data=df[which(df$notes!=''),],
mapping=aes(x=dates+0.5, xend=dates, y=y_vals+20, yend=y_vals+2),
color='red', curvature = 0.5,
arrow=arrow(length=unit(5,'pt'))
) +
geom_label(
data=df[which(df$notes!=''),],
aes(y=y_vals+20, label=notes),
size=4, color='red', hjust=0
)
Последнее: горизонтальные линии Последнее, что я заметил в вашем коде ранее, но забыл указать, это чтобы сделать горизонтальные линии, просто используйте geom_hline
. Это намного проще. Кроме того, вы можете сделать это за два вызова geom_hline
довольно легко (и даже всего за один вызов, если вы хотите передать фрейм данных в функцию):
p <- p + geom_hline(yintercept = 50, size=2, color='gray30') +
geom_hline(yintercept = c(25,75), linetype=2, color='gray30')
Просто обратите внимание, что желательно добавить эти два geom_hline
звонка до geom_line
или geom_point
в исходном сюжете p.basic
, поэтому они отстают от всего остального.