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

Boxplot outliers are shown in black using ggplotly

Open dfrail24 opened this issue 6 years ago • 17 comments

If I create a boxplot in ggplot2 and convert it using ggplotly command, the outliers are outlined in black. Here is a simple example:

library(ggplot2)
library(plotly)

p <- ggplot(mpg, aes(class, hwy))
g <- p + geom_boxplot(aes(colour = "red"))
ggplotly(g)

ggplot would show this chart: image

whereas plotly would show this chart: image

Is this something that can be fixed?

dfrail24 avatar Sep 08 '17 17:09 dfrail24

This persists even when the outliers should be discarded, in the examples also

library(plotly)
set.seed(123)

df <- diamonds[sample(1:nrow(diamonds), size = 1000),]

p <- ggplot(df, aes(cut, price, fill = cut)) + 
  geom_boxplot(outlier.shape = NA) + 
  ggtitle("Ignore outliers in ggplot2")

# Need to modify the plotly object and make outlier points have opacity equal to 0
p <- plotly_build(p)

p$data <- lapply(p$data, FUN = function(x){
  x$marker = list(opacity = 0)
  return(x)
})

p

screen shot 2017-11-17 at 6 54 06 pm

jonocarroll avatar Nov 17 '17 08:11 jonocarroll

I managed to set the opacity property of the outliers using the code below. This seems to work for the faceted charts I have tried so far also.

library(ggplot2)
library(plotly)

set.seed(123)

df <- diamonds[sample(1:nrow(diamonds), size = 1000),]

p <- ggplot(df, aes(cut, price, fill = cut)) + 
  geom_boxplot(outlier.shape = NA) + 
  ggtitle("Ignore outliers in ggplot2")

# Need to modify the plotly object and make outlier points have opacity equal to 0
p <- plotly_build(p)

for(i in 1:length(p$x$data)) {
  p$x$data[[i]]$marker$opacity = 0
}

p

image

petehilljnr avatar Apr 30 '18 23:04 petehilljnr

The replacement lapply code is then

p$x$data <- lapply(p$x$data, FUN = function(x){
  x$marker = list(opacity = 0)
  return(x)
})

(note p$x$data rather than p$data). I'm happy to PR this to the documentation if someone can point to the source.

jonocarroll avatar May 01 '18 00:05 jonocarroll

The problem is that when you also have geom_jitter in the plot (in addition to geom_boxplot), the lapply part will remove all the points. Is there a way to selectively remove outliers that belong to geom_boxplot only?

dmattek avatar Jul 10 '18 12:07 dmattek

p$x$data <- lapply(p$x$data, FUN = function(x){ x$marker$line$width = 0 return(x) })

modify marker$line$color

ningjingzhiyuan507 avatar Jul 19 '18 06:07 ningjingzhiyuan507

The problem is that when you also have geom_jitter in the plot (in addition to geom_boxplot), the lapply part will remove all the points. Is there a way to selectively remove outliers that belong to geom_boxplot only?

You can use the code above and just index to the layer you want to remove, e.g. say the boxplot outliers are on the first layer.

p$x$data[1] <- lapply(p$x$data[1], FUN = function(x){
  x$marker = list(opacity = 0)
  return(x)
})

brshallo avatar Feb 25 '19 05:02 brshallo

