There are many questions out there pertaining to this topic, but none of the answers I have tried have worked for me so far.
I have a plot that is a heatmap with fill and alpha mapped to different values, i.e. different variables in my data create different colors and alpha values. I want to get a finished product here to see if this figure is worthwhile, so let's not discuss whether this is a good idea at the moment.
What I want to do is combine my fill and alpha legend such that I have the four different transparencies of blue, the four different transparencies of red, and for yellow. I can get those legends separately, or just one of them, but not two in one.
My best guess for code thus far has been
dummy <- data.frame(model=c(rep("X",23),rep("Y",23)),
longvarname=rep(c("CBH","NDMI","CovType","CH","CBD","NDVI_NF_750","Slope","TPI_Valley_1200", "TPI_Ridge_1200",
"TPI_Ridge_100","TPI_Valley_100", "TSHarv","Treat","RxBurn",
"TSTreat","TSRx","Deficit","SpecHumid","MaxRH","MinTemp","MaxTemp", "MaxGustDir", "MaxGustSpd"),2),
vargrp=rep(c(rep("Veg",6), rep("Topo",5), rep("Mgmt",5),rep("Clim",7)),2),
value=runif(46, min=0, max=1),
binary_slope=sample(c("negative","positive", "zero"), 46, replace=TRUE))
ggplot(dummy, aes(x=model, y=longvarname)) +
geom_tile(aes(fill=binary_slope, alpha=value))+
scale_alpha_binned(breaks=c(0.4, 0.6, 0.8, 1))+
facet_grid(vargrp~., scales='free_y', space="free_y")+
xlab("Model")+
ylab("Variable")+
scale_fill_manual(values=c("midnightblue","yellow1","red4"))+
# guides(fill=guide_legend(override.aes = list(fill=c(rep("#191970",4),
# rep("#FFEA00",4),
# rep("#8b0000",4)),
# alpha=rep(c(0.4,0.6,0.8,1),3))))+
theme(panel.background = element_blank(),
axis.text.x = element_text(angle = 45, hjust=1),
strip.text.y = element_blank(),
axis.ticks = element_blank())
The above code produces both legends which you can see in the example I attached. If you uncomment the guides() lines, the error I am getting is Error in [[<-.data.frame(*tmp*, i, value = c("#191970", "#191970", :
replacement has 12 rows, data has 3.
But most of my efforts have just resulted in only the fill legend at alpha=1. Another thought I had which I thought might get me there was in guides(), putting the alpha hex codes in front of each color hex code and then making alpha guide = "none", but no dice.
Thanks very much for your help!
Instead of making use of both fill and alpha one option would be to make use of just fill like so:
Add a column with your desired fill colors to your dataset using e.g. a left_join.
Manually compute your alpha levels using e.g. cut.
Adjust the transparency of th colors according to the alpha values using colorspace::adjust_transparency
Map the resulting colors on the fill aes and make use of scale_fill_identity. Add guide=guide_legend to get a legend.
library(ggplot2)
library(dplyr)
library(colorspace)
cols <- c(negative = "midnightblue", positive = "yellow1", zero = "red4")
cols <- tibble::enframe(cols, name = "binary_slope", value = "fill")
dummy <- left_join(dummy, cols, by = "binary_slope")
dummy <- mutate(dummy,
alpha = cut(value, breaks = c(0, 0.4, 0.6, 0.8, 1), labels = c(0.4, 0.6, 0.8, 1)),
alpha = as.numeric(as.character(alpha)),
fill = colorspace::adjust_transparency(fill, alpha)
)
ggplot(dummy, aes(x = model, y = longvarname)) +
geom_tile(aes(fill = fill)) +
scale_fill_identity(guide = guide_legend()) +
facet_grid(vargrp ~ ., scales = "free_y", space = "free_y") +
xlab("Model") +
ylab("Variable") +
theme(
panel.background = element_blank(),
axis.text.x = element_text(angle = 45, hjust = 1),
strip.text.y = element_blank(),
axis.ticks = element_blank()
)
Related
I work inside a research environment and I can't copy paste the code I used there, but I have previously generated this plot, and have been helped by various people in labelling it with the count number. The problem arises when I screenshot the plot from inside the research environment, and the legends are illegible. I am hoping I can address this by making the labels (including the X-axis label) all bold.
I used some mock-data outside the environment and this is what I have so far.
library(ggplot2)
library(reshape2)
md.df = melt(df, id.vars = c('Group.1'))
tmp = c("virginica","setosa","versicolor")
md.df2 = md.df[order(match(md.df$Group.1, tmp)),]
md.df2$Group.1 = factor(as.character(md.df2$Group.1), levels = unique(md.df2$Group.1))
ggplot(md.df2, aes(x = Group.1, y = value, group = variable, fill = variable)) +
geom_bar(stat="identity",color='black', position = "dodge") +
xlab('Species') + ylab('Values') + theme_bw()+
ylim(0,8)+
theme(text = element_text(size=16),
axis.text.x = element_text(angle=0, hjust=.5),
plot.title = element_text(hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5))+
ggtitle("Order variables in barplot")+
geom_text(aes(label=value), vjust=-0.3, size=4, # adding values
position = position_dodge(0.9))+ element_text(face="bold")
I need to make the labels onto bold, and the element_text isn't working mainly because I am probably using it in the wrong way. I'd appreciate any help with this.
An example of this plot which I haven't been able to find mock data to re-create outside the environment, have asked a question about in the past, is the one where the axis ticks also need to be made bold. This is because the plot is illegible from the outside.
I've tried addressing the illegibility by saving all my plots using ggsave in 300 resolution but it is very illegible.
I'd appreciate any help with this, and thank you for taking the time to help with this.
As I mentioned in my comment to make the value labels bold use geom_text(..., fontface = "bold") and to make the axis labels bold use axis.text.x = element_text(angle=0, hjust=.5, face = "bold").
Using a a minimal reproducible example based on the ggplot2::mpg dataset:
library(ggplot2)
library(dplyr)
# Create exmaple data
md.df2 <- mpg |>
count(Group.1 = manufacturer, name = "value") |>
mutate(
variable = value >= max(value),
Group.1 = reorder(Group.1, -value)
)
ggplot(md.df2, aes(x = Group.1, y = value, group = variable, fill = variable)) +
geom_col(color = "black", position = "dodge") +
geom_text(aes(label = value), vjust = -0.3, size = 4, position = position_dodge(0.9), fontface = "bold") +
labs(x = "Species", y = "Values", title = "Order variables in barplot") +
theme_bw() +
theme(
text = element_text(size = 16),
axis.text.x = element_text(angle = 90, vjust = .5, face = "bold"),
plot.title = element_text(hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5)
)
In addition to #stefan 's answer, you can also set tick length and thickness like so:
## ... plot code +
theme(## other settings ...,
axis.ticks = element_line(linewidth = 5),
axis.ticks.length = unit(10, 'pt'))
However, the example "phenotype conditions" will probably remain hard to appraise, regardless of optimization on the technical level. On the conceptual level it might help to display aggregates, e. g. condition counts per Frequency and supplement a textual list of conditions (sorted alphabetically and by Frequency, or the other way round) for those readers who indeed want to look up any specific condition.
How do I control the font-family and size for text elements added to my boxplot:
Following the approach in this question, I have implemented the following code to show the number of observations:
library("ggplot2")
v_min <- -1
v_max <- 3.5
increm <- 0.5
y_limits <- c(v_min, v_max)
increms <- seq(v_min, v_max, increm)
counts <- function(x){
# you can experiment with 'adjust' and 'max-value' to find the perfect position
adjust <- 0.95
return(c(y = adjust * v_max, label = length(x)))
}
ggplot(d1, aes(x = ONRC_Hierarchy, y=lwpMeanRut_inc)) +
geom_boxplot(outlier.alpha = 0.2, outlier.size = 0.5) +
geom_hline(aes(yintercept=0), color="blue", linetype="dotted", size=1)+
stat_summary(fun.data = counts, geom = "text") +
scale_y_continuous(limits = y_limits, breaks = increms) +
xlab("") +
ylab("Rut Increment (mm/year)\n") +
theme_minimal() +
theme(
text = element_text(size = 10, family = "mono"),
axis.text.x=element_text(angle = -35, hjust = 0),
panel.grid.major.y = element_line(color = "lightgray",
size = 0.15,linetype = 2),
panel.grid.minor.y = element_blank(),
panel.grid.major.x = element_blank())
This solution works, as shown in the plot below, except that the font is different from the other graph elements. As you can see, I have tried to control this with the theme() statement, but it does not seem to work. Note, I deliberately used a small mono font to show the difference in the number of observations labels and the other graph elements.
Geom/stat fonts are set in the geom/stat layer, not in theme(). In this case, you can add family = "mono" as an argument to your stat_summary().
The size of fonts in geom_text/geom_label etc. is not on the same scale as element_text theme options, which are in points. You can read more about that here and here.
I´ve got the following code:
ggplot(dummy$Crustacean) +
geom_rect(
aes(
xmin = char2num(sites_fct) - 0.4,
xmax = char2num(sites_fct) + 0.4,
ymin = ifelse(trophic == "Crustacean", 0.01, 1),
ymax = summed_tu),
colour = 'black', alpha =0.7) +
labs(y= expression("Summed TU"[EC10-QSAR]), x= "Sampling sites")+
scale_y_log10(limits = c(0.0001, 1)) +
# Fake discrete axis
scale_x_continuous(labels = sort(unique(dummy$Crustacean$sites_fct)), breaks = 1:9) +
# before the dot means vertical plotting
facet_grid(dummy$Crustacean$metrics_fct ~ dummy$Crustacean$trophic) +
theme_bw()+
# facet_grid box colour
theme(strip.background.x = element_rect(colour = "black", fill = "white"),
strip.background.y = element_blank(), strip.text.y = element_blank())+
theme(axis.text.x = element_text(size=10, margin =margin(0,0,0,0), angle =45, vjust = 1, hjust=1),
axis.title.y = element_blank(),
axis.text.y = element_blank(),
axis.line.x = element_line(color = 'black', size=0.5),
axis.line.y = element_blank())
which give as uutput the following figure:
I need to change the colour of those boxes y > 0.01 in order to have this desire output:
I found several post about background (quite useful for the future) but I cloud not find something like my example.
Thanks!
OP, this should probably help you. You're trying to draw what appears to be a column or bar chart. In this case, it's probably best to use geom_col instead of geom_rect. With geom_col you only have to supply an x aesthetic (discrete value), and a y aesthetic for the height of the bar. You have not shared your data, but it seems the x axis is categorical already in your dataset, right?
Here's a reprex:
library(ggplot2)
set.seed(1234)
df <- data.frame(x=LETTERS, y=rnorm(26))
ggplot(df, aes(x,y)) +
geom_col(
aes(fill=ifelse(y>0, 'positive', 'negative')),
color='black', alpha=0.8
) +
scale_fill_manual(name='Value', values=c('positive'='orange', 'negative'='gray'))
What's going on here is that we only have to supply x and y to get the bars in the correct place and set the height. For the fill of each of the bars, you can actually just set the label to be "positive" or "negative" (or whatever your desired label would be) on the fly via an ifelse statement. Doing this alone will result in creating a legend automatically with fill colors chosen automatically. To fix a particular set of colors, I'm setting that manually via scale_fill_manual() and supplying a named vector to the values argument.
In your case, you can probably do something similar for geom_rect. That is, you could just try specifying fill= inside aes() and following a similar manner to here if you want... but I'd recommend switching to use geom_col, as it is most appropriate for what you're doing.
EDIT
As OP indicated in the comment, in the original question on which this is based, geom_rect is required since the bars minimum is not always the same number. The ymin aesthetic changes, so it makes sense to use geom_rect here.
The brute force way is to still use ifelse statements inside aes() for fill. It get's a bit dodgey, but it gets the job done:
ggplot(df) +
geom_rect(
aes(
xmin = char2num(sites) - 0.4,
xmax = char2num(sites) + 0.4,
ymin = ifelse(trop == "pt", 0.1, 1),
ymax = conc,
fill = ifelse(trop == "pt",
ifelse(conc > 0.1, 'positive', 'negative'),
ifelse(conc > 1, 'positive', 'negative'))
),
colour = 'black', alpha = 0.8
) +
scale_y_log10() +
# Fake discrete axis
scale_x_continuous(labels = sort(unique(df$sites)),
breaks = 1:3) +
scale_fill_manual(name='Conc', values=c('positive'='orange', 'negative'='gray')) +
facet_grid(. ~ trop) +
theme_bw()
To complete the setup, you may want to adjust the order of the items in the legend and avoid some of that kind of icky nested ifelse stuff. In that case, you can always do the checking outside the ggplot call. If you have more than the two values for df$trop, you can consider creating the df$conc_min column via a merge with another dataset, but it works just fine here.
df$conc_adjust <- char2num(df$sites)
df$conc_min <- ifelse(df$trop=='pt', 0.1, 1)
df$status <- ifelse(df$conc > df$conc_min, 'positive', 'negative')
# levels of the factor = the order appearing in the legend
df$status <- factor(df$status, levels=c('positive', 'negative'))
ggplot(df) +
geom_rect(
aes(
xmin = conc_adjust - 0.4,
xmax = conc_adjust + 0.4,
ymin = conc_min,
ymax = conc,
fill = status
),
colour = 'black', alpha = 0.8
) +
scale_y_log10() +
# Fake discrete axis
scale_x_continuous(labels = sort(unique(df$sites)),
breaks = 1:3) +
scale_fill_manual(name='Conc', values=c('positive'='orange', 'negative'='gray')) +
facet_grid(. ~ trop) +
theme_bw()
I would like to add a stacked bar to a dodged bar chart, showing the total. I don't want the column for the total to be a solid bar, but to consist of the stacked components.
I can add both geom_bars to the plot, but I haven't been able to move the total bar. I could add a dummy category with zero in the middle, but of course I'd prefer the total to be to the right of the components.
df=data.frame(
treatment=rep(c("Impact","Control")),
type=rep(c("Phylum1","Phylum2"),each=2),
total=c(2,3,4,5))
ggplot(df,aes(y=total,x=treatment,fill=type)) +
geom_bar(position= position_dodge(),stat="identity", alpha = 0.9, width = 0.25) +
geom_bar(position = position_stack(), stat = 'identity', alpha = 0.3, width = 0.125)
This is not the same question they want to stack/dodge by two variables. I just want to summarise the same info twice, but differently.
I can of course add a bar for the solid total and put in the stacked bar by hand, but I get so close with basic ggplot that I thought maybe a little hack (e.g. modifying the return object of position_stack) might be possible.
You could reshape your dataset and use facets to get the effect, although this will show all of your data and not just the largest value in each type/treatment combination.
Your dataset would need to be repeated twice, once for the the original type and once for plotting the totals. You also need a new variable, which I called type2.
df$type2 = df$type
df2 = df
df2$type2 = "Total"
Stack the two datasets together via rbind, and then plot using type2 as the x variable and the alpha variable.
ggplot(rbind(df, df2), aes(y = total, x = type2, fill = type, alpha = type2)) +
geom_col(width = .9) +
facet_wrap(~treatment, strip.position = "bottom") +
scale_alpha_manual(values = c(.9, .9, .3), guide = "none") +
theme(strip.background = element_blank(),
axis.text.x = element_blank(),
axis.ticks.length = unit(0, "mm"),
panel.spacing = unit(0, "mm"),
panel.grid.major.x = element_blank()) +
scale_x_discrete(expand = c(1, 0))
I plot a 2 geom_point graph with the following code:
source("http://www.openintro.org/stat/data/arbuthnot.R")
library(ggplot2)
ggplot() +
geom_point(aes(x = year,y = boys),data=arbuthnot,colour = '#3399ff') +
geom_point(aes(x = year,y = girls),data=arbuthnot,shape = 17,colour = '#ff00ff') +
xlab(label = 'Year') +
ylab(label = 'Rate')
I simply want to know how to add a legend on the right side. With the same shape and color. Triangle pink should have the legend "woman" and blue circle the legend "men". Seems quite simple but after many trial I could not do it. (I'm a beginner with ggplot).
If you rename your columns of the original data frame and then melt it into long format withreshape2::melt, it's much easier to handle in ggplot2. By specifying the color and shape aesthetics in the ggplot command, and specifying the scales for the colors and shapes manually, the legend will appear.
source("http://www.openintro.org/stat/data/arbuthnot.R")
library(ggplot2)
library(reshape2)
names(arbuthnot) <- c("Year", "Men", "Women")
arbuthnot.melt <- melt(arbuthnot, id.vars = 'Year', variable.name = 'Sex',
value.name = 'Rate')
ggplot(arbuthnot.melt, aes(x = Year, y = Rate, shape = Sex, color = Sex))+
geom_point() + scale_color_manual(values = c("Women" = '#ff00ff','Men' = '#3399ff')) +
scale_shape_manual(values = c('Women' = 17, 'Men' = 16))
This is the trick that I usually use. Add colour argument to the aes and use it as an indicator for the label names.
ggplot() +
geom_point(aes(x = year,y = boys, colour = 'Boys'),data=arbuthnot) +
geom_point(aes(x = year,y = girls, colour = 'Girls'),data=arbuthnot,shape = 17) +
xlab(label = 'Year') +
ylab(label = 'Rate')
Here is a way of doing this without using reshape::melt. reshape::melt works, but you can get into a bind if you want to add other things to the graph, such as line segments. The code below uses the original organization of data. The key to modifying the legend is to make sure the arguments to scale_color_manual(...) and scale_shape_manual(...) are identical otherwise you will get two legends.
source("http://www.openintro.org/stat/data/arbuthnot.R")
library(ggplot2)
library(reshape2)
ptheme <- theme (
axis.text = element_text(size = 9), # tick labels
axis.title = element_text(size = 9), # axis labels
axis.ticks = element_line(colour = "grey70", size = 0.25),
panel.background = element_rect(fill = "white", colour = NA),
panel.border = element_rect(fill = NA, colour = "grey70", size = 0.25),
panel.grid.major = element_line(colour = "grey85", size = 0.25),
panel.grid.minor = element_line(colour = "grey93", size = 0.125),
panel.margin = unit(0 , "lines"),
legend.justification = c(1, 0),
legend.position = c(1, 0.1),
legend.text = element_text(size = 8),
plot.margin = unit(c(0.1, 0.1, 0.1, 0.01), "npc") # c(bottom, left, top, right), values can be negative
)
cols <- c( "c1" = "#ff00ff", "c2" = "#3399ff" )
shapes <- c("s1" = 16, "s2" = 17)
p1 <- ggplot(data = arbuthnot, aes(x = year))
p1 <- p1 + geom_point(aes( y = boys, color = "c1", shape = "s1"))
p1 <- p1 + geom_point(aes( y = girls, color = "c2", shape = "s2"))
p1 <- p1 + labs( x = "Year", y = "Rate" )
p1 <- p1 + scale_color_manual(name = "Sex",
breaks = c("c1", "c2"),
values = cols,
labels = c("boys", "girls"))
p1 <- p1 + scale_shape_manual(name = "Sex",
breaks = c("s1", "s2"),
values = shapes,
labels = c("boys", "girls"))
p1 <- p1 + ptheme
print(p1)
output results
Here is an answer based on the tidyverse package. Where one can use the pipe, %>%, to chain functions together. Creating the plot in one continues manner, omitting the need to create temporarily variables. More on the pipe can be found in this post What does %>% function mean in R?
As far as I know, legends in ggplot2 are only based on aesthetic variables. So to add a discrete legend one uses a category column, and change the aesthetics according to the category. In ggplot this is for example done by aes(color=category).
So to add two (or more) different variables of a data frame to the legends, one needs to transform the data frame such that we have a category column telling us which column (variable) is being plotted, and a second column that actually holds the value. The tidyr::gather function, that was also loaded by tidyverse, does exactly that.
Then one creates the legend by just specifying which aesthetics variables need to be different. In this example the code would look as follows:
source("http://www.openintro.org/stat/data/arbuthnot.R")
library(tidyverse)
arbuthnot %>%
rename(Year=year,Men=boys,Women=girls) %>%
gather(Men,Women,key = "Sex",value = "Rate") %>%
ggplot() +
geom_point(aes(x = Year, y=Rate, color=Sex, shape=Sex)) +
scale_color_manual(values = c("Men" = "#3399ff","Women"= "#ff00ff")) +
scale_shape_manual(values = c("Men" = 16, "Women" = 17))
Notice that tidyverse package also automatically loads in the ggplot2 package. An overview of the packages installed can be found on their website tidyverse.org.
In the code above I also used the function dplyr::rename (also loaded by tidyverse) to first rename the columns to the wanted labels. Since the legend automatically takes the labels equal to the category names.
There is a second way to renaming labels of legend, which involves specifying the labels explicitly in the scale_aesthetic_manual functions by the labels = argument. For examples see legends cookbook. But is not recommended since it gets messy quickly with more variables.