How to animate the axis label using `gganimate`? - r

I am actually very amazed to see I cannot quickly find a guide to how to do this. Here is an example:
library(ggplot2)
library(gganimate)
library(data.table)
library(magrittr)
dt <- lapply(seq(10), function(i){
mean = i
label = paste0("T = ", i)
dt = data.table(x = seq(0, 50, length.out = 100))
set(dt, j = "y", value = dt[, dlnorm(x, meanlog = log(mean), sdlog = 0.2)])
set(dt, j = "frameN", value = i)
return(dt)
}) %>% rbindlist
print(dt)
p <- ggplot(dt, aes(x = x, y = y)) +
geom_line() +
scale_x_continuous(name = "x", breaks = c(0, 1)) +
transition_manual(frameN)
animate(p)
I want the breaks and labels of scale_x_continuous to follow my own definitions:
arr_breaks <- c(1, 3, 2, 4, 3, 5, 4, 6, 5, 7)
arr_labels <- paste0(seq(10, 100, 10), " kg")
And then
breaks = arr_breaks[1], labels = arr_labels[1] for frame 1
breaks = arr_breaks[2], labels = arr_labels[2] for frame 2
...
breaks = arr_breaks[10], labels = arr_labels[10] for frame 10
No matter how I do it I got errors. Any idea?

As #z-lin noted, gganimate is not currently set up (to my knowledge) to animate scales with different breaks. The effect could be closely approximated using geoms, and with some more work you could probably make an exact visual match to a changing scale.
breaks_df <- data.frame(
frameN = c(1:10),
arr_breaks = c(1, 3, 2, 4, 3, 5, 4, 6, 5, 7),
arr_labels = paste0(seq(10, 100, 10), " kg")
)
p <- ggplot(dt, aes(x = x, y = y)) +
geom_segment(data = breaks_df, color = "white",
aes(x = arr_breaks, xend = arr_breaks,
y = -Inf, yend = Inf)) +
geom_text(data = breaks_df, vjust = 3, size = 3.5, color = "gray30",
aes(x = arr_breaks, y = 0, label = arr_labels)) +
geom_line() +
scale_x_continuous(name = "x", breaks = c(0)) +
coord_cartesian(clip = "off") +
transition_manual(frameN)
animate(p, width = 600, height = 250)

Related

Produce an inset in each facet of an R ggplot while preserving colours of the original facet content

