Добавление среднего к geom_de density_ridges

Я пытаюсь добавить средства, использующие geom_segment, к geom_density_ridges сюжету, сделанному в ggplot2.

library(dplyr)
library(ggplot2)
library(ggridges)

Fig1 <- ggplot(Figure3Data,  aes(x = `hairchange`, y = `EffortGroup`)) +
  geom_density_ridges_gradient(aes(fill = ..x..), scale = 0.9, size = 1) 

ingredients <- ggplot_build(Fig1) %>% purrr::pluck("data", 1)

density_lines <- ingredients %>%
  group_by(group) %>% filter(density == mean(density)) %>% ungroup()

p <- ggplot(Figure3Data,  aes(x = `hairchange`, y = `EffortGroup`)) +
  geom_density_ridges_gradient(aes(fill = ..x..), scale = 0.9, size = 1) +
  scale_fill_gradientn(  colours = c("#0000FF", "#FFFFFF", "#FF0000"),name = 
  NULL, limits=c(-2,2))+ coord_flip() +
  theme_ridges(font_size = 20, grid=TRUE, line_size=1, 
               center_axis_labels=TRUE) + 
  scale_x_continuous(name='Average Self-Perceived Hair Change', limits=c(-2,2))+ 
  ylab('Total SSM Effort (hours)')+
  geom_segment(data =density_lines, 
               aes(x = x, y = ymin, xend = x, yend = ymin+density*scale*iscale))

print(p)

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

График плотности

Ниже приведены первые 35 строк данных:

structure(list(MonthsMassage = c(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 
2, 2, 1, 1), MinutesPerDayMassage = c("0-10 minutes daily", "0-10 minutes daily", 
"0-10 minutes daily", "0-10 minutes daily", "0-10 minutes daily", 
"0-10 minutes daily", "0-10 minutes daily", "0-10 minutes daily", 
"0-10 minutes daily", "0-10 minutes daily", "11-20 minutes daily", 
"11-20 minutes daily", "11-20 minutes daily", "0-10 minutes daily", 
"0-10 minutes daily", "0-10 minutes daily", "0-10 minutes daily", 
"0-10 minutes daily", "0-10 minutes daily", "0-10 minutes daily", 
"0-10 minutes daily", "0-10 minutes daily", "0-10 minutes daily", 
"0-10 minutes daily", "0-10 minutes daily", "0-10 minutes daily", 
"0-10 minutes daily", "0-10 minutes daily", "0-10 minutes daily", 
"0-10 minutes daily", "0-10 minutes daily", "0-10 minutes daily", 
"0-10 minutes daily", "11-20 minutes daily", "11-20 minutes daily"
), Minutes = c(5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 15, 15, 15, 5, 5, 
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 15, 15), 
    hairchange = c(-1, -1, 0, -1, 0, -1, -1, 0, 0, -1, 0, -1, 
    -1, 0, 0, -1, 0, -1, 0, -1, -1, -1, -1, -1, 0, -1, -1, -1, 
    0, 1, -1, 0, 0, -1, 0), HairType1 = c("Templefrontal", "Templefrontal", 
    "Templefrontal", "Templefrontal", "Templefrontal", "Templefrontal", 
    "Templefrontal", "other", "Templefrontal", "Templefrontal", 
    "Templefrontal", "Templefrontal", "Templefrontal", "Templefrontal", 
    "Templefrontal", "Templefrontal", "Templefrontal", "Templefrontal", 
    "Templefrontal", "Templefrontal", "Templefrontal", "Templefrontal", 
    "Templefrontal", "Templefrontal", "Templefrontal", "other", 
    "other", "other", "Templefrontal", "Templefrontal", "other", 
    "Templefrontal", "other", "Templefrontal", "Templefrontal"
    ), HairType2 = c("other", "other", "other", "other", "other", 
    "other", "other", "other", "other", "Vertexthinning", "Vertexthinning", 
    "other", "Vertexthinning", "other", "other", "Vertexthinning", 
    "other", "Vertexthinning", "Vertexthinning", "other", "other", 
    "other", "Vertexthinning", "other", "Vertexthinning", "other", 
    "other", "other", "other", "other", "other", "Vertexthinning", 
    "other", "other", "other"), HairType3 = c("other", "Diffusethinning", 
    "other", "Diffusethinning", "other", "other", "Diffusethinning", 
    "Diffusethinning", "Diffusethinning", "other", "Diffusethinning", 
    "Diffusethinning", "other", "other", "Diffusethinning", "Diffusethinning", 
    "other", "Diffusethinning", "Diffusethinning", "Diffusethinning", 
    "other", "other", "other", "other", "other", "other", "other", 
    "other", "other", "Diffusethinning", "other", "other", "other", 
    "other", "other"), Effort = c(0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 
    2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 5, 5, 5, 5, 5, 7.5, 7.5), EffortGroup = c("<5", 
    "<5", "<5", "<5", "<5", "<5", "<5", "<5", "<5", "<5", "<5", 
    "<5", "<5", "<5", "<5", "<5", "<5", "<5", "<5", "<5", "<5", 
    "<5", "<5", "<5", "<5", "<5", "<5", "<5", "12.5", "12.5", 
    "12.5", "12.5", "12.5", "12.5", "12.5")), row.names = c(NA, 
-35L), class = c("tbl_df", "tbl", "data.frame"))

