How can I plot line segments in python? Here's an example of the lines I want to plot alongside scatterplot data. However, in this example, the line with slope of 1 should run from 0 to 3 only, and the line with slope of -1 should run from 4 to 8 only. I have the slope and intercept of each line, as well as the starting and ending x values.
plt.scatter(x_1, y_1)
plt.axline((0,9), slope = -1, color = 'black')
plt.axline((0,0), slope = 1, color = 'black')
plt.show()
I'm not sure from your output but it looks like you have maybe two different data sets which you want to plot straight lines through. Below I am breaking 3 of your data points into two lists of x, y coordinates, and the other 4 into two lists of x, y coordinates.
Here is the link for the line of best fit one liner
import matplotlib.pyplot as plt
import numpy
# data which you want to fit lines through
x1 = [0, 1, 3]
y1 = [0, 1, 3]
x2 = [4, 5, 6, 8]
y2 = [5, 4, 3, 2]
# plots the two data set points
plt.scatter(x1, y1, color='black')
plt.scatter(x2, y2, color='blue')
# plot will connect a line between the range of the data
plt.plot(x1, y1, color='black')
plt.plot(x2, y2, color='blue')
# line of best fit one liner
plt.plot(np.unique(x1), np.poly1d(np.polyfit(x1, y1, 1))(np.unique(x1)),color='red')
plt.plot(np.unique(x2), np.poly1d(np.polyfit(x2, y2, 1))(np.unique(x2)), color='red')
plt.ylim([-1, 7])
plt.show()
By knowing both the intercept and the slope of your lines, you can essentially use the equation for a straight line to get the corresponding y-values of your endpoints on the x-axis. Then you can plot these points and style the plot to get a black line between the points. This solution looks like the following
import matplotlib.pyplot as plt
import numpy as np
slope1, intercept1 = 1, 0
slope2, intercept2 = -1, 9
l1x = np.array([0,3])
l1y = l1x*slope1+intercept1
l2x = np.array([4,8])
l2y = np.array(l2x*slope2+intercept2)
plt.plot(l1x, l1y, 'k-', l2x, l2y, 'k-')
# The following two lines are to make the plot look like the image in the question
plt.xlim([0,8])
plt.ylim([0,9])
plt.show()
The line can be defined either by two points xy1 and xy2, or by one point xy1 and a slope.
plt.scatter(x_1, y_1)
plt.axline((4,4), (8,0), color = 'blue')
plt.axline((0,0), (3,3), color = 'black')
plt.show()
Related
I would like to know how to smoothly draw a line on top of two lines I have. Take for example
the following code:
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 would like a draw a line from the top orange dot down to the blue line
where they intersect and up to the top right blue dot. I want this line to be smooth
preferably. How do I do this?
I was thinking I could make an array of the orange line to the blue then
have an array of the blue line till the blue dot. I would then draw a line here but this
is quite a crude solution. I am not sure if there is something smarter I can do.
Also it would be good to know how to generalise this to smooth lines (which is why I don't want this array solution). Thanks in advance.
first you have to calculate intersection between lines. I gave you example how to do it. Function draw_smooth_line will draw smooth line between 3 points. You can add logic to getting top points from you list if you need it.
import matplotlib.pyplot as plt
from scipy.ndimage.filters import gaussian_filter1d
import numpy as np
from scipy.interpolate import interp1d
def draw_smooth_line(left_x, left_y, mid_x, mid_y, right_x, right_y):
x = [left_x, mid_x, right_x]
y = [left_y, mid_y, right_y]
x_new = np.linspace(left_x, right_x,500)
f = interp1d(x, y, kind='quadratic')
y_smooth=f(x_new)
plt.plot (x_new,y_smooth)
def findIntersection(x1,y1,x2,y2,x3,y3,x4,y4):
px= ( (x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4) ) / ( (x1-x2)*(y3-y4)-(y1-y2)*(x3-x4) )
py= ( (x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4) ) / ( (x1-x2)*(y3-y4)-(y1-y2)*(x3-x4) )
return [px, py]
x1, y1 = [-1, 12], [1, 4]
x2, y2 = [1, 10], [3, 2]
x3, y3 = findIntersection(x1[0],y1[0],x1[1],y1[1],x2[0],y2[0],x2[1],y2[1])
draw_smooth_line(x1[1], y1[1], x3, y3, x2[0], y2[0])
plt.plot(x1, y1, x2, y2, x3, y3, marker = 'o')
plt.show()
Here is result
As the title says, I am trying to plot a system of linear equations to get the intersection point of the 2 equations.
8a-b = 9
4a+9b = 7.
below is the code i have tried.
import matplotlib.pyplot as plt
from numpy.linalg import inv
import numpy as np
a = np.array([[8,-1],[4,9]])
b = np.array([9,7])
c = np.linalg.solve(a,b)
plt.figure()
# Set x-axis range
plt.xlim((-10,10))
# Set y-axis range
plt.ylim((-10,10))
# Draw lines to split quadrants
plt.plot([-10,-10],[10,10], linewidth=4, color='blue' )
#draw the equations
plt.plot(a[0][0],a[0][1], linewidth=2, color='red' )
plt.plot(a[1][0],a[1][1], linewidth=2, color='red' )
plt.plot(c[0],c[1], marker='x', color="black")
plt.title('Quadrant plot')
plt.show()
I get only the intersection point, but not the lines on the 2D plane as shown in the below graph.
I want something like this.
To plot the lines it's easiest if you rearrange your equations to in terms of b. This way 8a-b=9 becomes b=8a-9 and 4a+9b=7 becomes b=(7-4a)/9
It also looks like you were trying to draw the "axis" of the graph, I've fixed this in the code below too.
The following should do the trick:
import matplotlib.pyplot as plt
import numpy as np
a = np.array([[8,-1],[4,9]])
b = np.array([9,7])
c = np.linalg.solve(a,b)
plt.figure()
# Set x-axis range
plt.xlim((-10,10))
# Set y-axis range
plt.ylim((-10,10))
# Draw lines to split quadrants
plt.plot([-10, 10], [0, 0], color='C0')
plt.plot([0, 0], [-10, 10], color='C0')
# Draw line 8a-b=9 => b=8a-9
x = np.linspace(-10, 10)
y = 8 * x - 9
plt.plot(x, y, color='C2')
# Draw line 4a+9b=7 => b=(7-4a)/9
y = (7 - 4*x) / 9
plt.plot(x, y, color='C2')
# Add solution
plt.scatter(c[0], c[1], marker='x', color='black')
# Annotate solution
plt.annotate('({:0.3f}, {:0.3f})'.format(c[0], c[1]), c+0.5)
plt.title('Quadrant plot')
plt.show()
This gave me the following plot:
x1 = np.arange(-10, 10, 0.01) # between -10 and 10, 0.01 stepsize
y1 = 8*x1-9
x2 = np.arange(-10, 10, 0.01) # between -10 and 10, 0.01 stepsize
y2 = (7-4*x2)/9
This is the equations of your lines.
Now plot these using plt.plot(x1,y1) etc.
plt.figure()
# Set x-axis range
plt.xlim((-10,10))
# Set y-axis range
plt.ylim((-10,10))
# Draw lines to split quadrants
plt.plot([-10,-10],[10,10], linewidth=4, color='blue' )
plt.plot(x1,y1)
plt.plot(x2,y2)
#draw the equations
plt.plot(a[0][0],a[0][1], linewidth=2, color='red' )
plt.plot(a[1][0],a[1][1], linewidth=2, color='red' )
plt.plot(c[0],c[1], marker='x', color="black")
plt.title('Quadrant plot')
plt.show()
I have two matrix, x and y. x has size of 10 rows and 50 columns, and so is y.
My data is row-to-row paired. It means that the
x[0][:] <-> y[0][:]
x[1][:] <-> y[1][:]
x[2][:] <-> y[2][:]
......
x[49][:] <-> y[0][:]
When I use following command to do the plot, the
plot(x[:][:],y[:][:],'b-o')
or
plot(x,y,'b-o')
to do the plot, the '-' connects the dots in horizontal direction like following:
However, when I do only plot one row of signal:
plot(x[0][:],y[0][:],'b-o')
it looks correct:
I would like for the '-' to connect the dots in a horizontal fashion. Something like this:
in stead of doing a for loop, how do I do it in matrix format? Thanks.
Make some data to demonstrate.
import numpy as np
from matplotlib import pyplot as plt
x = np.matrix(
[
[1, 1, 1, 1],
[2, 2, 2, 2],
[3, 3, 3, 3],
[4, 4, 4, 4]
]
)
y = x.transpose()
# Vertical Lines of grid:
plt.plot(x, y, 'b-o')
plt.show()
# Horizontal Lines
plt.plot(x, y, 'b-o')
plt.show()
# Together (this is what I think you want)
plt.plot(y, x, 'b-o')
plt.plot(x, y, 'b-o')
plt.show()
If you try to concatenate them to do it in one large matrix it does some seemingly silly things by connecting a couple of points that we really do not want connected.
# sillyness
x1 = np.concatenate((x, y), axis=0)
y1 = np.concatenate((y, x), axis=0)
plt.plot(x1, y1, 'b-o')
plt.show()
In R, there is a function called abline in which a line can be drawn on a plot based on the specification of the intercept (first argument) and the slope (second argument). For instance,
plot(1:10, 1:10)
abline(0, 1)
where the line with an intercept of 0 and the slope of 1 spans the entire range of the plot. Is there such a function in Matplotlib?
A lot of these solutions are focusing on adding a line to the plot that fits the data. Here's a simple solution for adding an arbitrary line to the plot based on a slope and intercept.
import matplotlib.pyplot as plt
import numpy as np
def abline(slope, intercept):
"""Plot a line from slope and intercept"""
axes = plt.gca()
x_vals = np.array(axes.get_xlim())
y_vals = intercept + slope * x_vals
plt.plot(x_vals, y_vals, '--')
I know this question is a couple years old, but since there is no accepted answer, I'll add what works for me.
You could just plot the values in your graph, and then generate another set of values for the coordinates of the best fit line and plot that over your original graph. For example, see the following code:
import matplotlib.pyplot as plt
import numpy as np
# Some dummy data
x = [1, 2, 3, 4, 5, 6, 7]
y = [1, 3, 3, 2, 5, 7, 9]
# Find the slope and intercept of the best fit line
slope, intercept = np.polyfit(x, y, 1)
# Create a list of values in the best fit line
abline_values = [slope * i + intercept for i in x]
# Plot the best fit line over the actual values
plt.plot(x, y, '--')
plt.plot(x, abline_values, 'b')
plt.title(slope)
plt.show()
As of 2021, in matplotlib 3.3.4, it supports drawing lines with slope value and a point.
fig, ax = plt.subplots()
ax.axline((0, 4), slope=3., color='C0', label='by slope')
ax.set_xlim(0, 1)
ax.set_ylim(3, 5)
ax.legend()
X = np.array([1, 2, 3, 4, 5, 6, 7])
Y = np.array([1.1,1.9,3.0,4.1,5.2,5.8,7])
scatter (X,Y)
slope, intercept = np.polyfit(X, Y, 1)
plot(X, X*slope + intercept, 'r')
It looks like this feature will be part of version 3.3.0:
matplotlib.axes.Axes.axline
You'll be, for example, able to draw a red line through points (0, 0) and (1, 1) using
axline((0, 0), (1, 1), linewidth=4, color='r')
I couldn't figure a way to do it without resorting to callbacks, but this seems to work fairly well.
import numpy as np
from matplotlib import pyplot as plt
class ABLine2D(plt.Line2D):
"""
Draw a line based on its slope and y-intercept. Additional arguments are
passed to the <matplotlib.lines.Line2D> constructor.
"""
def __init__(self, slope, intercept, *args, **kwargs):
# get current axes if user has not specified them
if not 'axes' in kwargs:
kwargs.update({'axes':plt.gca()})
ax = kwargs['axes']
# if unspecified, get the current line color from the axes
if not ('color' in kwargs or 'c' in kwargs):
kwargs.update({'color':ax._get_lines.color_cycle.next()})
# init the line, add it to the axes
super(ABLine2D, self).__init__([], [], *args, **kwargs)
self._slope = slope
self._intercept = intercept
ax.add_line(self)
# cache the renderer, draw the line for the first time
ax.figure.canvas.draw()
self._update_lim(None)
# connect to axis callbacks
self.axes.callbacks.connect('xlim_changed', self._update_lim)
self.axes.callbacks.connect('ylim_changed', self._update_lim)
def _update_lim(self, event):
""" called whenever axis x/y limits change """
x = np.array(self.axes.get_xbound())
y = (self._slope * x) + self._intercept
self.set_data(x, y)
self.axes.draw_artist(self)
I suppose for the case of (intercept, slope) of (0, 1) the following function could be used and extended to accommodate other slopes and intercepts, but won't readjust if axis limits are changed or autoscale is turned back on.
def abline():
gca = plt.gca()
gca.set_autoscale_on(False)
gca.plot(gca.get_xlim(),gca.get_ylim())
import matplotlib.pyplot as plt
plt.scatter(range(10),range(10))
abline()
plt.draw()
I'd like to expand on the answer from David Marx, where we are making sure that the sloped line does not expand over the original plotting area.
Since the x-axis limits are used to calculate the y-data for the sloped line, we need to make sure, that the calculated y-data does not extend the given ymin - ymax range. If it does crop the displayed data.
def abline(slope, intercept,**styles):
"""Plot a line from slope and intercept"""
axes = plt.gca()
xmin,xmax = np.array(axes.get_xlim())
ymin,ymax = np.array(axes.get_ylim()) # get also y limits
x_vals = np.linspace(xmin,xmax,num=1000) #increased sampling (only actually needed for large slopes)
y_vals = intercept + slope * x_vals
locpos = np.where(y_vals<ymax)[0] # if data extends above ymax
locneg = np.where(y_vals>ymin)[0] # if data extends below ymin
# select most restricitive condition
if len(locpos) >= len(locneg):
loc = locneg
else:
loc = locpos
plt.plot(x_vals[loc], y_vals[loc], '--',**styles)
return y_vals
Here's a possible workaround I came up with: suppose I have my intercept coordinates stored as x_intercept and y_intercept, and the slope (m) saved as my_slope which was found through the renowned equation m = (y2-y1)/(x2-x1), or in whichever way you managed to find it.
Using the other famous general equation for a line y = mx + q, I define the function find_second_point that first computes the q (since m, x and y are known) and then computes another random point that belongs to that line.
Once I have the two points (the initial x_intercept,y_intercept and the newly found new_x,new_y), I simply plot the segment through those two points. Here's the code:
import numpy as np
import matplotlib.pyplot as plt
x_intercept = 3 # invented x coordinate
y_intercept = 2 # invented y coordinate
my_slope = 1 # invented slope value
def find_second_point(slope,x0,y0):
# this function returns a point which belongs to the line that has the slope
# inserted by the user and that intercepts the point (x0,y0) inserted by the user
q = y0 - (slope*x0) # calculate q
new_x = x0 + 10 # generate random x adding 10 to the intersect x coordinate
new_y = (slope*new_x) + q # calculate new y corresponding to random new_x created
return new_x, new_y # return x and y of new point that belongs to the line
# invoke function to calculate the new point
new_x, new_y = find_second_point(my_slope , x_intercept, y_intercept)
plt.figure(1) # create new figure
plt.plot((x_intercept, new_x),(y_intercept, new_y), c='r', label='Segment')
plt.scatter(x_intercept, y_intercept, c='b', linewidths=3, label='Intercept')
plt.scatter(new_x, new_y, c='g', linewidths=3, label='New Point')
plt.legend() # add legend to image
plt.show()
here is the image generated by the code:
Short answer inspired by kite.com:
plt.plot(x, s*x + i)
Reproducible code:
import numpy as np
import matplotlib.pyplot as plt
i=3 # intercept
s=2 # slope
x=np.linspace(1,10,50) # from 1 to 10, by 50
plt.plot(x, s*x + i) # abline
plt.show()
One can simply create a list with the line's equation obtained from a particular intercept and slope. Put those values in a list and plot it against any set of numbers you would like. For example- (Lr being the Linear regression model)
te= []
for i in range(11):
te.append(Lr.intercept_ + Lr.coef_*i)
plt.plot(te, '--')
Gets the job done.
You can write a simple function by converting Slope-Intercept form to 2-Point Form.
def mxline(slope, intercept, start, end):
y1 = slope*start + intercept
y2 = slope*end + intercept
plt.plot([start, end], [y1, y2])
Calling the function
mxline(m,c, 0, 20)
OUTPUT
I am plotting to different datasets into one graph with pylab.plot(), which works great. But one dataset has values between 0% an 25% and the other has values between 75% and 100%. I want to skip 30% to 70% on the y-axis to save some space. Do you have any suggestions how this might be work with pyplot?
EDIT:
For clearness I added the following graphic. I want to skip 30% to 60% on the y axis, so that the red line and the green line come closer together.
The solution is based on Space_C0wb0ys post.
fig = pylab.figure()
ax = fig.add_subplot(111)
ax.plot( range(1,10), camean - 25, 'ro-' )
ax.plot( range(1,10), oemean , 'go-' )
ax.plot( range(1,10), hlmean , 'bo-' )
ax.set_yticks(range(5, 60, 5))
ax.set_yticklabels(["5","10","15","20","25","30","...","65","70","75"])
ax.legend(('ClassificationAccuracy','One-Error','HammingLoss'),loc='upper right')
pylab.show()
This code creates the following graphic.
You could subtract 40 from the x-values for your second functions to make the range of x-values continuous. This would give you a range from 0% to 70%. Then you can make set the tics and labes of the x-axis as follows:
x_ticks = range(71, 0, 10)
a.set_xticks(x_ticks)
a.set_xticklabels([str(x) for x in [0, 10, 20, 30, 70, 80, 90, 100]])
Where a is the current axes. So basically, you plot your functions in the range from 0% to 70%, but label the axis with a gap.
To illustrate - the following script:
from numpy import arange
import matplotlib.pyplot as plt
x1 = arange(0, 26) # first function
y1 = x1**2
x2 = arange(75, 100) # second function
y2 = x2*4 + 10
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x1, y1)
ax.plot(x2 - 40, y2) # shift second function 40 to left
ax.set_xticks(range(0, 61, 5)) # set custom x-ticks
# set labels for x-ticks - labels have the gap we want
ax.set_xticklabels([str(x) for x in range(0, 26, 5) + range(70, 101, 5)])
plt.show()
Produces the following plot (note the x-labels):
The matplotlib documentation actually has an example of how to do this.
The basic idea is to break up the plotting into two subplots, putting the same graph on each plot, then change the axes for each one to only show the specific part, then make it look nicer.
So, let's apply this. Imagine this is your starting code:
import matplotlib.pyplot as plt
import random, math
# Generates data
i = range(10)
x = [math.floor(random.random() * 5) + 67 for i in range(10)]
y = [math.floor(random.random() * 5) + 22 for i in range(10)]
z = [math.floor(random.random() * 5) + 13 for i in range(10)]
# Original plot
fig, ax = plt.subplots()
ax.plot(i, x, 'ro-')
ax.plot(i, y, 'go-')
ax.plot(i, z, 'bo-')
plt.show()
And we went to make it so that x is shown split off from the rest.
First, we want to plot the same graph twice, one on top of the other. To do this, the plotting function needs to be generic. Now it should look something like this:
# Plotting function
def plot(ax):
ax.plot(i, x, 'ro-')
ax.plot(i, y, 'go-')
ax.plot(i, z, 'bo-')
# Draw the graph on two subplots
fig, (ax1, ax2) = plt.subplots(2, 1)
plot(ax1)
plot(ax2)
Now this seems worse, but we can change the range for each axis to focus on what we want. For now I'm just choosing easy ranges that I know will capture all the data, but I'll focus on making the axes equal later.
# Changes graph axes
ax1.set_ylim(65, 75) # Top graph
ax2.set_ylim(5, 30) # Bottom graph
This is getting closer to what we're looking for. Now we need to just make it look a little nicer:
# Hides the spines between the axes
ax1.spines.bottom.set_visible(False)
ax2.spines.top.set_visible(False)
ax1.xaxis.tick_top()
ax1.tick_params(labeltop=False) # Don't put tick labels at the top
ax2.xaxis.tick_bottom()
# Adds slanted lines to axes
d = .5 # proportion of vertical to horizontal extent of the slanted line
kwargs = dict(
marker=[(-1, -d), (1, d)],
markersize=12,
linestyle='none',
color='k',
mec='k',
mew=1,
clip_on=False
)
ax1.plot([0, 1], [0, 0], transform=ax1.transAxes, **kwargs)
ax2.plot([0, 1], [1, 1], transform=ax2.transAxes, **kwargs)
Finally, let's fix the axes. Here you need to do a little math and decide more on the layout. For instance, maybe we want to make the top graph smaller, since the bottom graph has two lines. To do that, we need to change the height ratios for the subplots, like so:
# Draw the graph on two subplots
# Bottom graph is twice the size of the top one
fig, (ax1, ax2) = plt.subplots(2, 1, gridspec_kw={'height_ratios': [1, 2]})
Finally, It's a good idea to make the axes match. In this case, because the bottom image is twice the size of the top one, we need to change the axes of one to reflect that. I've chosen to modify the top one in this time. The bottom graph covers a range of 25, which means the top one should cover a range of 12.5.
# Changes graph axes
ax1.set_ylim(60.5, 73) # Top graph
ax2.set_ylim(5, 30) # Bottom graph
This looks good enough to me. You can play around more with the axes or tick labels if you don't want the ticks to overlap with the broken lines.
Final code:
import matplotlib.pyplot as plt
import random, math
# Generates data
i = range(10)
x = [math.floor(random.random() * 5) + 67 for i in range(10)]
y = [math.floor(random.random() * 5) + 22 for i in range(10)]
z = [math.floor(random.random() * 5) + 13 for i in range(10)]
# Plotting function
def plot(ax):
ax.plot(i, x, 'ro-')
ax.plot(i, y, 'go-')
ax.plot(i, z, 'bo-')
# Draw the graph on two subplots
# Bottom graph is twice the size of the top one
fig, (ax1, ax2) = plt.subplots(2, 1, gridspec_kw={'height_ratios': [1, 2]})
plot(ax1)
plot(ax2)
# Changes graph axes
ax1.set_ylim(60.5, 73) # Top graph
ax2.set_ylim(5, 30) # Bottom graph
# Hides the spines between the axes
ax1.spines.bottom.set_visible(False)
ax2.spines.top.set_visible(False)
ax1.xaxis.tick_top()
ax1.tick_params(labeltop=False) # Don't put tick labels at the top
ax2.xaxis.tick_bottom()
# Adds slanted lines to axes
d = .5 # proportion of vertical to horizontal extent of the slanted line
kwargs = dict(
marker=[(-1, -d), (1, d)],
markersize=12,
linestyle='none',
color='k',
mec='k',
mew=1,
clip_on=False
)
ax1.plot([0, 1], [0, 0], transform=ax1.transAxes, **kwargs)
ax2.plot([0, 1], [1, 1], transform=ax2.transAxes, **kwargs)
plt.show()