How to draw lines with dates on one axis with matplotlib - python

I have a graph with dates on the x axis and a scalar on the y axis.
when I do this:
plt.plot_date([date_from, date_to], [0, 10000], fmt='-', color='r')
it works as expected (although I'm not sure what the fmt='-' part does and would be happy to learn about it)
but, having a lot of line segments, when I do this:
import matplotlib.dates as mpd
lines = [[(mpd.date2num(date_from), 0), (mpd.date2num(date_to), 10000)]]
c = np.array([(1, 0, 0, 1)])
lc = mc.LineCollection(lines, colors=c, linewidths=2)
ax.add_collection(lc)
nothing is appearing; why is that?

Related

matplotlib fill_between ignore areas of non sequential data

I'm trying to fill the area under the curve where the y-value is 1. The x-axis is a datetime array with non-regular values. As you can see the fill also includes areas where there is no x-data. Is there a way to tell fill_between to only fill "between" valid data? i.e. in the plot below I'd like the areas between "missing" samples to be white
tx = array(datetimes) # Array of irregular datetimes
ty = array([ones and zeros]) # Array of ones and zeros same size as tx
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(tx, ty, 'r.')
ax.fill_between(tx, 0, 1, where(ty==1))
This might be what you're aiming for.
If it is then you can use rectangular patches. In this code:
y is the list of values meant to correspond to your 'irregular' pattern.
delta_float measures the horizontal distance in the graph corresponding to delta, the distance between ticks.
Notice that the patches are positioned and sized based on dates and delta_float units respectively.
import datetime
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.dates import DayLocator, HourLocator, DateFormatter, drange, date2num
from numpy import arange
date1 = datetime.datetime(2000, 3, 2)
date2 = datetime.datetime(2000, 3, 6)
delta = datetime.timedelta(hours=6)
dates = drange(date1, date2, delta)
delta_float = (dates[-1]-dates[0])/len(dates)
y = [1,1,0,0,1,1,1,0,1,1,0,0,1,1,0,0]
fig, ax = plt.subplots()
ax.plot_date(dates, y, 'r.')
ax.add_patch(patches.Rectangle((dates[0], 0), delta_float, 1, color='grey'))
ax.add_patch(patches.Rectangle((dates[4], 0), 2*delta_float, 1, color='grey'))
ax.add_patch(patches.Rectangle((dates[8], 0), delta_float, 1, color='grey'))
ax.add_patch(patches.Rectangle((dates[12], 0), delta_float, 1, color='grey'))
ax.xaxis.set_major_locator(DayLocator())
ax.xaxis.set_minor_locator(HourLocator(arange(0, 25, 6)))
ax.xaxis.set_major_formatter(DateFormatter('%Y-%m-%d'))
ax.fmt_xdata = DateFormatter('%Y-%m-%d %H:%M:%S')
fig.autofmt_xdate()
plt.show()

How to plot vectors in python using matplotlib

I am taking a course on linear algebra and I want to visualize the vectors in action, such as vector addition, normal vector, so on.
For instance:
V = np.array([[1,1],[-2,2],[4,-7]])
In this case I want to plot 3 vectors V1 = (1,1), M2 = (-2,2), M3 = (4,-7).
Then I should be able to add V1,V2 to plot a new vector V12(all together in one figure).
when I use the following code, the plot is not as intended
import numpy as np
import matplotlib.pyplot as plt
M = np.array([[1,1],[-2,2],[4,-7]])
print("vector:1")
print(M[0,:])
# print("vector:2")
# print(M[1,:])
rows,cols = M.T.shape
print(cols)
for i,l in enumerate(range(0,cols)):
print("Iteration: {}-{}".format(i,l))
print("vector:{}".format(i))
print(M[i,:])
v1 = [0,0],[M[i,0],M[i,1]]
# v1 = [M[i,0]],[M[i,1]]
print(v1)
plt.figure(i)
plt.plot(v1)
plt.show()
How about something like
import numpy as np
import matplotlib.pyplot as plt
V = np.array([[1,1], [-2,2], [4,-7]])
origin = np.array([[0, 0, 0],[0, 0, 0]]) # origin point
plt.quiver(*origin, V[:,0], V[:,1], color=['r','b','g'], scale=21)
plt.show()
Then to add up any two vectors and plot them to the same figure, do so before you call plt.show(). Something like:
plt.quiver(*origin, V[:,0], V[:,1], color=['r','b','g'], scale=21)
v12 = V[0] + V[1] # adding up the 1st (red) and 2nd (blue) vectors
plt.quiver(*origin, v12[0], v12[1])
plt.show()
NOTE: in Python2 use origin[0], origin[1] instead of *origin
This may also be achieved using matplotlib.pyplot.quiver, as noted in the linked answer;
plt.quiver([0, 0, 0], [0, 0, 0], [1, -2, 4], [1, 2, -7], angles='xy', scale_units='xy', scale=1)
plt.xlim(-10, 10)
plt.ylim(-10, 10)
plt.show()
Your main problem is you create new figures in your loop, so each vector gets drawn on a different figure. Here's what I came up with, let me know if it's still not what you expect:
CODE:
import numpy as np
import matplotlib.pyplot as plt
M = np.array([[1,1],[-2,2],[4,-7]])
rows,cols = M.T.shape
#Get absolute maxes for axis ranges to center origin
#This is optional
maxes = 1.1*np.amax(abs(M), axis = 0)
for i,l in enumerate(range(0,cols)):
xs = [0,M[i,0]]
ys = [0,M[i,1]]
plt.plot(xs,ys)
plt.plot(0,0,'ok') #<-- plot a black point at the origin
plt.axis('equal') #<-- set the axes to the same scale
plt.xlim([-maxes[0],maxes[0]]) #<-- set the x axis limits
plt.ylim([-maxes[1],maxes[1]]) #<-- set the y axis limits
plt.legend(['V'+str(i+1) for i in range(cols)]) #<-- give a legend
plt.grid(b=True, which='major') #<-- plot grid lines
plt.show()
OUTPUT:
EDIT CODE:
import numpy as np
import matplotlib.pyplot as plt
M = np.array([[1,1],[-2,2],[4,-7]])
rows,cols = M.T.shape
#Get absolute maxes for axis ranges to center origin
#This is optional
maxes = 1.1*np.amax(abs(M), axis = 0)
colors = ['b','r','k']
for i,l in enumerate(range(0,cols)):
plt.axes().arrow(0,0,M[i,0],M[i,1],head_width=0.05,head_length=0.1,color = colors[i])
plt.plot(0,0,'ok') #<-- plot a black point at the origin
plt.axis('equal') #<-- set the axes to the same scale
plt.xlim([-maxes[0],maxes[0]]) #<-- set the x axis limits
plt.ylim([-maxes[1],maxes[1]]) #<-- set the y axis limits
plt.grid(b=True, which='major') #<-- plot grid lines
plt.show()
EDIT OUTPUT:
What did you expect the following to do?
v1 = [0,0],[M[i,0],M[i,1]]
v1 = [M[i,0]],[M[i,1]]
This is making two different tuples, and you overwrite what you did the first time... Anyway, matplotlib does not understand what a "vector" is in the sense you are using. You have to be explicit, and plot "arrows":
In [5]: ax = plt.axes()
In [6]: ax.arrow(0, 0, *v1, head_width=0.05, head_length=0.1)
Out[6]: <matplotlib.patches.FancyArrow at 0x114fc8358>
In [7]: ax.arrow(0, 0, *v2, head_width=0.05, head_length=0.1)
Out[7]: <matplotlib.patches.FancyArrow at 0x115bb1470>
In [8]: plt.ylim(-5,5)
Out[8]: (-5, 5)
In [9]: plt.xlim(-5,5)
Out[9]: (-5, 5)
In [10]: plt.show()
Result:
Thanks to everyone, each of your posts helped me a lot.
rbierman code was pretty straight for my question, I have modified a bit and created a function to plot vectors from given arrays. I'd love to see any suggestions to improve it further.
import numpy as np
import matplotlib.pyplot as plt
def plotv(M):
rows,cols = M.T.shape
print(rows,cols)
#Get absolute maxes for axis ranges to center origin
#This is optional
maxes = 1.1*np.amax(abs(M), axis = 0)
colors = ['b','r','k']
fig = plt.figure()
fig.suptitle('Vectors', fontsize=10, fontweight='bold')
ax = fig.add_subplot(111)
fig.subplots_adjust(top=0.85)
ax.set_title('Vector operations')
ax.set_xlabel('x')
ax.set_ylabel('y')
for i,l in enumerate(range(0,cols)):
# print(i)
plt.axes().arrow(0,0,M[i,0],M[i,1],head_width=0.2,head_length=0.1,zorder=3)
ax.text(M[i,0],M[i,1], str(M[i]), style='italic',
bbox={'facecolor':'red', 'alpha':0.5, 'pad':0.5})
plt.plot(0,0,'ok') #<-- plot a black point at the origin
# plt.axis('equal') #<-- set the axes to the same scale
plt.xlim([-maxes[0],maxes[0]]) #<-- set the x axis limits
plt.ylim([-maxes[1],maxes[1]]) #<-- set the y axis limits
plt.grid(b=True, which='major') #<-- plot grid lines
plt.show()
r = np.random.randint(4,size=[2,2])
print(r[0,:])
print(r[1,:])
r12 = np.add(r[0,:],r[1,:])
print(r12)
plotv(np.vstack((r,r12)))
Vector addition performed on random vectors
All nice solutions, borrowing and improvising for special case -> If you want to add a label near the arrowhead:
arr = [2,3]
txt = “Vector X”
ax.annotate(txt, arr)
ax.arrow(0, 0, *arr, head_width=0.05, head_length=0.1)
In order to match the vector lenght and angle with the x,y coordinates of the plot, you can use to following options to plt.quiver:
plt.figure(figsize=(5,2), dpi=100)
plt.quiver(0,0,250,100, angles='xy', scale_units='xy', scale=1)
plt.xlim(0,250)
plt.ylim(0,100)
Quiver is a good method once you figure out its annoying nuances, like not plotting vectors in their original scales. To do as far as I can tell you must pass these params to quiver call as many have pointed out: angles='xy', scale_units='xy', scale=1 AND you should set your plt.xlim and plt.ylim such that you get a square or near square grid. That is the only way I have gotten it to consistently plot the way I want. For instance passing a origin as *[0,0] and U, V as *[5,3] means the resulting plot should be a vector centered at 0,0 origin that goes over 5 units to the right on the x-axis and 3 units up on the y-axis.

I want to plot perpendicular vectors in Python

I would like to simply plot perpendicular vectors in 2D. I've implemented 2 ways to plot them in the code below but the vectors don't "look" perpendicular to me when the plots are drawn. If it makes any difference I'm using Spyder.
import numpy as np
import matplotlib.pyplot as plt
x1=[0,0,4,3]
x2=[0,0,-3,4]
x3=[0,0,3,-4]
soa =np.array([x1,x2,x3])
X,Y,U,V = zip(*soa)
plt.figure()
ax = plt.gca()
ax.quiver(X,Y,U,V,angles='xy',scale_units='xy',scale=1)
ax.set_xlim([-10,10])
ax.set_ylim([-10,10])
plt.draw()
plt.show()
import pylab as pl
from matplotlib import collections as mc
lines = [[(0, 1), (4, 3)], [(-3, 4), (3, -4)]]
c = np.array([(1, 0, 0, 1), (0, 1, 0, 1), (0, 0, 1, 1)])
lc = mc.LineCollection(lines, colors=c, linewidths=2)
fig, ax = pl.subplots()
ax.add_collection(lc)
ax.autoscale()
ax.margins(0.1)
Your problem is that the size of the unit differs on the x and y axes. You need to force them to be equal.
In matplotlib.pyplot, add the line
plt.axes().set_aspect('equal')
just before you show the graph with
plt.show()
I get this result in the IPython console in Spyder:
In pylab, add the line
ax.set_aspect('equal')
at the end. However, these line segments still do not look perpendicular, and that is because they really are not perpendicular. The slope of your first, red line segment is 2/3, so your second, green line segment should have slope -3/2 but it actually has slope -4/3. Perhaps change your line to
lines = [[(0, 1), (4, 3)], [(-3, 4), (3, -5)]]
(I changed the ending -4 to -5) to get the correct second slope. You get a change from this first figure to the second:
and that last does look perpendicular.
The problem is the aspect ratio of the figure canvas.
Use:
plt.figure(figsize=(6,6))

Python: How do I make a plot with variable line thickness that changes gradually?

I have the following code, which makes a plot of variable line thickness:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
x = np.array(range(6))
y = [10, 15,10, 8, 13, 20]
widths = [1, 5,3, 8, 1, 2]
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
lc = LineCollection(segments, linewidths=widths,color='blue')
fig,a = plt.subplots()
a.add_collection(lc)
a.set_xlim(0,7)
a.set_ylim(0,25)
fig.show()
I would like to smooth the transitions between the line thicknesses so that the changes are gradual and look nice. I'm currently using Matplotlib but it doesn't have to be (would use Seaborn, etc. if that works). Does anyone know how to do this?
Another option would be to use Polygons instead of line segments. Unfortunately, I don't know how to translate the width (in points) of a line segment to Data coordinates. Here I've manually adjusted the widths to try and match the desired result.
fig, ax = plt.subplots()
ax.set_xlim((0,5))
ax.set_ylim((0,25))
new_w = np.array(widths)/5. # <<< change according to your needs
# FIXME: this should probably be done using some sort of affine
# transformation already build-in in matplotlib, but I don't know how
for i in range(len(x)-1):
c = [[x[i], y[i]+new_w[i]/2.],
[x[i+1], y[i+1]+new_w[i+1]/2.],
[x[i+1], y[i+1]-new_w[i+1]/2.],
[x[i], y[i]-new_w[i]/2.]
]
p = matplotlib.patches.Polygon(c)
ax.add_patch(p)
plt.show()
One solution, which is not perfect, would be to dissect each of your line segments in small chunks and interpolate the width of the segments. It works pretty well, except at the angles between two line segments, but it's a start:
new_x = np.linspace(x[0],x[-1],100*len(x)) # new_x has 100x more points than x
new_y = np.interp(new_x,x,y)
new_w = np.interp(new_x,x,widths)
points = np.array([new_x, new_y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
lc = LineCollection(segments, linewidths=new_w,color='blue')
fig,a = plt.subplots()
a.add_collection(lc)
a.set_xlim(0,7)
a.set_ylim(0,25)
fig.show()

How do I change the range of the x-axis with datetimes in matplotlib?

I'm trying to plot a graph of dates on the x-axis and values on the y-axis. It works fine, except that I can't get the range of the x-axis to be appropriate. The x-axis range is always Jan 2012 to Jan 2016, despite my dates being from today. I am even specifying that xlim should be the first and last date.
I'm writing this for python-django, if that's relevant.
import datetime
import matplotlib.pyplot as plt
x = [datetime.date(2014, 1, 29), datetime.date(2014, 1, 29), datetime.date(2014, 1, 29)]
y = [2, 4, 1]
fig, ax = plt.subplots()
ax.plot_date(x, y)
ax.set_xlim([x[0], x[-1]])
canvas = FigureCanvas(plt.figure(1))
response = HttpResponse(content_type='image/png')
canvas.print_png(response)
return response
And here is the output:
Edit:
Having seen actual data from the OP, all of the values are at the same date/time. So matplotlib is automatically zooming the x-axis out. You can still manually set the x-axis limits with datetime objects
If I do something like this on matplotlib v1.3.1:
import datetime
import matplotlib.pyplot as plt
x = [datetime.date(2014, 1, 29)] * 3
y = [2, 4, 1]
fig, ax = plt.subplots()
ax.plot_date(x, y, markerfacecolor='CornflowerBlue', markeredgecolor='white')
fig.autofmt_xdate()
ax.set_xlim([datetime.date(2014, 1, 26), datetime.date(2014, 2, 1)])
ax.set_ylim([0, 5])
I get:
And the axes limits match the dates that I specified.
With help from Paul H's solution, I was able to change the range of my time-based x-axis.
Here is a more general solution for other beginners.
import matplotlib.pyplot as plt
import datetime as dt
# Set X range. Using left and right variables makes it easy to change the range.
#
left = dt.date(2020, 3, 15)
right = dt.date(2020, 7, 15)
# Create scatter plot of Positive Cases
#
plt.scatter(
x, y, c="blue", edgecolor="black",
linewidths=1, marker = "o", alpha = 0.8, label="Total Positive Tested"
)
# Format the date into months & days
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%m-%d'))
# Change the tick interval
plt.gca().xaxis.set_major_locator(mdates.DayLocator(interval=30))
# Puts x-axis labels on an angle
plt.gca().xaxis.set_tick_params(rotation = 30)
# Changes x-axis range
plt.gca().set_xbound(left, right)
plt.show()

Categories

Resources