import numpy as np from scipy.linalg import lstsq import matplotlib.pyplot as plt def generateData(n = 30): # similar to peaks() function in MATLAB g = np.linspace(-3.0, 3.0, n) X, Y = np.meshgrid(g, g) X, Y = X.reshape(-1,1), Y.reshape(-1,1) Z = 3 * (1 - X)**2 * np.exp(- X**2 - (Y+1)**2) \ - 10 * (X/5 - X**3 - Y**5) * np.exp(- X**2 - Y**2) \ - 1/3 * np.exp(- (X+1)**2 - Y**2) return X, Y, Z def exp2model(e): # C[i] * X^n * Y^m return ' + '.join([ f'C[{i}]' + ('*' if x>0 or y>0 else '') + (f'X^{x}' if x>1 else 'X' if x==1 else '') + ('*' if x>0 and y>0 else '') + (f'Y^{y}' if y>1 else 'Y' if y==1 else '') for i,(x,y) in enumerate(e) ]) # generate some random 3-dim points X, Y, Z = generateData() # 1=linear, 2=quadratic, 3=cubic, ..., nth degree order = 11 # calculate exponents of design matrix #e = [(x,y) for x in range(0,order+1) for y in range(0,order-x+1)] e = [(x,y) for n in range(0,order+1) for y in range(0,n+1) for x in range(0,n+1) if x+y==n] eX = np.asarray([[x] for x,_ in e]).T eY = np.asarray([[y] for _,y in e]).T # best-fit polynomial surface A = (X ** eX) * (Y ** eY) C,resid,_,_ = lstsq(A, Z) # coefficients # calculate R-squared from residual error r2 = 1 - resid[0] / (Z.size * Z.var()) # print summary print(f'data = {Z.size}x3') print(f'model = {exp2model(e)}') print(f'coefficients =\n{C}') print(f'R2 = {r2}') # uniform grid covering the domain of the data XX,YY = np.meshgrid(np.linspace(X.min(), X.max(), 20), np.linspace(Y.min(), Y.max(), 20)) # evaluate model on grid A = (XX.reshape(-1,1) ** eX) * (YY.reshape(-1,1) ** eY) ZZ = np.dot(A, C).reshape(XX.shape) # plot points and fitted surface ax = plt.figure().add_subplot(projection='3d') ax.scatter(X, Y, Z, c='r', s=2) ax.plot_surface(XX, YY, ZZ, rstride=1, cstride=1, alpha=0.2, linewidth=0.5, edgecolor='b') ax.axis('tight') ax.view_init(azim=-60.0, elev=30.0) ax.set_xlabel('X') ax.set_ylabel('Y') ax.set_zlabel('Z') plt.show()