The same width of the bars in geom_bar(position = "dodge") - r

I would like to draw plot with the same width of the bars. Here's my minimal example code:
data <- data.frame(A = letters[1:17],
B = sample(1:500, 17),
C = c(rep(1, 5), rep(2, 6), rep(c(3,4,5), each = 2)))
ggplot(data,
aes(x = C, y = B, label = A,
fill = A)) +
geom_bar(stat = "identity", position = "dodge") +
geom_text(position = position_dodge(width = 0.9), angle = 90)
The result is shown in the picture above:
The width of the bars is dependent on numbers of observation in group given in variable C. I want to have each bar to have the same width.
The facet_grid(~C) works (bars are the same width) it's not what I mean:
ggplot(data,
aes(x = C, y = B, label = A,
fill = A)) +
geom_bar(stat = "identity", position = "dodge") +
geom_text(position = position_dodge(width = 0.9), angle = 90) +
facet_grid(~C)
What I want is to have plot like in the first picture but with bars's width independent on number of observation in each level from column C. How can I do it?
[EDIT] geom_bar(width) changes width of the bars'group but still bars in fifth group are wider than in the first group, so it's not the answer to my question.

Update
Since ggplot2_3.0.0 version you are now be able to use position_dodge2 with preserve = c("total", "single")
ggplot(data,aes(x = C, y = B, label = A, fill = A)) +
geom_col(position = position_dodge2(width = 0.9, preserve = "single")) +
geom_text(position = position_dodge2(width = 0.9, preserve = "single"), angle = 90, vjust=0.25)
Original answer
As already commented you can do it like in this answer:
Transform A and C to factors and add unseen variables using tidyr's complete. Since the recent ggplot2 version it is recommended to use geom_col instead of geom_bar in cases of stat = "identity":
data %>%
as.tibble() %>%
mutate_at(c("A", "C"), as.factor) %>%
complete(A,C) %>%
ggplot(aes(x = C, y = B, fill = A)) +
geom_col(position = "dodge")
Or work with an interaction term:
data %>%
ggplot(aes(x = interaction(C, A), y = B, fill = A)) +
geom_col(position = "dodge")
And by finally transforming the interaction to numeric you can setup the x-axis according to your desired output. By grouping (group_by) you can calculate the matching breaks. The fancy stuff with the {} around the ggplot argument is neseccary to directly use the vaiables Breaks and C within the pipe.
data %>%
mutate(gr=as.numeric(interaction(C, A))) %>%
group_by(C) %>%
mutate(Breaks=mean(gr)) %>%
{ggplot(data=.,aes(x = gr, y = B, fill = A, label = A)) +
geom_col(position = "dodge") +
geom_text(position = position_dodge(width = 0.9), angle = 90 ) +
scale_x_continuous(breaks = unique(.$Breaks),
labels = unique(.$C))}
Edit:
Another approach would be to use facets. Using space = "free_x" allows to set the width proportional to the length of the x scale.
library(tidyverse)
data %>%
ggplot(aes(x = A, y = B, fill = A)) +
geom_col(position = "dodge") +
facet_grid(~C, scales = "free_x", space = "free_x")
You can also plot the facet labels on the bottom using switch and remove x axis labels
data %>%
ggplot(aes(x = A, y = B, fill = A)) +
geom_col(position = "dodge") +
facet_grid(~C, scales = "free_x", space = "free_x", switch = "x") +
theme(axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
strip.background = element_blank())

Related

How to control the bar width of a grouped bar chart in ggplot2? [duplicate]

