geom_tile heatmap with different high fill colours based on factor - r

I'm interested in building a heatmap with geom_tile in ggplot2 that uses a different gradient high color based on a factor.
The plot below creates the plot where the individual tiles are colored blue or red based on the xy_type, but there is no gradient.
ggplot() +
geom_tile(data=mydata, aes(x=factor(myx), y=myy, fill=factor(xy_type))) +
scale_fill_manual(values=c("blue", "red"))
The plot below does not use the xy_type factor to choose the color, but I get a single group gradient based on the xy_avg_value.
ggplot() +
geom_tile(data=mydata, aes(x=factor(myx), y=myy, fill=xy_avg_value))
Is there a technique to blend these two plots? I can use a facet_grid(xy_type ~ .) to create separate plots of this data, with the gradient. As this is ultimately going to be a map (x~y coordinates), I'd like to find a way to display the different gradient together in a single geom_tile map.

In general, ggplot2 does not permit multiple scales of a single type (i.e. multiple colour or fill scales), so I suspect that this isn't (easily) possible.
The best nearest approximation I can come up with is this:
df <- data.frame(expand.grid(1:5,1:5))
df$z <- runif(nrow(df))
df$grp <- rep(letters[1:2],length.out = nrow(df))
ggplot(df,aes(x = Var1,y = Var2,fill = factor(grp),alpha = z)) +
geom_tile() +
scale_fill_manual(values = c('red','blue'))
But it's going to be tough to get a sensible legend.

Related

Stacked barplot with colour gradients for each bar

