Prevent matplotlib to sort axis - python

Consider the following test case:
import numpy as np
import matplotlib.pyplot as plt
x1 = np.arange(10)
x2 = np.hstack((np.linspace(0.12312, 3.94, 5), np.linspace(-10.0121, -9.333411, 5)))
y = 2.0 * x1
fig = plt.figure(1)
ax = fig.gca()
ax.plot(x1, y)
fig = plt.figure(2)
ax = fig.gca()
ax.plot(x2, y)
fig = plt.figure(3)
ax = fig.gca()
ax.plot(x1, y)
ax.set_xticklabels(x2[ax.get_xticks().astype(int)])
plt.show()
I'd like to plot figure(2) with x2 original ordering. But when I use it with pyplot.plot it's automatically sorted and it messes up my axis.
I can manually set the ticklabels like in figure(3) but I don't get automatic axis scaling, formatting and so on.
I could probably hack something myself with formatters, locators etc, but first I'd like to ask: is there a proper way to achieve what I'd like?

I don't think it's possible with this kind of plot. You can manage it somehow, like you did it with figure(3) but it's totally against intuition or good practice. Normally if you are looking on a 2D plot you assume that each axis is a continuous scale (ascending or descending). It can be even logarithmic or other scale.

Related

Matplotlib: How can I show only exponents in the y tick labels of a semi-log plot with secondary_yaxis()?

I've been working on matplotlib's secondary-yaxis and I can't figure out how I should set "functions" parameter in order to get the result that I want.
I want to make a semi-log plot and set set the labels of y-ticks in the 2 following formats:
ordinary format such as "10^1, 10^2, 10^3, ..., 10^(exponent), ..."
the exponents only: "1, 2, 3, ..."
And I want to put them in the former style in the y-axis of left side, and the latter right side.
What I want to do can be done by using twinx() like this:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(1, 3, 41)
y = 10**x
fig, ax1 = plt.subplots()
ax1.set_yscale('log')
ax1.plot(x, y)
ax2 = ax1.twinx()
ymin, ymax = ax1.get_ylim()
ax2.set_ylim(np.log10(ymin), np.log10(ymax))
plt.show()
You would see that i=(1, 2, 3) in the right label is located at the same height as 10^i in the left label.
However, I want to know how to do the same thing by secondary_yaxis. I've tried this but it didn't work.
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(1, 3, 41)
y = 10**x
fig, ax = plt.subplots()
ax.set_yscale('log')
ax.plot(x, y)
def forward(x):
return np.log10(x)
def backward(x):
return 10**x
secax = ax.secondary_yaxis('right', functions=(forward, backward))
plt.show()
It resulted in this:
You can see right-side tick labels are broken. I suspect that my way of setting the parameter "functions" of secondary_yaxis() might be invalid. I would appreciate it if you tell me how to do it.
I get the broken figure on matplotlib 3.1.0. and updating it to 3.3.0. has solved the problem. The same code as the second code block of the question generates this.
enter image description here

Share scaling of differntly sized subplots' axes (not sharing axes)