I would like to draw plot with the same width of the bars. Here's my minimal example code:
data <- data.frame(A = letters[1:17],
B = sample(1:500, 17),
C = c(rep(1, 5), rep(2, 6), rep(c(3,4,5), each = 2)))
ggplot(data,
aes(x = C, y = B, label = A,
fill = A)) +
geom_bar(stat = "identity", position = "dodge") +
geom_text(position = position_dodge(width = 0.9), angle = 90)
The result is shown in the picture above:
The width of the bars is dependent on numbers of observation in group given in variable C. I want to have each bar to have the same width.
The facet_grid(~C) works (bars are the same width) it's not what I mean:
ggplot(data,
aes(x = C, y = B, label = A,
fill = A)) +
geom_bar(stat = "identity", position = "dodge") +
geom_text(position = position_dodge(width = 0.9), angle = 90) +
facet_grid(~C)
What I want is to have plot like in the first picture but with bars's width independent on number of observation in each level from column C. How can I do it?
[EDIT] geom_bar(width) changes width of the bars'group but still bars in fifth group are wider than in the first group, so it's not the answer to my question.
Update
Since ggplot2_3.0.0 version you are now be able to use position_dodge2 with preserve = c("total", "single")
ggplot(data,aes(x = C, y = B, label = A, fill = A)) +
geom_col(position = position_dodge2(width = 0.9, preserve = "single")) +
geom_text(position = position_dodge2(width = 0.9, preserve = "single"), angle = 90, vjust=0.25)
Original answer
As already commented you can do it like in this answer:
Transform A and C to factors and add unseen variables using tidyr's complete. Since the recent ggplot2 version it is recommended to use geom_col instead of geom_bar in cases of stat = "identity":
data %>%
as.tibble() %>%
mutate_at(c("A", "C"), as.factor) %>%
complete(A,C) %>%
ggplot(aes(x = C, y = B, fill = A)) +
geom_col(position = "dodge")
Or work with an interaction term:
data %>%
ggplot(aes(x = interaction(C, A), y = B, fill = A)) +
geom_col(position = "dodge")
And by finally transforming the interaction to numeric you can setup the x-axis according to your desired output. By grouping (group_by) you can calculate the matching breaks. The fancy stuff with the {} around the ggplot argument is neseccary to directly use the vaiables Breaks and C within the pipe.
data %>%
mutate(gr=as.numeric(interaction(C, A))) %>%
group_by(C) %>%
mutate(Breaks=mean(gr)) %>%
{ggplot(data=.,aes(x = gr, y = B, fill = A, label = A)) +
geom_col(position = "dodge") +
geom_text(position = position_dodge(width = 0.9), angle = 90 ) +
scale_x_continuous(breaks = unique(.$Breaks),
labels = unique(.$C))}
Edit:
Another approach would be to use facets. Using space = "free_x" allows to set the width proportional to the length of the x scale.
library(tidyverse)
data %>%
ggplot(aes(x = A, y = B, fill = A)) +
geom_col(position = "dodge") +
facet_grid(~C, scales = "free_x", space = "free_x")
You can also plot the facet labels on the bottom using switch and remove x axis labels
data %>%
ggplot(aes(x = A, y = B, fill = A)) +
geom_col(position = "dodge") +
facet_grid(~C, scales = "free_x", space = "free_x", switch = "x") +
theme(axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
strip.background = element_blank())

ggplot piecharts on a ggmap: labels destroy the small plots

