Overlaying 2 histograms by 2 groups in plotly - r

I have a data.table, and I would like to create an histogram (or barplot) by 2 groups in plotly
library(data.table)
library(plotly)
library(ggplot2)
n = 7200
n1 = 4/3*n
n2 = 2*n
dt = data.table(x = sample(rep(c("0-20", "21-40", "41-60", "61-80"), n)),
group1 = sample(rep(c("A", "B", "C"), n1)),
group2 = sample(rep(c(0, 1), n2))
)
setorder(dt, x, group1, group2)
dt[, x := factor(x)]
dt[, group1 := factor(group1)]
dt[, group2 := factor(group2)]
ggplot(dt) + geom_bar(aes(x = x, fill = factor(group2)), width = 1) +
scale_fill_manual(values = c("#9c868b", "#038073"), guide = 'none') + guides(legend = 'none') +
scale_y_continuous(position = 'right') +
facet_grid(rows = vars(forcats::fct_rev(group1)), switch = 'y') +
coord_flip(clip = "off")
Here is the result I want to have (made with ggplot) and I don't want to use ggplotly(...)
I do not know if I have to handle data like below to create barplot instead of histogram
dt = dt[, .N, by = .(x, group1, group2)]
dt = dcast(dt,
group1 ~ x + group2,
value.var = c("N"))

You could make something similar in a few lines of code. If you want all the details lined up as you've depicted, it's a 'few more'.
By the way, I used set.seed(34) if you wanted to see the exact same plot.
# not really what you're looking for
plot_ly(subset(dt, group2 == "0"), type = 'histogram', name = 'group 0',
y = ~list(rev(group1), x), orientation = 'h') %>%
add_histogram(subset(dt, group2 == "1"), name = 'group 1',
y = ~list(rev(group1), x), orientation = 'h') %>%
layout(barmode = 'stack')
(I didn't include the axis title or legend in the image; I'm just trying to highlight the lack of gap)
You can always continue to mod this graph toward the desired plot. However, you won't get the gaps you're looking for between the bars.
Alternatively, you could use subplot and make a separate plot for each of the unique values used in faceting in your original plot.
lapply(1:length(unique(dt$group1)), # for each facet...
function(k) {
dt <- subset(dt, group1 == unique(dt$group1)[k]) # find facet data
p <- plot_ly(dt, type = "histogram", color = ~group2,
y = ~x, orientation = 'h', showlegend = F) %>% # no legend
layout(barmode = 'stack', bargap = 0)
assign(paste0('p', k), p, envir = .GlobalEnv) # put in global env
})
subplot(p1, p2, p3, nrows = 3, titleX = T, shareX = T) %>% # assemble facets
layout(xaxis = list(side = 'top', title = 'count', anchor = 'y1')) # anchor top plot
With a few more lines of code, you can add the labeling as you see in ggplot faceting.
lapply(1:length(unique(dt$group1)), # for each facet...
function(k) {
message(print(k))
dt <- subset(dt, group1 == unique(dt$group1)[k]) # find facet data
p <- plot_ly(dt, type = "histogram", color = ~group2,
y = ~x, orientation = 'h', showlegend = F) %>% # no legend
layout(barmode = 'stack', bargap = 0,
shapes = list( # like facet plot this is the gray bar behind label
type = "rect", xref = 'x', yref = 'paper', # set plot 'space'
y0 = 0, y1 = 1, x0 = -250, x1 = -50, # rect limits
fillcolor = 'lightgrey',
line = list(linewidth = 0.0001, color = 'lightgrey') # remove border
),
annotations = list( # like facet plot, this is the facet label
showarrow = F, text = unique(dt$group1), # no arrow; label
xref = 'x', yref = 'paper', x = -150, y = .5, # center of 'rect'
xanchor = 'center', yanchor = 'center', textangle = -90 # rotate text
))
assign(paste0('p', k), p, envir = .GlobalEnv) # put in global env
})
subplot(p1, p2, p3, nrows = 3, titleX = T, shareX = T) %>% # assemble facets
layout(xaxis = list(side = 'top', title = 'count', anchor = 'y1')) # anchor top plot