person jbearazesh    schedule 09.10.2018    source источник
comment
Добро пожаловать в SO. Сообщение об ошибке касается ваших данных. Поэтому, пожалуйста, отредактируйте свой вопрос и опубликуйте свои данные, желательно результат dput(...) (или dput(head(...)), если он слишком объемный). Чтобы ответить на ваш вопрос, нам нужен воспроизводимый пример. Спасибо.   -  person Uwe    schedule 09.10.2018
comment
Вы можете выложить, пожалуйста, результат str(density_lines)?   -  person Uwe    schedule 09.10.2018
comment
Нет результата для str (density_lines) (0 objs из 27 переменных), однако, когда я заменяю mean (density) на max (density), код работает, и я получаю следующее (10 obj из 27 переменных). Я пытаюсь вывести даже первую строку данных, но слишком много символов для публикации здесь. Спасибо за помощь!   -  person jbearazesh    schedule 09.10.2018
comment
Спасибо за dput() результат. Этого было достаточно, чтобы воспроизвести проблему. density_lines был пустым, потому что ни одна запись не имеет значения density, которое в точности равно mean(density). Используя max(density), строится одна горизонтальная линия для каждой линии гребня. Это то, что вы хотите? Или вам нужна горизонтальная линия для каждой вершины (и, возможно, впадины) каждой линии хребта?   -  person Uwe    schedule 09.10.2018
comment
Есть еще одна проблема. Группирующая переменная EffortGroup, которая составляет ось Y (Общее усилие SSM (часы)), приводится к коэффициенту для построения графика. Уровни факторов отсортированы в алфавитном порядке, что дает неправильный порядок. Я предлагаю превратить EffortGroup в фактор с явно указанными уровнями факторов в правильном порядке.   -  person Uwe    schedule 09.10.2018
comment
Спасибо Уве! Мне нужна горизонтальная линия в том месте, где плотность равна средней плотности для каждой из линий гребня. Поскольку среднее значение не работает, можно ли создать массив, содержащий средние значения (вычисленные с помощью ddply) для каждой из 10 линий гребня, а затем использовать geom_segment, который вызывает из этой области для местоположения y?   -  person jbearazesh    schedule 09.10.2018


Ответы (1)


Нанесение горизонтальных линий

Если я правильно понимаю, OP хочет построить горизонтальную линию в месте, где плотность равна средней плотности для каждой из линий гребня.

Выражение

density_lines <- ingredients %>%
  group_by(group) %>% filter(density == mean(density)) %>% ungroup()

возвращает пустой набор данных, поскольку нет записи, в которой значение density в точности совпадает с mean(density).

Однако он работает для общего максимума (но не для всех локальных максимумов).

density_lines <- ingredients %>%
  group_by(group) %>% filter(density == max(density)) %>% ungroup()

который дает

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

Найдите ближайшее значение

Поскольку нет точного совпадения, ближайшее значение может быть выбрано

density_lines <- ingredients %>%
  group_by(group) %>% 
  top_n(1, -abs(density - mean(density))) 

который строится как

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

Это строит один сегмент на каждую линию гребня, но мы ожидаем увидеть 4 сегмента в каждой из ветвей кривой (те, где максимум соседнего пика больше среднего). С участием

density_lines <- ingredients %>%
  group_by(group) %>% 
  top_n(4, -abs(density - mean(density))) 

мы получаем

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

Вы можете поиграть с параметром n на top_n(), но, IMHO, правильным способом было бы сгруппировать каждую линию гребня от пика к долине и от впадины к пику, чтобы получить один сегмент для каждой ветви кривой.

Найдите ценность поблизости

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

density_lines <- ingredients %>%
  group_by(group) %>% 
  filter(near(
    density, mean(density), 
    tol = ingredients %>% summarise(0.25 * max(abs(diff(density)))) %>% pull()
  )) 

Для тщательно подобранного коэффициента 0.25 (попытка и ошибка) получаем

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

РЕДАКТИРОВАТЬ: построение вертикальных линий

