matplotlib subplots: how to freeze x and y axis? - python

Good evening
matplotlib changes the scaling of the diagram when drawing with e.g. hist() or plot(), which is usually great.
Is it possible to freeze the x and y axes in a subplot after drawing, so that further drawing commands do not change them anymore? For example:
fig, (plt1, plt2) = plt.subplots(2, 1, figsize=(20, 10))
plt1.hist(…)
plt1.plot(…)
# How can this get done?:
plt1.Freeze X- and Y-Axis
# Those commands no longer changes the x- and y-axis
plt1.plot(…)
plt1.plot(…)
Thanks a lot, kind regards,
Thomas

Matplotlib has an autoscale() function that you can turn on or off for individual axis objects and their individual x- and y-axes:
from matplotlib import pyplot as plt
fig, (ax1, ax2) = plt.subplots(2)
#standard is that both x- and y-axis are autoscaled
ax1.plot([1, 3, 5], [2, 5, 1], label="autoscale on")
#rendering the current output
fig.draw_without_rendering()
#turning off autoscale for the x-axis of the upper panel
#the y-axis will still be autoscaled for all following artists
ax1.autoscale(False, axis="x")
ax1.plot([-1, 7], [-2, 4], label="autoscale off")
ax1.legend()
#other axis objects are not influenced
ax2.plot([-2, 4], [3, 1])
plt.show()
Sample output:

Use plt.xlim and plt.ylim to get the current limits after plotting the initial plots, then use those values to set the limits after plotting the additional plots:
import matplotlib.pyplot as plt
# initial data
x = [1, 2, 3, 4, 5]
y = [2, 4, 8, 16, 32]
plt.plot(x, y)
# Save the current limits here
xlims = plt.xlim()
ylims = plt.ylim()
# additional data (will change the limits)
new_x = [-10, 100]
new_y = [2, 2]
plt.plot(new_x, new_y)
# Then set the old limits as the current limits here
plt.xlim(xlims)
plt.ylim(ylims)
plt.show()
Output figure (note how the x-axis limits are ~ [1, 5] even though the orange line is defined in the range [-10, 100]) :

To freeze x-axis specify the domain on the plot function:
import matplotlib.pyplot as plt
fig, (plt1, plt2) = plt.subplots(2, 1, figsize=(20, 10))
# range(min, max, step)
n = range(0, 10, 1) # domain [min, max] = [0, 9]
# make sure your functions has equal length
f = [i * 2 for i in n]
g = [i ** 2 for i in n]
# keep x-axis scale the same by specifying x-axis on the plot function.
plt1.plot(n, f) # funtion (f) range depends on it's value [min, max]
plt1.plot(n, g) # funtion (g) range depends on it's value [min, max]
# range of (f) and (g) impacts the scaling of y-axis
See matplotlib.pyplot for hist function parameters.

The answer of #jfaccioni is almost perfect (thanks a lot!), but it does not work with matplotlib subplots (as asked) because Python, as unfortunately so often, does not have uniform attributes and methods (not even in the same module), and so the matplotlib interface to a plot and a subplot is different.
In this example, this code works with a plot but not with a subplot:
# this works for plots:
xlims = plt.xlim()
# and this must be used for subplots :-(
xlims = plt1.get_xlim()
therefore, this code works with subplots:
import matplotlib.pyplot as plt
fig, (plt1, plt2) = plt.subplots(2, 1, figsize=(20, 10))
# initial data
x = [1, 2, 3, 4, 5]
y = [2, 4, 8, 16, 32]
plt1.plot(x, y)
# Save the current limits here
xlims = plt1.get_xlim()
ylims = plt1.get_ylim()
# additional data (will change the limits)
new_x = [-10, 100]
new_y = [2, 2]
plt1.plot(new_x, new_y)
# Then set the old limits as the current limits here
plt1.set_xlim(xlims)
plt1.set_ylim(ylims)
plt.show()
btw: Freezing the x- and y axes can even be done by 2 lines because once again, python unfortunately has inconsistent attributes:
# Freeze the x- and y axes:
plt1.set_xlim(plt1.get_xlim())
plt1.set_ylim(plt1.get_ylim())
It does not make sense at all to set xlim to the value it already has.
But because Python matplotlib misuses the xlim/ylim attribute and sets the current plot size (and not the limits!), therefore this code works not as expected.
It helps to solve the task in question, but those concepts makes using matplotlib hard and reading matplotlib code is annoying because one must know hidden / unexpected internal behaviors.

