Как добавить прямые метки к гистограмме в ggplot для числовой оси X

Я пытаюсь создать гистограмму в ggplot, где ширина столбцов связана с переменной Cost$Sum.of.FS_P_Reduction_Kg. Я использую аргумент width=Sum.of.FS_P_Reduction_Kg для установки ширины полос в соответствии с переменной.

Я хочу добавить прямые метки к диаграмме, чтобы пометить каждую полосу, как показано на изображении ниже. Я также пытаюсь добавить метки оси x, соответствующие аргументу width=Sum.of.FS_P_Reduction_Kg.. Любая помощь будет принята с благодарностью. Я знаю о ggrepel, но пока не смог добиться желаемого эффекта.

Пример графика с прямыми метками и числовой осью X

Я использовал следующий код:

# Plot the data 
P1 <- ggplot(Cost,
       aes(x = Row.Labels,
           y = Average.of.Cost_Per_Kg_P_Removal.undiscounted..LOW_Oncost,
           width = Average.of.FS_Annual_P_Reduction_Kg, label = Row.Labels)) +
  geom_col(fill = "grey", colour = "black") + 
  geom_label_repel(
    arrow = arrow(length = unit(0.03, "npc"), type = "closed", ends = "first"),
    force = 10,
    xlim  = NA) +
  facet_grid(~reorder(Row.Labels, 
                      Average.of.Cost_Per_Kg_P_Removal.undiscounted..LOW_Oncost), 
             scales = "free_x", space = "free_x") +
  labs(x = "Measure code and average P reduction (kg/P/yr)",
       y = "Mean annual TOTEX (£/kg) of P removal (thousands)") +
  coord_cartesian(expand = FALSE) +     # remove spacing within each facet
  theme_classic() +
  theme(strip.text = element_blank(),   # hide facet title (since it's same as x label anyway)
        panel.spacing = unit(0, "pt"),  # remove spacing between facets
        plot.margin = unit(c(rep(5.5, 3), 10), "pt"), # more space on left for axis label
        axis.title=element_text(size=14),
        axis.text.y = element_text(size=12),
        axis.text.x = element_text(size=12, angle=45, vjust=0.2, hjust=0.1)) + 
  scale_x_discrete(labels = function(x) str_wrap(x, width = 10))

P1 = P1 + scale_y_continuous(labels = function(x) format(x/1000))
P1

Пример таблицы данных можно воспроизвести с помощью следующего кода:

> dput(Cost)
structure(list(Row.Labels = structure(c(1L, 2L, 6L, 9L, 4L, 3L, 
5L, 7L, 8L), .Label = c("Change the way P is applied", "Improve management of manure", 
"In channel measures to slow flow", "Keep stock away from watercourses", 
"No till trial ", "Reduce runoff from tracks and gateways", "Reversion to different vegetation", 
"Using buffer strips to intercept pollutants", "Water features to intercept pollutants"
), class = "factor"), Average.of.FS_Annual_P_Reduction_Kg = c(0.11, 
1.5425, 1.943, 3.560408144, 1.239230769, 18.49, 0.091238043, 
1.117113762, 0.11033263), Average.of.FS_._Change = c(0.07, 0.975555556, 
1.442, 1.071692763, 1.212307692, 8.82, 0.069972352, 0.545940711, 
0.098636339), Average.of.Cost_Per_Kg_P_Removal.undiscounted..LOW_Oncost = c(2792.929621, 
2550.611429, 964.061346, 9966.056875, 2087.021801, 57.77580744, 
165099.0425, 20682.62962, 97764.80805), Sum.of.Total_._Cost = c(358.33, 
114310.49, 19508.2, 84655, 47154.23, 7072, 21210, 106780.34, 
17757.89), Average.of.STW_Treatment_Cost_BASIC = c(155.1394461, 
155.1394461, 155.1394461, 155.1394461, 155.1394461, 155.1394461, 
155.1394461, 155.1394461, 155.1394461), Average.of.STW_Treatment_Cost_HIGH = c(236.4912345, 
236.4912345, 236.4912345, 236.4912345, 236.4912345, 236.4912345, 
236.4912345, 236.4912345, 236.4912345), Average.of.STW_Treatment_Cost_INTENSIVE = c(1023.192673, 
1023.192673, 1023.192673, 1023.192673, 1023.192673, 1023.192673, 
1023.192673, 1023.192673, 1023.192673)), class = "data.frame", row.names = c(NA, 
-9L))

person Warwick Wainwright    schedule 20.12.2019    source источник
comment
Я не вижу изображение, которым вы поделились. Можете ли вы исправить проблему, чтобы увидеть, что вы хотите.   -  person Suat Atan PhD    schedule 23.12.2019
comment
Теперь загрузили обновленный пример изображения.   -  person Warwick Wainwright    schedule 23.12.2019
comment
В чем логика использования фасетов вместо простого масштабирования ширины столбцов или использования geom_rect?   -  person camille    schedule 25.12.2019
comment
Вы можете разнести полосы (при сохранении их разной ширины), поместив каждую из них в свою собственную грань и установив как масштаб, так и расстояние, чтобы они свободно менялись. Использование Geom_rect было бы альтернативным вариантом.   -  person Warwick Wainwright    schedule 06.01.2020


Ответы (2)


