How to force plotly R to plot missing values with category axis

Viewed 2363

I want to plot a simple bar chart in plotly R and by default it will skip the NA observations. In ggplot we have a parameter to disable the default NA removal, or we can customize the axis limits. However I cannot get it done in plotly.

dt_plot <- data.frame(categories = letters[1:10], values = c(rep(NA_integer_, 3), 1:5, rep(NA_integer_, 2)))
plot_ly(data = dt_plot) %>%
  add_bars(x = ~categories, y = ~values)

I want to show the x axis as the consistent letters 1:10, because I'm actually including this plot in a shiny app with dynamic data selection. Some data have values in all x values, some only have values in a subset. I want to keep the plot to be consistent and always show complete x values.

There is a similar question here, but the answer doesn't apply to my case, because I'm using a category type x axis:

https://plot.ly/r/reference/#layout-xaxis

If the axis type is "category", it should be numbers, using the scale where each category is assigned a serial number from zero in the order it appears.

I tried different range combinations but it didn't work. plotly seemed always remove NA observations first.

There is a related issue where Carson Sievert suggested a hack, but it didn't really work me either.

# this do show all the x values but the plot is wrong
layout(xaxis = list(type = "category", tickvals = 1:10/10, ticktext = letters[1:10], range = c(0, 1)))

By inspecting the plot object, it looks like the NA data is removed before constructing the plot:

{
  "visdat": {
    "7abc7354f619": ["function () ", "plotlyVisDat"]
  },
  "cur_data": "7abc7354f619",
  "attrs": {
    "7abc7354f619": {
      "alpha_stroke": 1,
      "sizes": [10, 100],
      "spans": [1, 20],
      "x": {},
      "y": {},
      "type": "bar",
      "inherit": true
    }
  },
  "layout": {
    "margin": {
      "b": 40,
      "l": 60,
      "t": 25,
      "r": 10
    },
    "xaxis": {
      "domain": [0, 1],
      "automargin": true,
      "range": [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9],
      "title": "categories",
      "type": "category",
      "categoryorder": "array",
      "categoryarray": ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
    },
    "yaxis": {
      "domain": [0, 1],
      "automargin": true,
      "title": "values"
    },
    "hovermode": "closest",
    "showlegend": false
  },
  "source": "A",
  "config": {
    "showSendToCloud": false
  },
  "data": [
    {
      "x": ["d", "e", "f", "g", "h"],
      "y": [1, 2, 3, 4, 5],
      "type": "bar",
      "marker": {
        "color": "rgba(31,119,180,1)",
        "line": {
          "color": "rgba(31,119,180,1)"
        }
      },
      "error_y": {
        "color": "rgba(31,119,180,1)"
      },
      "error_x": {
        "color": "rgba(31,119,180,1)"
      },
      "xaxis": "x",
      "yaxis": "y",
      "frame": null
    }
  ],
...
1 Answers

There is info in reference: https://plotly.com/r/reference/layout/xaxis/#layout-xaxis-range, but not so obvious.

But first of all: categorical variable needs to be factor.

Second you need to specify range from 0 to number of levels -1. To better looking use +-0.5 on ends

dt_plot <- data.frame(categories = letters[1:10], values = c(rep(NA_integer_, 3), 1:5, rep(NA_integer_, 2)))
dt_plot$categories <- as.factor(dt_plot$categories)
plot_ly(data = dt_plot) %>%
  add_bars(x = ~categories, y = ~values)%>% 
  layout(xaxis = list(range = list(-0.5, 9.5)))
Related