I want to color a stacked barplot so that each bar has its own parent colour, with colours within each bar to be a gradient of this parent colour.
Example:
Here is a minimal example. I would like for the color of each bar to be different for color, with a gradient within each bar set by `clarity.
library(ggplot2)
ggplot(diamonds, aes(color)) +
geom_bar(aes(fill = clarity), colour = "grey")
In my real problem, I have many more groups of each: requiring 18 different bars with 39 different gradient colours.
I have made a function ColourPalleteMulti, which lets you create a multiple colour pallete based on subgroups within your data:
ColourPalleteMulti <- function(df, group, subgroup){
# Find how many colour categories to create and the number of colours in each
categories <- aggregate(as.formula(paste(subgroup, group, sep="~" )), df, function(x) length(unique(x)))
category.start <- (scales::hue_pal(l = 100)(nrow(categories))) # Set the top of the colour pallete
category.end <- (scales::hue_pal(l = 40)(nrow(categories))) # set the bottom
# Build Colour pallette
colours <- unlist(lapply(1:nrow(categories),
function(i){
colorRampPalette(colors = c(category.start[i], category.end[i]))(categories[i,2])}))
return(colours)
}
Essentially, the function identifies how many different groups you have, then counts the number of colours within each of these groups. It then joins together all the different colour palettes.
To use the palette, it is easiest to add a new column group, which pastes together the two values used to make the colour palette:
library(ggplot2)
# Create data
df <- diamonds
df$group <- paste0(df$color, "-", df$clarity, sep = "")
# Build the colour pallete
colours <-ColourPalleteMulti(df, "color", "clarity")
# Plot resultss
ggplot(df, aes(color)) +
geom_bar(aes(fill = group), colour = "grey") +
scale_fill_manual("Subject", values=colours, guide = "none")
Edit:
If you want the bars to be a different colour within each, you can just change the way the variable used to plot the barplot:
# Plot resultss
ggplot(df, aes(cut)) +
geom_bar(aes(fill = group), colour = "grey") +
scale_fill_manual("Subject", values=colours, guide = "none")
A Note of Caution: In all honesty, the dataset you have want to plot probably has too many sub-categories within it for this to work.
Also, although this is visually very pleasing, I would suggest avoiding the use of a colour scale like this. It is more about making the plot look pretty, and the different colours are redundant as we already know which group the data is in from the X-axis.
An easier approach to achieve a colour gradient is to use alpha to change the transparency of the colour. However, this can have unintended consequences as transparency means you can see the guidelines through the plot.
library(ggplot2)
ggplot(diamonds, aes(color, alpha = clarity)) +
geom_bar(aes(fill = color), colour = "grey") +
scale_alpha_discrete(range = c(0,1))
I have recently created the package ggnested which creates such plots. It is essentially a wrapper around ggplot2 that takes main_group and sub_group in the aesthetic mapping, where colours are generated for the main_group, and a gradient is generated for the levels of sub_group that are nested within each level of the main_group.
devtools::install_github("gmteunisse/ggnested")
require(ggnested)
data(diamonds)
ggnested(diamonds, aes(main_group = color, sub_group = clarity)) +
geom_bar(aes(x = color))
Another option is to use any custom color palette and simply darken/lighten those depending on the fill category. It can be slightly tricky to get a smooth gradient in each bar, but if you keep the natural order of the data (either appearance in data frame or the factor levels) this is not a big problem.
I am using the colorspace package for this task. The shades package also has the option to darken/lighten colors, but the syntax is slightly longer. It is more suitable for modification of entire palettes without specifying specific colors.
library(tidyverse)
library(colorspace)
## get some random colors, here n colors based on the Dark2 palette using the colorspace package.
## But ANY palette is possible
my_cols <- qualitative_hcl(length(unique(diamonds$color)), "Dark2")
## for easier assignment, name the colors
names(my_cols) <- unique(diamonds$color)
## assign the color to the category, by group
df_grad <-
diamonds %>%
group_by(color) %>%
## to keep the order of your stack and a natural gradient
## use order by occurrence in data frame or by factor
## clarity is an ordered factor, so I'm using a dense rank
mutate(
clarity_rank = dense_rank(as.integer(clarity)),
new_cols = my_cols[color],
## now darken or lighten according to the rank
clarity_dark = darken(new_cols, amount = clarity_rank / 10),
clarity_light = lighten(new_cols, amount = clarity_rank / 10)
)
## use this new color for your fill with scale_identity
## you additionally need to keep your ordering variable as group, in this case
## an interaction between color and your new rank
ggplot(df_grad, aes(color, group = interaction(color, clarity_rank))) +
geom_bar(aes(fill = clarity_dark)) +
scale_fill_identity()
ggplot(df_grad, aes(color, group = interaction(color, clarity_rank))) +
geom_bar(aes(fill = clarity_light)) +
scale_fill_identity()
Created on 2022-07-03 by the reprex package (v2.0.1)

3-variables plotting heatmap ggplot2

I'm currently working on a very simple data.frame, containing three columns:
x contains x-coordinates of a set of points,
y contains y-coordinates of the set of points, and
weight contains a value associated to each point;
Now, working in ggplot2 I seem to be able to plot contour levels for these data, but i can't manage to find a way to fill the plot according to the variable weight. Here's the code that I used:
ggplot(df, aes(x,y, fill=weight)) +
geom_density_2d() +
coord_fixed(ratio = 1)
You can see that there's no filling whatsoever, sadly.
I've been trying for three days now, and I'm starting to get depressed.
Specifying fill=weight and/or color = weight in the general ggplot call, resulted in nothing. I've tried to use different geoms (tile, raster, polygon...), still nothing. Tried to specify the aes directly into the geom layer, also didn't work.
Tried to convert the object as a ppp but ggplot can't handle them, and also using base-R plotting didn't work. I have honestly no idea of what's wrong!
I'm attaching the first 10 points' data, which is spaced on an irregular grid:
x = c(-0.13397460,-0.31698730,-0.13397460,0.13397460,-0.28867513,-0.13397460,-0.31698730,-0.13397460,-0.28867513,-0.26794919)
y = c(-0.5000000,-0.6830127,-0.5000000,-0.2320508,-0.6547005,-0.5000000,-0.6830127,-0.5000000,-0.6547005,0.0000000)
weight = c(4.799250e-01,5.500250e-01,4.799250e-01,-2.130287e+12,5.798250e-01,4.799250e-01,5.500250e-01,4.799250e-01,5.798250e-01,6.618956e-01)
any advise? The desired output would be something along these lines:
click
Thank you in advance.
From your description geom_density doesn't sound right.
You could try geom_raster:
ggplot(df, aes(x,y, fill = weight)) +
geom_raster() +
coord_fixed(ratio = 1) +
scale_fill_gradientn(colours = rev(rainbow(7)) # colourmap
Here is a second-best using fill=..level... There is a good explanation on ..level.. here.
# load libraries
library(ggplot2)
library(RColorBrewer)
library(ggthemes)
# build your data.frame
df <- data.frame(x=x, y=y, weight=weight)
# build color Palette
myPalette <- colorRampPalette(rev(brewer.pal(11, "Spectral")), space="Lab")
# Plot
ggplot(df, aes(x,y, fill=..level..) ) +
stat_density_2d( bins=11, geom = "polygon") +
scale_fill_gradientn(colours = myPalette(11)) +
theme_minimal() +
coord_fixed(ratio = 1)

Comparative Histograms Separated by Factor

Have an assignment where we need to provide one-dimensional graphs for EDA but the sample code given answers most of the requirements already (simple scatter and box plots and a histogram) so I am trying to "spice it up" a little by creating some more interesting graphs. Only need a couple.
The data set is the twin IQ data across several studies/authors and I was wanting to do a back-to-back histogram of the twins separated by author. So far I can do an overlay of authors or the back to back of the twins using ggplot but I am then stuck when trying to separate in to either 4 graphs or overlaid back-to-backs.
The code I was using for the overlay was ggplot with either geom_density or geom_histogram and the code for the back-to-back came from R-Bloggers and I used the first snippet:
ggplot(df, aes(IQ)) + geom_histogram(aes(x = x1, y = ..density..), fill = "blue") + geom_histogram( aes(x = x2, y = -..density..), fill = "green")
What I am looking for is a way to combine these two techniques or how to get ggplot to split the graphs up by factor in much the same was as plot/lattice does when you do, for example:
bwplot(y~x1.x2|Author, data=df)
The snippet that I am using to achieve separate plots includes facet_grid() such that the final code is:
ggplot(df, aes(y)) + facet_grid(~Author) + geom_histogram(aes(x = x1, y = ..density..), fill = "green") + geom_histogram(aes(x = x2, y = -..density..), fill = "blue")
I wasn't previously aware of the facet_grid() function of ggplot so thank you very much to MLavoie and Brandon Bertelsen.

Combine guides for continuous fill (color) and alpha scales

In ggplot2, I am making a geom_tile plot where both color and alpha vary with the same variable, I would like to make a single guide that shows the colors the way they appear on the plot instead of two separate guides.
library(ggplot2)
x <- seq(-10,10,0.1)
data <- expand.grid(x=x,y=x)
data$z <- with(data,y^2 * dnorm(sqrt(x^2 + y^2), 0, 3))
p <- ggplot(data) + geom_tile(aes(x=x,y=y, fill = z, alpha = z))
p <- p + scale_fill_continuous(low="blue", high="red") + scale_alpha_continuous(range=c(0.2,1.0))
plot(p)
This produces a figure with two guides: one for color and one for alpha. I would like to have just one guide on which both color and alpha vary together the way they do in the figure (so as the color shifts to blue, it fades out)
For this figure, I could achieve a similar effect by varying the saturation instead of alpha, but the real project in which I am using this, I will be overlaying this layer on top of a map, and want to vary alpha so the map is more clearly visible for smaller values of the z-variable.
I don't think you can combine continuous scales into one legend, but you can combine discrete scales. For example:
# Create discrete version of z
data$z.cut = cut(data$z, seq(min(data$z), max(data$z), length.out=10))
ggplot(data) +
geom_tile(aes(x=x, y=y, fill=z.cut, alpha=z.cut)) +
scale_fill_hue(h=c(-60, -120), c=100, l=50) +
scale_alpha_discrete(range=c(0.2,1))
You can of course cut z at different, perhaps more convenient, values and change scale_fill_hue to whatever color scale you prefer.

ggplot2 multiple stat_binhex() plots with different color gradients in one image

I'd like to use ggplot2's stat_binhex() to simultaneously plot two independent variables on the same chart, each with its own color gradient using scale_colour_gradientn().
If we disregard the fact that the x-axis units do not match, a reproducible example would be to plot the following in the same image while maintaining separate fill gradients.
d <- ggplot(diamonds, aes(x=carat,y=price))+
stat_binhex(colour="white",na.rm=TRUE)+
scale_fill_gradientn(colours=c("white","blue"),name = "Frequency",na.value=NA)
try(ggsave(plot=d,filename=<some file>,height=6,width=8))
d <- ggplot(diamonds, aes(x=depth,y=price))+
stat_binhex(colour="white",na.rm=TRUE)+
scale_fill_gradientn(colours=c("yellow","black"),name = "Frequency",na.value=NA)
try(ggsave(plot=d,filename=<some other file>,height=6,width=8))
I found some conversation of a related issue in ggplot2 google groups here.
Here is another possible solution: I have taken #mnel's idea of mapping bin count to alpha transparency, and I have transformed the x-variables so they can be plotted on the same axes.
library(ggplot2)
# Transforms range of data to 0, 1.
rangeTransform = function(x) (x - min(x)) / (max(x) - min(x))
dat = diamonds
dat$norm_carat = rangeTransform(dat$carat)
dat$norm_depth = rangeTransform(dat$depth)
p1 = ggplot(data=dat) +
theme_bw() +
stat_binhex(aes(x=norm_carat, y=price, alpha=..count..), fill="#002BFF") +
stat_binhex(aes(x=norm_depth, y=price, alpha=..count..), fill="#FFD500") +
guides(fill=FALSE, alpha=FALSE) +
xlab("Range Transformed Units")
ggsave(plot=p1, filename="plot_1.png", height=5, width=5)
Thoughts:
I tried (and failed) to display a sensible color/alpha legend. Seems tricky, but should be possible given all the legend-customization features of ggplot2.
X-axis unit labeling needs some kind of solution. Plotting two sets of units on one axis is frowned upon by many, and ggplot2 has no such feature.
Interpretation of cells with overlapping colors seems clear enough in this example, but could get very messy depending on the datasets used, and the chosen colors.
If the two colors are additive complements, then wherever they overlap equally you will see a neutral gray. Where the overlap is unequal, the gray would shift to more yellow, or more blue. My colors are not quite complements, judging by the slightly pink hue of the gray overlap cells.
I think what you want goes against the principles of ggplot2 and the grammar of graphics approach more generally. Until the issue is addressed (for which I would not hold my breath), you have a couple of choices
Use facet_wrap and alpha
This is will not produce a nice legend, but takes you someway to what you want.
You can set the alpha value to scale by the computed Frequency, accessed by ..Frequency..
I don't think you can merge the legends nicely though.
library(reshape2)
# in long format
dm <- melt(diamonds, measure.var = c('depth','carat'))
ggplot(dm, aes(y = price, fill = variable, x = value)) +
facet_wrap(~variable, ncol = 1, scales = 'free_x') +
stat_binhex(aes(alpha = ..count..), colour = 'grey80') +
scale_alpha(name = 'Frequency', range = c(0,1)) +
theme_bw() +
scale_fill_manual('Variable', values = setNames(c('darkblue','yellow4'), c('depth','carat')))
Use gridExtra with grid.arrange or arrangeGrob
You can create separate plots and use gridExtra::grid.arrange to arrange on a single image.
d_carat <- ggplot(diamonds, aes(x=carat,y=price))+
stat_binhex(colour="white",na.rm=TRUE)+
scale_fill_gradientn(colours=c("white","blue"),name = "Frequency",na.value=NA)
d_depth <- ggplot(diamonds, aes(x=depth,y=price))+
stat_binhex(colour="white",na.rm=TRUE)+
scale_fill_gradientn(colours=c("yellow","black"),name = "Frequency",na.value=NA)
library(gridExtra)
grid.arrange(d_carat, d_depth, ncol =1)
If you want this to work with ggsave (thanks to #bdemarest comment below and #baptiste)
replace grid.arrange with arrangeGrob something like.
ggsave(plot=arrangeGrob(d_carat, d_depth, ncol=1), filename="plot_2.pdf", height=12, width=8)

Resources