Matplotlib boxplot + imageshow (subplots) - python

I'm doing some methods for data visualization, being one of which to show the data with the box plot for this data, as follows:
def generate_data_heat_map(data, x_axis_label, y_axis_label, plot_title, file_path, box_plot=False):
plt.figure()
plt.title(plot_title)
if box_plot:
plt.subplot(1, 2, 1)
plt.boxplot(data.data.flatten(), sym='r+')
plt.subplot(1, 2, 2)
fig = plt.imshow(data.data, extent=[0, data.cols, data.rows, 0])
plt.xlabel(x_axis_label)
plt.ylabel(y_axis_label)
plt.colorbar(fig)
plt.savefig(file_path + '.png')
plt.close()
With this code, this is the image that I get:
First of all, I didn't get why my fliers are not represented as red +, but with the standard pattern. Besides this, as I want to plot the box plot and the data side by side, I divided my plot area. But this space is equally divided, and the figure plot gets really bad. I would like that the box plot took some as 1/3 of the plot area, and the data 2/3.
Thank you in advance.

The error is a simple mistake with your matplotlib code. You are plotting over your own image.
Where you have:
if box_plot:
plt.subplot(1, 1, 1)
plt.boxplot(data.data)
plt.subplot(1, 2, 2)
you need to specify the two rows of your subplots in both calls to plt.subplots
This will work.
if box_plot:
plt.subplot(1, 2, 1)
plt.boxplot(data.data)
plt.subplot(1, 2, 2)
If you want to size the plots independently then you can use gridspec. You might want to plot them above one another like this...
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.gridspec as gridspec
def generate_data_heat_map(data, x_axis_label, y_axis_label, plot_title, file_path, box_plot=False):
plt.figure()
gs = gridspec.GridSpec(2, 1,height_ratios=[1,4])
if box_plot:
plt.subplot(gs[0])
plt.boxplot(data.data.flatten(), 0, 'rs', 0)
plt.subplot(gs[1])
plt.title(plot_title)
fig = plt.imshow(data.data, extent=[0, data.cols, data.rows, 0])
plt.xlabel(x_axis_label)
plt.ylabel(y_axis_label)
plt.colorbar(fig)
plt.savefig(file_path + '.png')
plt.close()
class Data(object):
def __init__(self, rows=200, cols=300):
# The data grid
self.cols = cols
self.rows = rows
# The 2D data structure
self.data = np.zeros((rows, cols), float)
def randomise(self):
self.data = np.random.rand(*self.data.shape)
data = Data()
data.randomise()
generate_data_heat_map(data, 'x', 'y', 'title', 'heat_map', box_plot=True)

Related

Can you force the wspace and hspace of figure subplots to a fixed value in matplotlib regardless of figure size