With matplotlib, I want to plot two graphs with the same x-axis scale, but I want to show different sized sections. How can I accomplish that?
So far I can plot differently sized subplots with GridSpec or same sized ones who share the x-axis. When I try both at once, the smaller subplot has the same axis but smaller scaled, while I want the same scaling and a different axis, so sharing the axis might be a wrong idea.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
x=np.linspace(0,10,100)
y=np.sin(x)
x2=np.linspace(0,5,60)
y2=np.cos(x2)
fig=plt.figure()
gs=GridSpec(2,3)
ax1 = fig.add_subplot(gs[0, :])
ax1.plot(x,y)
ax2 = fig.add_subplot(gs[1,:-1])
#using sharex=ax1 here decreases the scaling of ax2 too much
ax2.plot(x2,y2)
plt.show()
I want the x.axes to have the same scaling, i.e. the same x values are always exactly on top of each other, this should give you an idea. The smaller plot's frame could be expanded or fit the plot, that doesn't matter. As it is now, the scales don't match.
Thanks in advance.
This is still a bit rough. I'm sure there's a slightly more elegant way to do this, but you can create a custom transformation (see Transformations Tutorial) between the Axes coordinates of ax2 and the data coordinates of ax1. In other word, your calculating what is the data-value (according to ax1) at the position corresponding to the left and right edges of ax2, and then adjust the xlim of ax2 accordingly.
Here is a demonstration showing that it works even if the second subplot is not aligned in any particular way with the first.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
x=np.linspace(0,25,100)
y=np.sin(x)
x2=np.linspace(10,30,60)
y2=np.cos(x2)
fig=plt.figure()
gs=GridSpec(2,6)
ax1 = fig.add_subplot(gs[0, :])
ax1.plot(x,y)
ax2 = fig.add_subplot(gs[1,3:-1])
ax2.plot(x2,y2)
# here is where the magic happens
trans = ax2.transAxes + ax1.transData.inverted()
((xmin,_),(xmax,_)) = trans.transform([[0,1],[1,1]])
ax2.set_xlim(xmin,xmax)
# for demonstration, show that the vertical lines end up aligned
for ax in [ax1,ax2]:
for pos in [15,20]:
ax.axvline(pos)
plt.show()
EDIT: One possible refinement would be to do the transform in the xlim_changed event callback. That way, the axes stay in sync even when zooming/panning in the first axes.
There is also a slight issue with tight_layout() as you noted, but that is easily fixed by calling the callback function directly.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
def on_xlim_changed(event):
# here is where the magic happens
trans = ax2.transAxes + ax1.transData.inverted()
((xmin, _), (xmax, _)) = trans.transform([[0, 1], [1, 1]])
ax2.set_xlim(xmin, xmax)
x = np.linspace(0, 25, 100)
y = np.sin(x)
x2 = np.linspace(10, 30, 60)
y2 = np.cos(x2)
fig = plt.figure()
gs = GridSpec(2, 6)
ax1 = fig.add_subplot(gs[0, :])
ax1.plot(x, y)
ax2 = fig.add_subplot(gs[1, 3:-1])
ax2.plot(x2, y2)
# for demonstration, show that the vertical lines end up aligned
for ax in [ax1, ax2]:
for pos in [15, 20]:
ax.axvline(pos)
# tight_layout() messes up the axes xlim
# but can be fixed by calling on_xlim_changed()
fig.tight_layout()
on_xlim_changed(None)
ax1.callbacks.connect('xlim_changed', on_xlim_changed)
plt.show()
I suggest setting limits of the second axis based on the limits of ax1.
Try this!
ax2 = fig.add_subplot(gs[1,:-1])
ax2.plot(x2,y2)
lb, ub = ax1.get_xlim()
# Default margin is 0.05, which would be used for auto-scaling, hence reduce that here
# Set lower bound and upper bound based on the grid size, which you choose for second plot
ax2.set_xlim(lb, ub *(2/3) -0.5)
plt.show()

matplotlib autoscale with respect to a specific plot [duplicate]

I use matplotib's Axes API to plot some figures. One of the lines I plot represents the theoretical expected line. It has no meaning outside of the original y and x limits. What I want, is for matlplotlib to ignore it when autoscaling the limits. What I used to do, is to check what are the current limits, then plot, and reset the limits. The problem is that when I plot a third plot, the limits get recalculated together with the theoretical line, and that really expands the graph.
# Boilerplate
from matplotlib.figure import Figure
from matplotlib.backends.backend_pdf import FigureCanvasPdf
from numpy import sin, linspace
fig = Figure()
ax = fig.add_subplot(1,1,1)
x1 = linspace(-1,1,100)
ax.plot(x1, sin(x1))
ax.plot(x1, 3*sin(x1))
# I wish matplotlib would not consider the second plot when rescaling
ax.plot(x1, sin(x1/2.0))
# But would consider the first and last
canvas_pdf = FigureCanvasPdf(fig)
canvas_pdf.print_figure("test.pdf")
The obvious way is to just manually set the limits to what you want. (e.g. ax.axis([xmin, xmax, ymin, ymax]))
If you don't want to bother with finding out the limits manually, you have a couple of options...
As several people (tillsten, Yann, and Vorticity) have mentioned, if you can plot the function you want to ignore last, then you can disable autoscaling before plotting it or pass the scaley=False kwarg to plot
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
ax.plot(x1, np.sin(x1))
ax.plot(x1, np.sin(x1 / 2.0))
ax.autoscale(False) #You could skip this line and use scalex=False on
ax.plot(x1, 3 * np.sin(x1)) #the "theoretical" plot. It has to be last either way
fig.savefig('test.pdf')
Note that you can adjust the zorder of the last plot so that it's drawn in the "middle", if you want control over that.
If you don't want to depend on the order, and you do want to just specify a list of lines to autoscale based on, then you could do something like this: (Note: This is a simplified version assuming you're dealing with Line2D objects, rather than matplotlib artists in general.)
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.transforms as mtransforms
def main():
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
line1, = ax.plot(x1, np.sin(x1))
line2, = ax.plot(x1, 3 * np.sin(x1))
line3, = ax.plot(x1, np.sin(x1 / 2.0))
autoscale_based_on(ax, [line1, line3])
plt.show()
def autoscale_based_on(ax, lines):
ax.dataLim = mtransforms.Bbox.unit()
for line in lines:
xy = np.vstack(line.get_data()).T
ax.dataLim.update_from_data_xy(xy, ignore=False)
ax.autoscale_view()
if __name__ == '__main__':
main()
Use the scalex/scaley kw arg:
plot(x1, 3*sin(x1), scaley=False)
LineCollection objects can be ignored by using the autolim=False argument:
from matplotlib.collections import LineCollection
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
# Will update limits
ax.plot(x1, np.sin(x1))
# Will not update limits
col = LineCollection([np.column_stack((x1, 3 * np.sin(x1)))], colors='g')
ax.add_collection(col, autolim=False)
# Will still update limits
ax.plot(x1, np.sin(x1 / 2.0))
This can be done regardless of plotting order by creating another axes to work on.
In this version, we create a twin axes and disable the autoscaling on that twin axes. In this way, the plot is scaled based on anything plotted in the original axes, but is not scaled by anything put into the twin axes.
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
twin_ax = ax.twinx() # Create a twin axes.
twin_ax.autoscale(False) # Turn off autoscaling on the twin axes.
twin_ax.set_yticks([]) # Remove the extra tick numbers from the twin axis.
ax.plot(x1, np.sin(x1))
twin_ax.plot(x1, 3 * np.sin(x1), c='green') # Plotting the thing we don't want to scale on in the twin axes.
ax.plot(x1, np.sin(x1 / 2.0))
twin_ax.set_ylim(ax.get_ylim()) # Make sure the y limits of the twin matches the autoscaled of the original.
fig.savefig('test.pdf')
Note, the above only prevents the un-twined axis from auto scaling (y in the above case). To get it to work for both x and y, we can do the twinning process for both x and y (or create the new axes from scratch):
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
x2 = np.linspace(-2,2,100) # Would extend the x limits if auto scaled
twin_ax = ax.twinx().twiny() # Create a twin axes.
twin_ax.autoscale(False) # Turn off autoscaling on the twin axes.
twin_ax.set_yticks([]) # Remove the extra tick numbers from the twin axis.
twin_ax.set_xticks([]) # Remove the extra tick numbers from the twin axis.
ax.plot(x1, np.sin(x1))
twin_ax.plot(x2, 3 * np.sin(x2), c='green') # Plotting the thing we don't want to scale on in the twin axes.
ax.plot(x1, np.sin(x1 / 2.0))
twin_ax.set_ylim(ax.get_ylim()) # Make sure the y limits of the twin matches the autoscaled of the original.
twin_ax.set_xlim(ax.get_xlim()) # Make sure the x limits of the twin matches the autoscaled of the original.
fig.savefig('test.png')
As a generalisation of jam's answer, a collection object can be obtained from any of matplotlib's plotting functions and then re-added with autolim=False. For example,
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
# Get hold of collection
collection = ax.plot(x1, np.sin(x1))
# Remove collection from the plot
collection.remove()
# Rescale
ax.relim()
# Add the collection without autoscaling
ax.add_collection(collection, autolim=False)

