0

I came across this post, which detailed how to incorporate a ggplot object into a matplotlib grid. The process involves first generating a ggplot object, getting the current figure, and then building more subplots onto the existing figure with the state-based API.

I wanted to replicate this using the object-oriented API in order to make this more extensible.

import ggplot as gp
import matplotlib.pyplot as plt
g = gp.ggplot(gp.aes(x='carat', y='price'), data=gp.diamonds) + gp.geom_point() + gp.ylab('price') + gp.xlab('carat')
g.make()
f1 = plt.gcf()
g = gp.ggplot(gp.aes(x='price', y='carat'), data=gp.diamonds) + gp.geom_point() + gp.ylab('carat') + gp.xlab('price')
g.make()
f2 = plt.gcf()


(fig, axes) = plt.subplots(1,2, figsize=(20,10))
axes[0] = f1.axes[0]
axes[0].set_title('Price vs. Carat')
axes[1] = f2.axes[0]
axes[1].set_title('Carat vs. Price')
fig.show()

However, when this is executed, the output comes out blank: enter image description here

My motivation for this post extends beyond just the ggplot library, as ideally I'd like to create grids using graphics from several different libraries like seaborn or bokeh too. What is the best way to extract an axis from a graph and add it onto a matplotlib figure grid using the object-oriented API?

EDIT: As @ImportanceOfBeingErnest suggested, I tried a different method:

import ggplot as gp
import matplotlib.pyplot as plt
g = gp.ggplot(gp.aes(x='carat', y='price'), data=gp.diamonds) + gp.geom_point() + gp.ylab('price')+ gp.xlab('carat')
g.make()
f1 = plt.gcf()
g = gp.ggplot(gp.aes(x='price', y='carat'), data=gp.diamonds) + gp.geom_point() + gp.ylab('carat')+ gp.xlab('price')
g.make()
f2 = plt.gcf()

plt.show()
def add_axis(fig, ggFig, pos):
    ggAxis = ggFig.axes[0]
    ggAxis.remove()
    ggAxis.figure = fig
    fig.axes.append(ggAxis)
    fig.add_axes(ggAxis)

    dummy = fig.axes[pos]
    ggAxis.set_position(dummy.get_position())
    dummy.remove()
    plt.close(ggFig)

(fig, axes) = plt.subplots(1,2, figsize=(20,10))
add_axis(fig, f2, 1)
add_axis(fig, f1, 0)

fig.show()

Oddly enough, this works if add_axis is called with pos=1 and then pos=0, but not the other way around. Why might this be?

Ed Doe
  • 45
  • 7
  • 1
    As for why the position `pos` matters: You are extending the list `fig.axes` and then removing from it. So after having done that, the order is not necessarily the same any more. – ImportanceOfBeingErnest Aug 03 '18 at 23:36

2 Answers2

1

axes[0] = f1.axes[0] just means that the first element of the axes array is now the axes from the other figure. It does not mean that this axes has anything to do or is even shown in the figure fig.

The linked question's answer shows how to proceed instead: You create a figure with ggplot and add further axes to it and possibly change the positions of the existing ones. This can also be done with seaborn.

You may also try to move (not copy) axes from an existing figure. This is shown in How to plot multiple Seaborn Jointplot in Subplot.

In Can I create AxesSubplot objects, then add them to a Figure instance? the general approach is detailed.

However, note that those are kind of hacks. One would not recommend to move axes around in general, and there are some caveats with the existing solutions. Instead one could ask the maintainers of those libraries to finally provide options to plot with their tool to existing matplotlib figures.

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • I tried this and it worked in a limited scope, though it broke when I tried to extend it. I submitted a feature request on their GitHub, so let's hope something changes soon. – Ed Doe Aug 03 '18 at 22:29
  • Thank you! It does feel like a bit of a hack, but it appears to be moderately functional at least. I'm curious whether there's a viable hack that involves saving the ggplot object as an image, then displaying it on the axes using plt.imshow()? – Ed Doe Aug 03 '18 at 22:40
  • That'll be pretty easy, but will [look horrible](https://i.stack.imgur.com/uSZkI.png). – ImportanceOfBeingErnest Aug 03 '18 at 22:46
0

In addition to the other comment, here's another hack: save the image to an image and then use plt.imshow()

import ggplot as gp
import matplotlib.pyplot as plt
import numpy as np

g = gp.ggplot(gp.aes(x='carat', y='price'), data=gp.diamonds) + gp.geom_point() + gp.ylab('price')+ gp.xlab('carat')
g.make()
f1 = plt.gcf()
f1.canvas.draw()
f1_im = np.fromstring(f1.canvas.tostring_rgb(), dtype=np.uint8, sep='')
f1_im = f1_im.reshape(f1.canvas.get_width_height()[::-1] + (3,))

g = gp.ggplot(gp.aes(x='price', y='carat'), data=gp.diamonds) + gp.geom_point() + gp.ylab('carat')+ gp.xlab('price')
g.make()
f2 = plt.gcf()
f2.canvas.draw()
f2_im = np.fromstring(f2.canvas.tostring_rgb(), dtype=np.uint8, sep='')
f2_im = f2_im.reshape(f2.canvas.get_width_height()[::-1] + (3,))

plt.show()

(fig, axes) = plt.subplots(1,2, figsize=(20,10))
axes[0].imshow(f1_im)
axes[1].imshow(f2_im)
fig.show()

It isn't pretty, but it works.

Ed Doe
  • 45
  • 7