Matplotlib to Plotly Chart Conversion
By: Shail Deliwala
Tags: Matplotlib, Data Science, Visualization, Data Analysis, Python

Introduction

Have you ever wanted to directly create Plotly charts from a dataframe?

Pandas currently uses Matplotlib as its default plotting backend. Since version 0.25, it also supports other backends such as Bokeh. However, Plotly isn’t currently supported. Therefore, I decided to see if it was possible to convert a Matplotlib chart to its Plotly equivalent.

Here is the full code if you would like to follow along as you progress through this article.

Peering under the hood

The first step was to inspect how each of the libraries represented plots internally.

The inner workings of both libraries are so well-abstracted that, for most common plotting purposes, we hardly ever need to tinker with the internals. df.plot takes plotting to an even higher level of abstraction. You simply call plot() on a dataframe, optionally specifying the plot kind, for an instant plot — no need to create an empty figure, set axis labels, legends, and so on. E.g.,

data = df.groupby('year').sum()[["major_death", "major_capture"]]

ax = data.plot.bar(rot=0)

Matplotlib axes properties

I started by examining ax , the AxesSubplot object returned by data.plot . After a quick search, I found that AxesSubplot objects have a properties() method that returns their properties as a dictionary.

Following is a bar chart from my Kaggle Notebook containing EDA on Game of Thrones battles and deaths data.

Below the chart is a snippet of its properties returned by ax.properties() .

Matplotlib plot: Major death/capture events by year in Game of Thrones

The children attribute contains the Rectangle objects rendered as bars. Naturally, Rectangle s encapsulate most of the plotting information (x-coordinates, height, and width). title is the plot’s title and xlabel and ylabel are the x-axis and y-axis titles, respectively.

{

'adjustable': 'box',

'axes_locator': None,

'children': [

<matplotlib.patches.Rectangle at 0x1f7aa154608>,

...

<matplotlib.legend.Legend at 0x1f7ab154ac8>

],

'data_ratio': 3.0,

'facecolor': (1.0, 1.0, 1.0, 1.0),

'figure': <Figure size 1080x432 with 1 Axes>,

...,

'title': 'Major death/capture events by year',

'xlabel': 'Year',

'xticklabels': <a list of 3 Text major ticklabel objects>,

'ylabel': 'No. of Death/Capture Events',

'yticks': array([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.]),

}

Plotly figure properties

Following is the Plotly rendition of the above bar chart. Its properties are below it.

Plotly plot: Major death/capture events by year in Game of Thrones

data is the attribute that contains almost all of the information required for the above plot. Under the layout attribute, title , xaxis.title , and yaxis.title contain the plot title and axes titles.

{

"data": [

{

"marker": {

"color": [

"rgb(31.0,119.0,180.0,255.0)",

...

"rgb(255.0,127.0,14.0,255.0)"

]

},

"type": "bar",

"width": [0.25, ..., 0.25],

"x": [-0.25, 0.75, 1.75, 0, 1, 2],

"y": [4, 8, 1, 3, 6, 2]

}

],

"layout": {

"barmode": "group",

"template": {

"data": {

"bar": [

{

"error_x": {"color": "#2a3f5f"},

"error_y": {"color": "#2a3f5f"},

"marker": {...}

}

], ...

"table": [...]

}, ...

},

"title": {"text": "Major death/capture events by year"},

"xaxis": {"title": {"text": "Year"}, ... },

"yaxis": {

"range": [0, 9],

"title": {"text": "No. of Death/Capture Events"}

}

}

}

Matplotlib figures vs. Plotly figures

Note that while a Plotly figure is the top-level counterpart of a Matplotlib figure, a Plotly figure is a different concept from a Matplotlib figure.

However, we directly reflected the properties of the AxesSubplot even though it’s contained inside the figure. This is because AxesSubplot contains all our objects of interest.

However, if the figure contained subplots as shown below, we’d have to reflect the properties of each of the subplots. Notice the four AxesSubplot objects contained in the figure fig .

fig, axes = plt.subplots(2, 2)

print(fig.properties()) # Output

{

"agg_filter": null,

...,

"axes": [

<matplotlib.axes._subplots.AxesSubplot at 0x1f...>,

<matplotlib.axes._subplots.AxesSubplot at 0x1f...>,

<matplotlib.axes._subplots.AxesSubplot at 0x1f...>,

<matplotlib.axes._subplots.AxesSubplot at 0x1f...>

],

...

}

Building a Matplotlib-to-Plotly converter

Here onwards, we’ll refer to plotly.graph_objects as just go .

Bar chart converter

Armed with the understanding from the previous section, I wrote the following function to convert a given Matplotlib bar chart to a Plotly bar chart figure. It takes a single argument of type AxesSubplot .

A function to convert a Matplotlib bar chart to a Plotly bar chart

It performs the following steps.

Extracts all rectangle objects from the axis. Builds lists of x-coordinates, widths, heights, and colors of the rectangles. Note that it converts colors to a format that Plotly understands. Builds lists of tick positions and labels. Creates a go.Figure object containing a single go.Bar . Adds titles and ticks.

Scatter plot converter

The following function takes a Matplotlib AxesSubplot containing a scatter plot and returns a Plotly go.Figure containing the corresponding scatter plot.

A function to convert a Matplotlib scatter plot to a Plotly scatter plot

Consider the following Matpotlib scatter plot.

Matplotlib scatter plot

If you pass it to the above function, you get the Plotly scatter plot below.

Plotly scatter plot

Limitations

While this approach works for a narrow range of plotting use cases, it is hacky and not suitable for a wider range of use cases. This can be easily seen by running a variety of plots through the conversion function and observing the output. Other limitations are as follows.

Plot legends are not mapped. If you create a grouped bar chart in Plotly using go or plotly.express , the grouping information is preserved. Unlike Plotly, though, bar grouping information is not stored explicitly under the hood in Matplotlib. Therefore, to achieve a “true” grouped bar chart in Plotly, we must somehow infer grouping information from the Matplotlib plot. Besides, I could not find a direct way to add a legend to a Plotly figure aside from adding traces with a name and show_legend=True . Hover labels show minimal information. If you create a chart using plotly.express , hover labels are automatically populated with useful information, which is one of the coolest features of Plotly. You could still customize hover labels, in any case, to show more information. Internal properties are subject to change. If a backward incompatible change is introduced in one of the libraries, the conversion code might break and would need to be updated.

Alternatives

Checkout Cufflinks, a third-party library that adds an iplot() method to dataframes. df.iplot() is to Plotly as df.plot() is to Matplotlib.

Bokeh is a cool, interactive plotting library. You could set it as your pandas plotting backend by running pd.set_option('plotting.backend', 'pandas_bokeh') .

Meanwhile, Nicolas Kruchten (VP Product at Plotly) recently commented that Plotly might be added as a plotting backend option to pandas in the next release!