Related

Update plot in a loop in Jupyter Notebook

I am making multiple plots on the same canvas using data from dataframe. I want to update the plot in a loop based on newly filtered data.
The code I am using is:
from IPython import display
fig = plt.figure(figsize = (10,13))
ax.set_xlim(-0.5,2.5)
ax.set_ylim(-0.5,3.5)
# d_a is a list of dataframes created using different filters
for data_filtered in d_a:
for index,row in data_filtered.iterrows():
x_values = [row['x'] - xy_offset[row['direction']][0]/2.1,
row['x']+xy_offset[row['direction']][0]/2.1]
y_values = [row['y']-xy_offset[row['direction']][1]/2.1,
row['y']+xy_offset[row['direction']][1]/2.1]
# for each row in the dataframe a plot is drawn
plt.plot(x_values,y_values, linewidth=20,color= 'green',
alpha = 0.1
)
t.sleep(0.5)
display.display(plt.gcf())
display.clear_output(wait =True)
Output:(Dynamic and changes with the iteration)
Now the idea is to use a varying value of 'alpha' in the plot based on a certain value in the row of the dataframe.
When I plot this, the opacity just keeps on increasing even when alpha is kept constant as in the code snipped shown.
Shouldn't the display be cleared entirely and a new plot made instead?
You need to clear either the matplotlib axis or figure also, with plt.cla() or plt.clf() respectively. Otherwise the lines will be drawn onto the same matplotlib axis object in memory, and redrawn at each iteration.
from IPython import display
import numpy as np
import time as t
fig = plt.figure(figsize = (10,13))
ax = fig.subplots()
shifts = [1, 3, 4, 1, 2, 5, 2]
for shift in shifts:
ax.plot([0, 1], [0 + shift/10, 1 - shift/10], linewidth=20, color= 'green', alpha = 0.1)
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
display.display(plt.gcf())
t.sleep(0.5)
plt.cla()
display.clear_output(wait =True)

How to turn off autoscaling in matplotlib.pyplot

I am using matplotlib.pyplot in python to plot my data. The problem is the image it generates seems to be autoscaled. How can I turn this off so that when I plot something at (0,0) it will be placed fixed in the center?
You want the autoscale function:
from matplotlib import pyplot as plt
# Set the limits of the plot
plt.xlim(-1, 1)
plt.ylim(-1, 1)
# Don't mess with the limits!
plt.autoscale(False)
# Plot anything you want
plt.plot([0, 1])
You can use xlim() and ylim() to set the limits. If you know your data goes from, say -10 to 20 on X and -50 to 30 on Y, you can do:
plt.xlim((-20, 20))
plt.ylim((-50, 50))
to make 0,0 centered.
If your data is dynamic, you could try allowing the autoscale at first, but then set the limits to be inclusive:
xlim = plt.xlim()
max_xlim = max(map(abs, xlim))
plt.xlim((-max_xlim, max_xlim))
ylim = plt.ylim()
max_ylim = max(map(abs, ylim))
plt.ylim((-max_ylim, max_ylim))
If you want to keep temporarily turn off the auto-scaling to make sure that the scale stays as it was at some point before drawing the last piece of the figure, this might become handy and seems to work better then plt.autoscale(False) as it actually preserves limits as they would have been without the last scatter:
from contextlib import contextmanager
#contextmanager
def autoscale_turned_off(ax=None):
ax = ax or plt.gca()
lims = [ax.get_xlim(), ax.get_ylim()]
yield
ax.set_xlim(*lims[0])
ax.set_ylim(*lims[1])
plt.scatter([0, 1], [0, 1])
with autoscale_turned_off():
plt.scatter([-1, 2], [-1, 2])
plt.show()
plt.scatter([0, 1], [0, 1])
plt.scatter([-1, 2], [-1, 2])
plt.show()

