getting colour scale gradient to work with ggplot converted to ggplotly - r

I'm not able to correctly draw a colour aesthetic line in plotly, using a ggplot object. What am I missing?
library(ggplot2)
library(plotly)
df <- data.frame(val = as.numeric(LakeHuron), idx = 1:length(LakeHuron))
p <- ggplot(df, aes(x = idx, y = val, colour = val)) + geom_line()
p <- p + scale_color_gradient2(low="red", mid = "gold", high="green", midpoint = mean(df$val))
p
p2 <- ggplotly(p)
p2
p prints the correct expected output.
When I print the plotly object p2, I dont get the line points joining correctly?
The problem is when i add the colour aesthetic I think.
Versions:
plotly 4.9, ggplot2 3.1.1

This is due to a limitation / difference in how plotly works vs. ggplot. Looks like there's an open issue here updated August 2018 suggesting it's not possible within the same structure ggplot uses -- a single series in plotly can't currently have varying color. ("We don't allow per-segment coloring on line traces")
But fear not! We could construct the plot a little differently using geom_segment to specify each part of the line as a separate segment. This structure is a separate object for each segment and will convert over to plotly fine:
df <- data.frame(val = as.numeric(LakeHuron), idx = 1:length(LakeHuron))
p_seg <- ggplot(df, aes(x = idx, y = val,
xend = lead(idx), yend = lead(val),
colour = val)) +
geom_segment()
p_seg <- p_seg + scale_color_gradient2(low="red", mid = "gold", high="green", midpoint = mean(df$val))
p_seg
p2 <- ggplotly(p_seg)

Related

Is there are a way to change the breaks of a ggplot legend without changing other properties of the aesthetic?

I wish to change the breaks of a ggplot legend without affecting the other properties of the aesthetic (e.g., palette, name, etc.). For example, a MWE where the aesthetic is colour:
## Original plot:
df <- data.frame(x = 1:10, y = 1:10, z = 1:10)
gg <- ggplot(df, aes(x, y, colour = z)) +
geom_point() +
scale_colour_distiller(palette = "Spectral", name = "Original title")
gg
## Plot with adjusted breaks:
gg + scale_colour_distiller(breaks = c(2.5, 7.5))
Original plot
Plot with adjusted breaks
In the second plot, the colour palette and the legend name are reset to their default values: I want to change the legend breaks only.
I understand why the above approach does not work; the first colour scale is completely replaced by the second scale. However, I don't know how to tackle this problem. Any advice is greatly appreciated!
I wrote a function which solves my question. It takes a ggplot object, the name of an aesthetic (as a string), and the breaks for the corresponding legend.
change_legend_breaks <- function(gg, aesthetic, breaks) {
## Find the scales associated with the specifed aesthetic
sc <- as.list(gg$scales)$scales
all_aesthetics <- sapply(sc, function(x) x[["aesthetics"]][1])
idx <- which(aesthetic == all_aesthetics)
## Overwrite the breaks of the specifed aesthetic
gg$scales$scales[[idx]][["breaks"]] <- breaks
return(gg)
}
This is my first time dealing with ggplot objects at a low level, so perhaps there is a better, more robust approach: This works for me, though.
Interestingly, it seems to be a mutating function, that is, it alters the plot object itself, rather than a copy of the object. I didn't know this was possible in R.
As a check that the function works as intended, here is a variant on the original MWE, this time with two aesthetics:
df <- data.frame(x = 1:10, y = 1:10, z1 = 1:10, z2 = 1:10)
gg <- ggplot(df, aes(x, y, colour = z1, size = z2)) +
geom_point() +
scale_size(name = "Original size title") +
scale_colour_distiller(palette = "Spectral", name = "Original colour title")
change_legend_breaks(gg, "colour", breaks = c(2.5, 7.5))
change_legend_breaks(gg, "size", breaks = c(1, 9))

Create "The Economist" Style Plots in R?