Я думаю, будет проще немного подготовить данные, чтобы вы могли поместить все прямоугольники в один фасет с общей осью X. Например, мы можем вычислить кумулятивную сумму сокращения Kg и использовать ее для определения начального x для каждого ящика.

РЕДАКТИРОВАТЬ - добавлено ylim = c(0, NA), xlim = c(0, NA),, чтобы текст ggrepel::geom_text_repel оставался в положительном диапазоне сюжета.

library(ggplot2)
library(ggrepel)
library(stringr) 
library(dplyr)

Cost %>%
  arrange(desc(Average.of.Cost_Per_Kg_P_Removal.undiscounted..LOW_Oncost)) %>%
  mutate(Row.Labels = forcats::fct_inorder(Row.Labels),
         cuml_reduc = cumsum(Average.of.FS_Annual_P_Reduction_Kg),
         bar_start  = cuml_reduc - Average.of.FS_Annual_P_Reduction_Kg,
         bar_center = cuml_reduc - 0.5*Average.of.FS_Annual_P_Reduction_Kg) %>%
  ggplot(aes(xmin = bar_start, xmax = cuml_reduc,
             ymin = 0, ymax = Average.of.Cost_Per_Kg_P_Removal.undiscounted..LOW_Oncost)) +
  geom_rect(fill = "grey", colour = "black") +
  geom_text_repel(aes(x = bar_center, 
                      y = Average.of.Cost_Per_Kg_P_Removal.undiscounted..LOW_Oncost,
                      label = str_wrap(Row.Labels, 15)), 
                  ylim = c(0, NA), xlim = c(0, NA),  ## EDIT
                  size = 3, nudge_y = 1E4, nudge_x = 2, lineheight = 0.7, 
                  segment.alpha = 0.3) +
  scale_y_continuous(labels = scales::comma) +
  labs(x = "Measure code and average P reduction (kg/P/yr)",
       y = "Mean annual TOTEX (£/kg) of P removal (thousands)")

введите описание изображения здесь

person Jon Spring    schedule 23.12.2019
comment
Это выглядит хорошо, спасибо. Есть ли способ сделать метки лучше, чтобы они не пересекались с осевыми линиями? - person Warwick Wainwright; 24.12.2019
comment
Хорошее предложение — в этом могут помочь параметры ylim и xlim для ggrepel::geom_text_repel. - person Jon Spring; 24.12.2019
comment
Я пытался изменить порядок переменных оси x на основе Average.of.Cost_Per_Kg_P_Removal.undiscounted..LOW_Oncost (от самого низкого до самого высокого). Когда я использую функцию переупорядочивания, похоже, она не работает. Есть идеи, почему? - person Warwick Wainwright; 06.01.2020
comment
Недостаточно информации, чтобы знать. Вы можете посмотреть на forcats::fct_reorder альтернативный синтаксис, который я предпочитаю. - person Jon Spring; 06.01.2020

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

Сначала мы преобразуем первый столбец в имена строк и удаляем его.

cost <- `rownames<-`(Cost[-1], Cost[,1])

Определить ширину в barplot довольно просто, так как у него есть опция width=, где мы вводим логарифмированные значения соответствующей переменной. Для меток столбцов нам нужно рассчитать позиции и использовать text; для переноса строк мы можем использовать strwrap. Метка может быть удобно опущена, если это трудный случай (как № 6 в примере). Наконец, мы используем (без головы) arrows .

# logarithmize values
w <- log(w1 <- cost$Average.of.Cost_Per_Kg_P_Removal.undiscounted..LOW_Oncost)
# define vector labels inside / outside, at best by hand
inside <- as.logical(c(0, 1, 0, 1, 1, 0, 1, 1, 1))
# calculate `x0` values of labels
x0 <- w / 2 + c(0, cumsum(w)[- length(w)])
# define y values o. labels
y0 <- ifelse(inside, colSums(t(cost)) / 2, 1.5e5)
# make labels using 'strwrap' 
labs <- mapply(paste, strwrap(rownames(cost), 15, simplify=F), collapse="\n")
# define nine colors
colores <- hcl.colors(9, "Spectral", alpha=.7)

# the actual plot
b <- barplot(cs <- colSums(t(cost)), width=w, space=0, ylim=c(1, 2e5), 
             xlim=c(-1, 80), xaxt="n", xaxs="i", col=colores, border=NA,
             xlab="Measure code and average P reduction (kg/P/yr)",
             ylab="Mean annual TOTEX (£/kg) of P removal (thousands)")

# place lables, leave out # 6
text(x0[-6], y0[-6], labels=labs[-6], cex=.7)
# arrows
arrows(x0[c(1, 3)], 1.35e5, x0[c(1, 3)], cs[c(1, 3)], length=0)
# label # 6
text(40, 1e5, labs[6], cex=.7)
# arrow # 6
arrows(40, 8.4e4, x0[6], cs[6], length=0)
# make x axis
axis(1, c(0, cumsum(log(seq(0, 1e5, 1e4)[-1]))), 
     labels=format(c(0, cumsum(seq(0, 1e5, 1e4)[-1])), format="d"), tck=-.02)
# put it in a box
box()

Результат

введите здесь описание изображения

Надеюсь, я правильно понял значения оси X.

Вероятно, вам придется немного разобраться в том, как работают, вероятно, новые функции, но это довольно легко сделать с помощью файлов справки, например. введите ?barplot.

person jay.sf    schedule 29.12.2019