python - strange error when plotting errorbars - python

I'm trying to combine 3 datasets in one plot. Each dataset has it's own y and x error. I'm receiving this error message:
Traceback (most recent call last):
File "SED_plot.py", line 310, in <module>
plt.errorbar(x0, y0, xerr=x0err, linestyle='None', ecolor="black", label= "Channel Width")
File "/Library/Python/2.7/site-packages/matplotlib-override/matplotlib/pyplot.py", line 2766, in errorbar
errorevery=errorevery, capthick=capthick, **kwargs)
File "/Library/Python/2.7/site-packages/matplotlib-override/matplotlib/axes/_axes.py", line 2749, in errorbar
in cbook.safezip(x, xerr[0])]
File "/Library/Python/2.7/site-packages/matplotlib-override/matplotlib/cbook.py", line 1479, in safezip
raise ValueError(_safezip_msg % (Nx, i + 1, len(arg)))
ValueError: In safezip, len(args[0])=16 but len(args[1])=48
when I run this code:
x0, y0 = x_val_all[0:16], y_val_all[0:16]
x0err, y0err = x_error_all[0:16], y_error_all[0:16]
x1, y1 = x_val_all[17:33], y_val_all[17:33]
x1err, y1err = x_error_all[17:33], y_error_all[17:33]
x2, y2 = x_val_all[33:49], y_val_all[33:49]
x2err, y2err = x_error_all[33:49], y_error_all[33:49]
plt.errorbar(x0, y0, xerr=x0err, linestyle='None', ecolor="black", label= "Channel Width")
plt.errorbar(x0, y0, yerr=y0err, linestyle='None', ecolor="black", label= "Standard Deviation")
plt.errorbar(x1, y1, xerr=x1err, yerr=y1err, ecolor="red")
plt.errorbar(x2, y2, xerr=x2err, yerr=y2err, ecolor="purple")
plt.show()
Could it be that list slicing isn't working in this case? All the x values and y values are in one list each (x_val_all, y_val_all respectively) and so are the corresponding errors.
Sample code to reproduce:
import matplotlib.pyplot as plt
y = range(0,21,1)
x = range(0,21,1)
y_err = [0.5]*21
x_low = [0.7]*21
x_upper = [1.4]*21
x_err = [x_low, x_upper]
plt.errorbar(x[0:7],y[0:7], xerr=x_err[0:7], yerr=y_err[0:7], linestyle="none", color="black")
plt.errorbar(x[8:15],y[8:15], xerr=x_err[8:15], yerr=y_err[8:15], linestyle="none", color="red")
plt.show()

Indexing x_err is the root cause of your error, as this is a list of two elements. My personal preference to fix this would be to use a list comprehension:
import matplotlib.pyplot as plt
y = range(0,21,1)
x = range(0,21,1)
y_err = [0.5]*21
x_low = [0.7]*21
x_upper = [1.4]*21
x_err = [x_low, x_upper]
plt.errorbar(x[0:7], y[0:7], xerr=[_x[0:7] for _x in x_err], yerr=y_err[0:7], linestyle="none", color="black")
plt.errorbar(x[8:15], y[8:15], xerr=[_x[8:15] for _x in x_err], yerr=y_err[8:15], linestyle="none", color="red")
plt.show()
(Note the use of _x within the list comprehension - list comprehension leaks into the local scope in Python 2.7, which would overwrite the earlier x variable if we used x as the variable within the comprehension.)
You could also do:
plt.errorbar(x[0:7], y[0:7], xerr=[x_err[0][0:7], x_err[1][0:7]], yerr=y_err[0:7], linestyle="none", color="black")
plt.errorbar(x[8:15], y[8:15], xerr=[x_err[0][8:15], x_err[1][8:15]], yerr=y_err[8:15], linestyle="none", color="red")
although this is a little more verbose.

Have a look at the docs you are presenting the x_error wrong, the list needs to be 2x7 however the way you slice it does does not produce that result. You are slicing a len 2 list with range 7. The code below gives you the plot you want
import matplotlib.pyplot as plt
y = range(0,21,1)
x = range(0,21,1)
y_err = [0.5]*21
x_low = [0.7]*21
x_upper = [1.4]*21
x_err = [x_low, x_upper]
fig, ax = plt.subplots()
idx = range(0, 16, 7)
for start, stop in zip(idx[:-1], idx[1:]):
ax.errorbar(x[start:stop], y[start:stop], y_err[start:stop], \
[ i[start:stop] for i in x_err])
Edit: for errors like this I recommend using numpy as its array allow you to easily check dimension and index into them easier than lists of lists.

