plotly-example-plots

Fixing the most common problem with Plotly histograms

Florian Wetschoreck Blog, Data Science

Data exploration is a critical step in every Data Science project here at STATWORX. In order to discover every insight, we prefer interactive charts to static charts, because we have the ability to dig deeper into the data. This helps us to reveal every insight.

In today’s blog post, we show you how to improve the interactivity of Plotly histograms in Python like you can see in the graphics below. You can find the code in our GitHub repo.

TL;DR: Quick Summary

  1. We show you the default Plotly histogram and its unexpected behaviour
  2. We improve the interactive histogram to match our human intuition and show you the code
  3. We explain the code line by line and give you some more context on the implementation

The problem of the default Plotly histogram

default histogram in plotly

This graphic shows you the behavior of a Plotly histogram if you zoom into a specific region. What you can see is that the bars just get bigger/wider. This is not what we expect! If we zoom into a plot, we want to dig deeper and see more fine-grained information for a special area. Therefore, we expect that the histogram shows more fine-grained information. In this specific case, this means that we expect that the histogram shows bins for the selected region. So, the histogram needs to be rebinned.

Improving the interactive histogram

rebinned histogram in plotly

In this graphic you can see our expected end result. If the user selects a new x-region we want to rebin the histogram based on the new x-range.

In order to implement this behavior, we need to change the graphic, when the selected x-range changes. This is a new feature of plotly.py 3.0.0 which was brought to the community by the great Jon Mease. You can read more about the new Plotly.py 3.0.0 features in this announcement.

Disclaimer: The implementation only works inside a Jupyter Notebook or JupyterLab because it needs an active Python kernel for the callback. It does not work in a standalone Python script and it does not work in a standalone HTML file.
The implementation idea is the following: We create an interactive figure and every time the x-axis changes, we will update the underlying data of the histogram.
Whoops, why didn’t we change the binning? Plotly histograms automatically handle the binning for the underlying data. Therefore, we can let the histogram do the work and just change the underlying data. This is a little bit counterintuitive but saves a lot of work.

Glimpse of the full code

So, finally here comes the relevant code without unnecessary imports etc. If you want to see the full code, please check this GitHub file.

x_values = np.random.randn(5000)
figure = go.FigureWidget(data=[go.Histogram(x=x_values,
                                            nbinsx=10)],
                         layout=go.Layout(xaxis={'range': [-4, 4]},
                                          bargap=0.05))
histogram = figure.data[0]

def adjust_histogram_data(xaxis, xrange):
    x_values_subset = x_values[np.logical_and(xrange[0] <= x_values,
                                              x_values <= xrange[1])]
    histogram.x = x_values_subset
figure.layout.xaxis.on_change(adjust_histogram_data, 'range')

Detailed explanations for each line of code

In the following, we will provide some detailed insights and explanations for each line of code.

1) Initializing the x_values

x_values = np.random.randn(5000)

We get 5000 new random x_values which are distributed according to a normal distribution. The values are created by the great numpy library which is abbreviated as np.

2) Creating the figure

figure = go.FigureWidget(data=[go.Histogram(x=x_values,
                                            nbinsx=10)],
                         layout=go.Layout(xaxis={'range': [-4, 4]},
                                          bargap=0.05))

We generate a new FigureWidget instance. The FigureWidget object is the new „magic object“ which was introduced by Jon Mease. You can display it within Jupyter Notebook or JupyterLab like a normal Plotly figure but there are some advantages. You can manipulate the FigureWidgetin various ways from Python and you can also listen for some events and execute some more Python code which gives you a lot of options. This flexibility is the great benefit which Jon Mease envisioned. The FigureWidget receives the attributes data and layout.

As data, we specify a list of all the traces (read: visualizations) that we want to show. In our case, we only want to show a single histogram. The x values for the histogram are our x_values. Also, we set the maximum number of bins with nbinsx to 10. Plotly will use this as a guideline but will not enforce the plot to exactly contain nbinsx bins. As layout, we specify a new layout object and set the range of the xaxis to [-4, 4]. With the bargap argument, we can enable the layout to show a gap between individual bars. This helps us to see where a bar stops and the next one begins. In our case, this value is set to 0.05.

