One colorbar for multiple scatter plots - python

I'm plotting multiple figures of the same variable on one plot using matplotlib library. I'm not looking for a colorbar for subplots, which is the dominant search material. I plot multiple scatters, but the colorbar is only set to the values of the last scatter I plot.
Here is the part of the code:
plt.scatter(x1, y1, c=z1,cmap='viridis_r',marker='s')
plt.scatter(x2, y2, c=z2,cmap='viridis_r',marker='o')
plt.scatter(x3, y3, c=z3,cmap='viridis_r',marker='^')
plt.colorbar().set_label('Wind speed',rotation=270)

It requires a bit of extra work:
You have to get the minimum and maximum of the cs (the colorbar values)
You have to clim each scatter plot
First the minimum and maximum:
zs = np.concatenate([z1, z2, z3], axis=0)
min_, max_ = zs.min(), zs.max()
Then the scatter plots with clim:
plt.scatter(x1, y1, c=z1,cmap='viridis_r',marker='s')
plt.clim(min_, max_)
plt.scatter(x2, y2, c=z2,cmap='viridis_r',marker='o')
plt.clim(min_, max_)
plt.scatter(x3, y3, c=z3,cmap='viridis_r',marker='^')
plt.clim(min_, max_)
plt.colorbar().set_label('Wind speed',rotation=270)
For a very simple dataset:
x1, x2, x3 = [1,2,3], [2,3,4], [3,4,5]
y1 = y2 = y3 = [1, 2, 3]
z1, z2, z3 = [1,2,3], [4,5,6], [7,8,9]

scatter has a norm argument. Using the same norm for all scatters ensures that the colorbar produced by any of the plots (hence also the last) is the same for all scatter plots.
The norm can be a Normalize instance, to which minimum and maximum value are set and which produces a linear scaling in between. Of course you can also use any other norm provided in matplotlib.colors like PowerNorm, LogNorm, etc.
mini, maxi = 0, 2 # or use different method to determine the minimum and maximum to use
norm = plt.Normalize(mini, maxi)
plt.scatter(x1, y1, c=z1,cmap='viridis_r',marker='s', norm=norm)
plt.scatter(x2, y2, c=z2,cmap='viridis_r',marker='o', norm=norm)
plt.scatter(x3, y3, c=z3,cmap='viridis_r',marker='^', norm=norm)
plt.colorbar().set_label('Wind speed',rotation=270)

Related

Stacked histogram with different histtype

I'd like to plot different data sets in an stacked histogram, but I want the data on top to have a step type.
I have done this one by splitting the data, first two sets in an stacked histogram and a sum of all the data sets in a different step histogram. Here is the code and plot
mu, sigma = 100, 10
x1 = list(mu + sigma*np.random.uniform(1,100,100))
x2 = list(mu + sigma*np.random.uniform(1,100,100))
x3 = list(mu + sigma*np.random.uniform(1,100,100))
plt.hist([x1, x2], bins=20, stacked=True, histtype='stepfilled', color=['green', 'red'], zorder=2)
plt.hist(x1+x2+x3, bins=20, histtype='step', ec='dodgerblue', ls='--', linewidth=3., zorder=1)
The problem with this example are the borders of the 'step' histogram that are wider than the width of the 'stepfilled' histogram. Any way of fixing this?
For the bars to coincide, two issues need to be solved:
The bin boundaries for the histograms should be exactly equal. They can be calculated dividing the distance from the overall minimum to maximum in N+1 equal parts. Both calls to plt.hist need the same bin boundaries.
The thick edge of the 'step' histogram makes the bars wider. Therefore, the other histogram needs edges of the same width. plt.hist doesn't seem to accept a list of colors for the different parts of the stacked histogram, so a fixed color needs to be set. Optionally, the edge color can be changed afterwards looping through the generated bars.
from matplotlib import pyplot as plt
import numpy as np
mu, sigma = 100, 10
x1 = mu + sigma * np.random.uniform(1, 100, 100)
x2 = mu + sigma * np.random.uniform(1, 100, 100)
x3 = mu + sigma * np.random.uniform(1, 100, 100)
xmin = np.min([x1, x2, x3])
xmax = np.max([x1, x2, x3])
bins = np.linspace(xmin, xmax, 21)
_, _, barlist = plt.hist([x1, x2], bins=bins, stacked=True, histtype='stepfilled',
color=['limegreen', 'crimson'], ec='black', linewidth=3, zorder=2)
plt.hist(np.concatenate([x1, x2, x3]), bins=bins, histtype='step',
ec='dodgerblue', ls='--', linewidth=3, zorder=1)
for bars in barlist:
for bar in bars:
bar.set_edgecolor(bar.get_facecolor())
plt.show()
This is how it would look like with cross-hatching (plt.hist(..., hatch='X')) and black edges:

2D plot xy-lists of different length

I have an arbitrary, large number (50-1000) of lists, representing X and Y coordinates, I'd like to plot them in one figure.
The lists are of different length, usually 100-1000 elements each. I get the lists as pairs of x and y coordinates (see example), but could easily convert them to 2xN arrays. They need to be plotted in order, from first to the last element. Each line separately.
Is there a way to pack all my lists to one (or two; x and y) object that matplotlib can read?
This example gives the wanted output but is unhandy when there is a lot of data.
I'm happy for a solution that takes advantage of numpy.
from matplotlib import pyplot as plt
fig, ax = plt.subplots(1,1)
x1 = [1,2,5] # usually much longer and a larger number of lists
y1 = [3,2,4]
x2 = [1,6,5,3]
y2 = [7,6,3,2]
x3 = [4]
y3 = [4]
for x, y, in zip([x1, x2, x3],[y1, y2, y3]):
ax.plot(x,y, 'k.-')
plt.show()
I would prefer something like this:
# f() is the function i need, to formats the data for plotting
X = f(x1, x2, x3)
Y = f(y1, y2, y3)
#... so that I can do some processing of the arrays X, and Y here.
ax.plot(X, Y, 'k.-')
You can use a LineCollection for that. Unfortunately, if you want to have markers in your lines, LineCollection does not support that, so you would need to do some trick like adding a scatter plot on top (see Adding line markers when using LineCollection).
from matplotlib import pyplot as plt
from matplotlib.collections import LineCollection
fig, ax = plt.subplots(1,1)
x1 = [1,2,5]
y1 = [3,2,4]
x2 = [1,6,5,3]
y2 = [7,6,3,2]
x3 = [4]
y3 = [4]
# Add lines
X = [x1, x2, x3]
Y = [y1, y2, y3]
lines = LineCollection((list(zip(x, y)) for x, y in zip(X, Y)),
colors='k', linestyles='-')
ax.add_collection(lines)
# Add markers
ax.scatter([x for xs in X for x in xs], [y for ys in Y for y in ys], c='k', marker='.')
# If you do not use the scatter plot you need to manually autoscale,
# as adding the line collection will not do it for you
ax.autoscale()
plt.show()
If you are working with arrays, you may also do as follows:
import numpy as np
# ...
X = [x1, x2, x3]
Y = [y1, y2, y3]
lines = LineCollection((np.stack([x, y], axis=1) for x, y in zip(X, Y)),
colors='k', linestyles='-')
ax.add_collection(lines)
ax.scatter(np.concatenate(X), np.concatenate(Y), c='k', marker='.')

Data structure for plotting disconnected lines between multiple points in Python

I'm looking for the correct data structure to use for the following. I want to plot a line between two points, for a multiple set of points.
For example, to plot a line between (-1, 1) and (12, 4) I do this:
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1,1)
x1 = [-1, 12]
y1 = [1, 4]
ax.plot( x1, y1 )
plt.show()
If I want to plot another line connecting two different points (unrelated to the first set of points) I do this:
x2 = [1, 10]
y2 = [3, 2]
ax.plot( x1, y1, x2, y2 )
plot.show()
How do I extend this? That is, what data structure should I use to represent a growing array of such points [x1, y1, x2, y2, ...] generated by my input data?
I have tried the following,
points = [x1, y1, x2, y2]
ax.plot( points )
But the plot ends up connecting all of the individual lines with lines I do not want.
You are close:
ax.plot(*points)
The asterisk operator * converts an iterable (the list in your case) into a sequence of function parameters.

