8

How can I get from a plot in Python an exact value on y - axis? I have two arrays vertical_data and gradient(temperature_data) and I plotted them as:

plt.plot(gradient(temperature_data),vertical_data)
plt.show()

Plot shown here:

plot

I need the zero value but it is not exactly zero, it's a float.

JohanC
  • 71,591
  • 8
  • 33
  • 66
Arcturus
  • 83
  • 1
  • 3
  • I guess the problem description is a bit misleading, if present at all. What you want is to find the zero of a numpy array. This has nothing to do with matplotlib. (Also there are more than one zero). This is in general a non-trivial task. But depending on the accuracy needed, can be simplified. – ImportanceOfBeingErnest Oct 24 '17 at 12:27
  • Well I should see it in that plotted graph, I think – Arcturus Oct 24 '17 at 15:04

1 Answers1

20

I did not find a good answer to the question of how to find the roots or zeros of a numpy array, so here is a solution, using simple linear interpolation.

import numpy as np
N = 750
x = .4+np.sort(np.random.rand(N))*3.5
y = (x-4)*np.cos(x*9.)*np.cos(x*6+0.05)+0.1


def find_roots(x,y):
    s = np.abs(np.diff(np.sign(y))).astype(bool)
    return x[:-1][s] + np.diff(x)[s]/(np.abs(y[1:][s]/y[:-1][s])+1)

z = find_roots(x,y)

import matplotlib.pyplot as plt

plt.plot(x,y)
plt.plot(z, np.zeros(len(z)), marker="o", ls="", ms=4)

plt.show()

enter image description here

Of course you can invert the roles of x and y to get

plt.plot(y,x)
plt.plot(np.zeros(len(z)),z, marker="o", ls="", ms=4)

enter image description here


Because people where asking how to get the intercepts at non-zero values y0, note that one may simply find the zeros of y-y0 then.
y0 = 1.4
z = find_roots(x,y-y0)
# ...
plt.plot(z, np.zeros(len(z))+y0)

enter image description here


People were also asking how to get the intersection between two curves. In that case it's again about finding the roots of the difference between the two, e.g.

x = .4 + np.sort(np.random.rand(N)) * 3.5
y1 = (x - 4) * np.cos(x * 9.) * np.cos(x * 6 + 0.05) + 0.1
y2 = (x - 2) * np.cos(x * 8.) * np.cos(x * 5 + 0.03) + 0.3

z = find_roots(x,y2-y1)

plt.plot(x,y1)
plt.plot(x,y2, color="C2")
plt.plot(z, np.interp(z, x, y1), marker="o", ls="", ms=4, color="C1")

enter image description here

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • 1
    Hey, just wanted to say that's a neat idea to turn places where the sign of the function changes sign into indices like this. Thanks! – Dominik Stańczak Oct 24 '17 at 16:14
  • @JohanC For regularly spaced data one could replace `np.diff(x)[s]` by `(x[1]-x[0])`. That would give a very small performance gain, if that's what you mean. – ImportanceOfBeingErnest Feb 26 '20 at 23:32
  • Why the last "+1" in `return x[:-1][s] + np.diff(x)[s]/(np.abs(y[1:][s]/y[:-1][s])+1)`? – catwith Sep 13 '21 at 06:09
  • @ImportanceOfBeingErnest Brilliant and elegant! Do you have a ref for the equation used / LaTeX version? :) – jtlz2 Apr 07 '22 at 11:34
  • Also, not that it probably matters, but how (i) expensive and (ii) accurate (as in second-order residuals) is this compared to other methods? Thanks again! :) – jtlz2 Apr 07 '22 at 11:35
  • Also also, could you use it iteratively and if so how would you yourself tackle it? – jtlz2 Apr 07 '22 at 11:38
  • Re REF, e.g. one of https://en.wikipedia.org/wiki/Root-finding_algorithms / Numerical Recipes? – jtlz2 Apr 07 '22 at 11:39
  • PS: Even handles `x.dtype` of `datetime` - amazing! – jtlz2 Apr 07 '22 at 11:50
  • Also, how would you handle only the rising (or falling) edges? – jtlz2 Apr 09 '22 at 20:10
  • @jtlz2 for posterity, rising edges can be selectively handled with `s = np.abs(np.maximum(np.diff(np.sign(y)), 0)).astype(bool)`. Same goes for falling edges with `np.minimum`. – yklcs Aug 16 '22 at 15:28