Get the y value of a given x - numpy

I have a simple question but have not found any answer..
Let's have a look at this code :
from matplotlib import pyplot
import numpy
x=[0,1,2,3,4]
y=[5,3,40,20,1]
pyplot.plot(x,y)
It is plotted and all the points ared linked.
Let's say I want to get the y value of x=1,3.
How can I get the x values matching with y=30 ? (there are two)
Many thanks for your help

You could use shapely to find the intersections:
import matplotlib.pyplot as plt
import numpy as np
import shapely.geometry as SG
x=[0,1,2,3,4]
y=[5,3,40,20,1]
line = SG.LineString(list(zip(x,y)))
y0 = 30
yline = SG.LineString([(min(x), y0), (max(x), y0)])
coords = np.array(line.intersection(yline))
print(coords[:, 0])
fig, ax = plt.subplots()
ax.axhline(y=y0, color='k', linestyle='--')
ax.plot(x, y, 'b-')
ax.scatter(coords[:, 0], coords[:, 1], s=50, c='red')
plt.show()
finds solutions for x at:
[ 1.72972973 2.5 ]

The following code might do what you want. The interpolation of y(x) is straight forward, as the x-values are monotonically increasing. The problem of finding the x-values for a given y is not so easy anymore, once the function is not monotonically increasing as in this case. So you still need to know roughly where to expect the values to be.
import numpy as np
import scipy.interpolate
import scipy.optimize
x=np.array([0,1,2,3,4])
y=np.array([5,3,40,20,1])
#if the independent variable is monotonically increasing
print np.interp(1.3, x, y)
# if not, as in the case of finding x(y) here,
# we need to find the zeros of an interpolating function
y0 = 30.
initial_guess = 1.5 #for the first zero,
#initial_guess = 3.0 # for the secon zero
f = scipy.interpolate.interp1d(x,y,kind="linear")
fmin = lambda x: np.abs(f(x)-y0)
s = scipy.optimize.fmin(fmin, initial_guess, disp=False)
print s

I use python 3.
print(numpy.interp(1.3, x, y))
Y = 30
eps = 1e-6
j = 0
for i, ((x0, x1), (y0, y1)) in enumerate(zip(zip(x[:-1], x[1:]), zip(y[:-1], y[1:]))):
dy = y1 - y0
if abs(dy) < eps:
if y0 == Y:
print('There are infinite number of solutions')
else:
t = (Y - y0)/dy
if 0 < t < 1:
sol = x0 + (x1 - x0)*t
print('solution #{}: {}'.format(j, sol))
j += 1

Related

Lambdify a function in two variables and plot a surface

I have a function f(x,y) where t is a parameter. I'm trying to plot the function where t = 1 for x and y values ranging from -5 to 5. The plot doesn't render.
import sympy as sp
import numpy as np
import matplotlib.pyplot as plt
%matplotlib notebook
C = sv.CoordSys3D("")
x, y, z = C.base_scalars()
t = sp.symbols("t")
f = sp.sin(2*sp.pi*t)*sp.exp(-(x-3*sp.sin(sp.pi*t))**2 -(y-3*sp.cos(sp.pi*t))**2)
fig = plt.figure(figsize=(6, 6))
ax = fig.add_subplot(projection='3d')
X = np.linspace(-5,5,100)
Y = np.linspace(-5,5,100)
xvals, yvals = np.meshgrid(X,Y)
zvals = sp.lambdify((x,y),f.subs(t,1),"numpy")(xvals,yvals)
ax.plot_surface(xvals,yvals,zvals)
plt.show()
I get an error 'int' object has no attribute 'ndim' which I don't know how to solve.
The problem is that when you execute f.subs(t,1) it returns a number (zero in this case). So, f=0 is the expression that you are going to lambdify. Let's see the function generated by lambdify:
import inspect
print(inspect.getsource(sp.lambdify((x,y),f.subs(t,1),"numpy")))
# def _lambdifygenerated(Dummy_25, Dummy_24):
# return 0
So, no matter the values and shape of xvals and yvals, that numerical function will always return 0, which is an integer number.
However, ax.plot_surface requires zvals to have the same shape as xvals or yval. Luckily, we can easily fix that with a simple if statement:
import sympy as sp
import sympy.vector as sv
import numpy as np
import matplotlib.pyplot as plt
C = sv.CoordSys3D("")
x, y, z = C.base_scalars()
t = sp.symbols("t")
f = sp.sin(2*sp.pi*t)*sp.exp(-(x-3*sp.sin(sp.pi*t))**2 -(y-3*sp.cos(sp.pi*t))**2)
fig = plt.figure(figsize=(6, 6))
ax = fig.add_subplot(projection='3d')
X = np.linspace(-5,5,100)
Y = np.linspace(-5,5,100)
xvals, yvals = np.meshgrid(X,Y)
zvals = sp.lambdify((x,y),f.subs(t,1),"numpy")(xvals,yvals)
# if zvals is just a number, create a proper matrix
if not isinstance(zvals, np.ndarray):
zvals = zvals * np.ones_like(xvals)
ax.plot_surface(xvals,yvals,zvals)
plt.show()
The fact that this doesn't render is bug in lambdify that it doesn't work well for constant expressions.
Your real problem though is that the expression you are trying to plot is just zero:
In [5]: f
Out[5]:
2 2
- (x_ - 3⋅sin(π⋅t)) - (y_ - 3⋅cos(π⋅t))
ℯ ⋅sin(2⋅π⋅t)
In [6]: f.subs(t, 1)
Out[6]: 0