I am trying to build a function to plot multiple images in a grid with a single colorbar and histogram. I would like the spacing between all the plots to be a fixed value and for the colorbar to span the height of a all images and histogram to span the width of the images/colorbar. I have some code that works, but it requires the figure size being set to a specific aspect ratio for it to work. This is not ideal because I want to use the function for images with varying aspect ratios and for a varying number of images 2x1, 1x2, 2x2, etc.
This code outputs 3 figures of varying aspect ratio. I would like if any excess dimension would be applied to the border spacing rather than the subplot wspace, hspace spacing.
fig wide: https://i.stack.imgur.com/BB1Cz.png
fig tall: https://i.stack.imgur.com/G5C34.png
fig nice: https://i.stack.imgur.com/AVX6C.png
Here is the code:
import math
import numpy as np
import matplotlib as mpl
from matplotlib import pyplot as plt
def compare_frames(frames, columns, bins=256, alpha=.5, vmin=None, vmax=None, fig=None):
if vmin is None:
vmin = min([f.min() for f in frames])
if vmax is None:
vmax = max([f.max() for f in frames])
if fig == None:
fig = plt.figure()
color_cycle = plt.get_cmap('tab10')
rows = math.ceil(len(frames)/columns)
width_ratios = [1 for col in range(columns)] + [.05]
gs = mpl.gridspec.GridSpec(rows + 1, columns + 1, figure=fig, width_ratios=width_ratios)
images = []
for row in range(rows):
for col in range(columns):
idx = row*columns + col
if idx < len(frames):
ax = fig.add_subplot(gs[row, col])
ax.get_xaxis().set_ticks([])
ax.get_yaxis().set_ticks([])
for spine in ['bottom', 'top', 'left', 'right']:
ax.spines[spine].set_color(color_cycle(idx))
ax.spines[spine].set_linewidth(3)
images.append(ax.imshow(frames[idx], vmin=vmin, vmax=vmax))
cax = fig.add_subplot(gs[0:-1, -1])
plt.colorbar(images[0], cax=cax)
hax = fig.add_subplot(gs[-1, :])
for i, frame in enumerate(frames):
hax.hist(frame.ravel(), bins=256, range=(vmin, vmax), color=color_cycle(i), alpha=alpha)
fig.subplots_adjust(wspace=.05, hspace=.05)
if __name__ == '__main__':
x_size = 640
y_size = 512
frames = []
for i in range(4):
frames.append(np.random.normal(i + 1, np.sqrt(i + 1), size=(y_size, x_size)))
fig_wide = plt.figure(figsize=(12, 8))
compare_frames(frames, 2, fig=fig_wide)
fig_tall = plt.figure(figsize=(6, 8))
compare_frames(frames, 2, fig=fig_tall)
fig_nice = plt.figure(figsize=(6.9, 8))
compare_frames(frames, 2, fig=fig_nice)
plt.show()
I've gathered that I should probably be using matplotlib axes_grid1 from mpl_toolkits. They have a built-in ImageGrid class which does a lot of what I would like to do (fixed spacing for images and colorbar):
def compare_frames(frames, columns, bins=256, alpha=.5, vmin=None, vmax=None, fig=None):
if vmin is None:
vmin = min([f.min() for f in frames])
if vmax is None:
vmax = max([f.max() for f in frames])
if fig == None:
fig = plt.figure()
color_cycle = plt.get_cmap('tab10')
rows = math.ceil(len(frames)/columns)
im_grid = axes_grid1.ImageGrid(fig, 111, nrows_ncols=(rows, columns), axes_pad=.1,
cbar_mode='single', cbar_pad=.1, cbar_size=.3)
for i, ax in enumerate(im_grid):
im = ax.imshow(frames[i], vmin=vmin, vmax=vmax)
ax.get_xaxis().set_ticks([])
ax.get_yaxis().set_ticks([])
for spine in ['bottom', 'top', 'left', 'right']:
ax.spines[spine].set_color(color_cycle(i))
ax.spines[spine].set_linewidth(3)
cbar = fig.colorbar(im, cax=im_grid.cbar_axes[0])
This is great, and I would love to figure out a way to use this ImageGrid class to do most of the work, and then add another axis at the bottom for the histogram. I haven't been able to crack how to do this however since all of the examples I've found use "append_axes()" on a Divider class. ImageGrid forms a SubplotDivider however, which doesn't have an append_axes function.

Python Plots - Plotting a subplots in a subplots

I want to plot a graph representing the changes as per the varying variables. The sample figure is shown below.
The idea is to plot subplot within a subplot. Note It is different from plotting a graph using subplot with a predefined number of rows and columns, i.e matplotlib.pyplot.subplots(nrows=2, ncols=2)
Can I plot such figures using matplotlib/seaborn?
I have drawn the frames and placed the axes inside the frames, everything is based on the no. of subplots/frame, the no. of rows and columns of the frames' grid and the physical dimensions of the different elements.
I imagine that most of the code is self explanatory, except the part where we place the axes in the precise locations, that's stolen from the Demo Fixed Size Axes, if you see points in need of elucidation please ask
import matplotlib
from mpl_toolkits.axes_grid1 import Divider, Size
from mpl_toolkits.axes_grid1.mpl_axes import Axes
import matplotlib.pyplot as plt
import numpy as np
from itertools import product
mm = lambda d: d/25.4
nplots = 2
wp, hp = mm(40), mm(28)
dxp, dyp = mm(16), mm(12)
nrows, ncols = 3, 2
wf, hf = nplots*(wp+dxp), hp+dyp
dxf, dyf = mm(10), mm(8)
xcorners, ycorners = (np.arange(dxf/2,ncols*(wf+dxf),wf+dxf),
np.arange(dyf/2,nrows*(hf+dyf),hf+dyf))
# plus 10 mm for suptitle
fig = plt.figure(figsize=(ncols*(wf+dxf), nrows*(hf+dyf)+mm(10)))
rect = lambda xy: plt.Rectangle(xy, wf, hf,
transform=fig.dpi_scale_trans,
figure=fig,
edgecolor='k', facecolor='none')
fig.patches.extend([rect(xy) for xy in product(xcorners, ycorners)])
t = np.linspace(0,3.14,315); s = np.sin(t)
for nframe, (y, x) in enumerate(product(ycorners, xcorners), 1):
for n in range(nplots):
divider = Divider(fig, (0.0, 0.0, 1., 1.),
[Size.Fixed(x+0.7*dxp+n*(wp+dxp)), Size.Fixed(wp)],
[Size.Fixed(y+0.7*dyp ), Size.Fixed(hp)],
aspect=False)
ax = Axes(fig, divider.get_position())
ax.set_axes_locator(divider.new_locator(nx=1, ny=1))
ax.plot(t, s)
fig.add_axes(ax)
fig.text(x, y, 'Frame %d'%nframe, transform=fig.dpi_scale_trans)
figsize = fig.get_size_inches()
width = figsize[0]*25.4 # mm
fig.suptitle('Original figure width is %.2f mm - everything is scaled'%width)
fig.savefig('pippo.png', dpi=118, facecolor='#f8f8f0')
You will need to use Matplotlib to plot these graphs
You can follow the following example to create your own figure with the graphs:
import matplotlib.pyplot as plt
plt.subplot(1, 2, 1) # Args ( Lines, Columns, Reference )
plt.plot(x, y, 'r') # Reference will say what graph we are modding
plt.subplot(1, 2, 2)
plt.plot(y, x, 'g')
plt.show()
The code will create one graph like this:
And you can use plt.xlabel('name'), plt.ylabel('name') and plt.title('name') to define the labels and the title of your figure
Note: The code above will create one image with 2 graphs, and you can use this code inside another block of code to create the image that you want.
You can also use the following code:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(nrows=5, ncols=5, figsize=(5, 5))
ax[0, 0].plot(x, y) # The method ax is now one array and is referred by indexes
ax[0, 0].set_title('Title')
ax[1, 1].plot(x, y)
ax[1, 1].set_title('Title')
plt.tight_layout() # It will separate the graphs to avoid overlays
plt.show()
It will create the following image:

Dynamically adding columns to a pyplot not working

Not really a pressing issue but it's bugging me: I want to display several images side by side (i.e. several columns) but for some reason the following code (taken from python tutorial) only displays the images in one column. So what I want is a layout like this
X X X
but what I get is
X
X
X
code:
...
plt.ion()
...
fig = plt.figure()
sample = face_dataset[65] # <== this is a simple image of size 640x480
for i, tsfrm in enumerate([scale, crop, composed]):
transformed_sample = tsfrm(sample)
ax = plt.subplot(1, 3, i + 1)
plt.tight_layout()
ax.set_title(type(tsfrm).__name__)
show_landmarks(**transformed_sample)
plt.show()
...
Here is the show_landmarks function definition:
def show_landmarks(image, landmarks):
"""Show image with landmarks"""
plt.imshow(image)
plt.scatter(landmarks[:, 0], landmarks[:, 1], s=10, marker='.', c='r')
plt.pause(0.001) # pause a bit so that plots are updated
I do not think the different transforms (scale,crop, composed) matter so I left them out but they can be found under the link above.
If I write basically the same as a test code the columns show up fine:
fig = plt.figure()
for i in (0,1,2):
ax = plt.subplot(1, 3, i + 1)
plt.tight_layout()
ax.set_title(i)
plt.show()
So my guess is that somehow show_landmarks messes things up. Can anybody point me in the right direction as to why/how that is?
You need to modify show_landmarks so it is making calls to the current plotting axis, rather than to plt. Then pass the axis to the function as well as the other args.
def show_landmarks(ax, image, landmarks):
"""Show image with landmarks"""
ax.imshow(image)
ax.scatter(landmarks[:, 0], landmarks[:, 1], s=10, marker='.', c='r')
...
for i, tsfrm in enumerate([scale, crop, composed]):
transformed_sample = tsfrm(sample)
ax = plt.subplot(1, 3, i + 1)
ax.set_title(type(tsfrm).__name__)
show_landmarks(ax, **transformed_sample)
plt.tight_layout()
...

matplotlib get ylim values

