Python - matplotlib: find intersection of lineplots

We could use scipy.interpolate.PiecewisePolynomial to create functions which are defined by your piecewise-linear data.

p1=interpolate.PiecewisePolynomial(x1,y1[:,np.newaxis])
p2=interpolate.PiecewisePolynomial(x2,y2[:,np.newaxis])

We could then take the difference of these two functions,

def pdiff(x):
    return p1(x)-p2(x)

and use optimize.fsolve to find the roots of pdiff:

import scipy.interpolate as interpolate
import scipy.optimize as optimize
import numpy as np

x1=np.array([1.4,2.1,3,5.9,8,9,23])
y1=np.array([2.3,3.1,1,3.9,8,9,11])
x2=np.array([1,2,3,4,6,8,9])
y2=np.array([4,12,7,1,6.3,8.5,12])    

p1=interpolate.PiecewisePolynomial(x1,y1[:,np.newaxis])
p2=interpolate.PiecewisePolynomial(x2,y2[:,np.newaxis])

def pdiff(x):
    return p1(x)-p2(x)

xs=np.r_[x1,x2]
xs.sort()
x_min=xs.min()
x_max=xs.max()
x_mid=xs[:-1]+np.diff(xs)/2
roots=set()
for val in x_mid:
    root,infodict,ier,mesg = optimize.fsolve(pdiff,val,full_output=True)
    # ier==1 indicates a root has been found
    if ier==1 and x_min<root<x_max:
        roots.add(root[0])
roots=list(roots)        
print(np.column_stack((roots,p1(roots),p2(roots))))

yields

[[ 3.85714286  1.85714286  1.85714286]
 [ 4.60606061  2.60606061  2.60606061]]

The first column is the x-value, the second column is the y-value of the first PiecewisePolynomial evaluated at x, and the third column is the y-value for the second PiecewisePolynomial.


Parametric solution

If the sequences {x1,y1} and {x2,y2} define arbitrary (x,y) curves, rather than y(x) curves, we need a parametric approach to finding intersections. Since it's not entirely obvious how to do so, and because @unutbu's solution uses a defunct interpolator in SciPy, I thought it might be useful to revisit this question.

import numpy as np
from numpy.linalg import norm
from scipy.optimize import fsolve
from scipy.interpolate import interp1d
import matplotlib.pyplot as plt

x1_array = np.array([1,2,3,4,6,8,9])
y1_array = np.array([4,12,7,1,6.3,8.5,12])
x2_array = np.array([1.4,2.1,3,5.9,8,9,23])
y2_array = np.array([2.3,3.1,1,3.9,8,9,11])

s1_array = np.linspace(0,1,num=len(x1_array))
s2_array = np.linspace(0,1,num=len(x2_array))

# Arguments given to interp1d:
#  - extrapolate: to make sure we don't get a fatal value error when fsolve searches
#                 beyond the bounds of [0,1]
#  - copy: use refs to the arrays
#  - assume_sorted: because s_array ('x') increases monotonically across [0,1]
kwargs_ = dict(fill_value='extrapolate', copy=False, assume_sorted=True)
x1_interp = interp1d(s1_array,x1_array, **kwargs_)
y1_interp = interp1d(s1_array,y1_array, **kwargs_)
x2_interp = interp1d(s2_array,x2_array, **kwargs_)
y2_interp = interp1d(s2_array,y2_array, **kwargs_)
xydiff_lambda = lambda s12: (np.abs(x1_interp(s12[0])-x2_interp(s12[1])),
                             np.abs(y1_interp(s12[0])-y2_interp(s12[1])))

s12_intercept, _, ier, mesg \
    = fsolve(xydiff_lambda, [0.5, 0.3], full_output=True) 

xy1_intercept = x1_interp(s12_intercept[0]),y1_interp(s12_intercept[0])
xy2_intercept = x2_interp(s12_intercept[1]),y2_interp(s12_intercept[1])

plt.plot(x1_interp(s1_array),y1_interp(s1_array),'b.', ls='-', label='x1 data')
plt.plot(x2_interp(s2_array),y2_interp(s2_array),'r.', ls='-', label='x2 data')
if s12_intercept[0]>0 and s12_intercept[0]<1:
    plt.plot(*xy1_intercept,'bo', ms=12, label='x1 intercept')
    plt.plot(*xy2_intercept,'ro', ms=8, label='x2 intercept')
plt.legend()

print('intercept @ s1={}, s2={}\n'.format(s12_intercept[0],s12_intercept[1]), 
      'intercept @ xy1={}\n'.format(np.array(xy1_intercept)), 
      'intercept @ xy2={}\n'.format(np.array(xy2_intercept)), 
      'fsolve apparent success? {}: "{}"\n'.format(ier==1,mesg,), 
      'is intercept really good? {}\n'.format(s12_intercept[0]>=0 and s12_intercept[0]<=1 
      and s12_intercept[1]>=0 and s12_intercept[1]<=1 
      and np.isclose(0,norm(xydiff_lambda(s12_intercept)))) )

which returns, for this particular choice of initial guess [0.5,0.3]:

intercept @ s1=0.4761904761904762, s2=0.3825944170771757
intercept @ xy1=[3.85714286 1.85714286]
intercept @ xy2=[3.85714286 1.85714286]
fsolve apparent success? True: "The solution converged."
is intercept really good? True

This method only finds one intersection: we would need to iterate over several initial guesses (as @unutbu's code does), check their veracity, and eliminate duplicates using np.close. Note that fsolve may falsely indicate successful detection of an intersection in the return value ier, which is why the extra checking is done here.

Here's the plot for this solution: Example solution