Related

The Matplotlib Result is Different From WolfarmAlpha

I want to plot some equation in Matplotlib. But it has different result from Wolframalpha.
This is the equation:
y = 10yt + y^2t + 20
The plot result in wolframalpha is:
But when I want to plot it in the matplotlib with these code
# Creating vectors X and Y
x = np.linspace(-2, 2, 100)
# Assuming α is 10
y = ((10*y*x)+((y**2)*x)+20)
# Create the plot
fig = plt.figure(figsize = (10, 5))
plt.plot(x, y)
The result is:
Any suggestion to modify to code so it has similar plot result as wolframalpha? Thank you
As #Him has suggested in the comments, y = ((10*y*x)+((y**2)*x)+20) won't describe a relationship, so much as make an assignment, so the fact that y appears on both sides of the equation makes this difficult.
It's not trivial to express y cleanly in terms of x, but it's relatively easy to express x in terms of y, and then graph that relationship, like so:
import numpy as np
import matplotlib.pyplot as plt
y = np.linspace(-40, 40, 2000)
x = (y-20)*(((10*y)+(y**2))**-1)
fig, ax = plt.subplots()
ax.plot(x, y, linestyle = 'None', marker = '.')
ax.set_xlim(left = -4, right = 4)
ax.grid()
ax.set_xlabel('x')
ax.set_ylabel('y')
Which produces the following result:
If you tried to plot this with a line instead of points, you'll get a big discontinuity as the asymptotic limbs try to join up
So you'd have to define the same function and evaluate it in three different ranges and plot them all so you don't get any crossovers.
import numpy as np
import matplotlib.pyplot as plt
y1 = np.linspace(-40, -10, 2000)
y2 = np.linspace(-10, 0, 2000)
y3 = np.linspace(0, 40, 2000)
x = lambda y: (y-20)*(((10*y)+(y**2))**-1)
y = np.hstack([y1, y2, y3])
fig, ax = plt.subplots()
ax.plot(x(y), y, linestyle = '-', color = 'b')
ax.set_xlim(left = -4, right = 4)
ax.grid()
ax.set_xlabel('x')
ax.set_ylabel('y')
Which produces this result, that you were after:

Matplotlib iterate to combine legend handles and labels

