линейчатая диаграмма «спина к спине» с независимыми осями R

#r #ggplot2

#r #ggplot2

Вопрос:

Я хочу построить линейчатую диаграмму «спина к спине», однако каждая сторона находится на независимых осях. Я могу построить их вплотную друг к другу, взяв отрицательное значение одного набора, но это оставляет их при одинаковом доступе, а поскольку значения pv меньше, их столбики едва представлены.

 library(ggplot2)
df <-structure(list(Description = c("a", "b", "c", "d", "e", "f", 
    "g", "h", "a", "b", "c", "d", "e", "f", "g", "h"), test = c("size", 
    "size", "size", "size", "size", "size", "size", "size", "p", 
    "p", "p", "p", "p", "p", "p", "p"), value = c(0.1, 0.1, 0.125, 
    0.1, 0.075, 0.1, 0.075, 0.125, 0.000230705311441713, 0.000314488619269942, 
    0.00106639822095382, 0.00108290238851994, 0.00114723539549198, 
    0.00160204850890075, 0.0019276388745184, 0.00320371567547557)), .Names = c("Description", 
    "test", "value"), row.names = c(NA, -16L), class = "data.frame")

df$value[df$test == 'p'] <- -(df$value[df$test == 'p'])

ggplot(df, aes(x=Description, y= value, group=test, fill=test))   geom_col()  coord_flip()
  

В идеале я хотел бы, чтобы каждая группа располагалась на независимых осях так, чтобы столбики сходились на нуле (в середине области графика), но были в разных масштабах, для этого примера ylim было бы что-то вроде ylim (0,0.13), а для pvalue c (0, 0.0035)

Комментарии:

1. Я бы подумал о том, является ли это хорошим способом представления данных. Если оси независимы, следует ли отображать значения так, как если бы они были сопоставимы? Возможно, диаграмма рассеяния, равная p в зависимости от размера, является лучшим способом передачи информации?

2. Я серьезно размышляю над тем, что размер был плохим описанием того, чем на самом деле являются мои данные. В основном это результат гипергеометрических тестов, если бы A-H были подгруппами, которые в данном случае перепредставлены, размер был бы отношением найденных в моих данных элементов, которые соответствуют каждой подгруппе, к общему количеству примерно в каждой подгруппе. Имеет ли это смысл? Я думаю, что два графика хорошо показали бы значимость наряду с пропорцией элементов

Ответ №1:

Вы можете сделать это с помощью фасетов и настройки для удаления интервала между фасетами:

 ggplot(df, aes(x=Description, y= value, fill=test))   
    facet_wrap(~ test, scales = "free_x")   
    geom_col()   
    coord_flip()  
    scale_y_continuous(expand = c(0, 0))  
    theme(panel.spacing.x = unit(0, "mm"))
  

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

Вывод:

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

PS: вы также можете удалить метки отрицательных осей чем-то вроде:

 scale_y_continuous(
    expand = c(0, 0), 
    labels = function(x) signif(abs(x), 3)
)
  

Комментарии:

1. Это здорово, отличное решение. Единственным улучшением было бы, если бы окно графика было немного больше, чем столбики. Я знаю, что вы можете независимо настроить ylim в ggplot v3 с помощью expand_scale , но из-за необходимости инвертировать один график, нижняя и верхняя границы двух графиков инвертируются. Я с радостью соглашусь с вашим ответом, спасибо.

2. Да, это сложно, я думаю, вам нужно было бы установить разные ограничения в каждом аспекте, что я не могу придумать, как сделать с самого начала.

Ответ №2:

Я адаптировал это элегантное решение к своим потребностям. Слава Линъюнь Чжану.

 library(dplyr)
library(ggplot2)

set.seed(123)
ten_positive_rand_numbers <- abs(rnorm(10))   0.1
the_prob <- ten_positive_rand_numbers / sum(ten_positive_rand_numbers)

fk_data <- data.frame(job_type = sample(LETTERS[1:10], 1000, 
                                        replace = TRUE, prob = the_prob),
                      gender = sample(c("Male", "Female"), 1000, 
                                      replace = TRUE))

# prepare data for plotting
plotting_df <-
  fk_data %>% 
  group_by(job_type, gender) %>% 
  summarise(Freq = n()) %>% 
  # a trick!
  mutate(Freq = if_else(gender == "Male", -Freq, Freq))
## find the order
temp_df <-
  plotting_df %>% 
  filter(gender == "Female") %>% 
  arrange(Freq)
the_order <- temp_df$job_type

# plot
p <- 
  plotting_df %>% 
  ggplot(aes(x = job_type, y = Freq, group = gender, fill = gender))  
  geom_bar(stat = "identity", width = 0.75)  
  coord_flip()  
  scale_x_discrete(limits = the_order)  
  # another trick!
  scale_y_continuous(breaks = seq(-150, 150, 50), 
                     labels = abs(seq(-150, 150, 50)))  
  labs(x = "Job type", y = "Count", title = "Back-to-back bar chart")  
  theme(legend.position = "bottom",
        legend.title = element_blank(),
        plot.title = element_text(hjust = 0.5),
        panel.background = element_rect(fill =  "grey90"))  
  # reverse the order of items in legend
  # guides(fill = guide_legend(reverse = TRUE))  
  # change the default colors of bars
  scale_fill_manual(values = c("red", "blue"),
                    name = "",
                    breaks = c("Male", "Female"),
                    labels = c("Male", "Female")) 
print(p)

  

Она может быть улучшена с помощью других незначительных деталей, в том числе geom_hline(yintercept = 0, colour = "black") .

Ответ №3:

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

Я должен удалить поля графика справа от p1 и слева от p2. По какой-то причине на левом поле есть отступ, который так необходим -3,5 пт, чтобы привести его в соответствие, не уверен, будет ли это согласовано на всех графиках. Другая ручная задача — изменить разрывы на одной оси, чтобы 0 не отображались друг над другом.

Мне также не нужно указывать отрицательные значения p, просто используйте scale_y_reverse

 p1 <- ggplot(df[df$test == 'p',], aes(x=Description, y= value))   geom_col(fill='red')   theme_minimal() 
  coord_flip()   scale_y_reverse(name= "axis1",expand = expand_scale(mult= c(c(0.05,0))))  
  theme(panel.spacing.x = unit(0, "mm"))  theme(plot.margin = unit(c(5.5, 0, 5.5, 5.5), "pt"))

p2 <- ggplot(df[df$test != 'p',], aes(x=Description, y= value))   geom_col(fill='blue')   
  scale_y_continuous(name = "axis2", breaks = seq(0.025, 0.125, 0.025) ,expand = expand_scale(mult= c(c(0,0.05))))  
  coord_flip()  
  theme(panel.spacing.x = unit(0, "mm"))  theme_minimal()  
  theme(axis.title.y=element_blank(), axis.text.y=element_blank(),
        axis.line.y = element_blank(), axis.ticks.y=element_blank(),
        plot.margin = unit(c(5.5, 5.5, 5.5, -3.5), "pt"))

grid.newpage()
grid.draw(cbind(ggplotGrob(p1), ggplotGrob(p2), size = "last"))
  

Я также использовал theme_minimal , но это было просто для моих эстетических предпочтений.
введите описание изображения здесь

Комментарии:

1. это тоже отличное решение, @George. Есть идеи, как можно добавить метки осей в решение, которое я добавил совсем недавно?