Find intersection between parametric spline curve and line - numpy

I am fitting a parametric spline curve(t) from a bunch of (x, y) sampling points. How do I compute the intersection point with a line given by slope and one point? In my special case the spline intersects with the line once or not at all but never multiple times.
Here's the code for spline & line...
import matplotlib.pyplot as plt
import numpy as np
from scipy import interpolate
# Fit spline from points
x = np.array([152, 200, 255, 306, 356, 407, 457, 507, 561, 611, 661, 711, 761, 811, 861])
y = np.array([225, 227, 229, 229, 228, 226, 224, 222, 218, 215, 213, 212, 212, 215, 224])
tck, u = interpolate.splprep((x, y), k=3, s=1)
# Plot it...
u = np.linspace(0, 1, 100)
xy = np.asarray(interpolate.splev(u, tck, der=0))
plt.plot(*xy)
# Line defined by slope and (x, y) point
m = 3
(x, y) = (500, 100)
# Plot it...
x_vals = np.array([400, 700])
y_vals = m * (x_vals - x) + y
plt.plot(x_vals, y_vals)
plt.show()
... which looks like this:

Add the following lines
from scipy.interpolate import interp1d
spline = interp1d(xy[0], xy[1]) # define function based on spline data points
line = interp1d(x_vals, y_vals) # define function based on line data points
import scipy.optimize as spopt
f = lambda x: spline(x) - line(x) # difference function, its zero marks the intersection
r = spopt.bisect(f, a = max(xy[0][0], x_vals[0]), b = min(xy[0][-1], x_vals[-1])) # find root via bisection
plt.scatter(r, spline(r))
print(r, spline(r))
plt.show()
First define functions for your spline and line based on its data. The root of the difference function f marks your intersection. Since there is exactly one, bisection works nicely to find it.
It would probably be more accurate to somehow re-use splev to define the function for the spline, but I'll leave that one to you.

Related

Cannot use custom non linear colormap in combination with imshow

