plotly.R icon indicating copy to clipboard operation
plotly.R copied to clipboard

Grouped boxplot data not centred on xaxis ticks, looses boxgap argument, and wont resize with subplots

Open l-jaye opened this issue 5 years ago • 5 comments

Hi @cpsievert!

Here after your Shiny webinar, great stuff!

I'm having issues with the boxplot data aligning with x-axis tick marks in subplots. Please see below:

library (data.table)
library (stringr)
library (plotly)
# some setup:
get_plot_title = function(this.plot.title, y.pos = 1, y.shift = 0){
  list(text = sprintf ("<b>%s</b>", this.plot.title), yshift = y.shift
       , xref = "paper", yref = "paper", yanchor = "center", xanchor = "center", align = "center"
       , x = 0.5, y = y.pos, showarrow = FALSE, font = list (size = 16, family = "Arial")
  )} 

generate_panel = function(dat, trace.name){
  my.plot = 
    plot_ly(dat, type = "box"
            , x= ~get(x_var), y= ~get(y_var), color = ~get(color_var)
            , whiskerwidth = 0.1
            , boxpoints = F
    )   %>% 
    layout(boxmode = "group", boxgroupgap = 0.1, boxgap = 0.15
           , plot_bgcolor = "#F5F5F5"
           , annotations = get_plot_title(trace.name, y.shift = 20)
           , xaxis = list(title = "", tickfont = list(size = 14, family = "Arial")
                          , tickmode = "linear", ticks = "outside")
           , yaxis = list(title = sprintf("<b>%s</b>", y_var), titlefont = list (size = 16, family = "Arial"))
           ) 
  my.plot #return
}

x_var = "cut"
y_var = "price"
color_var = "color" 
X_panel_var = "clarity"
test.dat = ggplot2::diamonds %>% setDT

Without subplots, everything looks great:

generate_panel(test.dat, trace.name = "all")

image

Adding two horizontal subplots:

test.dat[, get(X_panel_var) %>% levels][1:2] %>% 
  lapply(function(this.panel.var){
    generate_panel(dat = test.dat[this.panel.var, on = X_panel_var], trace.name = this.panel.var)
  }) %>% 
  subplot(shareY = T, shareX = F, titleX = F, nrows = 1, which_layout = 1, margin = 0.01)

Boxplot groups shift off the centre of the xaxis ticks, and the boxgap argument becomes irrelevant: image

Enlarging this plot does not work at all (cuts off the data at the original subplot width and height, yikes!):

image

Interestingly, with 3 horizontal subplots, the middle plot data is centered around ticks, but the outer plots are even further away (and the boxgaps are worse):

test.dat[, get(X_panel_var) %>% levels][1:3] %>% 
  lapply(function(this.panel.var){
    generate_panel(dat = test.dat[this.panel.var, on = X_panel_var], trace.name = this.panel.var)
  }) %>% 
  subplot(shareY = T, shareX = F, titleX = F, nrows = 1, which_layout = 1, margin = 0.01) 

image

Just wondering if you have any ideas / suggestions?

Thank you!

l-jaye avatar Mar 10 '19 17:03 l-jaye

I have a similar / the same issue here:

image

import plotly.graph_objects as go
from plotly.subplots import make_subplots
import plotly.express as px

fig = make_subplots(rows=2, cols=1)
df = px.data.tips()

style1 = dict(name='Smokers', legendgroup='Smokers', line=dict(color='red'))
style2 = dict(name='Non-Smokers', legendgroup='Non-Smokers', line=dict(color='blue'))

fig.add_trace(go.Box(y=df[df.smoker=='Yes'].total_bill, **style1,
                     x=df[df.smoker=='Yes'].time),
                     row=1, col=1)
fig.add_trace(go.Box(y=df[df.smoker=='No'].total_bill, **style2,
                     x=df[df.smoker=='No'].time),
                     row=1, col=1)

fig.add_trace(go.Box(y=df[df.smoker=='Yes'].tip, **style1,
                     x=df[df.smoker=='Yes'].time, showlegend=False),
                     row=2, col=1)