plotting a graph of a sequence and get a dimension error

I'm trying to create a plot in Python to illustrate how my sequence changes as n grows. I get a dimension error. How can I fix this?
My code:
import matplotlib.pyplot as plt
import numpy as np
x = np.zeros(101)
x[0] = 0
for n in range(0, 101):
x[n] = x[n-1] - n
if x[n] < 0:
x[n] = x[n-1] + n
y = set(x)
print(y)
i = np.linspace(0, 100)
plt.plot(y, i, 'g')
Error:
ValueError: x and y must have same first dimension, but have shapes (1,) and (50,)
The problem is you are using plt.plot with a set and because of that its saying first dimension has shape (1,). The x-argument in plt.plot should be a array-like or scalar you can read more in the documentation so you can convert it to a list:
y = list(set(x))
Also you need to make sure that the for each value in y there is a corresponding value in i (y and i need to be the same shape). So you need to set np.linspace to return len(y) values:
i = np.linspace(0, 100, len(y))
The code:
import matplotlib.pyplot as plt
import numpy as np
x = np.zeros(101)
x[0] = 0
for n in range(0,101):
x[n] = x[n-1] - n
if x[n]<0:
x[n] = x[n-1] + n
y = list(set(x))
i = np.linspace(0, 100, len(y))
plt.plot(y, i,'g')
plt.show()
Output:

Matplotlib 3d barplot failing to draw just one face

import numpy as np
import matplotlib.pyplot as plt
x, y = np.array([[x, y] for x in range(5) for y in range(x+1)]).T
z = 1/ (5*x + 5)
fig = plt.figure()
ax = fig.gca(projection = '3d')
ax.bar3d(x, y, np.zeros_like(z), dx = 1, dy = 1, dz = z)
yields
How do I get the face at (1,0) to display properly?
There is currently no good solution to this. Fortunately though, it happens only for some viewing angles. So you can choose an angle where it plots fine, e.g.
ax.view_init(azim=-60, elev=25)

Matplotlib/pyplot: Auto adjust unit of y Axis

