I'm working on a stacked bar chart. I'm trying to have a column of full length, where one part will be equal to actual value, and filled with the specific (!) color, and the second part is a "filler" (100-value) which always should be beige.
How could I fill it under the nested condition (if it is filler = gray, if value = painted depending on another column(with manual color selection))?
my_data <- data.frame(group = c("group1", "group2", "group3", "group4"),
value = c(10, 20, 30, 10))
my_data <- my_data %>% mutate(filler = 100-value)
my_data <- my_data %>% gather(key = "obs", value = "value", -1)
my_data <- my_data %>% mutate(col = case_when(value <=10 ~ "yellow",
value >10 & value <=20 ~ "pink",
value >20 ~ "red")
ggplot(my_data)+
geom_bar(aes(x=group, y=value, fill=obs),
stat="identity", alpha=1, position = position_stack())+
scale_fill_manual(breaks = c("value", "filler"),
values = c("filler" = "beige", "value" = "black"))
You can use scale_fill_identity for this
library(tidyverse)
my_data <- data.frame(group = c("group1", "group2", "group3", "group4"),
value = c(10, 20, 30, 10))
my_data <- my_data %>% mutate(filler = 100-value)
my_data <- my_data %>% gather(key = "obs", value = "value", -1)
my_data <- my_data %>% mutate(col = case_when(value <=10 ~ "yellow",
value >10 & value <=20 ~ "pink",
value >20 ~ "red"))
ggplot(my_data)+
geom_bar(aes(x=group, y=value, fill=col), stat="identity", alpha=1, position = position_stack())+
scale_fill_identity()
One way you can avoid manually specifying colour for the background beige is by simple creating 2 layers of geoms one will be in background, which doesn't require any calculation and your actual values will be front of it.
my_data <- data.frame(group = c("group1", "group2", "group3", "group4"),
value = c(10, 20, 30, 10))
my_data %>%
mutate(col = case_when(value <=10 ~ "yellow",
value >10 & value <=20 ~ "pink",
value >20 ~ "red")) %>%
ggplot() +
geom_col(aes(x = group, y= 100), fill = "beige") +
geom_col(aes(x = group, y = value, fill = col)) +
scale_color_identity()
Created on 2021-02-10 by the reprex package (v0.3.0)
Related
I have a question about changing color for geom_text & geom_label base on a condition.
Expected out put
Data label has color = blue for 2021, color = grey for 2020
Gap label is red when negative and green when positive
Data
data <- tibble(Factor = c("A", "B", "A", "B"),
Score = c(90, 85, 85, 90),
Year = c("2020", "2020", "2021", "2021"))
Data transform
df_2 = data %>%
pivot_wider(names_from = Year, names_prefix = "Year", values_from = Score) %>%
mutate(gap = Year2021 - Year2020) %>%
select(Factor, gap)
Plot
data %>%
left_join(df_2) %>%
ggplot(aes(x = Factor, y = Score, fill = Year)) +
geom_col(position='dodge') +
geom_text(aes(label=Score),
position=position_dodge(width = 0.9),
vjust=-0.40) +
geom_label(aes(y = 100, label = ifelse(Year == 2021, gap, NA_character_)), na.rm = TRUE) +
scale_y_continuous(limits = c(0,105))
theme_minimal()
Expected out put
85 & 90 = blue for 2021, 90 & 85 = grey for 2020
-5 = red and 5 = green
Second option would be to use a manual scale for the text and bar colors. For the labels we could use the ggnewscale package which allows for multiple scales for the same aesthetic. Doing so we could add a second fill scale:
library(ggplot2)
library(ggnewscale)
ggplot(dat, aes(x = Factor, y = Score, fill = Year)) +
geom_col(position = "dodge") +
geom_text(aes(label = Score, color = Year),
position = position_dodge(width = 0.9),
vjust = -0.40
) +
scale_color_manual(aesthetics = c("fill", "color"), values = c("2021" = "blue", "2020" = "grey")) +
ggnewscale::new_scale_fill() +
geom_label(aes(y = 100, label = ifelse(Year == 2021, gap, NA_character_), fill = gap > 0), na.rm = TRUE) +
scale_fill_manual(values = c("FALSE" = "red", "TRUE" = "green")) +
scale_y_continuous(limits = c(0, 105)) +
theme_minimal()
DATA
data <- data.frame(
Factor = c("A", "B", "A", "B"),
Score = c(90, 85, 85, 90),
Year = c("2020", "2020", "2021", "2021")
)
library(dplyr)
library(tidyr)
df_2 <- data %>%
pivot_wider(names_from = Year, names_prefix = "Year", values_from = Score) %>%
mutate(gap = Year2021 - Year2020) %>%
select(Factor, gap)
dat <- data %>%
left_join(df_2)
You can conditionally assign the colors to the aesthetics using case_when. You can use the following code:
library(tibble)
library(ggplot2)
library(dplyr)
library(tidyr)
data <- tibble(Factor = c("A", "B", "A", "B"),
Score = c(90, 85, 85, 90),
Year = c("2020", "2020", "2021", "2021"))
df_2 = data %>%
pivot_wider(names_from = Year, names_prefix = "Year", values_from = Score) %>%
mutate(gap = Year2021 - Year2020) %>%
select(Factor, gap)
df <- data %>% left_join(df_2)
#> Joining, by = "Factor"
ggplot(df, mapping = aes(x = Factor, y = Score, fill = Year)) +
geom_col(position='dodge') +
geom_text(df, mapping = aes(label=Score, color = Year),
color = case_when(
df$Score == 85 & df$Year == 2021 | df$Score == 90 & df$Year == 2021 ~ "blue",
df$Score == 85 & df$Year == 2020 | df$Score == 90 & df$Year == 2020 ~ "grey",
),
position=position_dodge(width = 0.9),
vjust=-0.40) +
geom_label(data = df, mapping = aes(y = 100, label = ifelse(Year == 2021, gap, NA_character_)),
fill = case_when(
df$gap == -5 ~ "red",
df$gap == 5 ~ "green"
), na.rm = TRUE) +
scale_y_continuous(limits = c(0,105)) +
theme_minimal()
Created on 2022-09-24 with reprex v2.0.2
There's a given dataframe
df <- data.frame("V1" = c(0,0,0,0,0,2,2,2,2,2,3,3,3),
"V2" = c(9,9,9,0,0,2,2,2,0,0,3,0,0))
and I would like to create a bar plot out of it, where each value has a specific colour. With the great help of one of users we managed to create code
p <- df %>%
mutate(index = 1) %>%
pivot_longer(cols = -index) %>%
mutate(color = case_when(value == 9 ~ 'white',
value == 0 ~ 'darkgreen',
value == 1 ~ 'blue',
value == 2 ~ 'red',
value == 3 ~ 'darkorange')) %>%
ggplot(aes(x = index, y = name, fill = color)) +
geom_col(width = 0.3) +
scale_fill_identity(guide = 'legend') +
theme_classic() +
scale_x_continuous(expand = c(0,0), breaks = pretty_breaks(2))
vec_colors <- df %>%
mutate(index = 1) %>%
pivot_longer(cols = -index) %>%
mutate(color = case_when(value == 9 ~ 'white',
value == 0 ~ 'darkgreen',
value == 1 ~ 'blue',
value == 2 ~ 'red',
value == 3 ~ 'darkorange')) %>%
arrange(name) %>%
pull(color)
q <- ggplot_build(p)
q$data[[1]] <- q$data[[1]] %>%
group_by(y) %>%
arrange(x, .by_group = TRUE)
q$data[[1]]$fill <- vec_colors
q <- ggplot_gtable(q)
plot(q)
that results in such a plot
Question: how do I create a legend that looks like this?
Or like this?
Well, first your approach to achieve your desired result is quite complicated. Instead you could simplify using a named color vector and switching to scale_fill_manual. Doing so will give you atomatically a legend similar to your desired result which I tweak a bit using the breaks argument. Also, instead of geom_col I would go for geom_tile. To this end use the row number as the index.
library(dplyr)
library(tidyr)
library(ggplot2)
df_long <- df %>%
mutate(index = row_number()) %>%
pivot_longer(cols = -index)
cols <- c( 'white', 'darkgreen', 'blue', 'red', 'darkorange')
names(cols) <- c(9, 0, 1, 2, 3)
ggplot(df_long, aes(x = index, y = name, fill = factor(value))) +
geom_tile(height = .3) +
scale_fill_manual(values = cols, limits = force, breaks = c(0, 3, 2), name = "State") +
theme_classic() +
scale_x_continuous(expand = c(0,0), breaks = scales::pretty_breaks(2))
I have a mirrored bar-chart, and I want avoid mirror bars and have the same graphs but 2 columns for each category:
Negative and positive (firstly positive values on right side and below negative values on left side)
Colours must be determined by 'Model' categorical variable and pos & neg values need to be different, like fully coloured bars = positive, bounded (with the same colour) but not filled bars = negative.
Also, USA based values must be on the top and Canada based values below
df <- data.frame (Origin = c("Canada", "Canada","Canada", "Canada","Canada", "Canada","USA","USA","USA","USA","USA","USA"),
Model = c("A","B","C","D","E","F","A","B","C","D","E","F"),
poschange = c(60, 45,34,56, 65, 44,40, 55, 35, 24,34,12),
negchange = c(-5,-2,-0.5,-2,-1,-0.05,-1,-3,-0.1,-3,-1.5,-0.9))
require(dplyr)
require(ggplot2)
require(tidyr)
df2 <- df %>% pivot_longer(., cols=c('poschange','negchange'),
names_to = 'value_category')
df2 <- df2 %>% mutate(Groups = paste(Origin, Model))
df2 <- df2 %>% mutate(label_position=ifelse(value>0, value-5,value-8)) # adjusting label position
df2 %>% arrange(value) %>% ggplot(aes(x=value, y=reorder(Groups,value),
fill=value_category,
group=value_category))+
geom_col(width=0.75)
coord_flip()
Output:
Desired output (something like this but colours must be corresponding to Model cat. variable):
Maybe something like this?
Use an ifelse statement to label the negative values as "white"
To have a fill of white, use scale_fill_manual with a my_color palette
To avoid "mirrored" bars, use position = "dodge"
To have negative and positive values side-by-side, you need to swap your x and y argument in ggplot
To avoid overlapping text on the x-axis, use theme(axis.text.x = element_text(angle = 90))
Use the breaks argument in both scale_xxx_manual function to remove the "white" label in the legend
library(tidyverse)
df <- data.frame (Origin = c("Canada", "Canada","Canada", "Canada","Canada", "Canada","USA","USA","USA","USA","USA","USA"),
Model = c("A","B","C","D","E","F","A","B","C","D","E","F"),
poschange = c(60, 45,34,56, 65, 44,40, 55, 35, 24,34,12),
negchange = c(-5,-2,-0.5,-2,-1,-0.05,-1,-3,-0.1,-3,-1.5,-0.9))
df2 <- df %>% pivot_longer(., cols=c('poschange','negchange'),
names_to = 'value_category') %>%
mutate(Groups = paste(Origin, Model),
value_category = factor(value_category, levels = c("negchange", "poschange")))
my_color = c("A" = '#7fc97f', "B" = '#beaed4', "C" = '#fdc086',
"D" = '#ffff99', "E" = '#386cb0', "F" = '#f0027f', "white" = "white")
ggplot(df2, aes(value, Model,
fill = ifelse(value_category == "negchange", "white", Model),
color = Model)) +
geom_col(position = "dodge") +
scale_fill_manual(values = my_color, breaks = df2$Model) +
scale_color_manual(values = my_color, breaks = df2$Model) +
labs(fill = "Model") +
facet_grid(Origin ~ ., switch = "y") +
theme(axis.text.x = element_text(angle = 90),
strip.background = element_rect(fill = "white"),
strip.placement = "outside",
strip.text.y.left = element_text(angle = 0),
panel.spacing = unit(0, "lines"))
Created on 2022-05-03 by the reprex package (v2.0.1)
How do I combine geom_smooth(method = "lm) function with gganimate()'s transition_layers, so that, as the individual bars drift/grow upwards, the linear line of geom_smooth appears, like so: Example of desired appearance of geom_smooth line The only difference is that in my case, instead of the points, the bars would drift upwards as the line appears.
The bars current work well, appearing by drifting upwards, made possible by using the transition_layers function of gganimate.
However, I can't figure out how to add the geom_smooth line, so it appears as the bars grow upwards. Right now, the line appears just at the end, as seen below.
See below for the current look of the animation.
Here is a simple reprex of my problem:
#Df for reprex
library(ggplot2)
library(tidyverse)
year <- as.numeric(c(1996:2002,
1996:2002,
1996:2002))
c <- c(39, 40, 67, 80, 30, 140, 90, 23, 100, 123,
140, 1, 2, 1, 13, 3, 3, 30, 1, 3, 3)
df <- data.frame(year, c) %>%
select(year, c) %>%
arrange(year)
#Static plot
(static_plot <- ggplot(data = df) +
geom_bar(data = df %>% filter(year == 1996), stat="identity", position ="stack",
aes(x = year, y = c)) +
geom_bar(data = df %>% filter(year == 1997), stat="identity", position ="stack",
aes(x = year, y = c)) +
geom_bar(data = df %>% filter(year == 1998), stat="identity", position ="stack",
aes(x = year, y = c)) +
geom_bar(data = df %>% filter(year == 1999), stat="identity", position ="stack",
aes(x = year, y = c)) +
geom_bar(data = df %>% filter(year == 2000), stat="identity", position ="stack",
aes(x = year, y = c)) +
geom_bar(data = df %>% filter(year == 2001), stat="identity", position ="stack",
aes(x = year, y = c)) +
geom_bar(data = df %>% filter(year == 2002), stat="identity", position ="stack",
aes(x = year, y = c)) +
labs(y = "year",
x = "c",
title = "Reprex") +
geom_smooth(df, mapping = aes(x = year, y = c), method = "lm",
colour = "black", se = F)
)
#Animation
library(gganimate)
anim <- static_plot +
transition_layers(layer_length = 1, transition_length = 1) +
enter_drift(x_mod = 0, y_mod = -max(df$c))
animate(anim, fps = 10, duration = 10,
width = 600, height = 500, renderer = gifski_renderer())
Here's an approach where the data is replicated and then filtered so each version shows progressively more years.
library(dplyr); library(tidyr)
animate(
df %>%
count(year, wt = c, name = "c") %>% # Aggregate for each year's total
uncount(7, .id = "year_disp") %>% # Make 7 copies, one for each year
arrange(year_disp, year) %>%
mutate(year_disp = year_disp + min(df$year) - 1) %>%
filter(year <= year_disp) %>% # Only keep years up to "year_disp"
ggplot(aes(year, c)) +
geom_col(aes(group = year)) + # "group" here connects each year to itself between frames
geom_smooth(method = "lm", se = F) +
transition_states(year_disp) +
enter_drift(y_mod = -max(df$c)),
fps = 10, duration = 10,
width = 600, height = 500, renderer = gifski_renderer())
The geom-line is calculated in the end and hence it appears only at the end. After each calculation of geom-bar, you have to calculate the geom-line as well, so that the line appears simultaneously with the Bars growing.
geom_bar(data = df %>% filter(year == 1997), stat="identity", position ="stack",
aes(x = year, y = c)) +
geom_line(filter(df, year %in% c(1996, 1997)), mapping = aes(x = year, y = lm),
colour = "black")
Do this for all the years and you should be getting the expected result!
library(tidyverse)
delta <- tibble(
type = c("alpha", "beta", "gamma"),
a = rnorm(3, 5),
b = rnorm(3, 6)
) %>%
mutate(delta = abs(a - b)) %>%
gather(`a`, `b`, `delta`, key = "letter", value = "value")
ggplot(delta %>% filter(letter != "delta"), aes(type, value, fill = letter)) +
geom_col(position = "dodge") +
geom_col(data = delta %>% filter(letter == "delta"), width = 0.5) +
scale_color_manual("grey", "black", "blue")
I'd like the a and b bars to be grey and black. And the delta bar to be blue. How do I do this with scale_color_manual()? Seems my syntax above is off.
There are two things that need to be changed:
Since you've used fill = letter, you should use scale_fill_manual instead of scale_color_manual (which would have been appropriate if you had used color = letter).
The manual color values should be provided as a vector.
library(tidyverse)
delta <- tibble(
type = c("alpha", "beta", "gamma"),
a = rnorm(3, 5),
b = rnorm(3, 6)
) %>%
mutate(delta = abs(a - b)) %>%
gather(`a`, `b`, `delta`, key = "letter", value = "value")
ggplot(delta %>% filter(letter != "delta"), aes(type, value, fill = letter)) +
geom_col(position = "dodge") +
geom_col(data = delta %>% filter(letter == "delta"), width = 0.5) +
scale_fill_manual(values = c("grey", "black", "blue"))
Created on 2018-10-08 by the reprex package (v0.2.1)