## imports
import numpy as np
import matplotlib.pyplot as plt
import plotly.graph_objects as goLinear Algebra - SVD Fit
Fit a line with Singular Value Decomposition
# fixing numpy random state for reproducibility
np.random.seed(0)# input data
n = 25
points = np.array( [5*np.arange(n),5+5*np.arange(n),5*np.ones(n)] ).T + 0.5-np.random.rand(n,3)
pointsarray([[ -0.43239394, 5.2846018 , 4.64166236],
[ 4.69710663, 10.34085376, 4.89428804],
[ 10.38433813, 14.77211184, 4.86253772],
[ 14.68806144, 20.02061545, 4.58513691],
[ 20.45065105, 25.20711143, 4.7849474 ],
[ 25.08189079, 30.32704865, 5.39278925],
[ 29.68266089, 35.02685702, 4.61771633],
[ 34.76671087, 40.09027379, 5.12648899],
[ 39.98436165, 44.61094005, 4.76272142],
[ 45.49484704, 49.80584215, 4.58049259],
[ 49.78954424, 55.32299422, 5.01648187],
[ 55.35968398, 60.14100472, 4.56288296],
[ 59.57669469, 65.21716315, 5.16036896],
[ 64.89978713, 69.5368027 , 5.35219867],
[ 70.24308336, 74.62644317, 5.00810777],
[ 74.60103891, 80.3144821 , 4.96733141],
[ 80.17373037, 85.18345744, 5.05312304],
[ 85.06692255, 90.14265312, 4.58502923],
[ 89.76825581, 94.77245301, 5.21008655],
[ 94.92229058, 99.72082057, 4.70440963],
[100.15546954, 104.72912724, 4.7641061 ],
[105.35849351, 109.63405453, 5.05867853],
[110.01358955, 115.05163082, 4.932154 ],
[114.87883075, 120.00182043, 4.63321146],
[119.87226524, 125.09857205, 5.08330824]])
# calculating the mean of the points
avg = np.mean(points, axis=0)
# subtracting the mean from all points
subtracted = points - avg
# performing SVD
_, _, V = np.linalg.svd(subtracted)# find the direction vector (which is the right singular vector corresponding to the largest singular value)
direction = V[0, :]
# A line is defined by the average and its direction
p0 = avg
d = direction
print(d)[7.08153575e-01 7.06058100e-01 6.87968476e-04]
d0 = np.array([0, 0, 1])
angle = np.arccos(np.dot(d0,d)/(np.linalg.norm(d0) * np.linalg.norm(d)))
print(angle*180/np.pi)89.96058230676098
pa = p0 + (-100)*d
pb = p0 + 100*d
print(pa, pb)[-10.83624093 -5.60662059 4.82481353] [130.79447411 135.60499941 4.96240723]
## plotly
trace1 = go.Scatter3d(
x=[pa[0],pb[0]],
y=[pa[1],pb[1]],
z=[pa[2],pb[2]],
mode='lines',
name='3D fitted line',
line=go.scatter3d.Line(color='rgb(255,0,0)', width=10),
hoverinfo='none')
labels = []
for i in range(n): labels += [str(i)]
trace2 = go.Scatter3d(
x=points[:,0],
y=points[:,1],
z=points[:,2],
mode='markers',
name='Points',
marker=go.scatter3d.Marker(
symbol='cross',
opacity=1,
color='rgb(0,200,127)'),
text=labels,
hoverinfo='text')
layout = go.Layout(
title="3D line fitting",
scene=go.layout.Scene(
xaxis_title="x",
yaxis_title="y",
zaxis_title="z",
camera=dict(
up=dict(x=0, y=0, z=1),
center=dict(x=0, y=0, z=0),
eye=dict(x=0, y=2.5, z=0))))
fig=go.Figure(data=[trace1, trace2], layout=layout)
fig.show(renderer="iframe_connected", config={'showLink': False})fitline
fitline (points)