How to update barchart in matplotlib?

I have bar chart, with a lot of custom properties ( label, linewidth, edgecolor)
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.gca()
x = np.arange(5)
y = np.random.rand(5)
bars = ax.bar(x, y, color='grey', linewidth=4.0)
ax.cla()
x2 = np.arange(10)
y2 = np.random.rand(10)
ax.bar(x2,y2)
plt.show()
With 'normal' plots I'd use set_data(), but with barchart I got an error: AttributeError: 'BarContainer' object has no attribute 'set_data'
I don't want to simply update the heights of the rectangles, I want to plot totally new rectangles. If I use ax.cla(), all my settings (linewidth, edgecolor, title..) are lost too not only my data(rectangles), and to clear many times, and reset everything makes my program laggy. If I don't use ax.cla(), the settings remain, the program is faster (I don't have to set my properties all the time), but the rectangles are drawn of each other, which is not good.
Can you help me with that?
In your case, bars is only a BarContainer, which is basically a list of Rectangle patches. To just remove those while keeping all other properties of ax, you can loop over the bars container and call remove on all its entries or as ImportanceOfBeingErnest pointed out simply remove the full container:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.gca()
x = np.arange(5)
y = np.random.rand(5)
bars = ax.bar(x, y, color='grey', linewidth=4.0)
bars.remove()
x2 = np.arange(10)
y2 = np.random.rand(10)
ax.bar(x2,y2)
plt.show()

How to make a non-square contourf-plot

I'm using contourf in pyplot to plot some scalar data, but when my domain is non-square i feel like the data is misrepresented because it always plots it in a square (though the axis values will increase faster on one side.) How can i force the axis scaling to be equal, such that if my domain is twice as long in the x-direction the image is actually plotted in a rectangle with this property?
I'm doing something like this:
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
contour = ax.contourf(X,Y,Z)
fig.colorbar(contour)
fig.canvas.draw()
Using ax.set_aspect:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111)
x=np.r_[-10:10:100j]
y=np.r_[-20:20:100j]
z= np.add.outer(x*x, y*y)
contour=ax.contour(x,y,z)
fig.colorbar(contour)
ax.set_aspect('equal')
# ax.axis('equal')
plt.show()
yields
while changing ax.set_aspect('equal') to
ax.axis('equal')
yields
This might help:
ax = fig.add_subplot(111, aspect="equal")
You need to change the axis setting:
axis('equal')
See all of the axis settings here:
http://matplotlib.sourceforge.net/api/pyplot_api.html#matplotlib.pyplot.axis

Categories

Resources