Похоже, я неверно истолковал намерения ОП. Теперь мы попытаемся построить вертикальную линию в точке mean(density), используя geom_hlinecoord_flip(), geom_hline() создает вертикальную линию).

Опять же, мы следуем умному подходу OP для извлечения плотностей и масштабных коэффициентов из созданного графика.

# create plot object
Fig1 <- ggplot(Figure3Data,  aes(x = hairchange, y = EffortGroup)) +
  geom_density_ridges_gradient(aes(fill = ..x..), scale = 0.9, size = 1) +
  scale_fill_gradientn(
    colours = c("#0000FF", "#FFFFFF", "#FF0000"),
    name =
      NULL,
    limits = c(-2, 2)
  ) + coord_flip() +
  theme_ridges(
    font_size = 20,
    grid = TRUE,
    line_size = 1,
    center_axis_labels = TRUE
  ) +
  scale_x_continuous(name = 'Average Self-Perceived Hair Change', limits =
                       c(-2, 2)) +
  ylab('Total SSM Effort (hours)')

# extract plot data and summarise
mean_density <- 
  ggplot_build(Fig1) %>% 
  purrr::pluck("data", 1) %>%
  group_by(group) %>% 
  summarise(density = mean(density), scale = first(scale), iscale = first(iscale))

# add hline and plot
Fig1 +
  geom_hline(aes(yintercept = group + density * scale * iscale),
             data = mean_density)

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

РЕДАКТИРОВАТЬ 2. Постройте горизонтальные линии в позиции среднего самооценки изменения волос.

OP пояснил, что

Я хочу, чтобы это было среднее самооцененное изменение волос (данные оси Y) для каждой из 10 линий гребня.

Этого можно добиться, выполнив следующие действия:

  1. Создайте объект ridgeplot.
  2. Вычислите среднее самооценку изменения волос для каждого EffortGroup.
  3. Выберите значения созданных значений плотности из данных графика.
  4. Присоединяйтесь к обоим наборам данных.
  5. Вычислите значения плотности в местах расположения средних, используя approx()
  6. Нарисуйте отрезки линии.

Среднее самооценка изменения волос для каждого EffortGroup рассчитывается с помощью

Figure3Data %>% 
  group_by(EffortGroup) %>% 
  summarise(x_mean = mean(hairchange))

что дает (для опубликованного подмножества данных OP):

  EffortGroup x_mean
  <chr>        <dbl>
1 <5          -0.643
2 12.5        -0.143

Все шаги вместе:

# create plot object
Fig1 <- ggplot(Figure3Data,  aes(x = hairchange, y = EffortGroup)) +
  geom_density_ridges_gradient(aes(fill = ..x..), scale = 0.9, size = 1) +
  scale_fill_gradientn(
    colours = c("#0000FF", "#FFFFFF", "#FF0000"),
    name = NULL,
    limits = c(-2, 2)) + 
  coord_flip() +
  theme_ridges(
    font_size = 20,
    grid = TRUE,
    line_size = 1,
    center_axis_labels = TRUE) +
  scale_x_continuous(name = 'Average Self-Perceived Hair Change', 
                     limits = c(-2, 2)) +
  ylab('Total SSM Effort (hours)')

density_lines <-
  Figure3Data %>% 
  group_by(EffortGroup) %>% 
  summarise(x_mean = mean(hairchange)) %>% 
  mutate(group = as.integer(factor(EffortGroup))) %>% 
  left_join(ggplot_build(Fig1) %>% purrr::pluck("data", 1), 
            on = "group") %>% 
  group_by(group) %>%
  summarise(x_mean = first(x_mean), 
            density = approx(x, density, first(x_mean))$y, 
            scale = first(scale), 
            iscale = first(iscale))

# add segments and plot
Fig1 +
  geom_segment(aes(x = x_mean,
                   y = group,
                   xend = x_mean,
                   yend = group + density * scale * iscale),
               data = density_lines)

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

РЕДАКТИРОВАТЬ 3: изменить порядок горизонтальной оси

OP попросил изменить заказ горизонтальную ось соответственно. Это можно сделать, предварительно переведя EffortGroup из типа character в factor, где уровни факторов явно указаны в ожидаемом порядке:

# turn EffortGroup into factor with levels in desired order
lvls <- c("<5", "12.5", "22.5", "35", "50", "75", "105", "152", "210", "210+")
Figure3Data <- 
  Figure3Data %>% 
  mutate(EffortGroup = factor(EffortGroup, levels = lvls))

В качестве альтернативы EffortGroup может быть получено непосредственно из заданных Effort значений с помощью

