Cublic spline interpolation produces straight lines - matplotlib

I would like to obtain a smooth curve going through specific points with integer coordinates. Instead of that I get straight line segments between the points. I tried interp1d(x,y,kind='cubic') and also CubicSpline, nothing works. Here is my code:
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import interp1d,CubicSpline
x = np.arange(34)
y = [8,3,0,1,6,2,1,7,6,2,0,2,6,0,1,6,2,2,0,2,7,0,2,8,6,3,6,2,0,1,6,2,7,2]
f = CubicSpline(x, y)
plt.figure(figsize=(10,3))
plt.plot(x, y, 'o', x, f(x))
plt.show()
and here is the result:
Can you tell me how to get smooth curves instead?

Now you are using the original x-values to draw the curve. You need a new array with much more intermediate x-values. Numpy's np.linspace() creates such an array between a given minimum and maximum.
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import interp1d, CubicSpline
y = [8, 3, 0, 1, 6, 2, 1, 7, 6, 2, 0, 2, 6, 0, 1, 6, 2, 2, 0, 2, 7, 0, 2, 8, 6, 3, 6, 2, 0, 1, 6, 2, 7, 2]
x = np.arange(len(y))
f = CubicSpline(x, y)
plt.figure(figsize=(10, 3))
xs = np.linspace(x.min(), x.max(), 500)
plt.plot(x, y, 'o', xs, f(xs))
plt.tight_layout()
plt.show()

Related

Numpy.polyfit Not Returning Polynomial

I am trying to create a python program in which the user inputs a set of data and the program spits out an output in which it creates a graph with a line/polynomial which best fits the data.
This is the code:
from matplotlib import pyplot as plt
import numpy as np
x = []
y = []
x_num = 0
while True:
sequence = int(input("Input 1 number in the sequence, type 9040321 to stop"))
if sequence == 9040321:
poly = np.polyfit(x, y, deg=2, rcond=None, full=False, w=None, cov=False)
plt.plot(poly)
plt.scatter(x, y, c="blue", label="data")
plt.legend()
plt.show()
break
else:
y.append(sequence)
x.append(x_num)
x_num += 1
I used the polynomial where I inputed 1, 2, 4, 8 each in separate inputs. MatPlotLib graphed it properly, however, for the degree of 2, the output was the following image:
This is clearly not correct, however I am unsure what the problem is. I think it has something to do with the degree, however when I change the degree to 3, it still does not fit. I am looking for a graph like y=sqrt(x) to go over each of the points and when that is not possible, create the line that fits the best.
Edit: I added a print(poly) feature and for the selected input above, it gives [0.75 0.05 1.05]. I do not know what to make of this.
Approximation by a second degree polynomial
np.polyfit gives the coefficients of a polynomial close to the given points. To plot the polynomial as a smooth curve with matplotlib, you need to calculate a lot of x,y pairs. Using np.linspace(start, stop, numsteps) for the xs, numpy's vectorization allows calculating all the corresponding ys in one go. E.g. ys = a * x**2 + b * x + c.
from matplotlib import pyplot as plt
import numpy as np
x = [0, 1, 2, 3, 4, 5, 6]
y = [1, 2, 4, 8, 16, 32, 64]
plt.scatter(x, y, color='crimson', label='given points')
poly = np.polyfit(x, y, deg=2, rcond=None, full=False, w=None, cov=False)
xs = np.linspace(min(x), max(x), 100)
ys = poly[0] * xs ** 2 + poly[1] * xs + poly[2]
plt.plot(xs, ys, color='dodgerblue', label=f'$({poly[0]:.2f})x^2+({poly[1]:.2f})x + ({poly[2]:.2f})$')
plt.legend()
plt.show()
Higher degree approximating polynomials
Given N points, an N-1 degree polynomial can pass exactly through each of them. Here is an example with 7 points and polynomials of up to degree 6,
from matplotlib import pyplot as plt
import numpy as np
x = [0, 1, 2, 3, 4, 5, 6]
y = [1, 2, 4, 8, 16, 32, 64]
plt.scatter(x, y, color='black', zorder=3, label='given points')
for degree in range(0, len(x)):
poly = np.polyfit(x, y, deg=degree, rcond=None, full=False, w=None, cov=False)
xs = np.linspace(min(x) - 0.5, max(x) + 0.5, 100)
ys = sum(poly_i * xs**i for i, poly_i in enumerate(poly[::-1]))
plt.plot(xs, ys, label=f'degree {degree}')
plt.legend()
plt.show()
Another example
x = [0, 1, 2, 3, 4]
y = [1, 1, 6, 5, 5]
import numpy as np
import matplotlib.pyplot as plt
x = [1, 2, 3, 4]
y = [1, 2, 4, 8]
coeffs = np.polyfit(x, y, 2)
print(coeffs)
poly = np.poly1d(coeffs)
print(poly)
x_cont = np.linspace(0, 4, 81)
y_cont = poly(x_cont)
plt.scatter(x, y)
plt.plot(x_cont, y_cont)
plt.grid(1)
plt.show()
Executing the code, you have the graph above and this is printed in the terminal:
[ 0.75 -1.45 1.75]
2
0.75 x - 1.45 x + 1.75
It seems to me that you had false expectations about the output of polyfit.