If I have the following plotting routine that plots a scatter plot and corresponding linear regression and combines the legend handles:
import pandas as pd
from scipy.stats import linregress
import numpy as np
import matplotlib.pyplot as plt
#data and Regression
x = np.arange(0,5,1)
y = np.arange(0,10,2)
df = pd.DataFrame(data = {'x':x,'y':y})
s, intcpt, r, p, serr = linregress(df.x, df.y)
xHat = np.linspace(0,5,100)
# do the plotting
fig,ax = plt.subplots()
df.plot(x='x',y='y',ax=ax,label='series1',ls=' ',marker='x',c='blue')
ls_handle, = ax.plot(xHat, s*xHat + intcpt, linestyle='-', marker=None, c='blue')
handle2merge = [ls_handle]
handles, labels = ax.get_legend_handles_labels()
handle_combined = zip(handles, handle2merge)
ax.legend(handle_combined, labels)
Which returns the where the marker and line handles are merged looking like:
Now I want to plot another dataset in a similar fashion:
#get current axis handles and labels
handle_start, label_start = ax.get_legend_handles_labels()
#second dataset and regression
x1 = np.arange(0,5,1)
y1 = np.arange(0,2.5,0.5)
df1 = pd.DataFrame(data = {'x':x1,'y':y1})
s1, intcpt1, r1, p1, serr1 = linregress(df1.x, df1.y)
xHat1 = np.linspace(0,5,100)
#plot second data set on same figure
marker_handle2, = ax.plot(df1.x, df1.y, marker = 'x', zorder=10,c='k', linestyle=' ')
line_handle2, = ax.plot(xHat, s1*xHat1 + intcpt1, linestyle='--', marker=None, c='k')
new_line_handles = [line_handle2]
new_marker_handles= [marker_handle2]
ax.legend(handle_start + zip(new_marker_handles,new_line_handles), label_start + ['series2'])
This returns a plot where the handles for series1 legend handle only contains the marker.
Why is len(handle_start)=1 when I constructed the handle with handle_combined = zip(handles, handle2merge)?
I have poked around the code a little. What you are doing is passing a list of tuples to ax.legend, which apparently draws each Artist in each tuple as one entry in the legend. I have actually not come across this behaviour before; it could be a bug, or unintended use of ax.legend.
Nevertheless, I think that in this case, since you know what your lines should look like beforehand, instead of going the roundabout way with zip and stuff, you could just pass a custom Line2D to legend directly:
import numpy as np
from scipy.stats import linregress
from matplotlib import pyplot as plt
from matplotlib import lines
x1 = np.arange(0, 5, 1)
y1 = np.arange(0, 10, 2)
x2 = np.arange(0, 5, 1)
y2 = np.arange(0, 2.5, 0.5)
m1, c1, r1, p1, serr1 = linregress(x1, y1)
m2, c2, r2, p2, serr2 = linregress(x2, y2)
x_pred = np.linspace(0,5,100)
fig, ax = plt.subplots()
first_line, = ax.plot(x_pred, x_pred * m1 + c1, ls='-', c='blue')
first_scatter = ax.scatter(x1, y1, marker='x', c='blue')
second_line, = ax.plot(x_pred, x_pred * m2 + c2, ls='--', c='black')
second_scatter = ax.scatter(x2, y2, marker='x', c='black')
ax.legend([lines.Line2D([0], [0], marker='x', ls='-', c='blue'),
lines.Line2D([0], [0], marker='x', ls='--', c='black')],
['series_1', 'series_2'])
I cleaned up your code a little, but feel free to take only the last line and the necessary import.
In the last line, just use the already created merged handle handle_combined instead of the handle_start.
ax.legend(handle_combined + list(zip(new_marker_handles,new_line_handles)),
label_start + ['series2'])
The length is 1 but if you look into the contents of the list, it is a tuple consisting of two objects. If you print handle_combined, you get a list of two Line2D objects, one of which is marker and the other is the line.
print (handle_combined)
# [(<matplotlib.lines.Line2D object at xxxxxxxxx>, <matplotlib.lines.Line2D object at xxxxxxxxx>)]
However, if you print handle_start, it returns just a single Line2D object
print (handle_start)
# [<matplotlib.lines.Line2D object at xxxxxxxxx>]

Conditional formatting of line colour in plot [duplicate]

I am trying to create a colored line with certain conditions. Basically I would like to have the line colored red when pointing down on the y axis, green when pointing up and blue when neither.
I played around with some similar examples I found but I have never been able to convert them to work with plot() on an axis. Just wondering how this could be done.
Here is some code that I have come up with so far:
#create x,y coordinates
x = numpy.random.choice(10,10)
y = numpy.random.choice(10,10)
#create an array of colors based on direction of line (0=r, 1=g, 2=b)
colors = []
#create an array that is one position away from original
#to determine direction of line
yCopy = list(y[1:])
for y1,y2 in zip(y,yCopy):
if y1 > y2:
colors.append(0)
elif y1 < y2:
colors.append(1)
else:
colors.append(2)
#add tenth spot to array as loop only does nine
colors.append(2)
#create a numpy array of colors
categories = numpy.array(colors)
#create a color map with the three colors
colormap = numpy.array([matplotlib.colors.colorConverter.to_rgb('r'),matplotlib.colors.colorConverter.to_rgb('g'),matplotlib.colors.colorConverter.to_rgb('b')])
#plot line
matplotlib.axes.plot(x,y,color=colormap[categories])
Not sure how to get plot() to accept an array of colors. I always get an error about the format type used as the color. Tried heximal, decimal, string and float. Works perfect with scatter().
I don't think that you can use an array of colors in plot (the documentation says that color can be any matlab color, while the scatter docs say you can use an array).
However, you could fake it by plotting each line separately:
import numpy
from matplotlib import pyplot as plt
x = range(10)
y = numpy.random.choice(10,10)
for x1, x2, y1,y2 in zip(x, x[1:], y, y[1:]):
if y1 > y2:
plt.plot([x1, x2], [y1, y2], 'r')
elif y1 < y2:
plt.plot([x1, x2], [y1, y2], 'g')
else:
plt.plot([x1, x2], [y1, y2], 'b')
plt.show()
OK. So I figured out how to do it using LineCollecion to draw the line on a axis.
import numpy as np
import pylab as pl
from matplotlib import collections as mc
segments = []
colors = np.zeros(shape=(10,4))
x = range(10)
y = np.random.choice(10,10)
i = 0
for x1, x2, y1,y2 in zip(x, x[1:], y, y[1:]):
if y1 > y2:
colors[i] = tuple([1,0,0,1])
elif y1 < y2:
colors[i] = tuple([0,1,0,1])
else:
colors[i] = tuple([0,0,1,1])
segments.append([(x1, y1), (x2, y2)])
i += 1
lc = mc.LineCollection(segments, colors=colors, linewidths=2)
fig, ax = pl.subplots()
ax.add_collection(lc)
ax.autoscale()
ax.margins(0.1)
pl.show()
There is an example on the matplotlib page showing how to use a LineCollection to plot a multicolored line.
The remaining problem is to get the colors for the line collection. So if y are the values to compare,
cm = dict(zip(range(-1,2,1),list("gbr")))
colors = list( map( cm.get , np.sign(np.diff(y)) ))
Complete code:
import numpy as np; np.random.seed(5)
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
x = np.arange(10)
y = np.random.choice(10,10)
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
cm = dict(zip(range(-1,2,1),list("rbg")))
colors = list( map( cm.get , np.sign(np.diff(y)) ))
lc = LineCollection(segments, colors=colors, linewidths=2)
fig, ax = plt.subplots()
ax.add_collection(lc)
ax.autoscale()
ax.margins(0.1)
plt.show()