I am trying to use a custom colormap to display a ConfusionMatrixDisplay object to have a finer range between 0 and 50 than between 50 and 100 using this answer.
from sklearn.datasets import make_classification
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from matplotlib.colors import LinearSegmentedColormap
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams["figure.figsize"] = (15, 15)
font = {'family' : 'DejaVu Sans',
'weight' : 'bold',
'size' : 22}
plt.rc('font', **font)
class nlcmap(LinearSegmentedColormap):
def __init__(self, cmap, levels):
self.cmap = cmap
self.N = cmap.N
self.monochrome = self.cmap.monochrome
self.levels = np.asarray(levels, dtype='float64')
self._x = self.levels
self.levmax = self.levels.max()
self.transformed_levels = np.linspace(0.0, self.levmax, len(self.levels))
def __call__(self, xi, alpha=1.0, **kw):
yi = np.interp(xi, self._x, self.transformed_levels)
return self.cmap(yi / self.levmax, alpha)
levels = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 100]
cmap_nonlin = nlcmap(plt.cm.viridis, levels)
X, y = make_classification(random_state=0)
X_train, X_test, y_train, y_test = train_test_split(X, y,
random_state=0)
clf = SVC(random_state=0)
clf.fit(X_train, y_train)
SVC(random_state=0)
predictions = clf.predict(X_test)
cm = confusion_matrix(y_test, predictions, labels=clf.classes_)
disp = ConfusionMatrixDisplay(confusion_matrix=cm,
display_labels=clf.classes_)
lin_cmap = plt.cm.viridis
levels = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 100]
cmap_nonlin = nlcmap(plt.cm.viridis, levels)
fig, ax = plt.subplots()
im = disp.plot(cmap=cmap_nonlin, colorbar=False)
disp.ax_.get_images()[0].set_clim(0, 100)
disp.figure_.colorbar(disp.im_, orientation="horizontal", pad=0.1)
plt.savefig("test.png")
Produces the following error:
Traceback (most recent call last):
File "/Users/me/anaconda3/envs/myenv/lib/python3.6/site-packages/matplotlib/backends/backend_macosx.py", line 61, in _draw
self.figure.draw(renderer)
File "/Users/me/anaconda3/envs/myenv/lib/python3.6/site-packages/matplotlib/artist.py", line 41, in draw_wrapper
return draw(artist, renderer, *args, **kwargs)
File "/Users/me/anaconda3/envs/myenv/lib/python3.6/site-packages/matplotlib/figure.py", line 1864, in draw
renderer, self, artists, self.suppressComposite)
File "/Users/me/anaconda3/envs/myenv/lib/python3.6/site-packages/matplotlib/image.py", line 131, in _draw_list_compositing_images
a.draw(renderer)
File "/Users/me/anaconda3/envs/myenv/lib/python3.6/site-packages/matplotlib/artist.py", line 41, in draw_wrapper
return draw(artist, renderer, *args, **kwargs)
File "/Users/me/anaconda3/envs/myenv/lib/python3.6/site-packages/matplotlib/cbook/deprecation.py", line 411, in wrapper
return func(*inner_args, **inner_kwargs)
File "/Users/me/anaconda3/envs/myenv/lib/python3.6/site-packages/matplotlib/axes/_base.py", line 2747, in draw
mimage._draw_list_compositing_images(renderer, self, artists)
File "/Users/me/anaconda3/envs/myenv/lib/python3.6/site-packages/matplotlib/image.py", line 131, in _draw_list_compositing_images
a.draw(renderer)
File "/Users/me/anaconda3/envs/myenv/lib/python3.6/site-packages/matplotlib/artist.py", line 41, in draw_wrapper
return draw(artist, renderer, *args, **kwargs)
File "/Users/me/anaconda3/envs/myenv/lib/python3.6/site-packages/matplotlib/image.py", line 646, in draw
renderer.draw_image(gc, l, b, im)
TypeError: Cannot cast array data from dtype('float64') to dtype('uint8') according to the rule 'safe'
It seems the error is related to imshow in conjunction with custom colormap since I can reproduce without sklearn with:
fig, ax = plt.subplots()
ax.imshow(np.array([[10, 15], [20, 30]]), cmap=cmap_nonlin)
Any idea ? I wish to modify the colormap not the data itself if possible.
According to matplotlib's doc on LinearSegmentedColormaps one can do the following to vary the contrast between segments with fast varying segment and slow varying segments.
In this case to answer my question let's have a finer range between 0 and 50 than between 50 and 100 but my solution can be extended to an arbitrary number of different paced segments by changing the levels:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.colors as colors
# A dict with {percentage_of_max_value: percentage_of_variation}. The keys are thus all < 1. and should be in ascending order alongside associated values in the colormap (also ordered and < 1.).
# In this example we have 90% of the variation of the colormap in its first half (until 0.5) and the remaining 10% in its right half
levels = {0.5: 0.9}
# We are not limited to one segment and we can provide for instance the following dict
# levels = {0.4:0.8, 0.5:0.9} to have 80% of variations between 0 and 40% of the colormap max then 10% between 40 and 50% and then the remaining 10% for the rest
cdict = {"red": None, "green": None, "blue": None}
num_values_per_segment = 50
for k, v in cdict.items():
cdict[k] = []
# We start the first segment by 0. both for value and cmap_value
left_val = 0.
left_cmap_val = 0.
for val, cmap_val in levels.items():
values = np.linspace(left_val, val, num_values_per_segment).tolist()
dynamic_range = np.linspace(left_cmap_val, cmap_val, num_values_per_segment).tolist()
for i, (v, r) in enumerate(zip(values, dynamic_range)):
cdict[k].append((v, r, r))
left_val = val
left_cmap_val = cmap_val
# Last segment towards 1.
values = np.linspace(val, 1., num_values_per_segment).tolist()
dynamic_range = np.linspace(cmap_val, 1., num_values_per_segment).tolist()
for i, (v, r) in enumerate(zip(values, dynamic_range)):
cdict[k].append((v, r, r))
# Mapping levels to colormap
cmap = plt.cm.viridis
for k, v in cdict.items():
if k == "red":
for i in range(len(v)):
cdict[k][i] = (v[i][0], cmap(v[i][1])[0], cmap(v[i][2])[0])
elif k == "green":
for j in range(len(v)):
cdict[k][j] = (v[j][0], cmap(v[j][1])[1], cmap(v[j][2])[1])
elif k == "blue":
for l in range(len(v)):
cdict[k][l] = (v[l][0], cmap(v[l][1])[2], cmap(v[l][2])[2])
else:
raise ValueError("Color not recognized")
cdict[k] = tuple(cdict[k])
cmap_nonlin = colors.LinearSegmentedColormap('MyCustomCMap', cdict)
fig, ax = plt.subplots()
my_image = np.array([[30, 45], [25, 10]])
confusion = ax.imshow(my_image, cmap=cmap_nonlin, vmin=0, vmax=100)
plt.colorbar(confusion, ax=ax)
plt.waitforbuttonpress()
And the resulting cmap_nonlin object can be used in conjunction with imshow without any issue:

Finding local minimum between two peaks

I have some time series data in Pandas where I need to extract specific local minimums from a column so I can use them as Features in a LSTM model. To visualize what I'm looking for I've attached a Picture, where the circled points are the values that I wish to locate.
The other red dots that you see at the bottom of the graph is my failed attempt of using "argrelextrema" with the following code:
#Trying to Locate Minimum Values
df['HKL Min'] = df.iloc[argrelextrema(df.hkla.values, np.less_equal,order=50)[0]]['hkla']
#Plotting a range of values from dataset:
sns.lineplot(x=df.index[0:3000], y= 'hkla', data=df[0:3000], label='Hookload');
sns.scatterplot(x=df.index[0:3000], y= 'HKL Min', data=df[0:3000], s= 50, color ='red', label='HKL Min');
As you may notice, my column data has a repetitive pattern, and the points I wish to locate are the minimas found between two "peaks-pairs".Is there some existing functions in Python that can help me locate these specific points? Any form of help would be highly appreciated. I am also open to other suggestions that can solve my issue here...
You could do something like this with your data:
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from scipy.signal import argrelextrema
np.random.seed(1234)
rs = np.random.randn(500)
xs = [0]
for r in rs:
xs.append(xs[-1] * 0.999 + r)
df = pd.DataFrame(xs, columns=['point'])
which gives this data
point
0 0.000000
1 0.471435
2 -0.720012
3 0.713415
4 0.400050
.. ...
496 3.176240
497 3.007734
498 3.123841
499 1.045736
500 0.041935
[501 rows x 1 columns]
You can choose how often you want to mark a local ma or min by playing with a parameter:
n = 10
df['min'] = df.iloc[argrelextrema(df.point.values, np.less_equal,
order=n)[0]]['point']
df['max'] = df.iloc[argrelextrema(df.point.values, np.greater_equal,
order=n)[0]]['point']
plt.scatter(df.index, df['min'], c='r')
plt.scatter(df.index, df['max'], c='r')
plt.plot(df.index, df['point'])
plt.show()
Which gives:
Another choice for n might be (and it all depends on what you want):
n = 40
df['min'] = df.iloc[argrelextrema(df.point.values, np.less_equal,
order=n)[0]]['point']
df['max'] = df.iloc[argrelextrema(df.point.values, np.greater_equal,
order=n)[0]]['point']
plt.scatter(df.index, df['min'], c='r')
plt.scatter(df.index, df['max'], c='g')
plt.plot(df.index, df['point'])
plt.show()
To get a marking for which points actually where max and min, you can make a new df:
new_df = pd.DataFrame(np.where(df.T == df.T.max(), 1, 0),index=df.columns).T
which gives the information about which row in df is a maximum or a minimum. Otherwise, the original df contains that information in the created min and max columns, those instance that aren't nan
EDIT: Finding peaks above threshold
If you are intrested of peaks above a certain value, then you should use find_peaks in the following way:
from scipy.signal import find_peaks
peaks, _ = find_peaks(df['point'], height = 15)
plt.plot(df['point'])
plt.plot(peaks, df['point'][peaks], "x")
plt.show()
which will produce:
peaks,_
(array([304, 309, 314, 317, 324, 329, 333, 337, 343, 349, 352, 363, 366,
369, 372, 374, 377, 379, 381, 383, 385, 387, 391, 394, 397, 400,
403, 410, 413, 418, 424, 427, 430, 433, 436, 439, 442, 444, 448],
dtype=int64),
{'peak_heights': array([15.68868141, 15.97184882, 15.04790966, 15.6146908 , 16.49191501,
18.0852033 , 18.11467247, 19.48469432, 21.32391722, 19.90407526,
19.93683051, 24.40980129, 28.00319793, 26.1080406 , 24.44322213,
23.16993982, 22.27505873, 21.47500832, 22.3236231 , 24.02484906,
23.83727054, 24.32609486, 21.25365717, 21.10295203, 20.03162979,
20.64021444, 19.78510855, 21.62624829, 22.34904425, 21.60431638,
18.41968769, 18.24153961, 18.00747871, 18.02793964, 16.72552016,
17.58573207, 16.90982675, 16.9905686 , 16.30563852])})
and graphically
I was able to fix my problem using the approach provided by #Serge de Gosson de Varennes. I switched out the "argrelextrema" with scipy "find_peaks()" as follows:
df['Min'] = df.iloc[find_peaks(-df.column[0:3000], height=(-350000,-250000), threshold = None,
distance=200, )[0]]['column']
The height input here gave me the option to choose an interval in the y-direction, which made it quite easy to detect the local minimas that I was looking for within said interval. When plotting the results like this:
plt.plot(df.index[0:3000], df.column[0:3000])
plt.plot(df.index, df['Min'],'ro', color = 'red', label = 'Min Values')
I got the following graph
Thank you for the assistance!