Related

Is there a way to add a shared axis title on a subplot?

I'm trying to create a 2x2 subplot, with both plots in each column having the same y-axis title, like this :
i.e. one 'title' (here called annotations, cf. later) for the left column (blue+green) and one for the right column (yellow+red).
I can easily have a yaxis title for each plot but I'm stumped as to making shared ones.
I tried using annotations, like this (this is the code used to render the plot shown above) :
if (!require("plotly")) install.packages("plotly")
library(plotly)
group <- c("a", "b", "c")
values <- c(0, 5, 10)
df <- data.frame(group, values)
plot <- df %>%
plot_ly() %>%
add_trace(x = ~group, y = ~values, type = "scatter", mode = "line") %>%
layout(yaxis = list(ticks = "outside"), xaxis = list(showline = TRUE))
plot
subdf1 <- subplot(plot, plot, nrows = 1, margin = 0.06)
subdf2 <- subplot(plot, plot, nrows = 1, margin = 0.06)
subdf <- subplot(subdf1, subdf2, nrows = 2, margin = 0.06) %>%
layout(annotations = list(list(x = -0.1, y = 0.5, text = "<b>First annotation</b>", xref = "paper", yref = "paper", xanchor = "center", yanchor = "center", showarrow = FALSE, textangle = -90, font = list(color = "black", size = 16)),
list(x = 0.48, y = 0.5, text = "<b>Second annotation</b>", xref = "paper", yref = "paper", xanchor = "center", yanchor = "center", showarrow = FALSE, textangle = -90, font = list(color = "black", size = 16))))
subdf
My main gripe with this method is that when the plot is resized, the annotations (mainly the first one, in the negative range for x-axis placement) move around the x-axis.
Same plot but wider :
I used xref = "paper" as I thought it meant the whole plot area i.e. the whole white background, but in such case, my annotation wouldn't disappear (and wouldn't be in negatives, but I'm possibly not thinking about this the right way). I did try using xref = x but it won't go into negatives and instead just push the data to the right.
So all in all, two questions :
Is there a native way to have a shared axis title for subplots?
If not, is there a way to make sure that my annotations stay in the same relative place as the plots and axes when resizing the subplot?
If you aren't tied to using plotly, this can be done in a straightforward way using faceting in ggplot. It may require some rearranging of your data into tidy format but gives some serious flexibility while plotting!
library(ggplot2)
group <- c("a", "b", "c")
values <- c(0, 5, 10)
df <- data.frame(group, values)
df <- data.frame(group = rep(c('a','b','c'), 4),
values = rep(c(0,5,10), 4),
facet = rep(c('W','X','Y','Z'), each = 3))
ggplot(df, aes(x = group, y = values, colour = facet, group = 1)) +
geom_line(size = 1.1) +
geom_point(size = 2) +
facet_wrap(~facet) +
theme_bw() +
labs(x = 'Shared X axis title', y = 'Shared Y axis title', colour = 'Traces') +
theme(
strip.background = element_blank(),
strip.text.x = element_blank()
)
You could create a separate title in each layout of both subplots and combine them using titleY like this:
library(plotly)
library(dplyr)
group <- c("a", "b", "c")
values <- c(0, 5, 10)
df <- data.frame(group, values)
plot <- df %>%
plot_ly() %>%
add_trace(x = ~group, y = ~values, type = "scatter", mode = "line") %>%
layout(yaxis = list(ticks = "outside"), xaxis = list(showline = TRUE))
subdf1 <- subplot(plot, plot, nrows = 1) %>%
layout(yaxis = list(title = "First annotation"))
subdf2 <- subplot(plot, plot, nrows = 1) %>%
layout(yaxis = list(title = "Second annotation"))
subdf <- subplot(subdf1, subdf2, nrows = 2, titleY = TRUE)
subdf
Created on 2023-01-23 with reprex v2.0.2
Edit
Change margin in layout:
library(plotly)
library(dplyr)
group <- c("a", "b", "c")
values <- c(0, 5, 10)
df <- data.frame(group, values)
plot <- df %>%
plot_ly() %>%
add_trace(x = ~group, y = ~values, type = "scatter", mode = "line") %>%
layout(yaxis = list(ticks = "outside"), xaxis = list(showline = TRUE))
subdf1 <- subplot(plot, plot, nrows = 1, margin = 0.06)
subdf2 <- subplot(plot, plot, nrows = 1, margin = 0.06)
subdf <- subplot(subdf1, subdf2, nrows = 2, margin = 0.06) %>%
layout(margin = 0.01,
annotations = list(list(x = -0.1, y = 0.5, text = "<b>First annotation</b>", xref = "paper", yref = "paper", xanchor = "center", yanchor = "center", showarrow = FALSE, textangle = -90, font = list(color = "black", size = 16)),
list(x = 0.48, y = 0.5, text = "<b>Second annotation</b>", xref = "paper", yref = "paper", xanchor = "center", yanchor = "center", showarrow = FALSE, textangle = -90, font = list(color = "black", size = 16))))
subdf
Created on 2023-01-23 with reprex v2.0.2