How to draw a line with matplotlib?

I cannot find a way to draw an arbitrary line with matplotlib Python library. It allows to draw horizontal and vertical lines (with matplotlib.pyplot.axhline and matplotlib.pyplot.axvline, for example), but i do not see how to draw a line through two given points (x1, y1) and (x2, y2). Is there a way? Is there a simple way?
This will draw a line that passes through the points (-1, 1) and (12, 4), and another one that passes through the points (1, 3) et (10, 2)
x1 are the x coordinates of the points for the first line, y1 are the y coordinates for the same -- the elements in x1 and y1 must be in sequence.
x2 and y2 are the same for the other line.
import matplotlib.pyplot as plt
x1, y1 = [-1, 12], [1, 4]
x2, y2 = [1, 10], [3, 2]
plt.plot(x1, y1, x2, y2, marker = 'o')
plt.show()
I suggest you spend some time reading / studying the basic tutorials found on the very rich matplotlib website to familiarize yourself with the library.
What if I don't want line segments?
[edit]:
As shown by #thomaskeefe, starting with matplotlib 3.3, this is now builtin as a convenience: plt.axline((x1, y1), (x2, y2)), rendering the following obsolete.
There are no direct ways to have lines extend to infinity... matplotlib will either resize/rescale the plot so that the furthest point will be on the boundary and the other inside, drawing line segments in effect; or you must choose points outside of the boundary of the surface you want to set visible, and set limits for the x and y axis.
As follows:
import matplotlib.pyplot as plt
x1, y1 = [-1, 12], [1, 10]
x2, y2 = [-1, 10], [3, -1]
plt.xlim(0, 8), plt.ylim(-2, 8)
plt.plot(x1, y1, x2, y2, marker = 'o')
plt.show()
As of matplotlib 3.3, you can do this with plt.axline((x1, y1), (x2, y2)).
I was checking how ax.axvline does work, and I've written a small function that resembles part of its idea:
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
def newline(p1, p2):
ax = plt.gca()
xmin, xmax = ax.get_xbound()
if(p2[0] == p1[0]):
xmin = xmax = p1[0]
ymin, ymax = ax.get_ybound()
else:
ymax = p1[1]+(p2[1]-p1[1])/(p2[0]-p1[0])*(xmax-p1[0])
ymin = p1[1]+(p2[1]-p1[1])/(p2[0]-p1[0])*(xmin-p1[0])
l = mlines.Line2D([xmin,xmax], [ymin,ymax])
ax.add_line(l)
return l
So, if you run the following code you will realize how does it work. The line will span the full range of your plot (independently on how big it is), and the creation of the line doesn't rely on any data point within the axis, but only in two fixed points that you need to specify.
import numpy as np
x = np.linspace(0,10)
y = x**2
p1 = [1,20]
p2 = [6,70]
plt.plot(x, y)
newline(p1,p2)
plt.show()
Just want to mention another option here.
You can compute the coefficients using numpy.polyfit(), and feed the coefficients to numpy.poly1d(). This function can construct polynomials using the coefficients, you can find more examples here
https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.poly1d.html
Let's say, given two data points (-0.3, -0.5) and (0.8, 0.8)
import numpy as np
import matplotlib.pyplot as plt
# compute coefficients
coefficients = np.polyfit([-0.3, 0.8], [-0.5, 0.8], 1)
# create a polynomial object with the coefficients
polynomial = np.poly1d(coefficients)
# for the line to extend beyond the two points,
# create the linespace using the min and max of the x_lim
# I'm using -1 and 1 here
x_axis = np.linspace(-1, 1)
# compute the y for each x using the polynomial
y_axis = polynomial(x_axis)
fig = plt.figure()
axes = fig.add_axes([0.1, 0.1, 1, 1])
axes.set_xlim(-1, 1)
axes.set_ylim(-1, 1)
axes.plot(x_axis, y_axis)
axes.plot(-0.3, -0.5, 0.8, 0.8, marker='o', color='red')
Hope it helps.
In case somebody lands here trying to plot many segments in one go, here is a way. Say the segments are defined by two 2-d arrays of same length, e.g. a and b. We want to plot segments between each a[i] and b[i]. In that case:
Solution 1
ab_pairs = np.c_[a, b]
plt_args = ab_pairs.reshape(-1, 2, 2).swapaxes(1, 2).reshape(-1, 2)
ax.plot(*plt_args, ...)
Example:
np.random.seed(0)
n = 32
a = np.random.uniform(0, 1, (n, 2))
b = np.random.uniform(0, 1, (n, 2))
fig, ax = plt.subplots(figsize=(3, 3))
ab_pairs = np.c_[a, b]
ab_args = ab_pairs.reshape(-1, 2, 2).swapaxes(1, 2).reshape(-1, 2)
# segments
ax.plot(*ab_args, c='k')
# identify points: a in blue, b in red
ax.plot(*a.T, 'bo')
ax.plot(*b.T, 'ro')
plt.show()
Solution 2
The above creates many matplotlib.lines.Line2D. If you'd like a single line, we can do it by interleaving NaN between pairs:
ax.plot(*np.c_[a, b, a*np.nan].reshape(-1, 2).T, ...)
Example:
# same init as example above, then
fig, ax = plt.subplots(figsize=(3, 3))
# segments (all at once)
ax.plot(*np.c_[a, b, a*np.nan].reshape(-1, 2).T, 'k')
# identify points: a in blue, b in red
ax.plot(*a.T, 'bo')
ax.plot(*b.T, 'ro')
plt.show()
(Same figure as above).
Based on #Alejandro's answer:
if you want to add a line to an existing Axes (e.g. a scatter plot), and
all you know is the slope and intercept of the desired line (e.g. a regression line), and
you want it to cover the entire visible X range (already computed), and
you want to use the object-oriented interface (not pyplot).
Then you can do this (existing Axes in ax):
# e.g. slope, intercept, r_value, p_value, std_err = scipy.stats.linregress(xs, ys)
xmin, xmax = ax.get_xbound()
ymin = (xmin * slope) + intercept
ymax = (xmax * slope) + intercept
l = matplotlib.lines.Line2D([xmin, xmax], [ymin, ymax])
ax.add_line(l)