My ggmap on which I would like small piecharts with labels is generated with the code:
p <-
get_googlemap(
"Poland",
maptype = "roadmap",
zoom = 6,
color = "bw",
crop = T,
style = 'feature:all|element:labels|visibility:off' #'feature:administrative.country|element:labels|visibility:off' or 'feature:all|element:labels|visibility:off'
) %>%
ggmap() + coord_cartesian() +
scale_x_continuous(limits = c(14, 24.3), expand = c(0, 0)) +
scale_y_continuous(limits = c(48.8, 55.5), expand = c(0, 0))
I am trying to plot my small ggplot piecharts on a ggmap following the answer
R::ggplot2::geom_points: how to swap points with pie charts?
I prepare data as follows:
df <-
df %>% mutate(Ours = Potential * MS, Others = Potential - Ours) %>%
na.omit() %>% filter(Potential > 0) %>%
select(-L.p., -MS) %>%
group_by(Miasto) %>%
summarise_each_(vars = c("Potential", "Ours", "Others"),
funs = funs(Sum = "sum")) %>%
left_join(coordinatesTowns, by = c("Miasto" = "address")) %>%
distinct(Miasto, .keep_all = T) %>%
select(-X) %>% ungroup()
df <-df %>% gather(key=component, value=sales, c(Ours_Sum,Others_Sum)) %>%
group_by(lon, lat,Potential_Sum)
My data looks then like
tibble::tribble(
~Miasto, ~Potential_Sum, ~lon, ~lat, ~component, ~sales,
"Bialystok", 100, 23.16433, 53.13333, "Ours_Sum", 70,
"Bialystok", 100, 23.16433, 53.13333, "Others_Sum", 30,
"Bydgoszcz", 70, 18.00762, 53.1235, "Ours_Sum", 0,
"Bydgoszcz", 70, 18.00762, 53.1235, "Others_Sum", 70,
"Gdansk", 50, 18.64637, 54.35205, "Ours_Sum", 25,
"Gdansk", 50, 18.64637, 54.35205, "Others_Sum", 75,
"Katowice", 60, 19.02754, 50.25842, "Ours_Sum", 20,
"Katowice", 60, 19.02754, 50.25842, "Others_Sum", 40
)
The last line group_by is essential for generating plots that will be pasted into my map. (I suspected maybe here is the reason of my problems described below).
Instead of totals, I would like to provide labels for each share in a piechart
In this answer I found the syntax, that should add labels to the piecharts https://stackoverflow.com/a/22804400/3480717
Below is the syntax in my script the line with geom_text (commented with hash) if uncommented, causes my plots to disappear and a long list (16 entries) for all small plots, of warnings:
1: Removed 1 rows containing missing values (geom_col).
I presume the reason can be in the last line of preparing the data, grouping it for the plotting.
The line I mark with a hash is a problem. If I put the hash plots are correct, if I include it, trying to get the desired labels on the slices, plots disappear or are very narrow vertical slices.
df.grobs <- df %>%
do(subplots = ggplot(., aes(1, sales, fill = component)) +
geom_bar(position = "fill", alpha = 0.5, colour = "white", stat="identity") +
# geom_text( aes(label = round(sales), y=sales), position = position_stack(vjust = 0.5), size = 2.5) +
coord_polar(theta = "y") +
scale_fill_manual(values = c("green", "red"))+
theme_void()+ guides(fill = F)) %>%
mutate(subgrobs = list(annotation_custom(ggplotGrob(subplots),
x = lon-Potential_Sum/300, y = lat-Potential_Sum/300,
xmax = lon+Potential_Sum/300, ymax = lat+Potential_Sum/300)))
df.grobs
df.grobs %>%
{p +
.$subgrobs +
geom_col(data = df,
aes(0,0, fill = component),
colour = "white")+ geom_text(data=df, aes(label = Miasto),nudge_y = -0.15, size=2.5)}
Why is the line marked with a hash (if uncommented) destroying the plot instead of adding labels? It seems to completely redefine aesthetics.
EDIT: I modified the marked line, now label=sales and y=sales. Now if I comment the line, the plots are generated, if I uncomment it, the labels are generated in correct position but without plots. Why I cannot have both?
Short answer:
I think the problem is actually in your earlier line:
geom_bar(position = "fill", alpha = 0.5, colour = "white", stat="identity") +
If you change the position from fill to stack (i.e. the default), it should work properly (at least it did on mine).
Long(-winded) explanation:
Let's use a summarised version of the mtcars dataset to reproduce the problem:
dfm <- mtcars %>% group_by(cyl) %>% summarise(disp = mean(disp)) %>% ungroup()
# correct pie chart
ggplot(dfm, aes(x = 1, y = disp, label = factor(cyl), fill = factor(cyl))) +
geom_bar(stat = "identity", position = "stack") +
geom_text(position = position_stack(vjust = 0.5)) +
coord_polar(theta = "y") + theme_void()
# "empty" pie chart
ggplot(dfm, aes(x = 1, y = disp, label = factor(cyl), fill = factor(cyl))) +
geom_bar(stat = "identity", position = "fill") +
geom_text(position = position_stack(vjust = 0.5)) +
coord_polar(theta = "y") + theme_void()
Why does changing geom_bar's position affect this? If we look at the plot before the coord_polar step, things may become clearer:
ggplot(dfm, aes(x = 1, y = disp, label = factor(cyl), fill = factor(cyl))) +
geom_bar(stat = "identity", position = "stack") +
geom_text(position = position_stack(vjust = 0.5))
Check the bar chart's y-axis. The bars & the labels are correctly positioned.
Now the version with position = "fill":
ggplot(dfm, aes(x = 1, y = disp, label = factor(cyl), fill = factor(cyl))) +
geom_bar(stat = "identity", position = "fill") +
geom_text(position = position_stack(vjust = 0.5))
Your bar chart now occupies the range 0-1 on the y-axis, while your labels continue to occupy the original full range, which is much larger. Thus when you convert the chart to polar coordinates, the bar chart is squeezed to a tiny slice that becomes practically invisible.