fig.add_trace(go.Box(y=df[df.smoker=='No'].tip, **style2,
                     x=df[df.smoker=='No'].time, showlegend=False),
                     row=2, col=1)

fig.update_layout(
    boxmode='group'
)
fig.show()

Is there a workaround for this problem?

I would have expected that legendgroup would work to properly group the boxplots when using boxmode='group'.

fabbra avatar Apr 24 '20 13:04 fabbra

I'm having the same problem. The code snippet provided by @fabbra is a good example.

It's interesting to note that when using plotly express w/ facet rows/cols, the boxplots are grouped properly. I haven't had a chance to dig into this yet, so I'm not sure why.

kmcentush avatar May 04 '20 16:05 kmcentush

Hi, I know this post is a bit old but I struggled with the same problem. The solution was to put the boxes I wanted aligned into the same "offsetgroup". Using Fanbras' code:

import plotly.graph_objects as go
from plotly.subplots import make_subplots
import plotly.express as px

fig = make_subplots(rows=2, cols=1)
df = px.data.tips()

style1 = dict(name='Smokers', legendgroup='Smokers', line=dict(color='red'))
style2 = dict(name='Non-Smokers', legendgroup='Non-Smokers', line=dict(color='blue'))

fig.add_trace(go.Box(y=df[df.smoker=='Yes'].total_bill, **style1,
                     x=df[df.smoker=='Yes'].time, offsetgroup="A"),
                     row=1, col=1)
fig.add_trace(go.Box(y=df[df.smoker=='No'].total_bill, **style2,
                     x=df[df.smoker=='No'].time, offsetgroup="B"),
                     row=1, col=1)

fig.add_trace(go.Box(y=df[df.smoker=='Yes'].tip, **style1,
                     x=df[df.smoker=='Yes'].time, showlegend=False, offsetgroup="A"),
                     row=2, col=1)
fig.add_trace(go.Box(y=df[df.smoker=='No'].tip, **style2,
                     x=df[df.smoker=='No'].time, showlegend=False, offsetgroup="B"),
                     row=2, col=1)

fig.update_layout(
    boxmode='group'
)
fig.show()

FelipeMoser avatar Dec 26 '20 13:12 FelipeMoser

Is there a solution for the error in plotly express using facet_row or facet_col? The workaround to add each trace individually seems very tedious.

LukasHebing avatar Nov 30 '23 14:11 LukasHebing

Still seeing this issue where grouped boxplots are being offset when two plots are combined with subplots. Because I'm working with tibbles, I don't see how I can apply the offset group suggested previously. Also, I need to be working with just plotly and not ggplotly, due to speed the plots need to be rendered at. @cpsievert Do you have any suggestions?

image

Here is a simplified version of my code:

library(plotly)
library(tidyverse)
library(scales)

expr <- read_tsv('../path/to/file.tsv')
colorLevels <- setNames(hue_pal()(9), levels(as.factor(names(expr$site_detail))))

plot_boxpot <- function(data, value_col, colorLevels, ids) {
  p <- plot_ly(data, 
               y = ~id, 
               x = as.formula(paste0("~", value_col)), 
               color = ~site_detail,
               colors = colorLevels,
               type = 'box', 
               boxpoints = 'all',
               pointpos = 0) %>%
    layout(
      boxmode = 'group',
      boxgap = 0.2
    ) %>%
    layout(
      yaxis = list(
        tickmode = "array",
        tickvals = ~id,
        ticktext = ~id
      )
    )
}

p1 <- plot_boxpot(expr, expr_col, colorLevels, ids)
p2 <- plot_boxpot(expr, expr_col_2, colorLevels, ids)

fig <- subplot(p1, p2, nrows = 1, shareY = TRUE) %>%
    layout(
      yaxis = list(
        tickmode = "category", 
        tickvals = ids, 
        ticktext = ids),
      legend = list(
        orientation = 'h',
        xanchor = 'center', 
        yanchor = 'top'
      )
    )
fig

mpage21 avatar Mar 11 '24 20:03 mpage21