Can I make a multi-color line in matplotlib?

I am trying to create a colored line with certain conditions. Basically I would like to have the line colored red when pointing down on the y axis, green when pointing up and blue when neither.
I played around with some similar examples I found but I have never been able to convert them to work with plot() on an axis. Just wondering how this could be done.
Here is some code that I have come up with so far:
#create x,y coordinates
x = numpy.random.choice(10,10)
y = numpy.random.choice(10,10)
#create an array of colors based on direction of line (0=r, 1=g, 2=b)
colors = []
#create an array that is one position away from original
#to determine direction of line
yCopy = list(y[1:])
for y1,y2 in zip(y,yCopy):
if y1 > y2:
colors.append(0)
elif y1 < y2:
colors.append(1)
else:
colors.append(2)
#add tenth spot to array as loop only does nine
colors.append(2)
#create a numpy array of colors
categories = numpy.array(colors)
#create a color map with the three colors
colormap = numpy.array([matplotlib.colors.colorConverter.to_rgb('r'),matplotlib.colors.colorConverter.to_rgb('g'),matplotlib.colors.colorConverter.to_rgb('b')])
#plot line
matplotlib.axes.plot(x,y,color=colormap[categories])
Not sure how to get plot() to accept an array of colors. I always get an error about the format type used as the color. Tried heximal, decimal, string and float. Works perfect with scatter().
I don't think that you can use an array of colors in plot (the documentation says that color can be any matlab color, while the scatter docs say you can use an array).
However, you could fake it by plotting each line separately:
import numpy
from matplotlib import pyplot as plt
x = range(10)
y = numpy.random.choice(10,10)
for x1, x2, y1,y2 in zip(x, x[1:], y, y[1:]):
if y1 > y2:
plt.plot([x1, x2], [y1, y2], 'r')
elif y1 < y2:
plt.plot([x1, x2], [y1, y2], 'g')
else:
plt.plot([x1, x2], [y1, y2], 'b')
plt.show()
OK. So I figured out how to do it using LineCollecion to draw the line on a axis.
import numpy as np
import pylab as pl
from matplotlib import collections as mc
segments = []
colors = np.zeros(shape=(10,4))
x = range(10)
y = np.random.choice(10,10)
i = 0
for x1, x2, y1,y2 in zip(x, x[1:], y, y[1:]):
if y1 > y2:
colors[i] = tuple([1,0,0,1])
elif y1 < y2:
colors[i] = tuple([0,1,0,1])
else:
colors[i] = tuple([0,0,1,1])
segments.append([(x1, y1), (x2, y2)])
i += 1
lc = mc.LineCollection(segments, colors=colors, linewidths=2)
fig, ax = pl.subplots()
ax.add_collection(lc)
ax.autoscale()
ax.margins(0.1)
pl.show()
There is an example on the matplotlib page showing how to use a LineCollection to plot a multicolored line.
The remaining problem is to get the colors for the line collection. So if y are the values to compare,
cm = dict(zip(range(-1,2,1),list("gbr")))
colors = list( map( cm.get , np.sign(np.diff(y)) ))
Complete code:
import numpy as np; np.random.seed(5)
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
x = np.arange(10)
y = np.random.choice(10,10)
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
cm = dict(zip(range(-1,2,1),list("rbg")))
colors = list( map( cm.get , np.sign(np.diff(y)) ))
lc = LineCollection(segments, colors=colors, linewidths=2)
fig, ax = plt.subplots()
ax.add_collection(lc)
ax.autoscale()
ax.margins(0.1)
plt.show()