ggplot anotate when x values are characters

I would like to 'annotate' a text on the top right hand corner of ggplot2 bar chart that has character for x axis and numeric for y axis. All the documentation I see is that, to annotate a text, both x and y coordinates have to be given numeric value.
Here is an example chart:-
Here is the data frame
df1 <- data.frame( p=c("a","b","c","a","b","c"),
v=c(10,9,8,6,5,2),
u=c("aa","bb","cc","aa","bb","cc")
)
summarized data frame
df2 <- df1 %>% select(p, v) %>% group_by(p) %>% summarise_each(funs(sum))
bar plot
p <- ggplot(data = df2, aes(p, v, label = v)) +
geom_bar(stat = "identity", position = "dodge") +
geom_text(position = position_dodge(.9), vjust = -1, fontface = "bold", size = 5)
p
You should be able to do it just putting the location inside of aes(). This worked for me (unless I am misunderstanding your intent):
ggplot(data = df2, aes(p, v, label = v)) +
geom_bar(stat = "identity", position = "dodge") +
geom_text(position = position_dodge(.9), vjust = -1, fontface = "bold", size = 5) +
geom_text(aes(x = "c", y = 15, label = "Here I am"))

stacked bar *bringing labels to the graph *

I'm plotting a stacked bar graph and use geom_text to insert the value and name of each stack. The problem is some stacks are very small/narrow, so that the text of two stacks overlap each other and hence is not very readable. How can I modify the code to solve this issue.
Type<-c("ddddddddddd","ddddddddddd","bbbbbbbbbbbbb","ddddddddddd","eeeeeeeeeeeeee","bbbbbbbbbbbbb","ddddddddddd","bbbbbbbbbbbbb","ddddddddddd",
"eeeeeeeeeeeeee","mmmmmmmmmmmmmmmmmmm","bbbbbbbbbbbbb","ddddddddddd","bbbbbbbbbbbbb","eeeeeeeeeeeeee")
Category<-c("mmmmm","mmmmm","gggggggggggggggggg","ffffffffffff","ffffffffffff","ffffffffffff","sanddddddddd","sanddddddddd","yyyyyyyyyyy",
"yyyyyyyyyyy","yyyyyyyyyyy","sssssssssssssss","sssssssssssssss","sssssssssssssss","ttttttttttttt")
Frequency<-c(4,1,30,7,127,11,1,1,6,9,1,200,3,4,5)
Data <- data.frame(Type, Category, Frequency)
p <- ggplot(Data, aes(x = Type, y = Frequency)) +
geom_bar(aes(fill = Category), stat="identity", show.legend = FALSE) +
geom_text(aes(label = Frequency), size = 3) +
geom_text(aes(label = Category), size = 3)
Considering your data, a facetted plot might be a better approach:
# summarise your data
library(dplyr)
d1 <- Data %>%
mutate_each(funs(substr(.,1,2)),Type,Category) %>%
group_by(Type,Category) %>%
summarise(Freq = sum(Frequency)) %>%
mutate(lbl = paste(Category,Freq)) # create a label by pasting the 'Category' and the 'Freq' variables together
# plot
ggplot(d1, aes(x = Category, y = Freq, fill = Category)) +
geom_bar(stat="identity", width = 0.7, position = position_dodge(0.8)) +
geom_text(aes(label = lbl), angle = 90, size = 5, hjust = -0.1, position = position_dodge(0.8)) +
scale_y_continuous(limits = c(0,240)) +
guides(fill = FALSE) +
facet_grid(.~Type, scales = "free", space = "free") +
theme_bw(base_size = 14)
which gives:
In the above plot I shortened the labels on purpose. If you don't want to do that, you could consider this:
d2 <- Data %>%
group_by(Type,Category) %>%
summarise(Freq = sum(Frequency)) %>%
mutate(lbl = paste(Category,Freq))
ggplot(d2, aes(x = Category, y = Freq, fill = Category)) +
geom_bar(stat="identity", width = 0.7, position = position_dodge(0.8)) +
geom_text(aes(y = 5, label = lbl), alpha = 0.6, angle = 90, size = 5, hjust = 0, position = position_dodge(0.8)) +
scale_y_continuous(limits = c(0,240)) +
guides(fill = FALSE) +
facet_grid(.~Type, scales = "free", space = "free") +
theme_bw(base_size = 14) +
theme(axis.text.x = element_blank(),
axis.ticks.x = element_blank())
which gives:

