I am generating 2D heat map plots of a set of 3D data. I would like to be able to have a mechanism to interactively page through each pane. Below is a simple sample code, I would like to be able to interactively view both panes (ie, z = [0,1]) via a slider bar (or some other means). Is this possible with matplotlib or is this something I'll need to do post processing after generating the image files?
import numpy as np
from matplotlib import pyplot as plt
data = np.random.randint(10, size=(5, 5, 2))
data_slice = np.zeros((5,5))
for i in range(0, 5):
for j in range(0, 5):
data_slice[i][j] = data[i][j][0]
plt.imshow(data_slice, cmap='hot', interpolation='nearest')
plt.show()
Edit : I want to be able to do this interactively and it appears that the possible duplicate is trying to do this automatically.
The solution could indeed be to use a Slider as in the excellent answer by #hashmuke. In his answer he mentioned that
"The slider is continuous while the layer index is a discrete integer [...]"
This brought me to think about a solution that wouldn't have this restriction and have
a more page-like look and feel.
The outcome is PageSlider. Subclassing Slider it makes use of the slider functionality, but displays the slider in integer steps starting at 1. It takes the number of pages numpages as init argument, but except of that works as Slider seen from the outside. Additionally it also provides a back- and forward button.
An example, similar to the one from #hashmuke, is given below the class.
import matplotlib.widgets
import matplotlib.patches
import mpl_toolkits.axes_grid1
class PageSlider(matplotlib.widgets.Slider):
def __init__(self, ax, label, numpages = 10, valinit=0, valfmt='%1d',
closedmin=True, closedmax=True,
dragging=True, **kwargs):
self.facecolor=kwargs.get('facecolor',"w")
self.activecolor = kwargs.pop('activecolor',"b")
self.fontsize = kwargs.pop('fontsize', 10)
self.numpages = numpages
super(PageSlider, self).__init__(ax, label, 0, numpages,
valinit=valinit, valfmt=valfmt, **kwargs)
self.poly.set_visible(False)
self.vline.set_visible(False)
self.pageRects = []
for i in range(numpages):
facecolor = self.activecolor if i==valinit else self.facecolor
r = matplotlib.patches.Rectangle((float(i)/numpages, 0), 1./numpages, 1,
transform=ax.transAxes, facecolor=facecolor)
ax.add_artist(r)
self.pageRects.append(r)
ax.text(float(i)/numpages+0.5/numpages, 0.5, str(i+1),
ha="center", va="center", transform=ax.transAxes,
fontsize=self.fontsize)
self.valtext.set_visible(False)
divider = mpl_toolkits.axes_grid1.make_axes_locatable(ax)
bax = divider.append_axes("right", size="5%", pad=0.05)
fax = divider.append_axes("right", size="5%", pad=0.05)
self.button_back = matplotlib.widgets.Button(bax, label=ur'$\u25C0$',
color=self.facecolor, hovercolor=self.activecolor)
self.button_forward = matplotlib.widgets.Button(fax, label=ur'$\u25B6$',
color=self.facecolor, hovercolor=self.activecolor)
self.button_back.label.set_fontsize(self.fontsize)
self.button_forward.label.set_fontsize(self.fontsize)
self.button_back.on_clicked(self.backward)
self.button_forward.on_clicked(self.forward)
def _update(self, event):
super(PageSlider, self)._update(event)
i = int(self.val)
if i >=self.valmax:
return
self._colorize(i)
def _colorize(self, i):
for j in range(self.numpages):
self.pageRects[j].set_facecolor(self.facecolor)
self.pageRects[i].set_facecolor(self.activecolor)
def forward(self, event):
current_i = int(self.val)
i = current_i+1
if (i < self.valmin) or (i >= self.valmax):
return
self.set_val(i)
self._colorize(i)
def backward(self, event):
current_i = int(self.val)
i = current_i-1
if (i < self.valmin) or (i >= self.valmax):
return
self.set_val(i)
self._colorize(i)
if __name__ == "__main__":
import numpy as np
from matplotlib import pyplot as plt
num_pages = 23
data = np.random.rand(9, 9, num_pages)
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.18)
im = ax.imshow(data[:, :, 0], cmap='viridis', interpolation='nearest')
ax_slider = fig.add_axes([0.1, 0.05, 0.8, 0.04])
slider = PageSlider(ax_slider, 'Page', num_pages, activecolor="orange")
def update(val):
i = int(slider.val)
im.set_data(data[:,:,i])
slider.on_changed(update)
plt.show()
You can either animate the layers as suggested by Andrew's comment or you can manually walk through the the layers using a slider as follow:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.widgets import Slider
# generate a five layer data
data = np.random.randint(10, size=(5, 5, 5))
# current layer index start with the first layer
idx = 0
# figure axis setup
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.15)
# display initial image
im_h = ax.imshow(data[:, :, idx], cmap='hot', interpolation='nearest')
# setup a slider axis and the Slider
ax_depth = plt.axes([0.23, 0.02, 0.56, 0.04])
slider_depth = Slider(ax_depth, 'depth', 0, data.shape[2]-1, valinit=idx)
# update the figure with a change on the slider
def update_depth(val):
idx = int(round(slider_depth.val))
im_h.set_data(data[:, :, idx])
slider_depth.on_changed(update_depth)
plt.show()
The slider is continues while the layer index is discrete integer, I hope that is not a problem. Here is the resulting figure,
Related
I'm try to label each of the differently-sized panels in a Gridspec at the upper left corner outside the panel. However, this results in the labels being offset by different distances. I think this is because the x and y offset are a function the axis dimensions, but what's the most elegant way to make the labels all the same absolute horizontal and vertical distance to their respective panels? Here's the code:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.gridspec import GridSpec
def label_panels(fig, panels, xloc = -0.1, yloc = 1.2, size = 20):
for n, ax in enumerate(fig.axes):
if n < len(panels):
ax.text(xloc, yloc, panels[n],
size=size, weight='bold')
fig_7 = plt.figure(constrained_layout=True, figsize = (4,5))
fig7_gs = GridSpec(5, 4, figure=fig_7)
exemplars_subplots = np.array([[fig_7.add_subplot(fig7_gs[i:i+1, j]) for i in range(2)] for j in range(4)])
STDP_subplots = [fig_7.add_subplot(fig7_gs[2+i:3+i,0:2]) for i in range(3)]
ax_imshow = fig_7.add_subplot(fig7_gs[2:5, 2:4])
label_panels(fig_7, range(len(fig_7.axes)), xloc = -0.25, yloc = 1.2)
plt.show()
And this is what it shows. As you can see, panels 0-7 have a different label offset than 8-10 and 11 has a different offset than both of them.:
You are quite correct, the text positions need to be corrected to the actual scale for each axis. For this, you need to obtain the dimension of the gridspec container and the relative dimension of each axis.
A solution (Note: this is quickly written, this should be cleaned up especially with regards to the parameters actually needed for label_panels()) might look like this:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.gridspec import GridSpec
## need to include transforms
import matplotlib.transforms as transforms
def label_panels(fig, panels, xloc = 0, yloc = 0, size = 20):
# first, we get the bounding box for the gridspec "container"
gs_bb=transforms.Bbox(fig7_gs[1].get_position(fig))
x0,y0,x1,y1= gs_bb.extents
_gsu_x=x1-x0
_gsu_y=y1-y0
for n, ax in enumerate(fig.axes):
if n < len(panels):
# now, we get the bounding box for each axis inside the gridspec
ax_bb=transforms.Bbox(ax.get_position(fig))
x0,y0,x1,y1= ax_bb.extents
ax_scaling_x=(x1-x0)/_gsu_x
ax_scaling_y=(y1-y0)/_gsu_y
print(f"ax {n} has scaling: ({ax_scaling_x:.2f},{ax_scaling_y:.2f})",)
ax.text(-0.1, 1+0.2/ax_scaling_y, panels[n],
ha="center",va="center",
transform=ax.transAxes,
size=size, weight='bold')
fig_7 = plt.figure(constrained_layout=True, figsize = (4,5))
fig7_gs = GridSpec(5, 4, figure=fig_7)
exemplars_subplots = np.array([[fig_7.add_subplot(fig7_gs[i:i+1, j]) for i in range(2)] for j in range(4)])
STDP_subplots = [fig_7.add_subplot(fig7_gs[2+i:3+i,0:2]) for i in range(3)]
ax_imshow = fig_7.add_subplot(fig7_gs[2:5, 2:4])
label_panels(fig_7, range(len(fig_7.axes)), xloc = -0.25, yloc = 1.2)
plt.show()
Thanks to Jody's link, this works:
import matplotlib.transforms as transforms
def label_panels(fig, labels, xloc = 0, yloc = 1.0, size = 20):
for n, ax in enumerate(fig.axes):
trans = transforms.ScaledTranslation(-20/72, 7/72, fig.dpi_scale_trans)
ax.text(xloc, yloc, labels[n], transform=ax.transAxes + trans,
fontsize=size, va='bottom')
I'm trying to make the interactive histogram but during
update old histogram is not been cleared, as shown in the image below.
)
above plot is generated using following code
from functools import lru_cache
import scipy.stats as ss
import matplotlib.pyplot as plt
import matplotlib.widgets as widgets
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)
weight = ss.lognorm(0.23, 0, 70.8)
#lru_cache
def sampling(n):
return [ weight.rvs().mean() for i in range(1000) ]
theme = {
'color' : "#1f77b4",
'alpha' : 0.7,
}
t = ax.hist(sampling(100), **theme)
slider = widgets.Slider(
ax = plt.axes([0.25, 0.1, 0.5, 0.03]),
label = "n",
valmin = 10,
valmax = 1000,
valinit = 100,
valstep = 1
def update(val):
global t
del t
t = ax.hist(sampling(int(val)), **theme)
fig.canvas.draw_idle()
slider.on_changed(update)
ax.set_title('Distribution of Sample Size Mean')
plt.show()
ax.hist is returning a tuple containing the number of bins, the edges of the bins and then the patches... you thus need to capture the patches and remove them in update.
You need to use t.remove() in update, as in:
*_, patches = ax.hist()
def update(val):
global patches
for p in patches:
p.remove()
*_, patches = ax.hist(sampling(int(val)), **theme)
fig.canvas.draw_idle()
I am generating 2D heat map plots of a set of 3D data. I would like to be able to have a mechanism to interactively page through each pane. Below is a simple sample code, I would like to be able to interactively view both panes (ie, z = [0,1]) via a slider bar (or some other means). Is this possible with matplotlib or is this something I'll need to do post processing after generating the image files?
import numpy as np
from matplotlib import pyplot as plt
data = np.random.randint(10, size=(5, 5, 2))
data_slice = np.zeros((5,5))
for i in range(0, 5):
for j in range(0, 5):
data_slice[i][j] = data[i][j][0]
plt.imshow(data_slice, cmap='hot', interpolation='nearest')
plt.show()
Edit : I want to be able to do this interactively and it appears that the possible duplicate is trying to do this automatically.
The solution could indeed be to use a Slider as in the excellent answer by #hashmuke. In his answer he mentioned that
"The slider is continuous while the layer index is a discrete integer [...]"
This brought me to think about a solution that wouldn't have this restriction and have
a more page-like look and feel.
The outcome is PageSlider. Subclassing Slider it makes use of the slider functionality, but displays the slider in integer steps starting at 1. It takes the number of pages numpages as init argument, but except of that works as Slider seen from the outside. Additionally it also provides a back- and forward button.
An example, similar to the one from #hashmuke, is given below the class.
import matplotlib.widgets
import matplotlib.patches
import mpl_toolkits.axes_grid1
class PageSlider(matplotlib.widgets.Slider):
def __init__(self, ax, label, numpages = 10, valinit=0, valfmt='%1d',
closedmin=True, closedmax=True,
dragging=True, **kwargs):
self.facecolor=kwargs.get('facecolor',"w")
self.activecolor = kwargs.pop('activecolor',"b")
self.fontsize = kwargs.pop('fontsize', 10)
self.numpages = numpages
super(PageSlider, self).__init__(ax, label, 0, numpages,
valinit=valinit, valfmt=valfmt, **kwargs)
self.poly.set_visible(False)
self.vline.set_visible(False)
self.pageRects = []
for i in range(numpages):
facecolor = self.activecolor if i==valinit else self.facecolor
r = matplotlib.patches.Rectangle((float(i)/numpages, 0), 1./numpages, 1,
transform=ax.transAxes, facecolor=facecolor)
ax.add_artist(r)
self.pageRects.append(r)
ax.text(float(i)/numpages+0.5/numpages, 0.5, str(i+1),
ha="center", va="center", transform=ax.transAxes,
fontsize=self.fontsize)
self.valtext.set_visible(False)
divider = mpl_toolkits.axes_grid1.make_axes_locatable(ax)
bax = divider.append_axes("right", size="5%", pad=0.05)
fax = divider.append_axes("right", size="5%", pad=0.05)
self.button_back = matplotlib.widgets.Button(bax, label=ur'$\u25C0$',
color=self.facecolor, hovercolor=self.activecolor)
self.button_forward = matplotlib.widgets.Button(fax, label=ur'$\u25B6$',
color=self.facecolor, hovercolor=self.activecolor)
self.button_back.label.set_fontsize(self.fontsize)
self.button_forward.label.set_fontsize(self.fontsize)
self.button_back.on_clicked(self.backward)
self.button_forward.on_clicked(self.forward)
def _update(self, event):
super(PageSlider, self)._update(event)
i = int(self.val)
if i >=self.valmax:
return
self._colorize(i)
def _colorize(self, i):
for j in range(self.numpages):
self.pageRects[j].set_facecolor(self.facecolor)
self.pageRects[i].set_facecolor(self.activecolor)
def forward(self, event):
current_i = int(self.val)
i = current_i+1
if (i < self.valmin) or (i >= self.valmax):
return
self.set_val(i)
self._colorize(i)
def backward(self, event):
current_i = int(self.val)
i = current_i-1
if (i < self.valmin) or (i >= self.valmax):
return
self.set_val(i)
self._colorize(i)
if __name__ == "__main__":
import numpy as np
from matplotlib import pyplot as plt
num_pages = 23
data = np.random.rand(9, 9, num_pages)
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.18)
im = ax.imshow(data[:, :, 0], cmap='viridis', interpolation='nearest')
ax_slider = fig.add_axes([0.1, 0.05, 0.8, 0.04])
slider = PageSlider(ax_slider, 'Page', num_pages, activecolor="orange")
def update(val):
i = int(slider.val)
im.set_data(data[:,:,i])
slider.on_changed(update)
plt.show()
You can either animate the layers as suggested by Andrew's comment or you can manually walk through the the layers using a slider as follow:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.widgets import Slider
# generate a five layer data
data = np.random.randint(10, size=(5, 5, 5))
# current layer index start with the first layer
idx = 0
# figure axis setup
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.15)
# display initial image
im_h = ax.imshow(data[:, :, idx], cmap='hot', interpolation='nearest')
# setup a slider axis and the Slider
ax_depth = plt.axes([0.23, 0.02, 0.56, 0.04])
slider_depth = Slider(ax_depth, 'depth', 0, data.shape[2]-1, valinit=idx)
# update the figure with a change on the slider
def update_depth(val):
idx = int(round(slider_depth.val))
im_h.set_data(data[:, :, idx])
slider_depth.on_changed(update_depth)
plt.show()
The slider is continues while the layer index is discrete integer, I hope that is not a problem. Here is the resulting figure,
I am using this function from the documentation to make a nice plot with matplotlib.
def demo_grid_with_single_cbar(fig):
"""
A grid of 2x2 images with a single colorbar
"""
grid = AxesGrid(fig, 132, # similar to subplot(132)
nrows_ncols = (2, 2),
axes_pad = 0.0,
share_all=True,
label_mode = "L",
cbar_location = "top",
cbar_mode="single",
)
Z, extent = get_demo_image()
for i in range(4):
im = grid[i].imshow(Z, extent=extent, interpolation="nearest")
#plt.colorbar(im, cax = grid.cbar_axes[0])
grid.cbar_axes[0].colorbar(im)
for cax in grid.cbar_axes:
cax.toggle_label(False)
# This affects all axes as share_all = True.
grid.axes_llc.set_xticks([-2, 0, 2])
grid.axes_llc.set_yticks([-2, 0, 2])
I would like to change the minorlocator. but I really have no clue where to change the code.
If it where just a single graphics I would manipulate the axes object. But with AxesGrid I am lost.
majorLocator = MultipleLocator(50)
majorFormatter = FormatStrFormatter('%d')
minorLocator = MultipleLocator(10)
ax.yaxis.set_major_locator(majorLocator)
ax.yaxis.set_major_formatter(majorFormatter)
ax.yaxis.set_minor_locator(minorLocator)
EDIT after the question was solved.
The result should look like the following picture, notice the minor ticks!
The result should look like this image
Maybe someone else can append it. I cannot because my reputation credits do not suffice.
Note the warning on main page. If you have a new enough version of matplotlib this works.
from mpl_toolkits.axes_grid1 import ImageGrid
def get_demo_image():
import numpy as np
from matplotlib.cbook import get_sample_data
f = get_sample_data("axes_grid/bivariate_normal.npy", asfileobj=False)
z = np.load(f)
# z is a numpy array of 15x15
return z, (-3,4,-4,3)
def demo_grid_with_single_cbar(fig):
"""
A grid of 2x2 images with a single colorbar
"""
grid = ImageGrid(fig, 132, # similar to subplot(132)
nrows_ncols = (2, 2),
axes_pad = 0.0,
share_all=True,
label_mode = "L",
cbar_location = "top",
cbar_mode="single",
)
Z, extent = get_demo_image()
for i in range(4):
im = grid[i].imshow(Z, extent=extent, interpolation="nearest")
#plt.colorbar(im, cax = grid.cbar_axes[0])
grid.cbar_axes[0].colorbar(im)
for cax in grid.cbar_axes:
cax.toggle_label(False)
# This affects all axes as share_all = True.
grid.axes_llc.set_xticks([-2, 0, 2])
grid.axes_llc.set_yticks([-2, 0, 2])
for i in range(4):
ax = grid[i]
ax.yaxis.set_minor_locator(MultipleLocator(.5))
I'm trying to use Python and matplotlib to define a custom class that
produces a complex figure. However, I'm having trouble getting the
boxplots to print correctly - they keep appearing without whiskers or
lines marking the median values. I can't embed a sample image, but you can see one here.
My custom class is defined as follows:
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.ticker import FixedLocator
from matplotlib.gridspec import GridSpec
from matplotlib.figure import Figure
from matplotlib.backends.backend_svg import FigureCanvasSVG as FigureCanvas
import numpy as np
import scipy as sp
import scipy.optimize
class DotDashHist(Figure):
"""A Tufte-style dot-dash plot with histograms along the x- and y-axes."""
def __init__(self, the_vals):
# Actually inherit all the attributes and methods of parent class
super(DotDashHist, self).__init__()
# Process incoming data
self.vals = the_vals
self.xvals, self.yvals = zip(*self.vals)
self.xvals_uniq = list(set(self.xvals))
self.yvals_uniq = list(set(self.yvals))
self.xmax = float(max(self.xvals_uniq))
self.xpadding = float(self.xmax / 50)
self.ymax = float(max(self.yvals_uniq))
self.ypadding = float(self.ymax / 50)
self.xlims = [-1 * self.xpadding, self.xmax + self.xpadding]
self.ylims = [-1 * self.ypadding, self.ymax + self.ypadding]
self.lims = [-1 * self.xpadding, self.xmax + self.xpadding,
-1 * self.ypadding, self.ymax + self.ypadding]
# Set some matplotlib default behavior
mpl.rcParams['backend'] = 'SVG'
mpl.rcParams['lines.antialiased'] = True
mpl.rcParams['font.family'] = 'sans-serif'
mpl.rcParams['font.sans-serif'] = 'Gill Sans MT Pro, Lucida Grande, Helvetica, sans-serif'
mpl.rcParams['axes.titlesize'] = 'large'
mpl.rcParams['axes.labelsize'] = 'xx-small'
mpl.rcParams['xtick.major.size'] = 2
mpl.rcParams['xtick.minor.size'] = 0.5
mpl.rcParams['xtick.labelsize'] = 'xx-small'
mpl.rcParams['ytick.major.size'] = 2
mpl.rcParams['ytick.minor.size'] = 0.5
mpl.rcParams['ytick.labelsize'] = 'xx-small'
def _makeskel(self):
# Set up the framework in which the figure will be drawn
# Define the canvas for the figure
self.canvas = FigureCanvas(self)
self.set_canvas(self.canvas)
# Place subplots on a 6x6 grid
gs = GridSpec(6,6)
# Add the main subplot, override weird axis and tick defaults
self.main = self.add_subplot(gs[1:, :-1])
self.main.set_frame_on(False)
self.main.get_xaxis().tick_bottom()
self.main.get_yaxis().tick_left()
self.main.axis(self.lims)
# Add the x-value histogram, override weird axis and tick defaults
self.xhist = self.add_subplot(gs[0, :-1])
self.xhist.set_xticks([])
self.xhist.set_yticks([])
self.xhist.set_frame_on(False)
self.xhist.get_xaxis().tick_bottom()
self.xhist.get_yaxis().tick_left()
self.xhist.set_xlim(self.xlims)
# Add the y-value histogram, override weird axis and tick defaults
self.yhist = self.add_subplot(gs[1:, -1])
self.yhist.set_xticks([])
self.yhist.set_yticks([])
self.yhist.set_frame_on(False)
self.yhist.get_xaxis().tick_bottom()
self.yhist.get_yaxis().tick_left()
self.yhist.set_ylim(self.ylims)
def _makehist(self):
# Draw the x- and y-value histograms
self.xhist.hist(self.xvals, normed=1, bins=min([50, self.xmax + 1]),
range=[0, self.xmax + self.xpadding])
self.yhist.hist(self.yvals, normed=1, bins=min([50, self.ymax + 1]),
range=[0, self.ymax + self.ypadding],
orientation='horizontal')
def makebox(self):
self._makeskel()
self._makehist()
# Aggregate to make boxplots
box_dict = {}
for point in self.vals:
if point[0] <= self.xmax and point[1] <= self.ymax:
box_dict.setdefault(round(float(point[0]), 0),
[]).append(point[1])
self.main.boxplot(box_dict.values(), positions=box_dict.keys(),
whis=1.0, sym='ro')
self.main.set_xticks(np.arange(0, self.xmax + 1, 12))
self.main.xaxis.set_minor_locator(FixedLocator(self.xvals_uniq))
self.main.yaxis.set_minor_locator(FixedLocator(self.yvals_uniq))
This test code displays the problem:
from numpy.random import randn
import mycustomfigures as hf
test_x = np.arange(0, 25, 0.01)
test_y = test_x + randn(2500)
test_data = zip(test_x, test_y)
test_fig = hf.DotDashHist(test_data)
test_fig.makebox()
test_fig.suptitle('Test Figure')
test_fig.savefig('testing.svg')
What's wrong with the way I've defined DotDashHist? I can produce whiskered boxplots using the MATLAB-style stateful syntax, but that approach generates a tremendous amount of code when drawing multiple figures.
The whiskers are in your original plot for me, they're just obscured by the outlier points you have plotted.
At any rate, I'd proceed a bit more like this:
import collections
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
import numpy as np
def main():
x = np.arange(0, 25, 0.01)
y = x + np.random.randn(x.size)
plot = DotDashHist(figsize=(10, 8))
plot.plot(x, y, whis=1.0, sym='r.')
plot.title('This is a Test')
plt.show()
class DotDashHist(object):
def __init__(self, **kwargs):
self.fig = plt.figure(**kwargs)
gs = GridSpec(6, 6)
self.ax = self.fig.add_subplot(gs[1:, :-1])
self.topax = self.fig.add_subplot(gs[0, :-1], sharex=self.ax)
self.rightax = self.fig.add_subplot(gs[1:, -1], sharey=self.ax)
for ax in [self.topax, self.rightax]:
ax.set_axis_off()
def plot(self, x, y, **kwargs):
_, _, self.topbars = self.topax.hist(x, normed=1, bins=50)
_, _, self.rightbars = self.rightax.hist(y, normed=1, bins=50,
orientation='horizontal')
boxes = collections.defaultdict(list)
for X, Y in zip(x, y):
boxes[int(X)].append(Y)
kwargs.pop('positions', None)
self.boxes = self.ax.boxplot(boxes.values(), **kwargs)
def title(self, *args, **kwargs):
self.topax.set_title(*args, **kwargs)
if __name__ == '__main__':
main()