Remove empty sub plots in matplotlib figure

How can I determine whether a subplot (AxesSubplot) is empty or not? I would like to deactivate empty axes of empty subplots and remove completely empty rows.
For instance, in this figure only two subplots are filled and the remaining subplots are empty.
import matplotlib.pyplot as plt
# create figure wit 3 rows and 7 cols; don't squeeze is it one list
fig, axes = plt.subplots(3, 7, squeeze=False)
x = [1,2]
y = [3,4]
# plot stuff only in two SubAxes; other axes are empty
axes[0][1].plot(x, y)
axes[1][2].plot(x, y)
# save figure
plt.savefig('image.png')
Note: It is mandatory to set squeeze to False.
Basically I want a sparse figure. Some subplots in rows can be empty, but they should be deactivated (no axes must be visible). Completely empty rows must be removed and must not be set to invisible.
You can use the fig.delaxes() method:
import matplotlib.pyplot as plt
# create figure wit 3 rows and 7 cols; don't squeeze is it one list
fig, axes = plt.subplots(3, 7, squeeze=False)
x = [1,2]
y = [3,4]
# plot stuff only in two SubAxes; other axes are empty
axes[0][1].plot(x, y)
axes[1][2].plot(x, y)
# delete empty axes
for i in [0, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20]:
fig.delaxes(axes.flatten()[i])
# save figure
plt.savefig('image.png')
plt.show(block=False)
One way of achieving what you require is to use matplotlibs subplot2grid feature. Using this you can set the total size of the grid (3,7 in your case) and choose to only plot data in certain subplots in this grid. I have adapted your code below to give an example:
import matplotlib.pyplot as plt
x = [1,2]
y = [3,4]
fig = plt.subplots(squeeze=False)
ax1 = plt.subplot2grid((3, 7), (0, 1))
ax2 = plt.subplot2grid((3, 7), (1, 2))
ax1.plot(x,y)
ax2.plot(x,y)
plt.show()
This gives the following graph:
EDIT:
Subplot2grid, in effect, does give you a list of axes. In your original question you use fig, axes = plt.subplots(3, 7, squeeze=False) and then use axes[0][1].plot(x, y) to specifiy which subplot your data will be plotted in. That is the same as what subplot2grid does, apart from it only shows the subplots with data in them which you have defined.
So take ax1 = plt.subplot2grid((3, 7), (0, 1)) in my answer above, here I have specified the shape of the 'grid' which is 3 by 7. That means I can have 21 subplots in that grid if I wanted, exactly like you original code. The difference is that your code displays all the subplots whereas subplot2grid does not. The (3,7) in ax1 = ... above specifies the shape of the whole grid and the (0,1) specifies where in that grid the subplot will be shown.
You can use any position the subplot wherever you like within that 3x7 grid. You can also fill all 21 spaces of that grid with subplots that have data in them if you require by going all the way up to ax21 = plt.subplot2grid(...).

How to set the axis scale and ticklabels using matplotlib object oriented API