I would like to modify the Y axis unit of the plot indicated below. Preferable would be the use of units like M (Million), k (Thousand) for large numbers. For example, the y Axis should look like: 50k, 100k, 150k, etc.
The plot below is generated by the following code snippet:
plt.autoscale(enable=True, axis='both')
plt.title("TTL Distribution")
plt.xlabel('TTL Value')
plt.ylabel('Number of Packets')
y = graphy # data from a sqlite query
x = graphx # data from a sqlite query
width = 0.5
plt.bar(x, y, width, align='center', linewidth=2, color='red', edgecolor='red')
fig = plt.gcf()
plt.show()
I saw this post and thought I could write my own formatting function:
def y_fmt(x, y):
if max_y > 1000000:
val = int(y)/1000000
return '{:d} M'.format(val)
elif max_y > 1000:
val = int(y) / 1000
return '{:d} k'.format(val)
else:
return y
But I missed that there is no plt.yaxis.set_major_formatter(tick.FuncFormatter(y_fmt)) function available for the bar plot I am using.
How I can achieve a better formatting of the Y axis?
[]
In principle there is always the option to set custom labels via plt.gca().yaxis.set_xticklabels().
However, I'm not sure why there shouldn't be the possibility to use matplotlib.ticker.FuncFormatter here. The FuncFormatter is designed for exactly the purpose of providing custom ticklabels depending on the ticklabel's position and value.
There is actually a nice example in the matplotlib example collection.
In this case we can use the FuncFormatter as desired to provide unit prefixes as suffixes on the axes of a matplotlib plot. To this end, we iterate over the multiples of 1000 and check if the value to be formatted exceeds it. If the value is then a whole number, we can format it as integer with the respective unit symbol as suffix. On the other hand, if there is a remainder behind the decimal point, we check how many decimal places are needed to format this number.
Here is a complete example:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter
def y_fmt(y, pos):
decades = [1e9, 1e6, 1e3, 1e0, 1e-3, 1e-6, 1e-9 ]
suffix = ["G", "M", "k", "" , "m" , "u", "n" ]
if y == 0:
return str(0)
for i, d in enumerate(decades):
if np.abs(y) >=d:
val = y/float(d)
signf = len(str(val).split(".")[1])
if signf == 0:
return '{val:d} {suffix}'.format(val=int(val), suffix=suffix[i])
else:
if signf == 1:
print val, signf
if str(val).split(".")[1] == "0":
return '{val:d} {suffix}'.format(val=int(round(val)), suffix=suffix[i])
tx = "{"+"val:.{signf}f".format(signf = signf) +"} {suffix}"
return tx.format(val=val, suffix=suffix[i])
#return y
return y
fig, ax = plt.subplots(ncols=3, figsize=(10,5))
x = np.linspace(0,349,num=350)
y = np.sinc((x-66.)/10.3)**2*1.5e6+np.sinc((x-164.)/8.7)**2*660000.+np.random.rand(len(x))*76000.
width = 1
ax[0].bar(x, y, width, align='center', linewidth=2, color='red', edgecolor='red')
ax[0].yaxis.set_major_formatter(FuncFormatter(y_fmt))
ax[1].bar(x[::-1], y*(-0.8e-9), width, align='center', linewidth=2, color='orange', edgecolor='orange')
ax[1].yaxis.set_major_formatter(FuncFormatter(y_fmt))
ax[2].fill_between(x, np.sin(x/100.)*1.7+100010, np.cos(x/100.)*1.7+100010, linewidth=2, color='#a80975', edgecolor='#a80975')
ax[2].yaxis.set_major_formatter(FuncFormatter(y_fmt))
for axes in ax:
axes.set_title("TTL Distribution")
axes.set_xlabel('TTL Value')
axes.set_ylabel('Number of Packets')
axes.set_xlim([x[0], x[-1]+1])
plt.show()
which provides the following plot:
You were pretty close; one (possibly) confusing thing about FuncFormatter is that the first argument is the tick value, and the second the tick position , which (when named x,y) can be confusing for the y-axis. For clarity, I renamed them in the example below.
The function should take in two inputs (tick value x and position pos) and return a string
(http://matplotlib.org/api/ticker_api.html#matplotlib.ticker.FuncFormatter)
Working example:
import numpy as np
import matplotlib.pylab as pl
import matplotlib.ticker as tick
def y_fmt(tick_val, pos):
if tick_val > 1000000:
val = int(tick_val)/1000000
return '{:d} M'.format(val)
elif tick_val > 1000:
val = int(tick_val) / 1000
return '{:d} k'.format(val)
else:
return tick_val
x = np.arange(300)
y = np.random.randint(0,2000000,x.size)
width = 0.5
pl.bar(x, y, width, align='center', linewidth=2, color='red', edgecolor='red')
pl.xlim(0,300)
ax = pl.gca()
ax.yaxis.set_major_formatter(tick.FuncFormatter(y_fmt))

How to drop connecting lines where the function is discontinuous

I'm plotting some functions that have several discontinuities. Each function is given as a list. I want to connect points with lines only where the function is continuous.
Here is a simplified example of what plot is doing.
x=linspace(0,1,100)
y=zeros(100)
y[x<0.5] = x[x<0.5]
y[x>=0.5] = 1 + x[x>=0.5]
plot(x, y, '-o')
There is a discontinuity at x=0.5, but plot connects all points with lines regardless.
My functions are different of course. They typically have several discontinuities in different places. The criterion for the discontinuity is simple. Say, if the function jumps by more than 0.5, I assume it is discontinuous at that point.
Is there an option in plot to tell it to drop the connecting lines between the points where the function is discontinuous? I recall being able to do that easily with gnuplot.
use nan to break the line into multiple segments:
import numpy as np
from pylab import *
x=linspace(0,1,100)
y=zeros(100)
y[x<0.5] = x[x<0.5]
y[x>=0.5] = 1 + x[x>=0.5]
pos = np.where(np.abs(np.diff(y)) >= 0.5)[0]
x[pos] = np.nan
y[pos] = np.nan
plot(x, y, '-o')
Edit:
to insert nan at discontinuities:
pos = np.where(np.abs(np.diff(y)) >= 0.5)[0]+1
x = np.insert(x, pos, np.nan)
y = np.insert(y, pos, np.nan)
Here is my suggestion for plotting tan(x):
import matplotlib.pyplot as plt
from math import *
x_lim = 3*pi/2
y_lim = 5
n = 1000
X = []
Y = []
Z = []
for i in range(0,2*n):
x = -x_lim + i*x_lim/n
y = tan(x)
if y<y_lim and y>-y_lim:
X.append(x)
Y.append(y)
else:
if len(X)>0 and len(Y)>0:
Z.append([X,Y])
del X,Y
X = []
Y = []
for i in range(0, len(Z)):
plt.plot(Z[i][0],Z[i][1])
plt.grid(True)
plt.show()