Source code for neurokit2.misc.find_knee
import warnings
import matplotlib.pyplot as plt
import numpy as np
import scipy.interpolate
from ..stats import rescale
[docs]
def find_knee(y, x=None, S=1, show=False, verbose=True):
"""**Find Knee / Elbow**
Find the knee / elbow in a curve using a basic adaptation of the *kneedle* algorithm.
Parameters
----------
x : list
A vector of values for which to detect the knee / elbow.
S : float
The sensitivity parameter allows us to adjust how aggressive we want to be when
detecting knees. Smaller values detect knees quicker, while larger values are more
conservative.
Examples
---------
.. ipython:: python
import neurokit2 as nk
y = np.log(np.arange(1, 100))
y += np.random.normal(0, 0.2, len(y))
nk.find_knee(y, show=True)
References
-----------
* Satopaa, V., Albrecht, J., Irwin, D., & Raghavan, B. (2011, June). Finding a" kneedle" in a
haystack: Detecting knee points in system behavior. In 2011 31st international conference on
distributed computing systems workshops (pp. 166-171). IEEE.
"""
n = len(y)
if n <= 5:
raise ValueError("Input vector must have at least six values.")
if x is None:
x = np.arange(n)
idx = rescale(x, to=[0, 1])
# Smooth using spline
spline = scipy.interpolate.UnivariateSpline(x=idx, y=y, k=5)
smoothed = spline(idx)
# Normalize to the unit square (0 - 1)
smoothed = (smoothed - np.min(smoothed)) / (np.max(smoothed) - np.min(smoothed))
Y_d = smoothed - idx
X_lm = []
Y_lm = []
maxima_ids = []
for i in range(1, n - 1):
if Y_d[i] > Y_d[i - 1] and Y_d[i] > Y_d[i + 1]:
X_lm.append(idx[i])
Y_lm.append(Y_d[i])
maxima_ids.append(i)
T_lm = Y_lm - S * np.sum(np.diff(idx)) / (n - 1)
knee_point_index = _locate(Y_d, T_lm, maxima_ids)
# If no knee point was found, return the last point
if knee_point_index is None:
if verbose is True:
warnings.warn("No knee point found, retuning last.")
knee = n - 1
else:
knee_point = X_lm[knee_point_index]
# Which index
knee = np.where(idx == knee_point)[0][0]
if show is True:
plt.plot(x, y, label="Original")
plt.plot(x, rescale(smoothed, to=[np.nanmin(y), np.nanmax(y)]), label="Smoothed")
plt.plot(x, rescale(Y_d, to=[np.nanmin(y), np.nanmax(y)]), label="Difference")
plt.axvline(x=x[knee], color="red", linestyle="--")
plt.legend()
return x[knee]
def _locate(Y_d, T_lm, maxima_ids):
n = len(Y_d)
for j in range(0, n):
for index, i in enumerate(maxima_ids):
if j <= i:
continue
if Y_d[j] <= T_lm[index]:
return index