I would like to produce a graphic combining four facets of a graph with insets in each facet showing a detail of the respective plot. This is one of the things I tried:
#create data frame
n_replicates <- c(rep(1:10,15),rep(seq(10,100,10),15),rep(seq(100,1000,100),15),rep(seq(1000,10000,1000),15))
sim_years <- rep(sort(rep((1:15),10)),4)
sd_data <- rep (NA,600)
for (i in 1:600) {
sd_data[i]<-rnorm(1,mean=exp(0.1 * sim_years[i]), sd= 1/n_replicates[i])
}
max_rep <- sort(rep(c(10,100,1000,10000),150))
data_frame <- cbind.data.frame(n_replicates,sim_years,sd_data,max_rep)
#do first basic plot
library(ggplot2)
plot1<-ggplot(data=data_frame, aes(x=sim_years,y=sd_data,group =n_replicates, col=n_replicates)) +
geom_line() + theme_bw() +
labs(title ="", x = "year", y = "sd")
plot1
#make four facets
my_breaks = c(2, 10, 100, 1000, 10000)
facet_names <- c(
`10` = "2, 3, ..., 10 replicates",
`100` = "10, 20, ..., 100 replicates",
`1000` = "100, 200, ..., 1000 replicates",
`10000` = "1000, 2000, ..., 10000 replicates"
)
plot2 <- plot1 +
facet_wrap( ~ max_rep, ncol=2, labeller = as_labeller(facet_names)) +
scale_colour_gradientn(name = "number of replicates", trans = "log",
breaks = my_breaks, labels = my_breaks, colours = rainbow(20))
plot2
#extract inlays (this is where it goes wrong I think)
library(ggpmisc)
library(tibble)
library(dplyr)
inset <- tibble(x = 0.01, y = 10.01,
plot = list(plot2 +
facet_wrap( ~ max_rep, ncol=2, labeller = as_labeller(facet_names)) +
coord_cartesian(xlim = c(13, 15),
ylim = c(3, 5)) +
labs(x = NULL, y = NULL, color = NULL) +
scale_colour_gradient(guide = FALSE) +
theme_bw(10)))
plot3 <- plot2 +
expand_limits(x = 0, y = 0) +
geom_plot_npc(data = inset, aes(npcx = x, npcy = y, label = plot)) +
annotate(geom = "rect",
xmin = 13, xmax = 15, ymin = 3, ymax = 5,
linetype = "dotted", fill = NA, colour = "black")
plot3
That leads to the following graphic:
As you can see, the colours in the insets are wrong, and all four of them appear in each of the facets even though I only want the corresponding inset of course. I read through a lot of questions here (to even get me this far) and also some examples in the ggpmisc user guide but unfortunately I am still a bit lost on how to achieve what I want. Except maybe to do it by hand extracting four insets and then combining them with plot2. But I hope there will be a better way to do this. Thank you for your help!
Edit: better graphic now thanks to this answer, but problem remains partially unsolved:
The following code does good insets, but unfortunately the colours are not preserved. As in the above version each inset does its own rainbow colours anew instead of inheriting the partial rainbow scale from the facet it belongs to. Does anyone know why and how I could change this? In comments I put another (bad) attempt at solving this, it preserves the colors but has the problem of putting all four insets in each facet.
library(ggpmisc)
library(tibble)
library(dplyr)
# #extract inlays: good colours, but produces four insets.
# fourinsets <- tibble(#x = 0.01, y = 10.01,
# x = c(rep(0.01, 4)),
# y = c(rep(10.01, 4)),
# plot = list(plot2 +
# facet_wrap( ~ max_rep, ncol=2) +
# coord_cartesian(xlim = c(13, 15),
# ylim = c(3, 5)) +
# labs(x = NULL, y = NULL, color = NULL) +
# scale_colour_gradientn(name = "number of replicates", trans = "log", guide = FALSE,
# colours = rainbow(20)) +
# theme(
# strip.background = element_blank(),
# strip.text.x = element_blank()
# )
# ))
# fourinsets$plot
library(purrr)
pp <- map(unique(data_frame$max_rep), function(x) {
plot2$data <- plot2$data %>% filter(max_rep == x)
plot2 +
coord_cartesian(xlim = c(12, 14),
ylim = c(3, 4)) +
labs(x = NULL, y = NULL) +
theme(
strip.background = element_blank(),
strip.text.x = element_blank(),
legend.position = "none",
axis.text=element_blank(),
axis.ticks=element_blank()
)
})
#pp[[2]]
inset_new <- tibble(x = c(rep(0.01, 4)),
y = c(rep(10.01, 4)),
plot = pp,
max_rep = unique(data_frame$max_rep))
final_plot <- plot2 +
geom_plot_npc(data = inset_new, aes(npcx = x, npcy = y, label = plot, vp.width = 0.3, vp.height =0.6)) +
annotate(geom = "rect",
xmin = 12, xmax = 14, ymin = 3, ymax = 4,
linetype = "dotted", fill = NA, colour = "black")
#final_plot
final_plot then looks like this:
I hope this clarifies the problem a bit. Any ideas are very welcome :)
Modifying off #user63230's excellent answer:
pp <- map(unique(data_frame$max_rep), function(x) {
plot2 +
aes(alpha = ifelse(max_rep == x, 1, 0)) +
coord_cartesian(xlim = c(12, 14),
ylim = c(3, 4)) +
labs(x = NULL, y = NULL) +
scale_alpha_identity() +
facet_null() +
theme(
strip.background = element_blank(),
strip.text.x = element_blank(),
legend.position = "none",
axis.text=element_blank(),
axis.ticks=element_blank()
)
})
Explanation:
Instead of filtering the data passed into plot2 (which affects the mapping of colours), we impose a new aesthetic alpha, where lines belonging to the other replicate numbers are assigned 0 for transparency;
Use scale_alpha_identity() to tell ggplot that the alpha mapping is to be used as-is: i.e. 1 for 100%, 0 for 0%.
Add facet_null() to override plot2's existing facet_wrap, which removes the facet for the inset.
Everything else is unchanged from the code in the question.
I think this will get you started although its tricky to get the size of the inset plot right (when you include a legend).
#set up data
library(ggpmisc)
library(tibble)
library(dplyr)
library(ggplot2)
# create data frame
n_replicates <- c(rep(1:10, 15), rep(seq(10, 100, 10), 15), rep(seq(100,
1000, 100), 15), rep(seq(1000, 10000, 1000), 15))
sim_years <- rep(sort(rep((1:15), 10)), 4)
sd_data <- rep(NA, 600)
for (i in 1:600) {
sd_data[i] <- rnorm(1, mean = exp(0.1 * sim_years[i]), sd = 1/n_replicates[i])
}
max_rep <- sort(rep(c(10, 100, 1000, 10000), 150))
data_frame <- cbind.data.frame(n_replicates, sim_years, sd_data, max_rep)
# make four facets
my_breaks = c(2, 10, 100, 1000, 10000)
facet_names <- c(`10` = "2, 3, ..., 10 replicates", `100` = "10, 20, ..., 100 replicates",
`1000` = "100, 200, ..., 1000 replicates", `10000` = "1000, 2000, ..., 10000 replicates")
Get overall plot:
# overall facet plot
overall_plot <- ggplot(data = data_frame, aes(x = sim_years, y = sd_data, group = n_replicates, col = n_replicates)) +
geom_line() +
theme_bw() +
labs(title = "", x = "year", y = "sd") +
facet_wrap(~max_rep, ncol = 2, labeller = as_labeller(facet_names)) +
scale_colour_gradientn(name = "number of replicates", trans = "log", breaks = my_breaks, labels = my_breaks, colours = rainbow(20))
#plot
overall_plot
which gives:
Then from the overall plot you want to extract each plot, see here. We can map over the list to extract one at a time:
pp <- map(unique(data_frame$max_rep), function(x) {
overall_plot$data <- overall_plot$data %>% filter(max_rep == x)
overall_plot + # coord_cartesian(xlim = c(13, 15), ylim = c(3, 5)) +
labs(x = NULL, y = NULL) +
theme_bw(10) +
theme(legend.position = "none")
})
If we look at one of these (I've removed the legend) e.g.
pp[[1]]
#pp[[2]]
#pp[[3]]
#pp[[4]]
Gives:
Then we want to add these inset plots into a dataframe so that each plot has its own row:
inset <- tibble(x = c(rep(0.01, 4)),
y = c(rep(10.01, 4)),
plot = pp,
max_rep = unique(data_frame$max_rep))
Then merge this into the overall plot:
overall_plot +
expand_limits(x = 0, y = 0) +
geom_plot_npc(data = inset, aes(npcx = x, npcy = y, label = plot, vp.width = 0.8, vp.height = 0.8))
Gives:
Here is a solution based on Z. Lin's answer, but using ggforce::facet_wrap_paginate() to do the filtering and keeping colourscales consistent.
First, we can make the 'root' plot containing all the data with no facetting.
library(ggpmisc)
library(tibble)
library(dplyr)
n_replicates <- c(rep(1:10,15),rep(seq(10,100,10),15),rep(seq(100,1000,100),15),rep(seq(1000,10000,1000),15))
sim_years <- rep(sort(rep((1:15),10)),4)
sd_data <- rep (NA,600)
for (i in 1:600) {
sd_data[i]<-rnorm(1,mean=exp(0.1 * sim_years[i]), sd= 1/n_replicates[i])
}
max_rep <- sort(rep(c(10,100,1000,10000),150))
data_frame <- cbind.data.frame(n_replicates,sim_years,sd_data,max_rep)
my_breaks = c(2, 10, 100, 1000, 10000)
facet_names <- c(
`10` = "2, 3, ..., 10 replicates",
`100` = "10, 20, ..., 100 replicates",
`1000` = "100, 200, ..., 1000 replicates",
`10000` = "1000, 2000, ..., 10000 replicates"
)
base <- ggplot(data=data_frame,
aes(x=sim_years,y=sd_data,group =n_replicates, col=n_replicates)) +
geom_line() +
theme_bw() +
scale_colour_gradientn(
name = "number of replicates",
trans = "log10", breaks = my_breaks,
labels = my_breaks, colours = rainbow(20)
) +
labs(title ="", x = "year", y = "sd")
Next, the main plot will be just the root plot with facet_wrap().
main <- base + facet_wrap(~ max_rep, ncol = 2, labeller = as_labeller(facet_names))
Then the new part is to use facet_wrap_paginate with nrow = 1 and ncol = 1 for every max_rep, which we'll use as insets. The nice thing is that this does the filtering and it keeps colour scales consistent with the root plot.
nmax_rep <- length(unique(data_frame$max_rep))
insets <- lapply(seq_len(nmax_rep), function(i) {
base + ggforce::facet_wrap_paginate(~ max_rep, nrow = 1, ncol = 1, page = i) +
coord_cartesian(xlim = c(12, 14), ylim = c(3, 4)) +
guides(colour = "none", x = "none", y = "none") +
theme(strip.background = element_blank(),
strip.text = element_blank(),
axis.title = element_blank(),
plot.background = element_blank())
})
insets <- tibble(x = rep(0.01, nmax_rep),
y = rep(10.01, nmax_rep),
plot = insets,
max_rep = unique(data_frame$max_rep))
main +
geom_plot_npc(data = insets,
aes(npcx = x, npcy = y, label = plot,
vp.width = 0.3, vp.height = 0.6)) +
annotate(geom = "rect",
xmin = 12, xmax = 14, ymin = 3, ymax = 4,
linetype = "dotted", fill = NA, colour = "black")
Created on 2020-12-15 by the reprex package (v0.3.0)

Add offset label to ggplot histogram

I wish to add a label to one specific bar of a histogram, but off to the side, not above. Like this:
I'm unsure as to how to ONLY label the red bar nor how to offset the label with an arrow.
Code
library(tidyverse)
tree_df <- tibble (
rank = c(1, 2, 3, 4, 5),
name = c("oak", "elm", "maple", "pine", "spruce"),
freq = c(300, 50, 20, 10, 5)
)
bar_colour <- c(rep("black", 4), rep("red", 1))
last_bar <- tree_df[5,]
ggplot(data = tree_df, aes(x = reorder(row.names(tree_df), freq), y = freq)) +
geom_col(fill = bar_colour) +
geom_label(data = tree_df, label = c("Norway"))
If this is just a one-off and you're OK specifying the label position manually, you can use annotate:
ggplot(data = tree_df, aes(x = reorder(row.names(tree_df), freq), y = freq)) +
geom_col(fill = bar_colour) +
annotate(geom = "segment", x = 4, xend = 4.5, y = 250, yend = 250,
arrow = arrow(length = unit(0.03, "npc"))) +
annotate(geom = "label", x = 4, y = 250, label = "Norway")
Result:

Why do geom_density and stat_density(geom = "line") give different results?

In the following illustration, why do geom_density and stat_density(geom = "line") give different results?
library(ggplot2)
df <- data.frame(
x.values = c(
rnorm(100, mean = 1, sd = 1),
rnorm(100, mean = 4, sd = 1),
rnorm(100, mean = 7, sd = 1),
rnorm(100, mean = 10, sd = 1)
),
mean.values = sort(rep(c(1, 4, 7, 10), 100))
)
p <- ggplot(df, aes(x = x.values, color = mean.values, group = mean.values))
p + geom_density()
p + stat_density(geom = "line")
It's a difference in the position argument. The default in stat_density is position = "stack", whilst with geom_density() it is position = "identity".
If you call p + stat_density(geom = "line", position = "identity") you get the same as geom_density():

Connect line through facet_wrap in ggplot

The question relates to this: Line graph customization (add circles, colors), but since I got a new task, I created a new question.
So again my data frame is the same as in the question I've posted in a link. With code below and (little of my own modification) that was given to me by #beetroot
value <- c(9, 4, 10, 7, 10,
10, 10, 4, 10,
4, 10, 2, 5, 5, 4)
names <- c("a","b",
"c","d","e",
"f", "g","h",
"i","j","k","l",
"m","n","p")
df <- data.frame(value, names)
df$names <- as.character(df$names)
df$part <- rep(c("part3", "part2", "part1"), each = 5)
library(dplyr)
library(tidyr)
df2 <- df %>%
group_by(part, names) %>%
expand(value = min(df$value):max(df$value))
p <- ggplot() +
geom_point(data = df2, aes(x = value, y = names),
shape = 1) +
geom_point(data = df, aes(y = names, x = value, group = 1),
colour = I("red"), shape = 21, lwd = 3, fill = "red") +
geom_line(data = df, aes(y = names, x = value, group = 1),
group = I(1),color = I("red")) +
theme_bw() +
facet_wrap(~part, ncol = 1, scales = "free_y")
p + theme(strip.background = element_rect(fill="dodgerblue3"),
strip.text.x = element_text(colour = "white"))+xlab("") +ylab("")
df <- data.frame(value, names)
df$names <- as.character(df$names)
I get this output:
But now I would like to connect lines through (PART1, PART2 and PART3) so that my output would look like:
I used black color of a line just it will be more visible that I would like to connect this parts with lines.
Although I am not completely satisfied I've found solution. I computed the bounding box.
Firstly I removed facet_wrap(~part, ncol = 1, scales = "free_y") so my code looks like this:
p <- ggplot() +
geom_point(data = df2, aes(x = value, y = names),
shape = 1) +
geom_point(data = df, aes(y = names, x = value, group = 1),
colour = I("red"), shape = 21, lwd = 3, fill = "red") +
geom_line(data = df, aes(y = names, x = value, group = 1),
group = I(1),color = I("red")) +
theme_bw()
Then the trick was to create data frame and add the width and height of text directly:
# PART 1
TextFrame <- data.frame(X = 6, Y = 15.5, LAB = "PART 1")
TextFrame <- transform(TextFrame,
w = strwidth(LAB, 'inches') + 8,
h = strheight(LAB, 'inches') + 0.3
)
# PART 2
TextFrame.1 <- data.frame(X = 6, Y = 10.5, LAB = "PART 2")
TextFrame.1 <- transform(TextFrame.1,
w = strwidth(LAB, 'inches') + 8,
h = strheight(LAB, 'inches') + 0.3
)
# PART 3
TextFrame.2 <- data.frame(X = 6, Y = 4.5, LAB = "PART 3")
TextFrame.2 <- transform(TextFrame.2,
w = strwidth(LAB, 'inches') + 8,
h = strheight(LAB, 'inches') + 0.3
)
Then I've used geom_rectand geom_text to create the illusion I am after.
p + geom_rect(data = TextFrame, aes(xmin = X - w/2, xmax = X + w/2,
ymin = Y - h/2, ymax = Y + h/2), fill = "dodgerblue3") +
geom_text(data = TextFrame,aes(x = X, y = Y, label = LAB), size = 5) +
geom_rect(data = TextFrame.1, aes(xmin = X - w/2, xmax = X + w/2,
ymin = Y - h/2, ymax = Y + h/2), fill = "dodgerblue3") +
geom_text(data = TextFrame.1,aes(x = X, y = Y, label = LAB), size = 5) +
geom_rect(data = TextFrame.2, aes(xmin = X - w/2, xmax = X + w/2,
ymin = Y - h/2, ymax = Y + h/2), fill = "dodgerblue3") +
geom_text(data = TextFrame.2,aes(x = X, y = Y, label = LAB), size = 5)
And the output is:

Multiple histograms with non-integer frequencies in R using ggplot

I'm trying to find a way to plot multiple histograms of non-integer frequencies in R. For example:
a = c(1,2,3,4,5)
a_freq = c(1.5, 2.5, 3.5, 4.5, 5.5)
b = c(2, 4, 6, 8, 10)
b_freq = c(2.5, 5, 6, 7, 8)
using something like
qplot(x = a, weight = a_freq, geom = "histogram")
works, but how do I superimpose b (and b_freq) onto this? any ideas?
This is what we would do if the frequencies are integer values:
require(ggplot2)
require(reshape2)
set.seed(1)
df <- data.frame(x = rnorm(n = 1000, mean = 5, sd = 2), y = rnorm(n = 1000, mean = 2), z = rnorm(n = 1000, mean = 10))
ggplot(melt(df), aes(value, fill = variable)) + geom_histogram(position = "dodge")
Something similar, when we have non_integer values?
Thanks,
Karan
I'm still not entirely sure what you're trying to do, so here are four options:
library(ggplot2)
a = c(1,2,3,4,5)
a_freq = c(1.5, 2.5, 3.5, 4.5, 5.5)
b = c(2, 4, 6, 8, 10)
b_freq = c(2.5, 5, 6, 7, 8)
dat <- data.frame(x = c(a,b),
freq = c(a_freq,b_freq),
grp = rep(letters[1:2],each = 5))
ggplot(dat,aes(x = x,weight = freq,fill = grp)) +
geom_histogram(position = "dodge")
ggplot(dat,aes(x = x,y = freq,fill = grp)) +
geom_bar(position = "dodge",stat = "identity",width = 0.5)
ggplot(dat,aes(x = x,y = freq,fill = grp)) +
facet_wrap(~grp) +
geom_bar(stat = "identity",width = 0.5)
ggplot() +
geom_bar(data = dat[dat$grp == 'a',],aes(x = x,y = freq),
fill = "blue",
alpha = 0.5,
stat = "identity",
width = 0.5) +
geom_bar(data = dat[dat$grp == 'b',],aes(x = x,y = freq),
fill = "red",
alpha = 0.5,
stat = "identity",
width = 0.5)
If you have a discrete x values and precomputed "heights" that is not a histogram, that is a bar plot, so I would opt for one of those.

Resources