Control Column Widths in a Ggplot2 Graph with a Series and Inconsistent Data

Control column widths in a ggplot2 graph with a series and inconsistent data

At the expense of doing your own calculation for the x coordinates of the bars as shown below, you can get a chart which may be close to what you're looking for.

x <- c("1","2","3","1","2","3","4")
s <- c("No","No","No","Yes","Yes","Yes","Yes")
y <- c(1,2,3,2,3,4,5)
df <- data.frame(cbind(x,s,y) )
df$x_pos[order(df$x, df$s)] <- 1:nrow(df)
x_stats <- as.data.frame.table(table(df$x), responseName="x_counts")
x_stats$center <- tapply(df$x_pos, df$x, mean)
df <- merge(df, x_stats, by.x="x", by.y="Var1", all=TRUE)
bar_width <- .7
df$pos <- apply(df, 1, function(x) {xpos=as.numeric(x[4])
if(x[5] == 1) xpos
else ifelse(x[2]=="No", xpos + .5 - bar_width/2, xpos - .5 + bar_width/2) } )
print(df)
gg <- ggplot(data=df, aes(x=pos, y=y, fill=s ) )
gg <- gg + geom_bar(position="identity", stat="identity", size=.3, colour="black", width=bar_width)
gg <- gg + scale_x_continuous(breaks=df$center,labels=df$x )
plot(gg)

----- edit --------------------------------------------------

Modified to place the labels at the center of bars.

Gives the following chart

Sample Image

Changing legend properties in ggplot2

I'm not entirely sure what you mean by: "I have already applied a fill function elsewhere". Since fill can appear just once per graph, you could incorporate breaks and labels into your "other fill function", which you are not displaying here.

The code below does what you want for the example you provided:

library(ggplot2)
# Create data
when <- as.factor(c(rep(c("Before","After"),each = 3),
rep("During", 2),"AEmpty"))
site <- as.factor(c(rep(c("a","b","c"), 2),
"b","a","c"))
mean <- c(6,4.5,4.5,1.75,3.25,2.5,3,0,0)
std_error <- c(2.31,1.04,0.5,0.63,1.70,1.32,1.53,NA,NA)
df <- data.frame(when,site,mean,std_error)

# Plot
limits <- aes(ymax = mean + std_error, ymin=mean-std_error)

ggplot(df, aes(site,mean,fill=when)) +
geom_bar(stat = "identity", position = position_dodge()) +
geom_errorbar(limits, width = 0.25, position = position_dodge(width = 0.9)) +
scale_fill_manual(values = c("AEmpty" = "grey1",
"After" = "grey15",
"Before" = "grey55",
"During" = "grey75"),
breaks = c("After", "Before", "During"),
labels = c("After", "Before", "During"))

Sample Image

To choose shades of grey, consult this list of R-colours.

Is it possible to specify the size / layout of a single plot to match a certain grid in R?

Another option is to draw the three components as separate plots and stitch them together in the desired ratio.

The below comes quite close to the desired ratio, but not exactly. I guess you'd need to fiddle around with the values given the exact saving dimensions. In the example I used figure dimensions of 7x3.5 inches (which is similar to 18x9cm), and have added the black borders just to demonstrate the component limits.

library(tidyverse)
library(patchwork)
data <- midwest %>%
head(5) %>%
select(2,23:25) %>%
pivot_longer(cols=2:4,names_to="Variable", values_to="Percent") %>%
mutate(Variable=factor(Variable, levels=c("percbelowpoverty","percchildbelowpovert","percadultpoverty"),ordered=TRUE))

p1 <-
ggplot(data=data, mapping=aes(x=county, y=Percent, fill=Variable)) +
geom_col() +
scale_fill_manual(values = c("#CF232B","#942192","#000000"))

p_legend <- cowplot::get_legend(p1)
p_main <- p1 <-
ggplot(data=data, mapping=aes(x=county, y=Percent, fill=Variable)) +
geom_col(show.legend = FALSE) +
scale_fill_manual(values = c("#CF232B","#942192","#000000"))