This question has two parts, one more general and the other a specific case:
Is there a theme or template in R for producing plots that have similar appearance to the charts published in "The Economist" magazine? Examples in other contexts include: Create "The Economist" style graphs from python for python and set scheme economist for Stata.
Specifically, what would be the syntax (e.g., in ggplot2) to produce a groups bar plot that would look like the example below, colored shaped markers with bold lines spanning the range between them (left panel), or rectangular confidence intervals (right panel)?
Source: https://www.economist.com/graphic-detail/2020/04/01/covid-19-may-be-far-more-prevalent-than-previously-thought
Yes you have it in ggthemes (extension of ggplot2) with theme_economist and theme_economist_white.
For the bar plot, you will need to play with geom_bar and coord_flip (here)
Examples from ggthemes doc (here)
library("ggplot2")
library("ggthemes")
p <- ggplot(mtcars) +
geom_point(aes(x = wt, y = mpg, colour = factor(gear))) +
facet_wrap(~am) +
# Economist puts x-axis labels on the right-hand side
scale_y_continuous(position = "right")
## Standard
p + theme_economist() +
scale_colour_economist()
## White
p + theme_economist_white() +
scale_colour_economist()
How to reproduce the plot given in example
Since I cannot install SciencesPo package in my computer, I propose you a ggplot + ggthemes approach.
A good starting point might be the following approach. I use as an example the diamond dataset.
library(dplyr)
library(ggplot2)
library(ggthemes)
df <- diamonds %>%
group_by(cut) %>%
summarise(mean = mean(price), sigma = sd(price),
n = n())
df <- df %>%
mutate(int_minus = mean - 1.96*sigma/sqrt(n),
int_plus = mean + 1.96*sigma/sqrt(n))
And then the plot
ggplot(df) +
geom_segment(aes(x = int_minus, xend = int_plus, y = factor(cut), yend = factor(cut)), size = 2L, alpha = 0.4) +
geom_point(aes(x = mean, y = factor(cut)), shape = 15, color = "blue", size = 4L) +
theme_economist_white()

How to plot a line with color vector in R Plotly

Say I have the following data frame:
ret <- rnorm(100, 0, 5)
df <- data.frame(
x = seq(1, 100, 1),
ret = ret,
y = 100 + cumsum(ret),
col = c(ifelse(ret > 0, "red", "forestgreen"), NA)[-1]
)
Here I'm simulating the returns of some fictional financial asset using rnorm named 'ret', and am defining a color vector named 'col' where upticks are green and downticks are red.
What I want to produce is something like the following:
library(ggplot2)
ggplot(df, aes(x=x, y=y)) + geom_line(aes(colour=col, group=1))
But I want to make a similar image using plotly so that I can zoom in on sections of the plot. My first thought was to try simply using the ggplotly() function around the code that produced the desired image:
library(plotly)
ggplotly(ggplot(df, aes(x=x, y=y)) + geom_line(aes(colour=col, group=1)))
But the plot is no longer grouped. Additionally, I tried using plot_ly() but can't seem to make the line segments get their color according to the 'col' attribute that I'm specifying:
plot_ly(data=df, x = ~x) %>% add_lines(y = ~y, line = list(color=~col))
But my color argument doesn't affect the color of the line. I've tried various other things but keep ending up with one of the two undesired plots. Any help would be much appreciated!
Note: I've already made candlestick and OHLC charts with plot_ly(), but I can't work with them because the y axis doesn't scale when you zoom in to a subsection of the plot.
I was able to get the desired behaviour from ggplotly by using geom_segment and making each segment link up to the next (x, y) value, regardless of colour:
library(dplyr)
df = df %>%
arrange(x) %>%
mutate(x_next = lead(x), y_next = lead(y))
p = ggplot(df, aes(x=x, y=y)) +
geom_segment(aes(xend = x_next, yend = y_next, colour=col))
ggplotly(p)
That said, I don't have a good answer for why ggplotly doesn't produce the desired output in the first place.

Different behavior between ggplot2 and plotly using ggplotly

