I wanted to make a barplot like the figure a of this one that I found in a publication with something like a table tab showing some information outside the plot using ggplot. I found this general approach of adding text outside of a plot using gridExtra.
However, my question is how to align the height of each row of the table tab to each bar of the barplot so they match?
Here is an example. I wanted to add the note as a table tab on the right of the barplot.
library(ggplot2)
library(gridExtra)
df <- data.frame(Model = c("Datsun 710","Duster 360","Hornet 4 Drive","Hornet
Sportabout","Mazda RX4 Wag","Merc 230","Merc 240D","Valiant"),
logFC = c(1.879,1.552,1.360,1.108,-2.407,-2.416,-2.670,-3.061),
Note = c("ModelA","ModelB","ModelC","ModelD","ModelE","ModelF","ModelG","ModelH"))
plot <- ggplot(df, aes(Model, logFC)) +
geom_bar(stat="identity") +
coord_flip() +
theme_bw() +
ggtitle("Data for cars") +
theme(plot.title = element_text(size = 18, hjust = 0.5))
tab <- as.data.frame(
c(Note = df$Note))
rownames(tab) <- NULL
p_tab <- tableGrob(unname(tab))
grid.arrange(plot, p_tab, ncol = 2)
Per Gregor's comment, this works for me:
plot <- ggplot(df, aes(Model, logFC, label=Note)) +
geom_bar(stat="identity") +
coord_flip(clip = "off") +
theme_bw() +
ggtitle("Data for cars") +
theme(plot.title = element_text(size = 18, hjust = 0.5))+
geom_text(y = 3,
hjust = 0,
size = 5) +
theme(plot.margin = unit(c(1,10,1,1), "lines"),
panel.border=element_blank(),
axis.line = element_line(),
panel.grid.major=element_blank(),
panel.grid.minor = element_blank())
Related
I'm trying to recreate a bar graph found on page 4 of the following report:
The figure has three bars with the first two stacked and the third dodged next to it. I've seen iterations of this question but none that recreate the figure in this exact way.
Here is the data:
a <- rep(c('RHB', 'FERS', 'CSRS'), 3)
b <- c(rep('Assets', 3), rep('Amount Past Due', 3),
rep('Actuarial Liability', 3))
c <- c(45.0, 122.5, 152.3, 47.2, 3.4, 4.8, 114.4, 143.4, 181.3)
df <- data.frame(a,b,c)
names(df) <- c('Fund', 'Condition', 'Value')
And what I've managed so far:
p <- ggplot(subset_data, aes(fill=Condition, y=Value, x=Fund)) +
geom_bar(position="stack", stat="identity") +
coord_flip()
I'm not partial to ggplot so if there's another tool that works better I'm ok using another package.
Taking some ideas from the link #aosmith posted.
You can call geom_bar twice, once with Assets and Amounts Past Due stacked, and again with just Actuarial Liability.
You can use width to make the bars thinner, then nudge one set of bars so the two geom_bar calls are not overlapping. I chose to make the width 0.3 and nudge by 0.3 so the edges just line up. If you nudge by more you will see a gap between the two bars.
Edit: add some more formatting and numeric labels
library(tidyverse)
library(scales)
df_al <- filter(df, Condition == 'Actuarial Liability')
df_xal <- filter(df, Condition != 'Actuarial Liability')
bar_width <- 0.3
hjust_lab <- 1.1
hjust_lab_small <- -0.2 # hjust for labels on small bars
ggplot() +
theme_classic() +
geom_bar(data = df_al,
aes(fill=Condition, y=Value, x=Fund),
position = position_nudge(x = -bar_width),
width = bar_width,
stat="identity") +
geom_bar(data = df_xal,
aes(fill=Condition, y=Value, x=Fund),
position="stack",
stat="identity",
width = bar_width) +
geom_text(data = df_al,
aes(label= dollar(Value, drop0trailing = TRUE), y=Value, x=Fund),
position = position_nudge(x = -bar_width),
hjust = hjust_lab) +
geom_text(data = df_xal,
aes(label= dollar(Value, drop0trailing = TRUE), y=Value, x=Fund),
position="stack",
hjust = ifelse(df_xal$Value < 5, hjust_lab_small, hjust_lab)) +
scale_fill_manual(values = c('firebrick3', 'lightsalmon', 'dodgerblue')) +
scale_y_continuous(breaks = seq(0,180, by = 20), labels = dollar) +
coord_flip() +
labs(x = NULL, y = NULL, fill = NULL) +
theme(legend.position = "bottom")
I think I would use the "sneaky facet" method, after adding a dummy variable to dodge the columns and making Fund a factor with the correct order:
df$not_liability <-df$Condition != "Actuarial Liability"
df$Fund <- factor(df$Fund, levels = c('RHB', 'FERS', 'CSRS'))
Most of the plotting code is then an attempt to copy the look of the supplied plot:
ggplot(df, aes(fill=Condition, y=Value, x=not_liability)) +
geom_bar(position = "stack", stat = "identity") +
scale_x_discrete(expand = c(0.5, 0.5)) +
scale_y_continuous(breaks = 0:10 * 20, labels = scales::dollar) +
coord_flip() +
facet_grid(Fund~., switch = "y") +
scale_fill_manual(values = c("#c00000", "#f7c290", "#0071bf"), name = "") +
theme_classic() +
theme(panel.spacing = unit(0, "points"),
strip.background = element_blank(),
axis.text.y = element_blank(),
axis.ticks.length.y = unit(0, "points"),
axis.title = element_blank(),
strip.placement = "outside",
strip.text = element_text(),
legend.position = "bottom",
panel.grid.major.x = element_line())
This question already has answers here:
Legend placement, ggplot, relative to plotting region
(4 answers)
Closed 3 years ago.
I would like my legend to be in this order: Positive, Neither, Negative, Cannot_Say. Additionally, I'm having difficulties moving it vertically closer and horizontally centered to the chart itself.
Here is my code:
#Load libraries
library(tidyverse)
library(ggplot2)
#Set working directory
setwd("~/Documents/Folder/")
#Create dataframe
dat <- data.frame(Film = c("The Road", "Cloverfield", "A Quiet Place"), Positive = c(6, 4, 6), Neither = c(21, 9, 13), Negative = c(54, 74, 69), Cannot_Say = c(19, 13, 12))
dat$Film <- factor(dat$Film, levels = c("A Quiet Place", "Cloverfield", "The Road"))
#Generate stacked bar chart
dat %>%
gather(Opinion, value, -Film) %>%
ggplot(aes(x = Film, y = value, group = Opinion, fill = Opinion)) + coord_flip() +
#Adjust the bar width
geom_bar(stat = "identity", width = 0.75) + theme(panel.background = element_blank()) +
#Establish chart attributes
ggtitle("Opinion of Post-Apocalyptic Films") + xlab("") + ylab("") +
theme(plot.title = element_text(face = "bold", family = "Open Sans", size = "14")) +
theme(plot.title = element_text(vjust = -0.25)) +
theme(plot.title = element_text(hjust = -0.4)) +
theme(axis.ticks = element_blank()) +
theme(axis.text = element_text(size = 12, color = "black", family = "Open Sans")) +
#Set colors
scale_fill_manual(values=c("#0A2240", "#981B1E", "#C1A783", "#008000")) +
#Adjust legend
theme(legend.position="bottom") +
theme(legend.title = element_blank()) +
theme(legend.text = element_text(size = 10)) +
theme(legend.spacing.x = unit(0.25, "inches")) +
#Center the plot title
theme(axis.text.y = element_text(margin = margin(r = -10))) +
scale_y_continuous(labels = function(x) paste0(x, "%")) +
#Remove y-axis label and tick marks
theme(axis.title.y=element_blank(),
axis.text.x=element_blank(),
axis.ticks.y=element_blank())
I'm getting the following graphic:
Opinion of Post-Apocalyptic Films
I belive that there is no way per se to move it closer, you can change the position or alternatively change the size of the legend to make it larger in a sense bringing it closer to the plot. Alternatively you can position the legend in the plot using p + theme(legend.position = c(0.8, 0.2))
p being your base plot code, colour etc.
I am creating some maps and want to remove all margins between the plot region and panel border.
This is the minimal example to reproduce my question
library(ggplot2)
library(grid)
df <- expand.grid(list(x = seq(1, 10), y = seq(1, 10), z = seq(1, 2)))
p <- ggplot(df) + geom_tile(aes(x, y)) + facet_wrap(~z)
p <- p + theme_minimal() + xlab('') + ylab('')
p <- p + theme(axis.text = element_blank(),
panel.grid = element_blank(),
axis.ticks = element_blank(),
panel.border = element_rect(colour = 'black', fill = 'transparent'),
panel.margin = unit(0, 'mm'))
p + ylim(2, 6) + xlim(2, 6)
This is the result of my codes.
How could I remove all white areas in the figure above? Thanks for any suggestions.
(Alright, here's my comment as an answer..)
Just add the following to the plot:
+ scale_y_continuous(expand = c(0,0)) + scale_x_continuous(expand = c(0,0))
I need to gather two facet columns into one column with ggplot2.
In the following example, I need to overlay the content of the two columns DEG and RAN into one, while giving different colours to DEG and RAN data (small points and smooth line) and provide the corresponding legend (so I can distinguish them as they are overlayed).
I feel my code is not too, too far from what I need, but the relative complexity of the dataset blocks me. How to go about achieving this in ggplot2?
Here's my code so far:
require(reshape2)
library(ggplot2)
library(RColorBrewer)
fileName = paste("./4.csv", sep = "") # csv file available here: https://www.dropbox.com/s/bm9hd0t5ak74k89/4.csv?dl=0
mydata = read.csv(fileName,sep=",", header=TRUE)
dataM = melt(mydata,c("id"))
dataM = cbind(dataM,colsplit(dataM$variable,pattern = "_",names = c("NM", "ORD", "CAT")))
dataM$variable <- NULL
dataM <- dcast(dataM, ... ~ CAT, value.var = "value")
my_palette <- colorRampPalette(rev(brewer.pal(11, "Spectral")))
ggplot(dataM, aes(x=NR ,y= ASPL)) +
geom_point(size = .4,alpha = .5) +
stat_smooth(se = FALSE, size = .5) +
theme_bw() +
theme(plot.background = element_blank(),
axis.line = element_blank(),
legend.key = element_blank(),
legend.title = element_blank()) +
scale_y_continuous("ASPL", expand=c(0,0), limits = c(1, 7)) +
scale_x_continuous("NR", expand=c(0,0), limits = c(0, 100)) +
theme(legend.position="bottom") +
theme(axis.title.x = element_text(vjust=-0.3, face="bold", size=12)) +
theme(axis.title.y = element_text(vjust=1.5, face="bold", size=12)) +
ggtitle("Title") + theme(plot.title = element_text(lineheight=.8, face="bold")) +
theme(title = element_text(vjust=2)) +
facet_grid(NM ~ ORD)
Here's what it gives me right now:
Extra question: how come DEG/SF doesn't show a smooth line?
You can use the group aesthetic to define that data points with the same value of ORD belong together. You can also map aesthetics shape and color to this variable. You can also use . to specify that the facets are not split along a specific dimension.
I have made the changes to your code below after transforming NR and ASPL to numeric variables:
dataM$NR <- as.integer(dataM$NR)
dataM$ASPL <- as.numeric(dataM$ASPL)
ggplot(dataM, aes(x=NR ,y= ASPL, group=ORD, color=ORD)) +
geom_point(size = .7,alpha = .5, aes(shape=ORD)) + ## increased size
stat_smooth(se = FALSE, size = .5) +
theme_bw() +
theme(plot.background = element_blank(),
axis.line = element_blank(),
legend.key = element_blank(),
legend.title = element_blank()) +
scale_y_continuous("ASPL", expand=c(0,0), limits = c(1, 7)) +
scale_x_continuous("NR", expand=c(0,0), limits = c(0, 100)) +
theme(legend.position="bottom") +
theme(axis.title.x = element_text(vjust=-0.3, face="bold", size=12)) +
theme(axis.title.y = element_text(vjust=1.5, face="bold", size=12)) +
ggtitle("Title") + theme(plot.title = element_text(lineheight=.8, face="bold")) +
theme(title = element_text(vjust=2)) +
facet_grid(NM ~.)
Using ggplot, I would like represent a graph tile with panel, but with same height tile for each panel.
I have this graph :
dataSta <- list(sites=rep(paste("S", 1:31),each=12), month=rep(1:12,31), value=round(runif(31*12, min=0, max=3000)), panel=c(rep("Group 1",16*12),rep("Group 2", 12*12), rep("Group 3", 3*12)))
library(ggplot2)
library(grid)
base_size <- 9
windows()
ggplot(data.frame(dataSta), aes(factor(month), sites)) +
geom_tile(aes(fill = value), colour = "black")+
facet_wrap(~panel, scale="free_y", nrow=3)+
theme_grey(base_size = base_size) +
labs(x = "",y = "") +
scale_x_discrete(expand = c(0, 0)) +
scale_y_discrete(expand = c(0, 0)) +
theme(legend.title = element_blank(),
axis.ticks = element_blank(),
axis.text.x = element_text(size = base_size *0.8, hjust = 0),
panel.margin = unit(0,"lines"),
strip.text = element_text(colour="red3", size=10, face=2))
But height of tiles is different between panel. I try to use facet_grid :
windows()
ggplot(data.frame(dataSta), aes(factor(month), sites)) +
geom_tile(aes(fill = value), colour = "black")+
facet_grid(panel~., scales="free_y", space="free")+
theme_grey(base_size = base_size) +
labs(x = "",y = "") +
scale_x_discrete(expand = c(0, 0)) +
scale_y_discrete(expand = c(0, 0)) +
theme(legend.title = element_blank(),
axis.ticks = element_blank(),
axis.text.x = element_text(size = base_size *0.8, hjust = 0),
panel.margin = unit(0,"lines"),
strip.text = element_text(colour="red3", size=10, face=2))
The problem with height of tiles is resolved, but labels of panel (Group 1 ... Group 3) are not on top of panel. Is it possible to change position of panel labels with facet_grid ? or combine facet_grid and facet_wrap ?
Thanks for your help, and sorry for my English !
You can look at what ggplot contains before plotting, and rescale the panels accordingly.
g <- ggplot_build(p)
## find out how many y-breaks are in each panel
## to infer the number of tiles
vtiles <- sapply(lapply(g$panel$ranges, "[[", "y.major"), length)
## convert the plot to a gtable object
gt <- ggplot_gtable(g)
## find out which items in the layout correspond to the panels
## we refer to the "t" (top) index of the layout
panels <- gt$layout$t[grepl("panel", gt$layout$name)]
## replace the default panel heights (1null) with relative sizes
## null units scale relative to each other, so we scale with the number of tiles
gt$heights[panels] <-lapply(vtiles, unit, "null")
## draw on a clean slate
library(grid)
grid.newpage()
grid.draw(gt)
It took me some time to find easier solution which is actually part of the facet_grid where you can set the space = "free_y". More info at recent question.
The ggforce package has a neat little function for this called facet_col. It requires no messing around with the grid package!
All you have to do is replace the call to facet_grid with the appropriate facet_col alternative:
library(ggplot2)
library(ggforce)
dataSta <- list(sites=rep(paste("S", 1:31),each=12),
month=rep(1:12,31), value=round(runif(31*12, min=0, max=3000)),
panel=c(rep("Group 1",16*12),rep("Group 2", 12*12), rep("Group 3", 3*12)))
base_size <- 9
ggplot(data.frame(dataSta), aes(factor(month), sites)) +
geom_tile(aes(fill = value), colour = "black") +
# Here's the line to alter:
facet_col(vars(panel), , scales = "free_y", space = "free") +
theme_grey(base_size = base_size) +
labs(x = "",y = "") +
scale_x_discrete(expand = c(0, 0)) +
scale_y_discrete(expand = c(0, 0)) +
theme(legend.title = element_blank(),
axis.ticks = element_blank(),
axis.text.x = element_text(size = base_size *0.8, hjust = 0),
panel.spacing = unit(0,"lines"),
strip.text = element_text(colour="red3", size=10, face=2))