在肘部曲线中如何找到曲线开始上升的点?
数据挖掘
Python
图表
离群值
matplotlib
2021-10-05 10:38:45
2个回答
TL;博士
使用下面的两个函数来获取肘部的索引:
elbow_index = find_elbow(data, get_data_radiant(data))
编辑:我将下面的所有代码都放入了一个名为的 python 包中。现在,您可以简单地这样做:
from kneebow.rotor import Rotor
rotor = Rotor()
rotor.fit_rotate(data)
elbow_index = rotor.get_elbow_index()
长答案
如果这条曲线代表所有曲线(例如单峰曲线和连续曲线),那么一种快速而肮脏的方法是将其旋转到一定程度并简单地取最小值。
旋转可以通过与旋转矩阵相乘来完成
在哪里 是以弧度为单位的所需角度。
在 python 中,您可以使用以下函数执行此操作:
def find_elbow(data, theta):
# make rotation matrix
co = np.cos(theta)
si = np.sin(theta)
rotation_matrix = np.array(((co, -si), (si, co)))
# rotate data vector
rotated_vector = data.dot(rotation_matrix)
# return index of elbow
return np.where(rotated_vector == rotated_vector.min())[0][0]
请注意,这theta
是以弧度为单位的角度。你可以通过 来计算np.radians(angle)
。
重要提示:要记住的一件事是 x 轴和 y 轴可能有不同的比例。因此,在您的情节中,看起来 45° 旋转就足够了,但实际上并非如此。因此,您可以使用以下函数来计算您应该使用哪个辐射。它将数据中从最小值到最大值的斜率转换为弧度:
def get_data_radiant(data):
return np.arctan2(data[:, 1].max() - data[:, 1].min(),
data[:, 0].max() - data[:, 0].min())
如果您想获得角度,请运行np.rad2deg(get_data_radiant(data))
.
例子
如何使用
让我们在与您类似的样本数据上测试该方法:
# Let's define our sample data:
data = np.array([
[1, 1],
[2, 2],
[3, 3],
[4, 4],
[5, 5],
[6, 6],
[7, 7],
[8, 8],
[9, 16],
[10, 32],
[11, 64],
[12, 128],
[13, 256],
[14, 512]
])
# y is linear until (8,8) and increases exponentially afterwards
plt.scatter(data[:, 0], data[:, 1])
绘制数据为我们提供了下图:
现在,让我们尝试通过组合上面的函数来找到肘部:
elbow_index = find_elbow(data, get_data_radiant(data))
print(elbow_index) # 10
print(data[elbow_index]) # array([11, 64])
详细
总而言之,我们只是计算了从最小值到最大值的斜率,然后以斜率为零的方式旋转绘图。随后,我们取数据的最小值得到肘部。
我们可以通过以下方式获得旋转角度:
angle = np.rad2deg(get_data_radiant(data))
print(angle) # 88.543
左边的图有一条橙色线的斜率。轴的刻度使它看起来像是 45° 的角度,而实际上,它的角度是 88.5°!矢量旋转后,数据看起来像右边的图。从这些数据中,我们取了第 11 个数据点的最小值。
缺点
请注意,这种方法有一个缺点:轴的比例越不相等,它会越多地选择有利于较大轴的点。
在使用此方法之前,您可以尝试使用 scikit-learnMinMaxScaler
来扩展您的数据,以减少这种影响。如果您使用膝盖弓包,默认情况下会缩放数据。
# Find Elbow Point, Trade off Point
def elbow_point(data):
curve = data
nPoints = len(curve)
allCoord = np.vstack((range(nPoints), curve)).T
np.array([range(nPoints), curve])
firstPoint = allCoord[0]
lineVec = allCoord[-1] - allCoord[0]
lineVecNorm = lineVec / np.sqrt(np.sum(lineVec**2))
vecFromFirst = allCoord - firstPoint
scalarProduct = np.sum(vecFromFirst * np.matlib.repmat(lineVecNorm, nPoints, 1), axis=1)
vecFromFirstParallel = np.outer(scalarProduct, lineVecNorm)
vecToLine = vecFromFirst - vecFromFirstParallel
distToLine = np.sqrt(np.sum(vecToLine ** 2, axis=1))
idxOfBestPoint = np.argmax(distToLine)
return idxOfBestPoint
其它你可能感兴趣的问题