Can't populate matplotlib animation frame one point at a time

I'm currently trying to build an N-body simulation but I'm having a little trouble with plotting the results the way I'd like.
In the code below (with some example data for a few points in an orbit) I'm importing the position and time data and organizing it into a pandas dataframe. To create the 3D animation I use matplotlib's animation class, which works perfectly.
However, the usual way to set up an animation is limited in that you can't customize the points in each frame individually (please let me know if I'm wrong here :p). Since my animation is showing orbiting bodies I would like to vary their sizes and colors. To do that I essentially create a graph for each body and set it's color etc. When it gets to the update_graph function, I iterate over the n bodies, retrieve their individual (x,y,z) coordinates, and update their graphs.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d.axes3d import get_test_data
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as animation
import pandas as pd
nbodies = 2
x = np.array([[1.50000000e-10, 0.00000000e+00, 0.00000000e+00],
[9.99950000e-01, 1.00000000e-02, 0.00000000e+00],
[4.28093585e-06, 3.22964816e-06, 0.00000000e+00],
[-4.16142210e-01, 9.09335149e-01, 0.00000000e+00],
[5.10376489e-06, 1.42204430e-05, 0.00000000e+00],
[-6.53770813e-01, -7.56722445e-01, 0.00000000e+00]])
t = np.array([0.01, 0.01, 2.0, 2.0, 4.0, 4.0])
tt = np.array([0.01, 2.0, 4.0])
x = x.reshape((len(tt), nbodies, 3))
x_coords = x[:, :, 0].flatten()
y_coords = x[:, :, 1].flatten()
z_coords = x[:, :, 2].flatten()
df = pd.DataFrame({"time": t[:] ,"x" : x_coords, "y" : y_coords, "z" : z_coords})
print(df)
def update_graph(num):
data=df[df['time']==tt[num]] # x,y,z of all bodies at current time
for n in range(nbodies): # update graphs
data_n = data[data['x']==x_coords[int(num * nbodies) + n]] # x,y,z of body n
graph = graphs[n]
graph.set_data(data_n.x, data_n.y)
graph.set_3d_properties(data_n.z)
graphs[n] = graph
return graphs
plt.style.use('dark_background')
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel('x (AU)')
ax.set_ylabel('y (AU)')
ax.set_zlabel('z (AU)')
plt.xlim(-1.5,1.5)
plt.ylim(-1.5,1.5)
# initialize
data=df[df['time']==0]
ms_list = [5, 1]
c_list = ['yellow', 'blue']
graphs = []
for n in range(nbodies):
graphs.append(ax.plot([], [], [], linestyle="", marker=".",
markersize=ms_list[n], color=c_list[n])[0])
ani = animation.FuncAnimation(fig, update_graph, len(tt),
interval=400, blit=True, repeat=True)
plt.show()
However, doing this gives me the following error:
Traceback (most recent call last):
File "/home/kris/anaconda3/lib/python3.7/site-packages/matplotlib/backend_bases.py", line 1194, in _on_timer
ret = func(*args, **kwargs)
File "/home/kris/anaconda3/lib/python3.7/site-packages/matplotlib/animation.py", line 1447, in _step
still_going = Animation._step(self, *args)
File "/home/kris/anaconda3/lib/python3.7/site-packages/matplotlib/animation.py", line 1173, in _step
self._draw_next_frame(framedata, self._blit)
File "/home/kris/anaconda3/lib/python3.7/site-packages/matplotlib/animation.py", line 1193, in _draw_next_frame
self._post_draw(framedata, blit)
File "/home/kris/anaconda3/lib/python3.7/site-packages/matplotlib/animation.py", line 1216, in _post_draw
self._blit_draw(self._drawn_artists, self._blit_cache)
File "/home/kris/anaconda3/lib/python3.7/site-packages/matplotlib/animation.py", line 1231, in _blit_draw
a.axes.draw_artist(a)
File "/home/kris/anaconda3/lib/python3.7/site-packages/matplotlib/axes/_base.py", line 2661, in draw_artist
a.draw(self.figure._cachedRenderer)
File "/home/kris/anaconda3/lib/python3.7/site-packages/matplotlib/artist.py", line 38, in draw_wrapper
return draw(artist, renderer, *args, **kwargs)
File "/home/kris/anaconda3/lib/python3.7/site-packages/mpl_toolkits/mplot3d/art3d.py", line 202, in draw
xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M)
File "/home/kris/anaconda3/lib/python3.7/site-packages/mpl_toolkits/mplot3d/proj3d.py", line 201, in proj_transform
vec = _vec_pad_ones(xs, ys, zs)
File "/home/kris/anaconda3/lib/python3.7/site-packages/mpl_toolkits/mplot3d/proj3d.py", line 189, in _vec_pad_ones
return np.array([xs, ys, zs, np.ones_like(xs)])
File "/home/kris/anaconda3/lib/python3.7/site-packages/pandas/core/series.py", line 871, in __getitem__
result = self.index.get_value(self, key)
File "/home/kris/anaconda3/lib/python3.7/site-packages/pandas/core/indexes/base.py", line 4405, in get_value
return self._engine.get_value(s, k, tz=getattr(series.dtype, "tz", None))
File "pandas/_libs/index.pyx", line 80, in pandas._libs.index.IndexEngine.get_value
File "pandas/_libs/index.pyx", line 90, in pandas._libs.index.IndexEngine.get_value
File "pandas/_libs/index.pyx", line 138, in pandas._libs.index.IndexEngine.get_loc
File "pandas/_libs/hashtable_class_helper.pxi", line 997, in pandas._libs.hashtable.Int64HashTable.get_item
File "pandas/_libs/hashtable_class_helper.pxi", line 1004, in pandas._libs.hashtable.Int64HashTable.get_item
KeyError: 0
Aborted (core dumped)
I'm not sure what this really means, but I do know the problem is something to do with updating the graphs with only one row of coordinates rather than all three. Because if I instead have
def update_graph(num):
data=df[df['time']==tt[num]] # x,y,z of all bodies at current time
for n in range(nbodies): # update graphs
#data_n = data[data['x']==x_coords[int(num * nbodies) + n]] # x,y,z of body n
graph = graphs[n]
graph.set_data(data.x, data.y) # using data rather than data_n here now
graph.set_3d_properties(data.z)
graphs[n] = graph
return graphs
it actually works, and plots three copies of the bodies with varying colors and sizes on top of each other as you would expect.
Any help would be much appreciated. Thanks!
I don't understand why you are going through a pandas DataFrame, when you seem to already have all the data you need in your numpy array. I couldn't reproduce the initial problem, by I propose this solution that uses pure numpy arrays, which may fix the problem:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d.axes3d import get_test_data
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as animation
import pandas as pd
nbodies = 2
x = np.array([[1.50000000e-10, 0.00000000e+00, 0.00000000e+00],
[9.99950000e-01, 1.00000000e-02, 0.00000000e+00],
[4.28093585e-06, 3.22964816e-06, 0.00000000e+00],
[-4.16142210e-01, 9.09335149e-01, 0.00000000e+00],
[5.10376489e-06, 1.42204430e-05, 0.00000000e+00],
[-6.53770813e-01, -7.56722445e-01, 0.00000000e+00]])
t = np.array([0.01, 0.01, 2.0, 2.0, 4.0, 4.0])
tt = np.array([0.01, 2.0, 4.0])
x = x.reshape((len(tt), nbodies, 3))
def update_graph(i):
data = x[i, :, :] # x,y,z of all bodies at current time
for body, graph in zip(data, graphs): # update graphs
graph.set_data(body[0], body[1])
graph.set_3d_properties(body[2])
return graphs
plt.style.use('dark_background')
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel('x (AU)')
ax.set_ylabel('y (AU)')
ax.set_zlabel('z (AU)')
plt.xlim(-1.5, 1.5)
plt.ylim(-1.5, 1.5)
# initialize
ms_list = [50, 10]
c_list = ['yellow', 'blue']
graphs = []
for n in range(nbodies):
graphs.append(ax.plot([], [], [], linestyle="", marker=".",
markersize=ms_list[n], color=c_list[n])[0])
ani = animation.FuncAnimation(fig, func=update_graph, frames=len(tt),
interval=400, blit=True, repeat=True)
plt.show()

