Building up on another question (How to remove duplicate legend entries w/ plotly subplots()), I am facing a new problem. I want all plots in both rows to have the same Y-axis. However, If I turn "shareY = TRUE", the plots on the upper row share an axis, and the plots on the lower row do, but the axis differ from one another.
The code is basically the one from the answer by #Joris Chau, but added "shareY = TRUE" on the last line.
library(plotly)
library(tidyverse)
mpg %>%
mutate_at("trans", as.factor) %>%
group_by(class) %>%
group_map(.f = ~{
## fill missing levels w/ displ = 0, cyl = first available value
complete(.x, trans, fill = list(displ = 0, cyl = head(.x$cyl, 1))) %>%
plot_ly(x = ~cyl, y = ~displ, color = ~trans, colors = "Paired", type = "bar",
showlegend = (.y == "2seater"), legendgroup = ~trans) %>%
layout(yaxis = list(title = as.character(.y)), barmode = "stack")
}) %>%
subplot(nrows = 2, shareX = TRUE, shareY = TRUE, titleY = TRUE)
How can I tell plotly to use the same scale across all plots?
You should define range of yaxis manually. Here, I used c(0,ceiling(max(aggregate(displ ~ cyl+class, mpg, sum)$displ)/10)*10)).
aggregate(displ ~ cyl+class, mpg, sum)$displ gets the summation of displ grouped by cyl + class.
Then I get its maximum and at the end I round it up using ceiling.
library(plotly)
library(tidyverse)
mpg %>%
mutate_at("trans", as.factor) %>%
group_by(class) %>%
group_map(.f = ~{
complete(.x, trans, fill = list(displ = 0, cyl = head(.x$cyl, 1))) %>%
plot_ly(x = ~cyl, y = ~displ, color = ~trans, colors = "Paired", type = "bar",
showlegend = (.y == "2seater"), legendgroup = ~trans) %>%
layout(yaxis = list(title = as.character(.y),
range=c(0, ceiling(max(
aggregate(displ~cyl+class, mpg, sum)$displ)/10)*10)),
barmode = "stack")
}) %>%
subplot(nrows = 2, shareX = TRUE, shareY = FALSE, titleY = TRUE, margin = 0.05)
Related
When using plotly (in R), after combining subplots there remains an unused and blank subplot. I've recreated the issue using the ggplot2 dataset mpg below.
library(dplyr)
library(ggplot2)
library(plotly)
audi <- mpg %>%
filter(manufacturer == "audi")
chevy <- mpg %>%
filter(manufacturer == "chevrolet")
fig1 <- plot_ly(audi, x = ~hwy, y = ~year, name = "", type = 'scatter',
mode = "markers", marker = list(color = "blue", symbol = 'x-dot'))
fig2 <- plot_ly(chevy, x = ~hwy, y = ~year, name = "", type = 'scatter',
mode = "markers", marker = list(color = "red", symbol = 'circle'))
fig <- subplot(fig1, fig2)
fig <- fig %>% subplot(shareX = TRUE,shareY = TRUE,which_layout = "merge")
fig <- fig %>% layout(
title = "Audi and Chevy",
xaxis = list(title = "Highway MPG"),
yaxis = list(title = "Year"),
margin = list(l = 100)
)
The only solution I've been able to find is tinkering with the width of the used subplot, but this leaves quite a bit of unused white space on the right and causes the title to be far off to the right (as it adjusts into the center of the used and unused subplots).
Is there a way to remove the unused subplot? If not, is there a way to organize/subset the dataframe such that only one plot needs to be used in the first place?
Thanks!
You can assign the colours based on the manufacturer column:
data.subs <- mpg %>%
filter(manufacturer == "audi" | manufacturer == "chevrolet")
fig <- plot_ly(data.subs, x = ~hwy, y = ~year, name = "",
type = 'scatter', mode = "markers",
marker = list(color = factor(data.subs$manufacturer,
labels = c("red", "blue")),
symbol = 'circle'),
text = factor(data.subs$manufacturer,
labels = c("audi", "chevy")), hoverinfo = 'text'))
fig <- fig %>% layout(
title = "Audi and Chevy",
xaxis = list(title = "Highway MPG"),
yaxis = list(title = "Year"),
margin = list(l = 100)
)
fig
This makes generating multiple subplots unnecessary.
This is my code. Just a simple historgram. But what I wanted to do is to customize the hover text so that when I hover, it will display all species included in that histogram bar. Can you help me?
iris %>%
plot_ly(x=~Sepal.Length, color=~Sepal.Width, text=~Species) %>%
add_histogram()
Here's the output. But when I hover it seems the text is only displaying the first species in the table.
plotly_hist
I'm not sure whether this is possible. Probably you are demanding too much from plotly. After trying some options I think there are two ways to go if you want the different Species to show up in the tooltip:
First option is to use a stacked histogram using hovermode = "unified" like so:
library(plotly)
fig <- plot_ly()
fig <- fig %>% add_trace(data = filter(iris, Species == "setosa"),
x = ~Sepal.Length,
color = ~Species,
text = ~Species,
type='histogram',
bingroup=1, showlegend = FALSE)
fig <- fig %>% add_trace(data = filter(iris, Species == "versicolor"),
x = ~Sepal.Length,
color = ~Species,
text = ~Species,
type='histogram',
bingroup=1, showlegend = FALSE)
fig <- fig %>% add_trace(data = filter(iris, Species == "virginica"),
x = ~Sepal.Length,
color = ~Species,
text = ~Species,
type='histogram',
bingroup=1, showlegend = FALSE)
fig <- fig %>% layout(
hovermode="unified",
barmode="stack",
bargap=0.1)
fig
The second option would be to make the computations yourself, i.e. binning and summarising and to make a bar chart of the counts.
iris %>%
mutate(Sepal.Length.Cut = cut(Sepal.Length, breaks = seq(4, 8, .5), right = FALSE)) %>%
group_by(Sepal.Length.Cut, Species) %>%
summarise(n = n(), Sepal.Width = sum(Sepal.Width)) %>%
tidyr::unite("text", Species, n, sep = ": ", remove = FALSE) %>%
summarise(n = sum(n), Sepal.Width = sum(Sepal.Width) / n, text = paste(unique(text), collapse = "\n")) %>%
plot_ly(x = ~Sepal.Length.Cut, y = ~n, text = ~text) %>%
add_bars(marker = list(colorscale = "Rainbow"), hovertemplate = "%{y}<br>%{text}")
Edit A third option would be to use ggplotly(). This way it is an easy task to add annotations displayling the total numbers per bin. This way we can make use of the stats layers in ggplot2 which will do all the computations. To the best of my knowledge that couldn't be done that easily using "pure" plotly.
library(plotly)
ggplot(iris, aes(Sepal.Length, fill = Species)) +
stat_bin(breaks = seq(4, 8, .5), closed = "left") +
stat_bin(breaks = seq(4, 8, .5), closed = "left", geom = "text", mapping = aes(Sepal.Length, label = ..count..), inherit.aes = FALSE, vjust = -.5) +
theme_light()
ggplotly()
How can I remove the duplicates in my legend when using plotly's subplots()?
Here is my MWE:
library(plotly)
library(ggplot2)
library(tidyr)
mpg %>%
group_by(class) %>%
do(p = plot_ly(., x = ~cyl, y = ~displ, color = ~trans, type = 'bar')) %>%
subplot(nrows = 2, shareX = TRUE, titleX = TRUE) %>%
layout(barmode = 'stack')
plotly does not have facet like ggplot2 so it will add legend for each subplot or you can turn it off for some of them.
Here we do not have a layer with all the ~class entries nor two plots with no intersection in class which their combination also covers all of them. In that case, we could set showlegend to TRUE for those specific plot(s) and set it to FALSE for the rest and also set the legendgroup to trans so we get a unique but also complete legend.
As I said, here we do not have that special case. So What I can think of are two possibilities:
Adding the whole data (duplicating whole dataframe) and assigning class of All to them. Then plotting that along with original data but keep the legend only for class == All.
Using ggplot::facet_wrap and then ggplotly to make a plotly object. However, this would cause some issues with x-axis (compare ggplot object to plotly ones).
library(plotly)
library(ggplot2)
library(dplyr)
ly_plot <- . %>%
plot_ly(x = ~cyl, y = ~displ, color = ~trans,
type = 'bar', showlegend = ~all(legendC)) %>%
add_annotations(
text = ~unique(class),
x = 0.5,
y = 1,
yref = "paper",
xref = "paper",
xanchor = "middle",
yanchor = "top",
showarrow = FALSE,
font = list(size = 15))
mpg %>%
mutate(class= "_All_") %>%
rbind(.,mpg) %>%
mutate(legendC = (class == "_All_")) %>%
group_by(class) %>%
do(p = ly_plot(.)) %>%
subplot(nrows = 2, shareX = TRUE, titleX = TRUE) %>%
layout(barmode = 'stack')
#> Warning in RColorBrewer::brewer.pal(N, "Set2"): n too large,
#> allowed maximum for palette Set2 is 8
#> Returning the palette you asked for with that many colors
p <- ggplot(data = mpg, aes(x=cyl, y=displ, fill=trans))+
geom_bar(stat="identity") +
facet_wrap(~class)
p
ggplotly(p) #seems for this we should also set "colour = trans"
Another workaround using the tidyverse. The following steps are added to the original MWE:
Convert the trans column to a factor.
Use tidyr's complete to fill (non-NA) dummy values for the missing factor levels in each class group.
Follow M-M's suggestion setting showlegend to TRUE for a single group and legendgroup to trans to link the legend entries between subplots.
library(plotly)
library(tidyverse)
mpg %>%
mutate_at("trans", as.factor) %>%
group_by(class) %>%
group_map(.f = ~{
## fill missing levels w/ displ = 0, cyl = first available value
complete(.x, trans, fill = list(displ = 0, cyl = head(.x$cyl, 1))) %>%
plot_ly(x = ~cyl, y = ~displ, color = ~trans, colors = "Paired", type = "bar",
showlegend = (.y == "2seater"), legendgroup = ~trans) %>%
layout(yaxis = list(title = as.character(.y)), barmode = "stack")
}) %>%
subplot(nrows = 2, shareX = TRUE, titleY = TRUE)
I am plotting the grouped boxplot with jittering with the following function:
plot_boxplot <- function(dat) {
# taking one of each joine_group to be able to plot it
allx <- dat %>%
mutate(y = median(y, na.rm = TRUE)) %>%
group_by(joined_group) %>%
sample_n(1) %>%
ungroup()
p <- dat %>%
plotly::plot_ly() %>%
# plotting all the groups 1:20
plotly::add_trace(data = allx,
x = ~as.numeric(joined_group),
y = ~y,
type = "box",
hoverinfo = "none",
boxpoints = FALSE,
color = NULL,
opacity = 0,
showlegend = FALSE) %>%
# plotting the boxes
plotly::add_trace(data = dat,
x = ~as.numeric(joined_group),
y = ~y,
color = ~group1,
type = "box",
hoverinfo = "none",
boxpoints = FALSE,
showlegend = FALSE) %>%
# adding ticktext
layout(xaxis = list(tickvals = 1:20,
ticktext = rep(levels(dat$group1), each = 4)))
p <- p %>%
# adding jittering
add_markers(data = dat,
x = ~jitter(as.numeric(joined_group), amount = 0.2),
y = ~y,
color = ~group1,
showlegend = FALSE)
p
}
The problem is that when some of the levels have NA as y variable the width of the jittered boxes changes. Here is an example:
library(plotly)
library(dplyr)
set.seed(123)
dat <- data.frame(group1 = factor(sample(letters[1:5], 100, replace = TRUE)),
group2 = factor(sample(LETTERS[21:24], 100, replace = TRUE)),
y = runif(100)) %>%
dplyr::mutate(joined_group = factor(
paste0(group1, "-", group2)
))
# do the plot with all the levels
p1 <- plot_boxplot(dat)
# now the group1 e is having NAs as y values
dat$y[dat$group1 == "e"] <- NA
# create the plot with missing data
p2 <- plot_boxplot(dat)
# creating the subplot to see that the width has changed:
subplot(p1, p2, nrows = 2)
The problem is that the width of boxes in both plots is different:
I've realised that the boxes have the same size without jittering so I know that the jittering is "messing" with the width but I don't know how to fix that.
Does anyone know how to make the width in both jittered plots exactly the same?
I see two separate plot shifts:
due to jittering
due to NAs
First can be solved by declaring new jitter function with fixed seed
fixed_jitter <- function (x, factor = 1, amount = NULL) {
set.seed(42)
jitter(x, factor, amount)
}
and using it instead of jitter in add_markers call.
Second problem can be solved by assigning -1 instead of NA and setting
yaxis = list(range = c(0, ~max(1.1 * y)))
as a second parameter to layout.
I am making a pie-chart in plotly in R.
I want my labels to be on the chart, so I use textposition = "inside", and for the very small slices those values are not visible.
I am trying to find a way to exclude those labels.
Ideally, I would like to like to not print any lables on my plot that are below 10%.
Setting textposition = "auto" doesn't work well, since there are a lot of small slices, and it makes the graph look very messy.
Is there a way to do it?
For example these piecharts from plotly website (https://plot.ly/r/pie-charts/)
library(plotly)
library(dplyr)
cut <- diamonds %>%
group_by(cut) %>%
summarize(count = n())
color <- diamonds %>%
group_by(color) %>%
summarize(count = n())
clarity <- diamonds %>%
group_by(clarity) %>%
summarize(count = n())
plot_ly(cut, labels = cut, values = count, type = "pie", domain = list(x = c(0, 0.4), y = c(0.4, 1)),
name = "Cut", showlegend = F) %>%
add_trace(data = color, labels = color, values = count, type = "pie", domain = list(x = c(0.6, 1), y = c(0.4, 1)),
name = "Color", showlegend = F) %>%
add_trace(data = clarity, labels = clarity, values = count, type = "pie", domain = list(x = c(0.25, 0.75), y = c(0, 0.6)),
name = "Clarity", showlegend = F) %>%
layout(title = "Pie Charts with Subplots")
In the plot for Clarity 1.37% are outside of the plot, while I would like them not to show at all.
You'll have to specify sector labels manually like so:
# Sample data
df <- data.frame(category = LETTERS[1:10],
value = sample(1:50, size = 10))
# Create sector labels
pct <- round(df$value/sum(df$value),2)
pct[pct<0.1] <- 0 # Anything less than 10% should be blank
pct <- paste0(pct*100, "%")
pct[grep("0%", pct)] <- ""
# Install devtools
install.packages("devtools")
# Install latest version of plotly from github
devtools::install_github("ropensci/plotly")
# Plot
library(plotly)
plot_ly(df,
labels = ~category, # Note formula since plotly 4.0
values = ~value, # Note formula since plotly 4.0
type = "pie",
text = pct, # Manually specify sector labels
textposition = "inside",
textinfo = "text" # Ensure plotly only shows our labels and nothing else
)
Check out https://plot.ly/r/reference/#pie for more information...