# create Effort Group from scratch
lvls <- c("<5", "12.5", "22.5", "35", "50", "75", "105", "152", "210", "210+")
brks <- c(-Inf, 5, 12.5, 22.5, 35, 50, 75, 105, 152, 210, Inf)
Figure3Data <- 
  Figure3Data %>% 
  mutate(EffortGroup = cut(Effort, brks, lvls, right = FALSE))

В любом случае, необходимо изменить вычисление density_lines, поскольку EffortGroup уже является фактором:

density_lines <-
  Figure3Data %>% 
  group_by(EffortGroup) %>% 
  summarise(x_mean = mean(hairchange)) %>% 
  mutate(group = as.integer(EffortGroup)) %>%   # remove call to factor() here
  left_join( ...

С полным набором данных, предоставленным OP (ссылка), график наконец становится

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

Местоположение среднего самооценки изменения волос для каждого EffortGroup дается выражением

Figure3Data %>% 
  group_by(EffortGroup) %>% 
  summarise(x_mean = mean(hairchange)) 
# A tibble: 10 x 2
   EffortGroup  x_mean
   <fct>         <dbl>
 1 <5          -0.643 
 2 12.5        -0.393 
 3 22.5        -0.118 
 4 35          -0.0606
 5 50           0.286 
 6 75           0     
 7 105          0.152 
 8 152          0.167 
 9 210          0.379 
10 210+         0.343
person Uwe    schedule 09.10.2018
comment
Спасибо Уве. Я заинтересован в добавлении одной линии на каждую линию гребня в месте средней плотности. Можно ли вызвать значения из массива средних, рассчитанных ниже? structure(list(EffortGroup = c("<5", "105", "12.5", "152", "210", "210+", "22.5", "35", "50", "75"), mean = c(-0.642857142857143, 0.151515151515152, -0.392857142857143, 0.166666666666667, 0.379310344827586, 0.342857142857143, -0.117647058823529, -0.0606060606060606, 0.285714285714286, 0)), class = "data.frame", row.names = c(NA, -10L)) - person jbearazesh; 09.10.2018
comment
Спасибо за помощь с вертикальной линией, однако в идеале я хочу получить среднее самооцененное изменение волос (данные оси Y) для каждой из 10 линий гребня. Например, используя приведенные выше данные, я знаю, что среднее значение для ‹5 и 12,5 Total SSM Effort составляет -0,643 и -0,392. Я хотел, чтобы горизонтальная линия на -0,643 продолжалась от линии хребта с плотностью ‹5, и горизонтальная линия на -0,392, идущая от линии гребня с плотностью 12,5, и так далее и так далее. - person jbearazesh; 10.10.2018
comment
Привет, Уве, у меня все еще возникают проблемы с построением этого плана. Сначала со строкой `mutate (group = as.integer (factor (EffortGroup)))%›% `я получаю Error in factor(EffortGroup) : object 'EffortGroup' not found. Не знаете, что здесь делать? Во-вторых, есть ли способ построить ось x с увеличивающимся числовым значением, а не в алфавитном порядке? В настоящее время он отображается как structure(list(EffortGroup = c("<5", "105", "12.5", "152", "210", "210+", "22.5", "35", "50", "75"), , но я хочу <5, 12,5, 22,5 и так далее. - person jbearazesh; 18.10.2018
comment
@jbearazesh, Ваш образец набора данных действительно включал переменную EffortGroup, которая впоследствии используется в моем ответе. Пожалуйста, дважды проверьте, включена ли эта переменная в ваши данные (или написана по-другому). Что касается порядка оси x, см. Мой соответствующий прокомментируйте свой вопрос от 9 октября. - person Uwe; 18.10.2018
comment
Спасибо. У меня все еще есть EffortGroup в моем наборе данных, и когда он вызывается ранее в строке ggplot, он работает нормально. Возможно, это связано с тем, что я не могу воспроизвести среднее самооцененное изменение волос для каждой группы EffortGroup, используя предоставленный вами код Figure3Data %>% group_by(EffortGroup) %>% summarise(x_mean = mean(hairchange)) Это дает 0,02, что является средним значением для всего набора данных. - person jbearazesh; 18.10.2018
comment
@jbearazesh Отладка без доступа к полному набору данных довольно сложна. Есть ли шанс сделать ваш полный набор данных доступным для загрузки? Возможно, вам пригодится моя третья правка. - person Uwe; 18.10.2018
comment
3-е редактирование чрезвычайно полезно. Полный набор данных: s000.tinyupload.com/?file_id=69397165619155339536 - person jbearazesh; 18.10.2018
comment
@jbearazesh Спасибо за предоставление полного набора данных. Я смог без проблем создать финальный сюжет. - person Uwe; 19.10.2018