Secondary axis in plotly does not work for different scales

How can I reproduce the following graph in plotly
library(dplyr)
library(ggplot2)
tibble(x =1:10, y = 1:10) %>%
ggplot(aes(x,y)) +
geom_line() +
scale_y_continuous(sec.axis = ~.*2)
I tried the following code based on this answer here
library(dplyr)
library(plotly)
tibble(x =1:10, y = 1:10) %>%
mutate(y2 = y*2) %>%
plot_ly() %>%
add_lines(x =~x, y =~y) %>%
add_lines(x= ~x, y=~y2,
yaxis = "y2", color = I("transparent"),
hoverinfo='skip', showlegend=FALSE) %>%
layout(yaxis2 = list(side = "right", overlaying = "y", showgrid = FALSE),
margin = list(r = 50))
While at first glance it appears to work, it only provides a partial solution, since if I interactively try to change the scale of the main (left) y axis on the produced graph (by dragging it up or down), the right axis does not move with the graph (because it is linked only to the second invisible graph). This of course is not acceptable as it does not allow using any of interactive features of plotly reliably which is the reason I wanted to use it to begin with instead of ggplot.
Edit: Just realized that this plotly solution does not seem to work at all in the case of non linear transformation between the axes (while ggplot handles it beautifully).
You just need to set up dtick and tick0 for plotly to have the same graph as ggplot2 one. See below;
library(plotly)
library(dplyr)
tibble(x =1:10, y = 1:10) %>%
mutate(y2 = y*2) -> df1
n0 <- 4
y0 <- max(df1$y)/n0
x0 <- max(df1$x)/n0
df1 %>%
plot_ly(data = . , x = ~x, y = ~y,
type = "scatter", mode = "lines", width = 800,
color = I("red"), name = "") %>%
add_trace(x = ~x, y = ~y2, yaxis = "y2",
color = I("red"), name = "") %>%
layout(yaxis = list(showline = FALSE, side = "left",
title = "", color = "red",
dtick = y0, tick0 = y0, fixedrange=TRUE),
yaxis2 = list(showline = FALSE, side = "right", overlaying = "y", position = 1,
title = "", anchor = "free", color = "blue",
dtick = 2*y0, tick0 = 2*y0, fixedrange=TRUE),
xaxis = list(showline = FALSE, zeroline = FALSE, title = "",
dtick = x0, tick0 = x0),
showlegend = FALSE,
margin = list(l = 50, r = 50, b = 50, t = 50, pad = 4)
)
Created on 2020-06-19 by the reprex package (v0.3.0)

R ggplotly() and colour annotations - How do you do it?