3) Saving a reference to the histogram

histogram = figure.data[0]

We get the reference to the histogram because we want to manipulate the histogram in the last step. We don’t actually get the data but a reference to the Plotly trace object. This Plotly syntax might be a little bit misleading but it is consistent with the definition of the figure where we also specified the „traces“ as „data“.

4) Overview of the callback

def adjust_histogram_data(xaxis, xrange):
    x_values_subset = x_values[np.logical_and(xrange[0] <= x_values,
                                              x_values <= xrange[1])]
    hist.x = x_values_subset
figure.layout.xaxis.on_change(adjust_histogram_data, 'range')

In this chunk, we first define what we want to do at the event when the xaxis changes. Afterwards, we register the callback function adjust_histogram_data. We will break this down further but we will start with the last line because Python will execute this line first at runtime. Therefore, it makes more sense to read the code based on this reverse execution order. A little bit more background on the callback: The code within the callback method adjust_histogram_data will be called when the xaxis.on_change event actually happens because the user interacted with the chart. But first, Python needs to register the adjust_histogram_data callback method. Way later, when the callback event xaxis.on_change occurs, Python will execute the callback method adjust_histogram_data and its contents. Go back and read this section 3-4 times until you fully understand it.

4a) Registering the callback

figure.layout.xaxis.on_change(adjust_histogram_data, 'range')

In this line, we tell the figure object to always call the callback function adjust_histogram_data whenever the xaxis changes. Please note, that we only specify the name of the function adjust_histogram_data without the round brackets (). This is because we only need to pass the reference to the function and do not want to call the function. This is a common error and source of confusion. Also we specify, that we are only interested in the range attribute. Therefore, the figure object will only send this information to the callback function.

But how does the callback function look like and what is the task of the callback function? Those questions are explained in the next steps:

4b) Defining the callback signature

def adjust_histogram_data(xaxis, xrange):

In this line, we start to define our callback function. The first argument which is passed to the function is the xaxis object which initiated the callback. This is a Plotly convention and we just need to put this placeholder here although we don’t use it. The second argument is the xrange which contains the lower and upper limit of the new xrange configuration. You might wonder: „where do the arguments xaxis and xrange come from?“ Those arguments are automatically provided by the figure when the callback gets called. When you use callbacks for the first time, this might seem like intransparent magic. But you will get used to it…

4c) Updating the x_values

x_values_subset = x_values[np.logical_and(xrange[0] <= x_values,
                                          x_values <= xrange[1])]

In this line, we define our new x_values which in most cases are a subset of the original x_values. However, if the lower and upper limit are very far away from each other, we might end up selecting all the original x_values. So the subset is not always a strict subset. The lower limit of the xrange is defined by xrange[0] and the upper limit via xrange[1]. In order to select the subset of the x_values which is within the lower and upper limit of the xrange we use the logical_and function from numpy. There are multiple ways how we can select data subsets in Python. For example, you can also do this via pandas selectors if you use pandas dataframes/series.

4d) Updating the histogram data

histogram.x = x_values_subset

In this line, we update the underlying data of the histogram and set it to the new x_values_subset. This line will trigger the update of the Histogram and the automatic rebinning. The histogram is the reference which we created in step 3 of the code because we need it here.

Wrapping up

In this blog post, we showed you how to improve the default Plotly histogram via interactive binning. We gave you the code and explained every line of the implementation. We hope that you were able to follow along and gained some good understanding of the new possibilities thanks to plotly.py 3.0.0.

Maybe you sometimes have the feeling that you cannot understand the code that you find on the internet or Stackoverflow. If you want intuitive explanations for everything you need to know in the world of Data Science, consider some of our open courses which are taught by our Data Science experts here at STATWORX.

References

Über den Autor
Florian Wetschoreck

Florian Wetschoreck

I am writing my Master thesis at STATWORX. The goal is to reduce the effort for an initial data analysis with a Python package.

ABOUT US


STATWORX
is a consulting company for data science, statistics, machine learning and artificial intelligence located in Frankfurt, Zurich and Vienna. Sign up for our NEWSLETTER and receive reads and treats from the world of data science and AI. If you have questions or suggestions, please write us an e-mail addressed to blog(at)statworx.com.