Data structure for plotting disconnected lines between multiple points in Python - 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.

Related

How to stack multiple graphs

I have a function that takes a dictionary (key: string, values: 2D numbers) and displays the values of the dictionary in a graph (one color per key).
I would like to use this function directly to display in the same page four graphs, each graph corresponding to a particular dictionary
if possible I would like to have a script like :
def my_function(dico):
display picture
def global_function(multiple):
plt.subplot(1,2,1)
my_function(multiple[0])
plt.subplot(1,2,2)
my_function(multiple[1])
You can plot these graphs as subplots to one figure.
import numpy as np
import matplotlib.pyplot as plt
#plotting function
def plot_2_subplots (x1, y1, x2, y2):
f, (ax1, ax2) = plt.subplots(1, 2, sharey=True)
ax1.plot(x1, y1)
ax1.set_title('Plot 1')
ax2.plot(x2, y2)
ax2.set_title('Plot 2')
lists_a = sorted(d.items()) # return a list of tuples (sorted by key)
x1, y1 = zip(*lists_a) # unpack into tuple
lists_b = sorted(d.items()) # return a list of tuples (sorted by key)
x2, y2 = zip(*lists_b) # unpack into tuple
#Call plotting function
plot_2_subplots(x1, y1, x2, y2)
This generates this figure:

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='.')

One colorbar for multiple scatter plots

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)

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)

Data interpolation in python

I have four one dimensional lists: X1, Y1, X2, Y2.
X1 and Y1 each have 203 data points.
X2 and Y2 each have 1532 data points.
X1 and X2 are at different intervals, but both measure time.
I want to graph Y1 vs Y2.
I can plot just fine once I get the interpolated data, but can't think of how to interpolate data. I've thought and researched this a couple hours, and just can't figure it out. I don't mind a linear interpolation, but just can't figure out a way.
I think this is what you want:
import numpy as np
import matplotlib.pyplot as plt
# first data set
X1 = np.linspace(0,1,203)
Y1 = np.sin(X1)
# second data set
X2 = np.linspace(0, 0.5, 1532)
Y2 = np.cos(X2)
# get interpolated values of Y1 evaluated at X2
Y1_interp = np.interp(X2, X1, Y1)
# plot interpolated Y1 vs Y2
plt.plot(Y1_interp, Y2)
plt.show()
If you use matplotlib, you can just call plot(X1, Y1, 'bo', X2, Y2, 'r+'). Change the formatting as you'd like, but it can cope with different lengths just fine. You can provide more than two without any issue.

Categories