Create abbreviated legends manually for long X labels in ggplot2

I would like to create a simple bar chart with ggplot2 and my problem is that my x variable contains long strings so the labels are overlaid.
Here are fake datas and the plot :
library(dplyr)
library(tidyr)
library(ggplot2)
set.seed(42)
datas <- data.frame(label = sprintf("aLongLabel%d", 1:8),
ok = sample(seq(0, 1, by = 0.1), 8, rep = TRUE)) %>%
mutate(err = abs(ok - 1)) %>%
gather(type, freq, ok, err)
datas %>%
ggplot(aes(x = label, y = freq)) +
geom_bar(aes(fill = type), stat = "identity")
I would like to replace the labels by shorter ones and create a legend to show the matches.
What I've tried :
I use the shape aes parameter in geo_point which will create a legend with shapes (and plots shapes that I hide with alpha = 0). Then I change the shapes with scale_shape_manual and replace the x labels with scale_x_discrete. With guides I override the alpha parameter of my shapes so they wont be invisible in the legend.
leg.txt <- levels(datas$label)
x.labels <- structure(LETTERS[seq_along(leg.txt)],
.Names = leg.txt)
datas %>%
ggplot(aes(x = label, y = freq)) +
geom_bar(aes(fill = type), stat = "identity") +
geom_point(aes(shape = label), alpha = 0) +
scale_shape_manual(name = "Labels", values = x.labels) +
guides(shape = guide_legend(override.aes = list(size = 5, alpha = 1))) +
scale_x_discrete(name = "Label", labels = x.labels)
It gives me the expected output but I feel like this is very hacky.
Does ggplot2 provides a way to do this more directly ? Thanks.
Rotation solution suggested by Pascal
Rotate the labels and align them to the edge :
datas %>%
ggplot(aes(x = label, y = freq)) +
geom_bar(aes(fill = type), stat = "identity") +
theme(axis.text.x = element_text(angle = 90, hjust = 1))

Resources