How to plot a tuple as x axis and a list on y axis

Suppose I have a df in the following form
import pandas as pd
import numpy as np
import matplotlib as plt
import matplotlib.pyplot as plt
col1
(0, 0, 0, 0) 1
(0, 0, 0, 2) 2
(0, 0, 2, 2) 3
(0, 2, 2, 2) 4
I want to plot my index in x axis and col1 in y axis.
What I tried
plt.plot(list(df.index), df['col1'])
However, it generates a plot that is not what I am looking for.
If you give a list of 4-tuples as x for plt.plot(), they are interpreted as 4 line plots, one with the first elements from the tuples, one with the second elements, etc.
You can convert the tuples to strings to show them as such:
import matplotlib.pyplot as plt
import pandas as pd
df = pd.DataFrame({'y': [1, 2, 3, 4]}, index=[(0, 0, 0, 0), (0, 0, 0, 2), (0, 0, 2, 2), (0, 2, 2, 2)])
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(12, 3))
ax1.plot(list(df.index), df['y'])
ax2.plot([str(i) for i in df.index], df['y'])
plt.show()

Matplotlib 3d alpha transparency bug?

Using matplotlib, I am trying to create a 3d plot that has three semi-transparent planes along the xy, yz, and xz planes. I am basing my code off of this post, which has a partial workaround for a transparency bug reported three years ago.
If you try out the below code and rotate the graph, you'll see that there are sudden color shifts in the areas where the planes overlap. For example below you see the center area suddenly change from green to blue. Is there a further workaround to prevent this?
Here is my code:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d as mp3d
xy = [ (-1, -1, 0),
( 1, -1, 0),
( 1, 1, 0),
(-1, 1, 0),
]
yz = [ (0, -1, -1),
(0, 1, -1),
(0, 1, 1),
(0, -1, 1),
]
xz = [ (-1, 0, -1),
( 1, 0, -1),
( 1, 0, 1),
(-1, 0, 1),
]
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter([-1, -1, -1, -1, 1, 1, 1, 1], [-1, -1, 1, 1, -1, -1, 1, 1], [-1, 1, -1, 1, -1, 1, -1, 1])
face1 = mp3d.art3d.Poly3DCollection([xy], alpha=0.5, linewidth=1)
face2 = mp3d.art3d.Poly3DCollection([yz], alpha=0.5, linewidth=1)
face3 = mp3d.art3d.Poly3DCollection([xz], alpha=0.5, linewidth=1)
# This is the key step to get transparency working
alpha = 0.5
face1.set_facecolor((0, 0, 1, alpha))
face2.set_facecolor((0, 1, 0, alpha))
face3.set_facecolor((1, 0, 0, alpha))
ax.add_collection3d(face1)
ax.add_collection3d(face2)
ax.add_collection3d(face3)
plt.show()
As suggested in the comments, you can divide every plane into its four quadrant planes and draw those individually. This way matplotlib is able to determine which of them should be in front and the planes obey transparency.
A minimal example:
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d as mp3d
a = np.array([( 0, 0, 0),( 1, 0, 0),( 1, 1, 0),( 0, 1, 0)])
R1 = np.array([[0,-1,0],[1,0,0],[0,0,1]])
R2 = (R1[::-1].T)[:,[1,0,2]]
R3 = (R1[::-1])[:,[1,0,2]]
f = lambda a,r: np.matmul(r, a.T).T
g = lambda a,r: [a, f(a,r), f(f(a,r),r), f(f(f(a,r),r),r)]
fig = plt.figure()
ax = fig.add_subplot(111, projection=Axes3D.name)
ax.scatter([-1,1], [-1,1], [-1,1], alpha=0.0)
for i, ind , r in zip(range(3),[[0,1,2],[2,0,1],[1,2,0]], [R1,R2,R3]):
xy = g(a[:,ind], r )
for x in xy:
face1 = mp3d.art3d.Poly3DCollection([x] , alpha=0.5, linewidth=1)
face1.set_facecolor((i//2, i%2, i==0, 0.5))
ax.add_collection3d(face1)
plt.show()

Two Lower X axes matplot lib

I was wondering if it is possible to have two distinct X axes in matplotlib, but not so that they are on opposite sides of the graph. Instead, would it be possible to put them next to each other?
Is this what you're looking for?
from mpl_toolkits.axes_grid1 import host_subplot
import mpl_toolkits.axisartist as AA
import matplotlib.pyplot as plt
host = host_subplot(111, axes_class=AA.Axes)
plt.subplots_adjust(bottom=0.2)
par2 = host.twiny()
offset = -40
new_fixed_axis = par2.get_grid_helper().new_fixed_axis
par2.axis["bottom"] = new_fixed_axis(loc="bottom",
axes=par2,
offset=(0, offset))
par2.axis["top"].toggle(all=False)
host.set_xlim(0, 2)
host.set_ylim(0, 2)
host.set_ylabel("Distance")
host.set_xlabel("Density")
par2.set_xlabel("Velocity")
p1, = host.plot([0, 1, 2], [0, 1, 2], label="Density")
p3, = par2.plot([50, 30, 15], [0, 1, 2], label="Velocity")
par2.set_xlim(1, 65)
host.legend()
host.axis["bottom"].label.set_color(p1.get_color())
par2.axis["bottom"].label.set_color(p3.get_color())

Matplotlib and numpy histogram2d axis issue

I'm struggling to get the axis right:
I've got the x and y values, and want to plot them in a 2d histogram (to examine correlation). Why do I get a histogram with limits from 0-9 on each axis? How do I get it to show the actual value ranges?
This is a minimal example and I would expect to see the red "star" at (3, 3):
import numpy as np
import matplotlib.pyplot as plt
x = (1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 3)
y = (1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 3)
xedges = range(5)
yedges = range(5)
H, xedges, yedges = np.histogram2d(y, x)
im = plt.imshow(H, origin='low')
plt.show()
I think the problem is twofold:
Firstly you should have 5 bins in your histogram (it's set to 10 as default):
H, xedges, yedges = np.histogram2d(y, x,bins=5)
Secondly, to set the axis values, you can use the extent parameter, as per the histogram2d man pages:
im = plt.imshow(H, interpolation=None, origin='low',
extent=[xedges[0], xedges[-1], yedges[0], yedges[-1]])
If I understand correctly, you just need to set interpolation='none'
import numpy as np
import matplotlib.pyplot as plt
x = (1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 3)
y = (1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 3)
xedges = range(5)
yedges = range(5)
H, xedges, yedges = np.histogram2d(y, x)
im = plt.imshow(H, origin='low', interpolation='none')
Does that look right?