I would need some help with plotting in Matplotlib.pyplot under Python2.7!
I want to generate a plot with the following x-axis:
x-axis as it should be
I got so far by using myaxis.set_xticks([0,0.5,1,2,4,6,8]) and it looks good, but if I want to create **an logarithmic x-axis* **, then my axis labels look like this!
wrong x-axis labels
What can I do to have both a log-scaled x-axis and integer formated labels (not logarithmic values as labels either!). Please read the note regarding to the log-scale!!!
While browsing Stackoverflow I found the following similar question, but nothing of the suggestions worked for me and I do not know what I did wrong.
Matplotlib: show labels for minor ticks also
Thanks!
Note: This plot is called Madau-Plot (see:adsabs[dot]harvard[dot]edu Madau (1998) DOI=10.1086/305523). It is common to plot it log-scales and show the z=0.0 value although the axis is log-scaled axis and log10(0)=Error. I definitely want to point out here that this is common use in my field but should not be applied one to one to any other plots. So actually the plot is made with a trick! You plot (1+z) [1,1.5,2,3,5,7,9]] and then translate the x-axis to the pure z-values 0.0 < z 8.0! So what I need to find is how to set xticks to the "translated" values ([0,0.5,1,2,4,6,8])
What if you plotted your datapoint corresponding to x=0 somewhere else, like at x=0.25, then relabel it. For example,
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
plot_vals = [0.25, 0.5, 1, 2, 4, 6, 8]
label_vals = [0, 0.5, 1, 2, 4, 6, 8]
ax.plot(plot_vals, plot_vals, 'k-o')
ax.set_xscale('log')
ax.set_xticks(plot_vals)
ax.set_xticklabels(label_vals) # relabeling the ticklabels
This yields what I think is what you want.
You can turn off minor ticks by doing something like:
ax.tick_params(axis='x', which='minor', bottom='off', top='off')
Edit: Given the edit to the op, this can be done easily by:
import matplotlib.pyplot as plt
original_values = [0, 0.5, 1, 2, 4, 6, 8]
# if using numpy:
# import numpy as np
# plot_values = np.array(original_values) + 1
# if using pure python
plot_values = [i + 1 for i in original_values]
fig, ax = plt.subplots()
ax.plot(plot_values, plot_values, 'k-o') #substitute actual plotting here
ax.set_xscale('log')
ax.set_xticks(plot_values)
ax.set_xticklabels(original_values)
which yields:

How to relabel axis ticks for a matplotlib heatmap

I am following this example to produce a heat map. Would it be possible to relabel the values over the X-axis, and add a constant to it.
Instead of 0, 1, 2, 3, 4 on the x-axis, I would like to have, for example, 5, 6, 7, 8, 9.
You can label the x and y axes by using the keyword argument extent in the calls to imshow. Here is some documentation,
extent : scalars (left, right, bottom, top), optional, default: None
Data limits for the axes. The default assigns zero-based row,
column indices to the `x`, `y` centers of the pixels.
working off the example you linked, you can do the following,
import matplotlib.pyplot as plt
import numpy as np
A = np.random.rand(5, 5)
plt.figure(1)
plt.imshow(A, interpolation='nearest')
plt.grid(True)
left = 4.5
right = 9.5
bottom = 4.5
top = -0.5
extent = [left, right, bottom, top]
plt.figure(2)
plt.imshow(A, interpolation='nearest', extent=extent)
plt.grid(True)
plt.show()
This will change only the x-axis labels. Note that you have to account for the fact that the default labels the pixels while extent labels the whole axis (hence the factors of 0.5). Also note that the default labelling of the y-axis in imshow increases from top to bottom (going from 0 at the top to 4 at the bottom), this means our bottom will be larger than our top variable.
You can simple add the constant via for-loop or list comprehension and use it as new axis label, for example:
import matplotlib.pyplot as plt
CONST = 10
x = range(10)
y = range(10)
labels = [i+CONST for i in x]
fig, ax = plt.subplots()
plt.plot(x, y)
plt.xlabel('x-value + 10')
# set ticks followed by setting labels
ax.set_xticks(range(10))
ax.set_xticklabels(labels)
plt.show()
I added it to my matplotlib gallery in IPython notebooks here with other examples if it is useful:
If you just want to relabel the current plot (click on it to select it) you use the xticks() function (Note the arange() upper limit needs to be one more than the desired maximum) - e.g. from iPython/Python:
xticks(arange(0,5),arange(5,10))
If you wanted to modify the python script file then you would use:
plt.xticks(arange(0,5),arange(5,10))

Categories