I'm using matplotlib to plot data (using plot and errorbar functions) from Python. I have to plot a set of totally separate and independent plots, and then adjust their ylim values so they can be easily visually compared.
How can I retrieve the ylim values from each plot, so that I can take the min and max of the lower and upper ylim values, respectively, and adjust the plots so they can be visually compared?
Of course, I could just analyze the data and come up with my own custom ylim values... but I'd like to use matplotlib to do that for me. Any suggestions on how to easily (and efficiently) do this?
Here's my Python function that plots using matplotlib:
import matplotlib.pyplot as plt
def myplotfunction(title, values, errors, plot_file_name):
# plot errorbars
indices = range(0, len(values))
fig = plt.figure()
plt.errorbar(tuple(indices), tuple(values), tuple(errors), marker='.')
# axes
axes = plt.gca()
axes.set_xlim([-0.5, len(values) - 0.5])
axes.set_xlabel('My x-axis title')
axes.set_ylabel('My y-axis title')
# title
plt.title(title)
# save as file
plt.savefig(plot_file_name)
# close figure
plt.close(fig)
Just use axes.get_ylim(), it is very similar to set_ylim. From the docs:
get_ylim()
Get the y-axis range [bottom, top]
ymin, ymax = axes.get_ylim()
If you are using the plt api directly, you can avoid calls to axes altogether:
def myplotfunction(title, values, errors, plot_file_name):
# plot errorbars
indices = range(0, len(values))
fig = plt.figure()
plt.errorbar(tuple(indices), tuple(values), tuple(errors), marker='.')
plt.ylim([-0.5, len(values) - 0.5])
plt.xlabel('My x-axis title')
plt.ylabel('My y-axis title')
# title
plt.title(title)
# save as file
plt.savefig(plot_file_name)
# close figure
plt.close(fig)
Leveraging from the good answers above and assuming you were only using plt as in
import matplotlib.pyplot as plt
then you can get all four plot limits using plt.axis() as in the following example.
import matplotlib.pyplot as plt
x = [1, 2, 3, 4, 5, 6, 7, 8] # fake data
y = [1, 2, 3, 4, 3, 2, 5, 6]
plt.plot(x, y, 'k')
xmin, xmax, ymin, ymax = plt.axis()
s = 'xmin = ' + str(round(xmin, 2)) + ', ' + \
'xmax = ' + str(xmax) + '\n' + \
'ymin = ' + str(ymin) + ', ' + \
'ymax = ' + str(ymax) + ' '
plt.annotate(s, (1, 5))
plt.show()
The above code should produce the following output plot.
Just use plt.ylim(), it can be used to set or get the min and max limit
ymin, ymax = plt.ylim()
I put above-mentioned methods together using ax instead of plt
import numpy as np
import matplotlib.pyplot as plt
x = range(100)
y = x
fig, ax = plt.subplots(1, 1, figsize=(7.2, 7.2))
ax.plot(x, y);
# method 1
print(ax.get_xlim())
print(ax.get_xlim())
# method 2
print(ax.axis())
It's an old question, but I don't see mentioned that, depending on the details, the sharey option may be able to do all of this for you, instead of digging up axis limits, margins, etc. There's a demo in the docs that shows how to use sharex, but the same can be done with y-axes.

Pyplot, plot 2 dataset into one figure, skip part of the y-axis