How can I create a single x axis label and y axis label for mutiple subplots?

I have this code:
pyplot.figure()
pyplot.suptitle('Finding the roots of the equation: z^4 - 1 = 0')
ticks = [-2.0, -1.0, 0.0, 1.0, 2.0]
pyplot.subplot(221)
pyplot.imshow(roots, extent=(x0, x1, y0, y1))
pyplot.xticks(ticks)
pyplot.yticks(ticks)
pyplot.subplot(222)
pyplot.imshow(iterations, extent=(x0, x1, y0, y1))
pyplot.colorbar(orientation='vertical')
pyplot.xticks(ticks)
pyplot.yticks(ticks)
pyplot.subplot(223)
pyplot.imshow(roots_zoom, extent=(x2, x3, y2, y3))
pyplot.subplot(224)
pyplot.imshow(iterations_zoom, extent=(x2, x3, y2, y3))
pyplot.colorbar(orientation='vertical')
pyplot.show()
I'm plotting four arrays of numbers as images in a 2x2 figure of subplots. I want the same x label and y label for each. How do I create one x label that is centered below the two bottom plots and one y label that is centered to the left of the left two plots?
Also, how do I change the specified tick marks to floats? I've created them as [-2.0 ... 2.0] but they are shown in the figure as -2, -1, 0 etc.
Probably the easiest way to do this is with Figure.text
from matplotlib import pyplot as plt
fig = plt.gcf()
fig.text(.5, .1, 'xlabel', ha='center')
fig.text(.1, .5, 'ylabel', ha='center', rotation='vertical')
You will need to tweak the location of the text to put it exactly where you want.
You will want to look into Formatters to control how your tick labels are displayed. You can get one decimal place floats with
ax = plt.gca()
ax.xaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter('%.1f'))

Categories