Input Format to pcolormesh

I'm attempting to make heat/intensity map using Basemap. My inputs are a set of lats, lons, and intensity at that point. The dataset looks like this:
lat[0], lon[0] = intensity[0]
lat[1], lon[1] = intensity[1]
...
lat[n], lon[n] = intensity[n]
At each index the lat and lon correspond to the correct sensor reading. My code looks something like this:
fig = plt.figure(figsize=(10, 8))
# Set title
fig.suptitle("Intensities {} {}".format(start_time, stop_time))
# US Centered Map
map_axis = fig.add_subplot(111)
map = Basemap(
ax = map_axis,
lat_0 = 40, lon_0 = -95,
width = 6500e3, height = 6500e3,
projection = 'stere',
resolution = 'l'
)
map.drawcoastlines()
lats = ...
lons = ...
intn = ...
# Convert coordinates
lons, lats = map(lons, lats)
LONS, LATS = np.meshgrid(lons, lats)
map.pcolormesh(
LONS, LATS,
intn,
vmin = 0, vmax = 100
)
fig.savefig(file_name)
plt.close(fig)
This code never completes. I've successfully plotted the Basemap by itself. The pcolormesh is what is failing. The program crashes with this error.
$ ./plot_intensities.py
Running 2013-04-10 00:02:30 2013-04-10 00:02:45
Traceback (most recent call last):
File "./plot_intensities.py", line 151, in <module>
make_maps(samples)
File "./plot_intensities.py", line 144, in make_maps
make_map(bin_samples, start, walk)
File "./plot_intensities.py", line 117, in make_map
vmin = 0, vmax = 100
File "/usr/lib/python3/dist-packages/mpl_toolkits/basemap/__init__.py", line 521, in with_transform
return plotfunc(self,x,y,data,*args,**kwargs)
File "/usr/lib/python3/dist-packages/mpl_toolkits/basemap/__init__.py", line 3418, in pcolormesh
ret = ax.pcolormesh(x,y,data,**kwargs)
File "/usr/lib/python3/dist-packages/matplotlib/__init__.py", line 1814, in inner
return func(ax, *args, **kwargs)
File "/usr/lib/python3/dist-packages/matplotlib/axes/_axes.py", line 5395, in pcolormesh
X, Y, C = self._pcolorargs('pcolormesh', *args, allmatch=allmatch)
File "/usr/lib/python3/dist-packages/matplotlib/axes/_axes.py", line 4995, in _pcolorargs
numRows, numCols = C.shape
ValueError: not enough values to unpack (expected 2, got 1)
I understand that my data, the third argument intn is not formatted correctly. I cannot find any documentation as to how I should shape that list. How do I format it to the correct shape?
Thanks.
As you know, pcolormesh is used to plot a quadrilateral mesh by creating a pseudocolor plot of a 2-D array. The error details indeed indicated that: at line numRows, numCols = C.shape, it expect C to be a 2-D array, while the C you provided seems to be a 1-D array, judging from ValueError: not enough values to unpack (expected 2, got 1). The dataset you introduced seems to me having only intensity values on the diagonal (where lat == lon). To get a colormesh, you need to at least extend intensity data into 2-D array and somehow fill in missing values. For example:
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import numpy as np
fig = plt.figure(figsize=(10, 8))
# Set title
fig.suptitle("Intensities {} {}".format('start_time', 'stop_time'))
# US Centered Map
map_axis = fig.add_subplot(111)
map = Basemap(
ax = map_axis,
lat_0 = 40, lon_0 = -95,
width = 6500e3, height = 6500e3,
projection = 'stere',
resolution = 'l'
)
map.drawcoastlines()
# Tried my best to simulate your data example. Don't be surprise if the result is ugly ...
nstep = 1
lats = np.arange(map.latmin, map.latmax, nstep)
lons = np.arange(map.lonmin, map.lonmax, nstep)
l = min(len(lats), len(lons))
lats = lats[:l]
lons = lons[:l]
intn = np.random.randint(0, 100, size=l)
# Convert coordinates
lons, lats = map(lons, lats)
LONS, LATS = np.meshgrid(lons, lats)
# The following 3 lines are just an example of the minimum you got to do before it works.
intn_array = np.zeros(LONS.shape)
for i in range(l):
intn_array[i, i] = intn[i]
intn = intn_array
map.pcolormesh(
LONS, LATS,
intn_array,
vmin = 0, vmax = 100
)
plt.show()