I am plotting to different datasets into one graph with pylab.plot(), which works great. But one dataset has values between 0% an 25% and the other has values between 75% and 100%. I want to skip 30% to 70% on the y-axis to save some space. Do you have any suggestions how this might be work with pyplot?
EDIT:
For clearness I added the following graphic. I want to skip 30% to 60% on the y axis, so that the red line and the green line come closer together.
The solution is based on Space_C0wb0ys post.
fig = pylab.figure()
ax = fig.add_subplot(111)
ax.plot( range(1,10), camean - 25, 'ro-' )
ax.plot( range(1,10), oemean , 'go-' )
ax.plot( range(1,10), hlmean , 'bo-' )
ax.set_yticks(range(5, 60, 5))
ax.set_yticklabels(["5","10","15","20","25","30","...","65","70","75"])
ax.legend(('ClassificationAccuracy','One-Error','HammingLoss'),loc='upper right')
pylab.show()
This code creates the following graphic.
You could subtract 40 from the x-values for your second functions to make the range of x-values continuous. This would give you a range from 0% to 70%. Then you can make set the tics and labes of the x-axis as follows:
x_ticks = range(71, 0, 10)
a.set_xticks(x_ticks)
a.set_xticklabels([str(x) for x in [0, 10, 20, 30, 70, 80, 90, 100]])
Where a is the current axes. So basically, you plot your functions in the range from 0% to 70%, but label the axis with a gap.
To illustrate - the following script:
from numpy import arange
import matplotlib.pyplot as plt
x1 = arange(0, 26) # first function
y1 = x1**2
x2 = arange(75, 100) # second function
y2 = x2*4 + 10
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x1, y1)
ax.plot(x2 - 40, y2) # shift second function 40 to left
ax.set_xticks(range(0, 61, 5)) # set custom x-ticks
# set labels for x-ticks - labels have the gap we want
ax.set_xticklabels([str(x) for x in range(0, 26, 5) + range(70, 101, 5)])
plt.show()
Produces the following plot (note the x-labels):
The matplotlib documentation actually has an example of how to do this.
The basic idea is to break up the plotting into two subplots, putting the same graph on each plot, then change the axes for each one to only show the specific part, then make it look nicer.
So, let's apply this. Imagine this is your starting code:
import matplotlib.pyplot as plt
import random, math
# Generates data
i = range(10)
x = [math.floor(random.random() * 5) + 67 for i in range(10)]
y = [math.floor(random.random() * 5) + 22 for i in range(10)]
z = [math.floor(random.random() * 5) + 13 for i in range(10)]
# Original plot
fig, ax = plt.subplots()
ax.plot(i, x, 'ro-')
ax.plot(i, y, 'go-')
ax.plot(i, z, 'bo-')
plt.show()
And we went to make it so that x is shown split off from the rest.
First, we want to plot the same graph twice, one on top of the other. To do this, the plotting function needs to be generic. Now it should look something like this:
# Plotting function
def plot(ax):
ax.plot(i, x, 'ro-')
ax.plot(i, y, 'go-')
ax.plot(i, z, 'bo-')
# Draw the graph on two subplots
fig, (ax1, ax2) = plt.subplots(2, 1)
plot(ax1)
plot(ax2)
Now this seems worse, but we can change the range for each axis to focus on what we want. For now I'm just choosing easy ranges that I know will capture all the data, but I'll focus on making the axes equal later.
# Changes graph axes
ax1.set_ylim(65, 75) # Top graph
ax2.set_ylim(5, 30) # Bottom graph
This is getting closer to what we're looking for. Now we need to just make it look a little nicer:
# Hides the spines between the axes
ax1.spines.bottom.set_visible(False)
ax2.spines.top.set_visible(False)
ax1.xaxis.tick_top()
ax1.tick_params(labeltop=False) # Don't put tick labels at the top
ax2.xaxis.tick_bottom()
# Adds slanted lines to axes
d = .5 # proportion of vertical to horizontal extent of the slanted line
kwargs = dict(
marker=[(-1, -d), (1, d)],
markersize=12,
linestyle='none',
color='k',
mec='k',
mew=1,
clip_on=False
)
ax1.plot([0, 1], [0, 0], transform=ax1.transAxes, **kwargs)
ax2.plot([0, 1], [1, 1], transform=ax2.transAxes, **kwargs)
Finally, let's fix the axes. Here you need to do a little math and decide more on the layout. For instance, maybe we want to make the top graph smaller, since the bottom graph has two lines. To do that, we need to change the height ratios for the subplots, like so:
# Draw the graph on two subplots
# Bottom graph is twice the size of the top one
fig, (ax1, ax2) = plt.subplots(2, 1, gridspec_kw={'height_ratios': [1, 2]})
Finally, It's a good idea to make the axes match. In this case, because the bottom image is twice the size of the top one, we need to change the axes of one to reflect that. I've chosen to modify the top one in this time. The bottom graph covers a range of 25, which means the top one should cover a range of 12.5.
# Changes graph axes
ax1.set_ylim(60.5, 73) # Top graph
ax2.set_ylim(5, 30) # Bottom graph
This looks good enough to me. You can play around more with the axes or tick labels if you don't want the ticks to overlap with the broken lines.
Final code:
import matplotlib.pyplot as plt
import random, math
# Generates data
i = range(10)
x = [math.floor(random.random() * 5) + 67 for i in range(10)]
y = [math.floor(random.random() * 5) + 22 for i in range(10)]
z = [math.floor(random.random() * 5) + 13 for i in range(10)]
# Plotting function
def plot(ax):
ax.plot(i, x, 'ro-')
ax.plot(i, y, 'go-')
ax.plot(i, z, 'bo-')
# Draw the graph on two subplots
# Bottom graph is twice the size of the top one
fig, (ax1, ax2) = plt.subplots(2, 1, gridspec_kw={'height_ratios': [1, 2]})
plot(ax1)
plot(ax2)
# Changes graph axes
ax1.set_ylim(60.5, 73) # Top graph
ax2.set_ylim(5, 30) # Bottom graph
# Hides the spines between the axes
ax1.spines.bottom.set_visible(False)
ax2.spines.top.set_visible(False)
ax1.xaxis.tick_top()
ax1.tick_params(labeltop=False) # Don't put tick labels at the top
ax2.xaxis.tick_bottom()
# Adds slanted lines to axes
d = .5 # proportion of vertical to horizontal extent of the slanted line
kwargs = dict(
marker=[(-1, -d), (1, d)],
markersize=12,
linestyle='none',
color='k',
mec='k',
mew=1,
clip_on=False
)
ax1.plot([0, 1], [0, 0], transform=ax1.transAxes, **kwargs)
ax2.plot([0, 1], [1, 1], transform=ax2.transAxes, **kwargs)
plt.show()

Categories

Resources