Hi! Just wanted to bring this issue to your attention again, as none of the workarounds mentioned above seem to be working (and aren't working in the documentation either)! (There's also an interesting phenomenon where, for coloured barplots, the most extreme outliers are coloured with black outlines, but closer to the barplot, they're black with coloured outlines, i.e. the reverse.)

kojisposts avatar Jul 12 '19 19:07 kojisposts

There's a WIP here https://github.com/ropensci/plotly/pull/1514 that fixes this issue, feel free to test it out and let me know if you run into problems.

cpsievert avatar Jul 12 '19 19:07 cpsievert

I didn't see the solution being mentioned #1514 on the last release. I was to get the visual I wanted by altering the lapply function to filter only layer that are type == "box"

p$x$data <- lapply(p$x$data, FUN = function(x){

  if (x$type == "box") {
    x$marker = list(opacity = 0)
  }
  return(x)
})

jcoronel25 avatar Dec 12 '19 23:12 jcoronel25

This will do the trick for the original question coloring outliers! Plotly differentiates outliers from extreme outliers. We go under the hood and override all outlier colors manually.

library(ggplot2)
library(plotly)

p <- ggplot(mpg, aes(class, hwy)) + geom_boxplot(color="red")
output = ggplotly(p)

# overrides black outline of outliers
output$x$data[[1]]$marker$line$color = "red"
# overrides black extreme outlier color
output$x$data[[1]]$marker$outliercolor = "red"
# overrides black not as extreme outlier color
output$x$data[[1]]$marker$color = "red"

output

image

isaaczhao23 avatar Feb 26 '20 04:02 isaaczhao23

Using this code:

set.seed(123)

df <- diamonds[sample(1:nrow(diamonds), size = 1000),]
df <- subset(df,df$cut=="Fair")


p <- ggplot(df, aes(cut, price, fill = cut)) + 
  geom_boxplot() 

# Need to modify the plotly object and make outlier points have opacity equal to 0
fig <- plotly_build(p)

fig$x$data <- lapply(fig$x$data, FUN = function(x){
  x$marker$line = list(opacity = 0)
  x$marker$line$color = list(opacity = 0)
  x$marker$outliercolor = list(opacity = 0)
  x$marker$color = list(opacity = 0)
  return(x)
})
fig

I was able to get to this plot: Rplot

As you can see the outliers are different. I am aiming to get a transparent outlier in this case but the extreme outlier remains filled. From the output of fig$x$data I cannot understand which parameter effects the extreme outlier. Looks at the html styling output I can say that the two points are definitely getting different rgb values and fill-opacity values. image

[[1]]$marker
[[1]]$marker$opacity
[1] NA

[[1]]$marker$outliercolor
[[1]]$marker$outliercolor$opacity
[1] 0


[[1]]$marker$line
[[1]]$marker$line$opacity
[1] 0

[[1]]$marker$line$color
[[1]]$marker$line$color$opacity
[1] 0



[[1]]$marker$size
[1] 5.669291

[[1]]$marker$color
[[1]]$marker$color$opacity
[1] 0



[[1]]$line
[[1]]$line$color
[1] "rgba(51,51,51,1)"

[[1]]$line$width
[1] 1.889764


[[1]]$name
[1] "Fair"

[[1]]$legendgroup
[1] "Fair"

[[1]]$showlegend
[1] TRUE

[[1]]$xaxis
[1] "x"

[[1]]$yaxis
[1] "y"

[[1]]$frame
[1] NA

meldarionqeusse avatar Mar 05 '21 17:03 meldarionqeusse

While I have not solved the issue above. I would like to add a different more complete solution to the provided by @isaaczhao23 in cases where there are boxplots with different colors.

df <- diamonds[sample(1:nrow(diamonds), size = 1000),]
#df <- subset(df,df$cut=="Fair")


p <- ggplot(df, aes(cut, price, color = cut)) + 
  geom_boxplot() 

# Need to modify the plotly object and make outlier points have opacity equal to 0
fig <- plotly_build(p)

fig$x$data <- lapply(fig$x$data, FUN = function(x){
  x$marker$outliercolor = x$line$color # When creating plot p with ggplot if you specify fill = cut use x$fill$color instead of $line$color
  x$marker$color = x$line$color # When creating plot p with ggplot if you specify fill = cut use x$fill$color instead $line$color
  x$marker$line = x$line$color # When creating plot p with ggplot if you specify fill = cut use x$fill$color instead $line$color
  return(x)
})
fig

Will produce this:

Rplot01

meldarionqeusse avatar Mar 06 '21 07:03 meldarionqeusse

I'm still having the issue of outliers shown after apply ggplotly() even if outlier.shape = NA is passed to geom_boxplot(). I'm using Plotly 4.9.4.1. Any chance to see this issue fixed? Thanks.

lucazav avatar Aug 24 '21 16:08 lucazav

In the meantime I solved the issue of hiding outliers using the following code:

library(purrr)

hideOutliers <- function(x) {  
  if (x$hoverinfo == 'y') {  
    x$marker = list(opacity = 0)
    x$hoverinfo = NA    
  }  
  return(x)  
}

p[["x"]][["data"]] <- map(p[["x"]][["data"]], ~ hideOutliers(.))

It works also with facets.

lucazav avatar Aug 24 '21 17:08 lucazav

The problem is that when you also have geom_jitter in the plot (in addition to geom_boxplot), the lapply part will remove all the points. Is there a way to selectively remove outliers that belong to geom_boxplot only?

You can use the code above and just index to the layer you want to remove, e.g. say the boxplot outliers are on the first layer.

p$x$data[1] <- lapply(p$x$data[1], FUN = function(x){
  x$marker = list(opacity = 0)
  return(x)
})

Thank you so much for this solution, @brshallo ! This fixed my problem after hours of looking for a fix!

swati-mahapatra avatar Sep 07 '22 11:09 swati-mahapatra