I am trying to replicate a plot from ggplot with the added functionality from Plotly with hover points, but it strips the annotations out and have tried everything to achieve the same view with no success .
library("ggplot2")
library("plotly")
test_data <- data.frame(A = c(1,5,7,4,2),
B = c(3,3,6,8,4))
my_days <- as.Date(c("2010-01-01", "2010-02-01",
"2010-03-01", "2010- 4-01",
"2010-05-01"))
df <- data.frame(test_data, my_days)
# Anotate Box
s_1 <- unique(min(df$my_days))
s_2 <- unique(max(df$my_days))
target <- 1
plot_out <- df %>%
group_by(my_days) %>%
summarise(prop = sum(A / B)) %>%
ggplot(aes(x =my_days, y = prop)) +
geom_line(color = "purple") +
annotate("rect", xmin = s_1, xmax = s_2, ymin = -Inf, ymax = target, alpha = .2, fill = "red") +
annotate("rect", xmin = s_1, xmax = s_2, ymin = target, ymax = Inf, alpha = .2, fill = "green")
plot_out # Plot with Colour
ggplotly(plot_out) # This gives the hover info points , but removes the annotates
Not a ggplotly solution but a plotly solution. (; At least in my opinion ggplotly is nice if you want to make a quick interactive version of a ggplot. However, ggplotly still has a lot of issues and is not able to convert every ggplot. Try this:
library("ggplot2")
library("plotly")
test_data <- data.frame(
A = c(1, 5, 7, 4, 2),
B = c(3, 3, 6, 8, 4)
)
my_days <- as.Date(c(
"2010-01-01", "2010-02-01",
"2010-03-01", "2010- 4-01",
"2010-05-01"
))
df <- data.frame(test_data, my_days)
# Anotate Box
s_1 <- unique(min(df$my_days))
s_2 <- unique(max(df$my_days))
target <- 1
p <- df %>%
group_by(my_days) %>%
summarise(prop = sum(A / B)) %>%
plot_ly(x = ~my_days, y = ~prop) %>%
add_lines(
line = list(color = "purple"),
hoverinfo = "text",
text = ~ paste0(
"mydays: ", my_days,
"\n", "prop: ", round(prop, 7)
)) %>%
# Add the rectangles and set x-axis as in ggplot
layout(
xaxis = list(
type = "date",
tickformat = "%b",
nticks = 5
),
shapes = list(
list(
type = "rect",
fillcolor = "red", opacity = 0.2,
x0 = s_1, x1 = s_2, xref = "x",
y0 = -Inf, y1 = target, yref = "y"
),
list(
type = "rect",
fillcolor = "green", opacity = 0.2,
x0 = s_1, x1 = s_2, xref = "x",
# Setting y1 to Inf results in a yaxis which spans up to 2.5. So I chose 1.8 to mimic the ggplot
y0 = target, y1 = 1.8, yref = "y"
)
)
)
p
Created on 2020-04-05 by the reprex package (v0.3.0)

Sharing x-axis labels in a horizontal plotly subplot figure

I have a list of figures created with R's plotly, just as an example:
library(plotly)
library(dplyr)
set.seed(1)
data.df <- data.frame(val = c(rnorm(100,0,1),rnorm(100,1,1)), group = c(rep("A",100),rep("B",100)))
density.df <- do.call(rbind,lapply(levels(data.df$group),function(g){
dens <- density(dplyr::filter(data.df,group == g)$val)
data.frame(x = dens$x, y = dens$y, group = g)
}))
plot.list <- lapply(1:5,function(x)
plot_ly(x = density.df$x, y = density.df$y, type = 'scatter', mode = 'lines',color = density.df$group, showlegend = (x == 5)) %>%
layout(xaxis = list(title= "Value", zeroline = F), yaxis = list(title = "Density", zeroline = F))
)
Which I'd like to put together horizontally, where there will be only a single shared x-axis label and a single shared y-axis label.
I'm using:
plotly::subplot(plot.list, nrows = 1, shareX = T, shareY = T, titleX = T, titleY = T)
And getting:
Is it not possible to get a single x-axis label in a horizontal plot?
The same occurs for the y-axis label if I change the nrows argument value from 1 to 5.
You can manually add an annotation
https://community.plot.ly/t/subplots-how-to-add-master-axis-titles/13927/5
myplotly(df) %>%
add_annotations(
text = "my x title",
x = 0.5,
y = 0,
yref = "paper",
xref = "paper",
xanchor = "center",
yanchor = "bottom",
yshift = -35,
showarrow = FALSE,
font = list(size = 15)
)

Duplicated legends when faceting in ggplotly

I'm making some figures with ggplotly() and have noticed that facet_wrap and facet_grid causes each item in the legend to be repeated by the number of facets. Is there a way to stop this?
For example:
library("ggplot2")
library("plotly")
diamonds = diamonds[diamonds$cut %in% c("Fair", "Good"),]
dia = ggplot(diamonds, aes(x = cut)) +
geom_bar(aes(stat = "identity", fill = cut)) +
facet_grid(.~color)
ggplotly(dia)
The ?plotly documentation isn't very elaborate, and none of these have legends.
Here's what comes up when I just type ggplotly if that gives any insight:
function (p = ggplot2::last_plot(), filename, fileopt, world_readable = TRUE)
{
l <- gg2list(p)
if (!missing(filename))
l$filename <- filename
if (!missing(fileopt))
l$fileopt <- fileopt
l$world_readable <- world_readable
hash_plot(p$data, l)
}
UPDATE
Issues appear fixed with Plotly 3.6.0 -- 16 May 2016
Due to the ggplotly bug for geom_bar, which distorts the data for the bars, there may not be a good way to do this. For this particular case, facet is not needed. You can use plot_ly() to build an effective plot.
Plot_ly
require(plotly)
require(dplyr)
d <- diamonds[diamonds$cut %in% c("Fair", "Good"),] %>%
count(cut, color)
plot_ly(d, x = color, y = n, type = "bar", group = cut)
Use Plotly subplot()
If this plot type is a must, you can build a facet-like plot using Plotly's subplot. It's not pretty.
d2 <- diamonds[diamonds$cut %in% c("Fair", "Good"),] %>%
count(cut, color) %>%
transform(color = factor(color, levels=rev(levels(color)))) %>%
mutate(id = as.integer(color))
p <- plot_ly(d2, x = cut, y = n, type = "bar", group = color, xaxis = paste0("x", id), marker = list(color = c("#0000FF","#FF0000"))) %>%
layout(yaxis = list(range = range(n), linewidth = 0, showticklabels = F, showgrid = T, title = ""),
xaxis = list(title = ""))
subplot(p) %>%
layout(showlegend = F,
margin = list(r = 100),
yaxis = list(showticklabels = T),
annotations = list(list(text = "Fair", showarrow = F, x = 1.1, y = 1, xref = "paper", yref = "paper"),
list(text = "Good", showarrow = F, x = 1.1, y = 0.96, xref = "paper", yref = "paper")),
shapes = list(list(type = "rect", x0 = 1.1, x1 = 1.13, y0 = 1, y1 = 0.97, line = list(width = 0), fillcolor = "#0000FF", xref = "paper", yref = "paper"),
list(type = "rect", x0 = 1.1, x1 = 1.13, y0 = 0.96, y1 = 0.93, line = list(width = 0), fillcolor = "#FF0000", xref = "paper", yref = "paper")))
You could just turn off the guide/legend in this case as you don't really need it.
library("ggplot2")
library("plotly")
diamonds = diamonds[diamonds$cut %in% c("Fair", "Good"),]
dia = ggplot(diamonds, aes(x = cut)) +
geom_bar(aes(stat = "identity", fill = cut)) +
guides(fill=FALSE) +
facet_grid(.~color)
ggplotly(dia)

Resources