I have this bar graph.
I generate the graph with this code:
# Speedup Graph
p <- ggplot(speedup_df, aes(x= benchmark, y = speedup, fill = factor(technique))) +
geom_bar(stat = "identity", position = "dodge", width = 0.7) +
scale_fill_discrete(name="Technique", labels=c("No Compression", "Compression Perfect", "Compression BDI", "Precompression BDI Hash",
"Precompression BDI Similarity", "Compression CPack", "Precompression CPack Hash",
"Precompression CPack Similarity", "Compression FPCD", "Precompression FPCD Hash",
"Precompression FPCD Similarity")) +
labs(title = plot_name, y="Speedup", x="Benchmarks") +
coord_cartesian(ylim=c(min(speedup_df$speedup), max(speedup_df$speedup))) +
theme(axis.text.x = element_text(angle=45, size=10, hjust=1)) +
geom_text(data=speedup_df, aes(label=sprintf("%0.4f", round(speedup, digits = 4)), fontface = "bold"), size = 5, position=position_dodge(width=0.7),
hjust=0.5, vjust=-0.7)
I want to insert gaps between the bars at arbitrary points. For example I want to have a gap before and after all the "BDI" bars. I tried using breaks in scale_fill_discrete but I get the error that they need to be the same number as the labels.
If you provide a reproducible example, I can test it on my side. The idea is to change the width within geom_bar and the width within position_dodge(). You may need to adjust the values in the following example using mtcars data.
library(ggplot2)
# without space
ggplot(mtcars, aes(x= 1, y = mpg, fill = factor(cyl))) +
geom_bar(stat = "identity", position= "dodge", width = 0.7)
# add space
ggplot(mtcars, aes(x= 1, y = mpg, fill = factor(cyl))) +
geom_bar(stat = "identity", position = position_dodge(width=0.9), width = 0.7)
Created on 2020-01-17 by the reprex package (v0.3.0)
Edit
There may be several ways to insert gap between specific bars. An intuitive way is to use add_row() to a few empty rows and re-set levels:
library(tidyverse)
df <- data.frame(x = c("a", "b", "c", "d", "e"),
y = c(1, 2, 5, 6, 3))
df <- add_row(df, x = c(" ", " "), y = c(NA))
df$x <- factor(df$x, levels = c("a", " ", "b", "c", "d", " ", "e"))
ggplot(df, aes(x= x, y = y, fill = x)) +
geom_bar(stat = "identity", na.rm = TRUE,
position = "dodge", width = 1) +
scale_fill_manual(values=c("red", "white", "green","blue","maroon",
"white","navy"))
Created on 2020-01-18 by the reprex package (v0.3.0)
Related
Alright, after a long silent read along, here's my first question. I am trying to add corresponding labels of unhighlighted items for a grouped barplot. When I insert gghighlight in front of the geom_text I get the following plot:
library(tidyverse)
library(gghighlight)
df <- data.frame (group = c("A", "A", "B", "B", "C", "C"),
value = c("value_1", "value_2","value_1", "value_2","value_1", "value_2"),
mean = c(1.331, 1.931, 3.231, 3.331, 4.631, 3.331)
)
ggplot(data = df, aes(x = group, y = mean, fill = value)) +
geom_bar(stat = "identity", position = "dodge") +
gghighlight(group != "B",
label_key = group
) +
geom_text(aes(label = round(mean, digits = 2)),
stat= "identity",
vjust = -.5,
position = position_dodge(width = .9)
)
If I move gghightlight behind the geom_text I get the following plot:
ggplot(data = df, aes(x = group, y = mean, fill = value)) +
geom_bar(stat = "identity", position = "dodge") +
geom_text(aes(label = round(mean, digits = 2)),
stat= "identity",
vjust = -.5,
position = position_dodge(width = .9)
) +
gghighlight(group != "B",
label_key = group)
Is there a way to label the unhighligthed bars like the highlighted ones?
Thanks in advance.
############## EDIT ###########
Besides graying out certain columns (see #TarJae's answer), there is also the possibility to make them transparent (essential parts are from this post: ggplot transparency on individual bar):
subset_df <- df %>%
mutate(alpha.adj = as.factor(ifelse(group != "B", 1, 0.6)))
ggplot(data = subset_df, aes(x = group, y = mean, fill = value, alpha=factor(alpha.adj))) +
geom_bar(stat = "identity", position = "dodge") +
geom_text(aes(label = round(mean, digits = 2)),
stat= "identity",
vjust = -.5,
position = position_dodge(width = .9)
) +
scale_alpha_manual(values = c("0.6"=0.6, "1"=1), guide='none')
[]
Are you looking for this?
This is a solution without using gghighlight package:
library(tidyverse)
subset_df <- df %>%
mutate(highlight = if_else(group != "B", mean, NA_real_))
ggplot(data = subset_df, aes(x = group, y = mean, group=value)) +
geom_col(fill = 'grey', alpha = 0.6, position = 'dodge') +
geom_col(aes(y = highlight, fill = value), position = 'dodge') +
geom_text(aes(group, label = round(mean, digits = 2)),
position = position_dodge(width = 1))
This is a solution with the gghighlight package and some limited hacky code.
When reading the vignette, I noticed that the author of the package "filters out" the data that are not highlighted. You can see that if you save your highlighted plot in p_h and then look at p_h$data, the values for group B have disappeared.
library(tidyverse)
library(gghighlight)
p_h <- ggplot(data = df, aes(x = group, y = mean, fill = value)) +
geom_bar(stat = "identity", position = "dodge") +
gghighlight(group != "B",
label_key = group) +
geom_text(aes(label = round(mean, digits = 2)),
stat= "identity",
vjust = -.5,
position = position_dodge(width = .9))
> p_h$data
group value mean
1 A value_1 1.331
2 A value_2 1.931
5 C value_1 4.631
6 C value_2 3.331
If we re-insert the data (after the call to gghighlight() has removed them), then geom_text() will be able to find the means for group B again.
One can "recover" the data and re-insert them with the following code:
### create a ggplot object with the original complete data
### you could check that with p_to_copy_data$data
p_to_copy_data <- ggplot(data = df)
### copy the complete data to your highlighted plot data section
p_h$data <- p_to_copy_data$data
p_h
This yields the following graph:
I think the question is sufficiently complex that a code example will help:
library(ggplot2)
df <- data.frame(
Group = c("A", "A", "A", "A", "B", "B"),
Subgroup = c("A.1", "A.2", "A.1", "A.2", "B.1", "B.2"),
Value = c(10, 7, 8, 9, 11, 12),
Pair = c(1, 1, 2, 2, 3, 3)
)
dodge <- position_dodge(width = 0.9)
ggplot(data = df, mapping = aes(x = Group, y = Value, fill = Subgroup)) +
geom_bar(stat = "summary", fun = "mean", position = dodge) +
geom_point(position = dodge) +
geom_line(color = "red", mapping = aes(group = Pair), position = dodge)
The point is to have a bar chart with groups and subgroups (success), with the individual dots plotted centered above each bar (success), and with lines connecting pairwise samples (fail). The result is not too far off, but apparently, instead of making the points avoid each other and then drawing the lines, ggplot2 draws the vertical lines and then makes them avoid each other.
Actual:
Expected:
May be easier to facet by Group and forgo position adjustments entirely. The below also futzes with labels, panel spacing, etc to mimic the appearance of your original plot as much as possible.
library(ggplot2)
ggplot(df, aes(x = Subgroup, y = Value, fill = Subgroup)) +
geom_bar(stat = "summary", fun = "mean", width = 1) +
geom_point() +
geom_line(aes(group = Pair), color = "red", ) +
facet_wrap(vars(Group), scales = "free_x", strip.position = "bottom") +
labs(x = "Group") +
theme(
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
strip.background = element_blank(),
panel.spacing = unit(0, units = "line")
)
I have the following geom_bar dodged plot and think the single bars for Ages 8, 17, 26 and 27 would look better centralized rather than off to the left. I am not sure what to add to the script to achieve this. Any assistance would be greatly appreciated.
This is the script:
ggplot(data = combo1, aes(x = Age_Year, fill = Tactic)) +
geom_bar(position = position_dodge(preserve = 'single')) +
theme_classic() +
labs(x = "Age (years)", y = "Counts of Fish", show.legend = FALSE)+
theme(legend.position = "none")+
scale_fill_manual("legend", values = c("Migr" = "skyblue", "OcRes" = "pale green", "EstRes" = "pink"))
OP, use position_dodge2(preserve="single") in place of position_dodge(preserve="single"). For some reason, centering bars/columns doesn't quite work correctly with position_dodge(), but it does with position_dodge2(). Note the slight difference in spacing you get when you switch the position function, but should overall be the fix to your problem.
Reproducible Example for OP's question
library(ggplot2)
set.seed(8675309)
df <- data.frame(
x=c("A", "A", "A", "B", "C", "C"),
grouping_var = c("Left", "Middle", "Right", "Middle", "Left", "Right"),
values = sample(1:100, 6))
Basic plot with position_dodge():
ggplot(df, aes(x=x, y=values, fill=grouping_var)) +
geom_col(position=position_dodge(preserve = "single")) +
theme_classic()
When you use position_dodge2():
ggplot(df, aes(x=x, y=values, fill=grouping_var)) +
geom_col(position=position_dodge2(preserve = "single")) +
theme_classic()
Hey I have the following code:
df = data.frame(Type = c("A", "B", "A", "A", "B"), FLAG = c(1, 1, 0, 1, 0))
df
ggplot(df, aes(x = Type)) + geom_bar(stat = "count", aes(fill = factor(FLAG)), position = "dodge") + coord_flip() + stat_count(geom = "text", colour = "white", size = 3.5,
aes(label = ..count..),position=position_stack(vjust=0.5)) + theme_bw()
but it doesnt work as I want. The graph is OK but instead displaying the total number of observations of each type I want to display the number of each flag (so instead 2 for "B" type I want to display 1 and 1 because for "B" we have 1 observation with FLAG 1 and 1 observations with FLAG 0). What should I change?
With the interaction between Type and FLAG the bars display the counts per groups of both.
ggplot(df, aes(x = interaction(Type, FLAG))) +
geom_bar(stat = "count",
aes(fill = factor(FLAG)), position = "dodge") +
coord_flip() +
stat_count(geom = "text",
aes(label = ..count..),
position=position_stack(vjust=0.5),
colour = "white", size = 3.5) +
theme_bw()
You could replace the stat_count() and geom_bar() with a little pre-processing with count() and geom_col(). Here is an example:
df %>%
janitor::clean_names() %>%
count(type, flag) %>%
ggplot(aes(type, n, fill = as.factor(flag))) +
geom_col(position = "dodge") +
geom_text(aes(label = n, y = n - 0.05), color = "white",
position = position_dodge(width = 1)) +
scale_y_continuous(breaks = 0:3, limits = c(0,3)) +
labs(fill = "flag") +
coord_flip() +
theme_bw()
The only thing janitor::clean_names() does is transform variable names, from uppercase and spaces to lowercase and underscores, respectively.
Here's my data:
# Data:
mydf <- data.frame(
Species = rep(c("Ungulate","Ungulate","Elk","Elk","Rodent","Rodent","Deer","Deer"),
times = 3),
Space = rep(c("W", "C", "E"), each = 8),
Age = rep(c("Adult", "Juvenile"), times = 12),
value = c(0.03,0.17,0.02,0.23,0.33,0.00,0.05,0.12,0.04,0.28,0.09,0.23,0.17,0.00,0.13,
0.17,0.02,0.14,0.01,0.23,0.29,0.00,0.06,0.13))
mydf$spaceage <- as.factor(paste(mydf$Space, mydf$Age))
mydf
myPalette <- c("#f4a582", "#b2182b", "#92c5de", "#2166ac", "#a6dba0", "#1b7837")
For my plot:
example <- ggplot(mydf,
aes(x = factor(Space, levels = c("W", "C", "E")),
y = value,
fill = factor(spaceage))) +
geom_bar(stat = 'identity', position = 'stack') +
facet_grid(~ Species) +
scale_fill_manual(values = myPalette, name = "Age") + #legend
labs(x="") +
theme_bw()
example
Returns:
Is it possible to combine redundant legend items, so dark and light shades are combined, to produce something like this? (couldn't get very even sizes using paint):
I'm open to other ideas for making this legend more concise. Thanks for any advice!
You can assign "" as legend label to some legend entries in order to achieve the effect.
However, I would first of all caution to be very careful with the manual fill scale first, as you want to make sure each colour corresponds to the correct spaceage value before obscuring its label.
Here's an implementation:
# ensure correct mapping between colour & label
names(myPalette) <- levels(mydf$spaceage)
ggplot(mydf,
aes(x = factor(Space, levels = c("W", "C", "E")),
y = value,
fill = factor(spaceage))) +
# minor point, but geom_col() is equivalent to geom_bar(position = "identity"),
# and position = "stack" is default in both cases.
geom_col() +
facet_grid(~ Species) +
scale_fill_manual(values = myPalette, name = "Age",
# ensures colour order follows x-axis order
breaks = c("W Adult", "W Juvenile", "C Adult", "C Juvenile",
"E Adult", "E Juvenile"),
# comment out this line to verify that right colour
# is mapped to the right label
labels = c("", "", "", "", "Adult", "Juvenile"),
# specify 2 rows for legends
guide = guide_legend(nrow = 2, byrow = FALSE)) +
labs(x = "") +
theme_bw()
Create the legend, which is actually a ggplot2.
library(ggplot2)
ds_palette <- tibble::tibble(
fill = c("#a6dba0", "#1b7837", "#f4a582", "#b2182b", "#92c5de", "#2166ac"),
x = c(2, 1, 2, 1, 2, 1),
y = c(3, 3, 2, 2, 1, 1),
text = c("W", "W", "C", "C", "E", "E"),
text_color = c("black", "white", "black", "white", "black", "white")
)
legend_inset <- ggplot(ds_palette, aes(x=x, y=y, fill=fill)) +
geom_tile() +
geom_text(aes(label=text, color=text_color)) +
annotate("text", x=1, y=3.6, label="Juvenile", vjust=0) +
annotate("text", x=2, y=3.6, label="Adult", vjust=0) +
scale_color_identity() +
scale_fill_identity() +
coord_cartesian(ylim=c(0.5, 4), expand = F) +
theme_void() +
labs(x="")
Then put it all together. The objects in vpList define the proportions of the partitioned areas.
grid.newpage()
plot_width <- .8
tree <- vpTree(
viewport(w=1, h=1, name="A"),
vpList(
viewport(x=0, y=0 , w= plot_width, h=1 , just=c("left", "bottom"), name="bar_graph"),
viewport(x=1, y=.5, w=1-plot_width, h=0.3, just=c("right", "top") , name="legend")
)
)
pushViewport(tree)
print(example , vp = "bar_graph")
print(legend_inset, vp = "legend")
I rotated your 2x3 legend so the words would be more space-efficient.
You can add labels and change the position of your legend as the code below.
example<-ggplot(mydf, aes(x = factor(Space, levels=c("W", "C", "E")), y = value, fill = factor(spaceage))) +
geom_bar(stat = 'identity', position = 'stack') + facet_grid(~ Species) +
scale_fill_manual(values = myPalette,name = "Age",labels=c("Adult","Juvenile","Adult","Juvenile","Adult","Juvenile")) + #legend
labs(x="") +
theme(legend.position = "top")
example
The result looks like below.