33

When plotting a graph with a discontinuity/asymptote/singularity/whatever, is there any automatic way to prevent Matplotlib from 'joining the dots' across the 'break'? (please see code/image below).
I read that Sage has a [detect_poles] facility that looked good, but I really want it to work with Matplotlib.

import matplotlib.pyplot as plt 
import numpy as np
from sympy import sympify, lambdify
from sympy.abc import x

fig = plt.figure(1) 
ax = fig.add_subplot(111) 

# set up axis 
ax.spines['left'].set_position('zero') 
ax.spines['right'].set_color('none') 
ax.spines['bottom'].set_position('zero') 
ax.spines['top'].set_color('none') 
ax.xaxis.set_ticks_position('bottom') 
ax.yaxis.set_ticks_position('left') 

# setup x and y ranges and precision
xx = np.arange(-0.5,5.5,0.01) 

# draw my curve 
myfunction=sympify(1/(x-2))
mylambdifiedfunction=lambdify(x,myfunction,'numpy')
ax.plot(xx, mylambdifiedfunction(xx),zorder=100,linewidth=3,color='red') 

#set bounds 
ax.set_xbound(-1,6)
ax.set_ybound(-4,4) 

plt.show()

Discontinuity

Adaline Simonian
  • 4,596
  • 2
  • 24
  • 35
Geddes
  • 1,191
  • 2
  • 11
  • 25
  • 1
    Thank you for this question; though you tagged it `python`, be aware that it is more generally a `matplotlib` question; I used myself one of the answers from Julia rather than Python. Regards. – Thomas Baruchel Mar 12 '16 at 08:23

4 Answers4

26

By using masked arrays you can avoid plotting selected regions of a curve.

To remove the singularity at x=2:

import matplotlib.numerix.ma as M    # for older versions, prior to .98
#import numpy.ma as M                # for newer versions of matplotlib
from pylab import *

figure()

xx = np.arange(-0.5,5.5,0.01) 
vals = 1/(xx-2)        
vals = M.array(vals)
mvals = M.masked_where(xx==2, vals)

subplot(121)
plot(xx, mvals, linewidth=3, color='red') 
xlim(-1,6)
ylim(-5,5) 

This simple curve might be a bit more clear on which points are excluded:

xx = np.arange(0,6,.2) 
vals = M.array(xx)
mvals = M.masked_where(vals%2==0, vals)
subplot(122)
plot(xx, mvals, color='b', linewidth=3)
plot(xx, vals, 'rx')
show()

enter image description here

tom10
  • 67,082
  • 10
  • 127
  • 137
  • 2
    why is it that when I run your code I still get the straight vertical line connecting the red curves? – lo tolmencre Jan 17 '17 at 00:08
  • This old answer should be modified for your purposes, as of 2022, [you shouldn't use `pylab` anymore](https://matplotlib.org/stable/api/index.html#module-pylab). – Mr. T Mar 24 '22 at 06:15
17

This may not be the elegant solution you are looking for, but if just want results for most cases, you can "clip" large and small values of your plotted data to +∞ and -∞ respectively. Matplotlib does not plot these. Of course you have to be careful not to make your resolution too low or your clipping threshold too high.

utol = 100.
ltol = -100.
yy = 1/(xx-2)
yy[yy>utol] = np.inf
yy[yy<ltol] = -np.inf

ax.plot(xx, yy, zorder=100, linewidth=3, color='red') 
Paul
  • 42,322
  • 15
  • 106
  • 123
  • This works very well. You could also use `np.nan` instead of `np.inf` if you had another reason for avoiding `∞` – MackM Mar 05 '15 at 20:19
  • It took me a minute to realize yy had to be an instance of np.array and not just a list. – Ron Jensen Feb 08 '22 at 01:29
6

No, I think there is no built-in way to tell matplotlib to ignore these points. After all, it just connects points and knows nothing about functions or what happens in between the points.

However, you can use sympy to find the poles, and then patch the continuous pieces of your function together. Here some admittedly ugly code that does exactly that:

from pylab import *
from sympy import solve
from sympy.abc import x
from sympy.functions.elementary.complexes import im

xmin = -0.5
xmax = 5.5
xstep = 0.01

# solve for 1/f(x)=0 -- we will have poles there
discontinuities = sort(solve(1/(1/(x-2)),x))

# pieces from xmin to last discontinuity
last_b = xmin
for b in discontinuities:
    # check that this discontinuity is inside our range, also make sure it's real
    if b<last_b or b>xmax or im(b):
      continue
    xi = np.arange(last_b, b, xstep)
    plot(xi, 1./(xi-2),'r-')
    last_b = b

# from last discontinuity to xmax
xi = np.arange(last_b, xmax, xstep)
plot(xi, 1./(xi-2),'r-')

xlim(xmin, xmax)
ylim(-4,4)
show()

example

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Benjamin Bannier
  • 55,163
  • 11
  • 60
  • 80
  • 1
    This is very limited in applicability: if the function was tan(x) instead of 1/(x-2), the solve command would not find any roots of 1/tan(x). It only works here because 1/(1/(x-2)) is simplified to x-2. –  Apr 17 '16 at 17:05
0

Had the same issue. The solution for me was to separate X into two different intervals: one before and the other after the singularity. The plot separate curves on the same plot.

AKJ
  • 950
  • 2
  • 13
  • 18