p_main + plot_spacer() + p_legend +
plot_layout(widths = c(12.5, 1.5, 4)) &
theme(plot.margin = margin(),
plot.background = element_rect(colour = "black"))

Sample Image

Created on 2021-04-02 by the reprex package (v1.0.0)

update

My solution is only semi-satisfactory as pointed out by the OP. The problem is that one cannot (to my knowledge) define the position of the grob in the third panel.

Other ideas for workarounds:

  • One could determine the space needed for text (but this seems not so easy) and then to size the device accordingly
  • Create a fake legend - however, this requires the tiles / text to be aligned to the left with no margin, and this can very quickly become very hacky.

In short, I think teunbrand's solution is probably the most straight forward one.

Update 2

The problem with the left alignment should be fixed with Stefan's suggestion in this thread

Updating color fill effects and widths in a ggplot barplot

There are several ways to fix the bar widths. This is happening as you don't have any data for some of the factor combinations, and ggplot dumps them.

I think the easiest way is to use complete from tidyr so that we have all combinations of factors:

library(tidyr)
df <- df %>% complete(when, site, fill = list(0))

This will make all non existing factor combinations as 0.

To fix the colours we can force ggplot to use grey-scale using scale_fill_grey().

Sample Image
Here's the complete code:

library(tidyr)
library(plotrix)
library(dplyr)
library(ggplot2)
grouped_df <- df %>% complete(when, site, fill = list(0)) %>%
group_by(when, site) %>%
summarise_each(funs(mean = mean, std_error = std.error))
limits <- aes(ymax = mean + std_error, ymin = mean - std_error)
g <- ggplot(summarised_df, aes(site, mean, fill = when)) +
geom_bar(stat = "identity", position = position_dodge()) +
geom_errorbar(limits, width = 0.25, position = position_dodge(width = 0.9)) +
scale_fill_grey()
g

Joining list of nest/ggplot2 generated images to two columns with consistent proportions

you could try setting the panel size to fixed dimensions and then arranging the gtables together,

library(egg)
library(gridExtra)
lg <- purrr::map2(plots$plots, plots$heights,
function(p,h) gtable_frame(ggplotGrob(p),
height =unit(h/10,'npc'), #tweak
width =unit(0.7,'npc'))) #tweak

grid.arrange(gtable_rbind(lg[[1]],lg[[2]], egg::.dummy_gtable),
gtable_rbind(lg[[3]],lg[[4]], egg::.dummy_gtable), ncol=2)

(tested with set.seed(12); I don't know what sample() OP had)

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

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)

Sample Image

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")

Sample Image

Or work with an interaction term:

data %>% 
ggplot(aes(x = interaction(C, A), y = B, fill = A)) +
geom_col(position = "dodge")

Sample Image

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))}

Sample Image

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")

Sample Image

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())

Sample Image

Grouped bar plot column width uneven due to no data

There are two ways that this can be done.

  1. If you are using the latest version of ggplot2(from 2.2.1 I believe), there is a parameter called preserve in the function position_dodge which preserves the vertical position and adjust only the horizontal position. Here is the code for it.

Code:

import(ggplot2)
plot = ggplot(Checkouts, aes(fill=Checkouts$State, x=Checkouts$Month, y=Checkouts$Income)) +
geom_bar(colour = "black", stat = "identity", position = position_dodge(preserve = 'single'))

  1. Another way is to precompute and add dummy rows for each of the missing. using table is the best solution.

Specify widths and heights of plots with grid.arrange

Try plot_grid from the cowplot package:

library(ggplot2)
library(gridExtra)
library(cowplot)
p1 <- qplot(mpg, wt, data=mtcars)
p2 <- p1
p3 <- p1 + theme(axis.text.y=element_blank(), axis.title.y=element_blank())
plot_grid(p1, p2, p3, align = "v", nrow = 3, rel_heights = c(1/4, 1/4, 1/2))

Sample Image



Related Topics



Leave a reply



Submit