I want to make a line chart in plotly so that it does not have the same color on its whole length. The color is given continuous scale. It is easy in ggplot2 but when I translate it to plotly using ggplotly function the variable determining color behaves like categorical variable.
require(dplyr)
require(ggplot2)
require(plotly)
df <- data_frame(
x = 1:15,
group = rep(c(1,2,1), each = 5),
y = 1:15 + group
)
gg <- ggplot(df) +
aes(x, y, col = group) +
geom_line()
gg # ggplot2
ggplotly(gg) # plotly
ggplot2 (desired):
plotly:
I found one work-around that, on the other hand, behaves oddly in ggplot2.
df2 <- df %>%
tidyr::crossing(col = unique(.$group)) %>%
mutate(y = ifelse(group == col, y, NA)) %>%
arrange(col)
gg2 <- ggplot(df2) +
aes(x, y, col = col) +
geom_line()
gg2
ggplotly(gg2)
I also did not find a way how to do this in plotly directly. Maybe there is no solution at all. Any ideas?
It looks like ggplotly is treating group as a factor, even though it's numeric. You could use geom_segment as a workaround to ensure that segments are drawn between each pair of points:
gg2 = ggplot(df, aes(x,y,colour=group)) +
geom_segment(aes(x=x, xend=lead(x), y=y, yend=lead(y)))
gg2
ggplotly(gg2)
Regarding #rawr's (now deleted) comment, I think it would make sense to have group be continuous if you want to map line color to a continuous variable. Below is an extension of the OP's example to a group column that's continuous, rather than having just two discrete categories.
set.seed(49)
df3 <- data_frame(
x = 1:50,
group = cumsum(rnorm(50)),
y = 1:50 + group
)
Plot gg3 below uses geom_line, but I've also included geom_point. You can see that ggplotly is plotting the points. However, there are no lines, because no two points have the same value of group. If we hadn't included geom_point, the graph would be blank.
gg3 <- ggplot(df3, aes(x, y, colour = group)) +
geom_point() + geom_line() +
scale_colour_gradient2(low="red",mid="yellow",high="blue")
gg3
ggplotly(gg3)
Switching to geom_segment gives us the lines we want with ggplotly. Note, however, that line color will be based on the value of group at the first point in the segment (whether using geom_line or geom_segment), so there might be cases where you want to interpolate the value of group between each (x,y) pair in order to get smoother color gradations:
gg4 <- ggplot(df3, aes(x, y, colour = group)) +
geom_segment(aes(x=x, xend=lead(x), y=y, yend=lead(y))) +
scale_colour_gradient2(low="red",mid="yellow",high="blue")
ggplotly(gg4)

Adding a table to ggplot with gridExtra and annotation_custom() changes y-axis limits

I tried adding a little summary table to a plot which I created with ggplot2::ggplot(). The table is added via gridExtra::tableGrob() to the saved ggplot object.
My problem is that this seems to change the y-limits of my original plot.
Is there a way to avoid that without having to specify the limits again via ylim()?
Here is a minimal example for the problem using the ChickWeight dataset:
# load packages
require(ggplot2)
require(gridExtra)
# create plot
plot1 = ggplot(data = ChickWeight, aes(x = Time, y = weight, color = Diet)) +
stat_summary(fun.data = "mean_cl_boot", size = 1, alpha = .5)
plot1
# create table to add to the plot
sum_table = aggregate(ChickWeight$weight,
by=list(ChickWeight$Diet),
FUN = mean)
names(sum_table) = c('Diet', 'Mean')
sum_table = tableGrob(sum_table)
# insert table into plot
plot1 + annotation_custom(sum_table)
EDIT:
I just figured out that it seems to be an issue with stat_summary(). When I use another geom/layer, then the limits stay as they were in the original plot. Another example for that:
plot2 = ggplot(data = ChickWeight, aes(x = Time, y = weight, color = Diet)) +
geom_jitter()
plot2
plot2 + annotation_custom(sum_table)
The y-range for plot1 is different from plot2, the reason being that annotation_custom takes its aesthetics from the original aes statement, not the modified data frame used by stat_summary(). To get the y-ranges for the two plots to be the same (or roughly the same - see below), stop annotation_custom getting its aesthetics from the original data. That is, move aes() inside the stat_summary().
# load packages
require(ggplot2)
require(gridExtra)
# create plot
plot1 = ggplot(data = ChickWeight) +
stat_summary(aes(x = Time, y = weight, color = Diet), fun.data = "mean_cl_boot", size = 1, alpha = .5)
plot1
# create table to add to the plot
sum_table = aggregate(ChickWeight$weight,
by=list(ChickWeight$Diet),
FUN = mean)
names(sum_table) = c('Diet', 'Mean')
sum_table = tableGrob(sum_table)
# insert table into plot
plot2 = plot1 + annotation_custom(sum_table, xmin = 10, xmax = 10, ymin = 200, ymax = 200)
plot2
By the way, the reason the two plots will not give the exact same y-range is because of the bootstrap function in stat_summary(). Indeed, plot p1 repeatedly, and you might notice slight changes in the y-range. Or check the y-ranges in the build data.
Edit Updating to ggplot2 ver 3.0.0
ggplot_build(plot1)$layout$panel_params[[1]]$y.range
ggplot_build(plot2)$layout$panel_params[[1]]$y.range
Recall that ggplot does not evaluate functions until drawing time - each time p1 or p2 is drawn, a new bootstrap sample is selected.

Resources