How do you create a legend for a contour plot in matplotlib?

I can't seem to find the answer anywhere! I found a discussion here, but trying this I get a TypeError: 'NoneType' object is not iterable:
>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> x, y = np.meshgrid(np.arange(10),np.arange(10))
>>> z = x + y
>>> cs = plt.contourf(x,y,z,levels=[2,3])
>>> cs.collections[0].set_label('test')
>>> plt.legend()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/pyplot.py", line 2791, in legend
ret = gca().legend(*args, **kwargs)
File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/axes.py", line 4475, in legend
self.legend_ = mlegend.Legend(self, handles, labels, **kwargs)
File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/legend.py", line 365, in __init__
self._init_legend_box(handles, labels)
File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/legend.py", line 627, in _init_legend_box
handlebox)
File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/legend_handler.py", line 110, in __call__
handlebox.get_transform())
File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/legend_handler.py", line 352, in create_artists
width, height, fontsize)
File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/legend_handler.py", line 307, in get_sizes
size_max = max(orig_handle.get_sizes())*legend.markerscale**2
TypeError: 'NoneType' object is not iterable
EDIT: I'm looking for something like this:
You could also do it directly with the lines of the contour, without using proxy artists.
import matplotlib
import numpy as np
import matplotlib.cm as cm
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
matplotlib.rcParams['xtick.direction'] = 'out'
matplotlib.rcParams['ytick.direction'] = 'out'
delta = 0.025
x = np.arange(-3.0, 3.0, delta)
y = np.arange(-2.0, 2.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
# difference of Gaussians
Z = 10.0 * (Z2 - Z1)
# Create a simple contour plot with labels using default colors. The
# inline argument to clabel will control whether the labels are draw
# over the line segments of the contour, removing the lines beneath
# the label
plt.figure()
CS = plt.contour(X, Y, Z)
plt.clabel(CS, inline=1, fontsize=10)
plt.title('Simplest default with labels')
labels = ['line1', 'line2','line3','line4',
'line5', 'line6']
for i in range(len(labels)):
CS.collections[i].set_label(labels[i])
plt.legend(loc='upper left')
Will produce:
However, you might also want to look into annotations for your own need. In my opinion it will give you a more fine grained control on where and what you write on the image, here is the same example with some annotation:
### better with annotation, more flexible
plt.figure(2)
CS = plt.contour(X, Y, Z)
plt.clabel(CS, inline=1, fontsize=10)
plt.title('Simplest default with labels')
plt.annotate('some text here',(1.4,1.6))
plt.annotate('some text there',(-2,-1.5))
You can create proxy artists to make the legend:
import numpy as np
import matplotlib.pyplot as plt
x, y = np.meshgrid(np.arange(10),np.arange(10))
z = np.sqrt(x**2 + y**2)
cs = plt.contourf(x,y,z,levels=[2,3,4,6])
proxy = [plt.Rectangle((0,0),1,1,fc = pc.get_facecolor()[0])
for pc in cs.collections]
plt.legend(proxy, ["range(2-3)", "range(3-4)", "range(4-6)"])
plt.show()
Adding to this answer to make it less manual:
import numpy as np
import matplotlib.pyplot as plt
x, y = np.meshgrid(np.arange(10),np.arange(10))
z = np.sqrt(x**2 + y**2)
levels=[2,3,4,6]
cs = plt.contourf(x,y,z,levels=levels)
proxy = [plt.Rectangle((0,0),1,1,fc = pc.get_facecolor()[0])
for pc in cs.collections]
plt.legend(proxy, [f"{lower:2.1f} - {upper:2.1f}" for lower, upper in zip(levels[:-1], levels[1:])])
plt.show()
I had a similar question but needed to go a bit beyond HYRY's answer. To make a package user friendly I wanted ax.legend() to work without requiring users to pass any handles, which can be achieved by passing the label on to the proxy
proxy = plt.Rectangle((0, 0), 1, 1, fc='red', label='some label')
and then adding the proxy to the axis' patches:
ax.patches += [proxy]
(do ax = plt.gca() to get the current axis)
This is described in more detail in this answer.

Categories

Resources