Related
I want to draw a plot with matplotlib with cylindrical symmetry. it means on both sides on X-axes positive (absolute values) numbers similar to this plot.
Plot that I get, but numbers on the left side of X-axes negative (that need to be positive too):
You are trying to change the format of the matplotlib.xaxis.Tick object label. In matplotlib there are usually a lot of ways to do things and this is no exception.
Matplotlib figures out where the ticks should be with a tick locator. Then a tick formatter defines what kind of text should be in those tick labels. The default formatter for numerical data is ScalarFormatter.
An alternative locator or formatter is easy to define with the matplotlib ticker library. Once you get the hang of this, its actually quite easy. Here is an example that shows how to fix your problem.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import ticker
# define fig and axes
fig, axes = plt.subplots(ncols=1, nrows=2, figsize=(17.5,6))
axes = axes.flatten() # make axes a 1D array
x = np.linspace(-20,20) # make some x values between -20 and 20
y = np.sin(x/4) # make some y values that are sin(x/4)
for ax in axes:
ax.plot(x, y, linewidth=2.5) # plot x and y values
#Make a function that makes negative values positive (lots of ways to do this)
def neg_to_pos(n, position):
n = int(n)
if n < 0:
return str(n * -1)
else:
return str(n)
#access the bottom plot object xaxis object and set the
#major tick formatter to be a FunctionFormatter that uses
#the neg_to_pos function
xaxis_formatter = axes[1].xaxis.set_major_formatter(ticker.FuncFormatter(neg_to_pos))
#add titles, x labels, and y labels
title_ax0 = axes[0].set_title('Default formatter', fontsize=14, fontweight='bold')
title_ax1 = axes[1].set_title('FuncFormatter', fontsize=14, fontweight='bold')
xlabels = [ax.set_xlabel('x_values') for ax in axes]
ylabels = [ax.set_ylabel('y_values') for ax in axes]
plt.subplots_adjust(hspace = 0.5) #fix the vertical spacing between the plots
The links I placed above for locators and formatters are bookmarked for me and I refer to them often when working on axis tick problems.
I'm trying to reverse the label and key columns in a matplotlib legend and I'm really struggling to even know where to start.
In a normal matplotlib legend the pattern is key, then label, like in the example below where it goes key (blue line), then label (First Line):
To match our company plotting style we plot things the reverse, i.e., label first then key (see the legend below). So the plot above would be First line, then the key (blue line).
The additional complication is that the keys should be in one column (so the align in one vertical column) regardless of the length of the label.
Well, there is the keyword markerfirst for this.
from matplotlib import pyplot as plt
import numpy as np
np.random.seed(1234)
n=7
fig, ax = plt.subplots()
ax.plot(np.arange(n), np.random.random(n), label="ABCDEF")
ax.plot(np.arange(n), np.random.random(n), label="G")
ax.legend(markerfirst=False)
plt.show()
Sample output
I would be tempted to write a standalone function that ignores ax.legend() entirely and instead draws a white box, the labels, and the markers where you need them. All the coordinates would be expressed in ax coordinates via transform=ax.transAxes to ensure a proper positioning and replace the locator keyword of ax.legend().
The following code will automatically cram all the artists found on the ax in the legend box boundaries that you defined. You might need to adjust the "padding" a bit.
Note that for some reason it does not work with lines of width 0 that only use a marker, but it shouldn't be an issue considering your question.
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
# Dummy data.
X = np.linspace(-5, +5, 100)
Y1 = np.sin(X)
Y2 = np.cos(X/3)
Y3 = Y2-Y1
Y4 = Y3*Y1
ax.plot(Y1, label="Y1")
ax.plot(Y2, label="Y2")
ax.plot(Y3, label="Y3", linestyle="--")
ax.plot(Y4, label="Y4", marker="d", markersize=4, linewidth=0)
fig.show()
def custom_legend(ax):
"""Adds a custom legend to the provided ax. Its labels are aligned
on the left and the markers on the right. Both are taken automatically
from the ax."""
handles, labels = ax.get_legend_handles_labels()
# Boundaries of your custom legend.
xmin, xmax = 0.7, 0.9
ymin, ymax = 0.5, 0.9
N = len(handles)
width = xmax-xmin
height = ymax-ymin
dy = height/N
r = plt.Rectangle((xmin, ymin),
width=width,
height=height,
transform=ax.transAxes,
fill=True,
facecolor="white",
edgecolor="black",
zorder=1000)
ax.add_artist(r)
# Grab the tiny lines that would be created by a call to `ax.legend()` so
# that we don't have to retrieve all the attributes ourselves.
legend = ax.legend()
handles = legend.legendHandles.copy()
legend.remove()
for n, (handle, label) in enumerate(zip(handles, labels)):
# Place the labels on the left of the legend box.
x = xmin + 0.01
y = ymax - n*dy - 0.05
ax.text(x, y, label, transform=ax.transAxes, va="center", ha="left", zorder=1001)
# Move a bit to the right and place the line artists.
x0 = (xmax - 1/2*width)
x1 = (xmax - 1/8*width)
y0, y1 = (y, y)
handle.set_data(((x0, x1), (y0, y1)))
handle.set_transform(ax.transAxes)
handle.set_zorder(1002)
ax.add_artist(handle)
custom_legend(ax)
fig.canvas.draw()
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)
I am trying to create a figure in which the colorbar will extend beyond the data range (go higher than the max value of data). The ultimate purpose is that I need to plot a series of images (as time progresses) of model output, and each hour is stored in a separate file. I would like the colorbar for all the figures to be the same, so that they can be joined into an animation.
Here is a sample script:
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(0, 360, 1.5)
y = np.arange(-90, 90, 1.5)
lon, lat = np.meshgrid(x, y)
noise = np.random.random(lon.shape) # values in range [0, 1)
fig = plt.figure()
ax = fig.add_subplot(111)
plt.hold(True)
plt.contourf(lon, lat, noise)
plt.colorbar()
This produces the following figure:
I've been trying to set the limits of the colorbar to values outside the data range (for example, from -1. to 2.) using two methods that I've found online:
Setting vmin=-1 and vmax=2 inside the plotting line:
fig = plt.figure()
ax = fig.add_subplot(111)
plt.hold(True)
plt.contourf(lon, lat, noise, vmin=-1., vmax=2.)
plt.colorbar()
This seems to only change the colors displayed, so that the first color in the colormap would correspond to -1 and the last one to 2, but it does not extend the colorbar to show those values (left figure in link below).
The other one was to try and enforce ticks in the colorbar to extend to that range:
fig = plt.figure()
ax = fig.add_subplot(111)
plt.hold(True)
plt.contourf(lon, lat, noise)
plt.colorbar(ticks=np.arange(-1,2.1, .2))
This results in tick position as defined, but only for the range in which there's data, i.e., the colorbar still doesn't extend from -1 to 2 (middle figure in link below).
Does anyone know how I would get it to do what I want? Something like the right figure at this link: http://orca.rsmas.miami.edu/~ajdas1/SOF/n.html
For most 2D plotting function (such as imshow, pcolor, etc.) setting vmin and vmax does the job. However, contourf (and also contour) take the levels at which you ask it to draw the contours into account when mapping the colors:
If you don't specify the levels argument, then the function automatically generates 10 equally spaced levels from the minimal to maximal value of your data. So to achieve what you want (consistency over varying input data) you have to specify the levels explicitly:
import matplotlib.pyplot as plt
import numpy as np
# generate data
x = np.arange(0, 360, 1.5)
y = np.arange(-90, 90, 1.5)
lon, lat = np.meshgrid(x, y)
noise = np.random.random(lon.shape)
# specify levels from vmim to vmax
levels = np.arange(-1, 2.1, 0.2)
# plot
fig = plt.figure()
ax = fig.add_subplot(111)
plt.contourf(lon, lat, noise, levels=levels)
plt.colorbar(ticks=levels)
plt.show()
Result:
Colorbar limits are not respecting set vmin/vmax in plt.contourf. How can I more explicitly set the colorbar limits? gives a good example to solve this problem.
These can be done if the colorbars of a series of images share a same ScalarMappable instance, but not the corresponding ContourSet instance which is created by each plt.contourf().
More details in https://matplotlib.org/3.2.1/api/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.colorbar
We can solve the problem like this:
import matplotlib.pyplot as plt
import matplotlib.cm as cm
fig = plt.figure()
ax = fig.add_subplot(111)
m0=ax.contourf(lon, lat, noise, vmin=-1., vmax=2.)
m = plt.cm.ScalarMappable(cmap=cm.coolwarm)
m.set_clim(-1, 2)
fig.colorbar(m,ax=ax)
Instead of using m0 (QuadContourSet instance created by contourf), we use m (ScalarMappable instance) in fig.colorbar(), because colorbar is used to describe the mappable parameter.
https://matplotlib.org/3.2.1/api/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.colorbar
clim in m.set_clim should be matched to vmin/vmax in contourf.
TL;DR -> How can one create a legend for a line graph in Matplotlib's PyPlot without creating any extra variables?
Please consider the graphing script below:
if __name__ == '__main__':
PyPlot.plot(total_lengths, sort_times_bubble, 'b-',
total_lengths, sort_times_ins, 'r-',
total_lengths, sort_times_merge_r, 'g+',
total_lengths, sort_times_merge_i, 'p-', )
PyPlot.title("Combined Statistics")
PyPlot.xlabel("Length of list (number)")
PyPlot.ylabel("Time taken (seconds)")
PyPlot.show()
As you can see, this is a very basic use of matplotlib's PyPlot. This ideally generates a graph like the one below:
Nothing special, I know. However, it is unclear what data is being plotted where (I'm trying to plot the data of some sorting algorithms, length against time taken, and I'd like to make sure people know which line is which). Thus, I need a legend, however, taking a look at the following example below(from the official site):
ax = subplot(1,1,1)
p1, = ax.plot([1,2,3], label="line 1")
p2, = ax.plot([3,2,1], label="line 2")
p3, = ax.plot([2,3,1], label="line 3")
handles, labels = ax.get_legend_handles_labels()
# reverse the order
ax.legend(handles[::-1], labels[::-1])
# or sort them by labels
import operator
hl = sorted(zip(handles, labels),
key=operator.itemgetter(1))
handles2, labels2 = zip(*hl)
ax.legend(handles2, labels2)
You will see that I need to create an extra variable ax. How can I add a legend to my graph without having to create this extra variable and retaining the simplicity of my current script?
Add a label= to each of your plot() calls, and then call legend(loc='upper left').
Consider this sample (tested with Python 3.8.0):
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 20, 1000)
y1 = np.sin(x)
y2 = np.cos(x)
plt.plot(x, y1, "-b", label="sine")
plt.plot(x, y2, "-r", label="cosine")
plt.legend(loc="upper left")
plt.ylim(-1.5, 2.0)
plt.show()
Slightly modified from this tutorial: http://jakevdp.github.io/mpl_tutorial/tutorial_pages/tut1.html
You can access the Axes instance (ax) with plt.gca(). In this case, you can use
plt.gca().legend()
You can do this either by using the label= keyword in each of your plt.plot() calls or by assigning your labels as a tuple or list within legend, as in this working example:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-0.75,1,100)
y0 = np.exp(2 + 3*x - 7*x**3)
y1 = 7-4*np.sin(4*x)
plt.plot(x,y0,x,y1)
plt.gca().legend(('y0','y1'))
plt.show()
However, if you need to access the Axes instance more that once, I do recommend saving it to the variable ax with
ax = plt.gca()
and then calling ax instead of plt.gca().
Here's an example to help you out ...
fig = plt.figure(figsize=(10,5))
ax = fig.add_subplot(111)
ax.set_title('ADR vs Rating (CS:GO)')
ax.scatter(x=data[:,0],y=data[:,1],label='Data')
plt.plot(data[:,0], m*data[:,0] + b,color='red',label='Our Fitting
Line')
ax.set_xlabel('ADR')
ax.set_ylabel('Rating')
ax.legend(loc='best')
plt.show()
You can add a custom legend documentation
first = [1, 2, 4, 5, 4]
second = [3, 4, 2, 2, 3]
plt.plot(first, 'g--', second, 'r--')
plt.legend(['First List', 'Second List'], loc='upper left')
plt.show()
A simple plot for sine and cosine curves with a legend.
Used matplotlib.pyplot
import math
import matplotlib.pyplot as plt
x=[]
for i in range(-314,314):
x.append(i/100)
ysin=[math.sin(i) for i in x]
ycos=[math.cos(i) for i in x]
plt.plot(x,ysin,label='sin(x)') #specify label for the corresponding curve
plt.plot(x,ycos,label='cos(x)')
plt.xticks([-3.14,-1.57,0,1.57,3.14],['-$\pi$','-$\pi$/2',0,'$\pi$/2','$\pi$'])
plt.legend()
plt.show()
Add labels to each argument in your plot call corresponding to the series it is graphing, i.e. label = "series 1"
Then simply add Pyplot.legend() to the bottom of your script and the legend will display these labels.