Adding an arbitrary line to a matplotlib plot in ipython notebook

I'm rather new to both python/matplotlib and using it through the ipython notebook. I'm trying to add some annotation lines to an existing graph and I can't figure out how to render the lines on a graph. So, for example, if I plot the following:
import numpy as np
np.random.seed(5)
x = arange(1, 101)
y = 20 + 3 * x + np.random.normal(0, 60, 100)
p = plot(x, y, "o")
I get the following graph:
So how would I add a vertical line from (70,100) up to (70,250)? What about a diagonal line from (70,100) to (90,200)?
I've tried a few things with Line2D() resulting in nothing but confusion on my part. In R I would simply use the segments() function which would add line segments. Is there an equivalent in matplotlib?
You can directly plot the lines you want by feeding the plot command with the corresponding data (boundaries of the segments):
plot([x1, x2], [y1, y2], color='k', linestyle='-', linewidth=2)
(of course you can choose the color, line width, line style, etc.)
From your example:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(5)
x = np.arange(1, 101)
y = 20 + 3 * x + np.random.normal(0, 60, 100)
plt.plot(x, y, "o")
# draw vertical line from (70,100) to (70, 250)
plt.plot([70, 70], [100, 250], 'k-', lw=2)
# draw diagonal line from (70, 90) to (90, 200)
plt.plot([70, 90], [90, 200], 'k-')
plt.show()
It's not too late for the newcomers.
plt.axvline(x, color='r') # vertical
plt.axhline(x, color='r') # horizontal
It takes the range of y as well, using ymin and ymax.
Using vlines:
import numpy as np
np.random.seed(5)
x = arange(1, 101)
y = 20 + 3 * x + np.random.normal(0, 60, 100)
p = plot(x, y, "o")
vlines(70,100,250)
The basic call signatures are:
vlines(x, ymin, ymax)
hlines(y, xmin, xmax)
Rather than abusing plot or annotate, which will be inefficient for many lines, you can use matplotlib.collections.LineCollection:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
np.random.seed(5)
x = np.arange(1, 101)
y = 20 + 3 * x + np.random.normal(0, 60, 100)
plt.plot(x, y, "o")
# Takes list of lines, where each line is a sequence of coordinates
l1 = [(70, 100), (70, 250)]
l2 = [(70, 90), (90, 200)]
lc = LineCollection([l1, l2], color=["k","blue"], lw=2)
plt.gca().add_collection(lc)
plt.show()
It takes a list of lines [l1, l2, ...], where each line is a sequence of N coordinates (N can be more than two).
The standard formatting keywords are available, accepting either a single value, in which case the value applies to every line, or a sequence of M values, in which case the value for the ith line is values[i % M].
Matplolib now allows for 'annotation lines' as the OP was seeking. The annotate() function allows several forms of connecting paths and a headless and tailess arrow, i.e., a simple line, is one of them.
ax.annotate("",
xy=(0.2, 0.2), xycoords='data',
xytext=(0.8, 0.8), textcoords='data',
arrowprops=dict(arrowstyle="-",
connectionstyle="arc3, rad=0"),
)
In the documentation it says you can draw only an arrow with an empty string as the first argument.
From the OP's example:
%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(5)
x = np.arange(1, 101)
y = 20 + 3 * x + np.random.normal(0, 60, 100)
plt.plot(x, y, "o")
# draw vertical line from (70,100) to (70, 250)
plt.annotate("",
xy=(70, 100), xycoords='data',
xytext=(70, 250), textcoords='data',
arrowprops=dict(arrowstyle="-",
connectionstyle="arc3,rad=0."),
)
# draw diagonal line from (70, 90) to (90, 200)
plt.annotate("",
xy=(70, 90), xycoords='data',
xytext=(90, 200), textcoords='data',
arrowprops=dict(arrowstyle="-",
connectionstyle="arc3,rad=0."),
)
plt.show()
Just as in the approach in gcalmettes's answer, you can choose the color, line width, line style, etc..
Here is an alteration to a portion of the code that would make one of the two example lines red, wider, and not 100% opaque.
# draw vertical line from (70,100) to (70, 250)
plt.annotate("",
xy=(70, 100), xycoords='data',
xytext=(70, 250), textcoords='data',
arrowprops=dict(arrowstyle="-",
edgecolor = "red",
linewidth=5,
alpha=0.65,
connectionstyle="arc3,rad=0."),
)
You can also add curve to the connecting line by adjusting the connectionstyle.