def test_valid_curve_direction(): """Test that arguments to curve and direction are valid""" with pytest.raises(ValueError): kl = KneeLocator(range(3), [1, 3, 5], curve="bad curve") with pytest.raises(ValueError): kl = KneeLocator(range(3), [1, 3, 5], direction="bad direction")
def test_plot_knee(): """Test that plotting is functional""" x, y = dg.figure2() kl = KneeLocator(x, y, S=1.0, curve="concave", interp_method="interp1d") num_figures_before = plt.gcf().number kl.plot_knee() num_figures_after = plt.gcf().number assert num_figures_before < num_figures_after
def test_flat_maxima(): """The global maxima has a sequentially equal value in the difference curve""" x = [ 0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, ] y = [ 1, 0.787701317715959, 0.7437774524158126, 0.6559297218155198, 0.5065885797950219, 0.36749633967789164, 0.2547584187408492, 0.16251830161054173, 0.10395314787701318, 0.06734992679355783, 0.043923865300146414, 0.027818448023426062, 0.01903367496339678, 0.013177159590043924, 0.010248901903367497, 0.007320644216691069, 0.005856515373352855, 0.004392386530014641, ] # When S=0.0 the first local maximum is found. kl = KneeLocator(x, y, curve="convex", direction="decreasing", S=0.0) assert math.isclose(kl.knee, 1.0, rel_tol=0.05) # When S=1.0 the global maximum is found. kl = KneeLocator(x, y, curve="convex", direction="decreasing", S=1.0) assert math.isclose(kl.knee, 8.0, rel_tol=0.05)
def test_concave_decreasing(interp_method): """test a concave decreasing function""" x, y = dg.concave_decreasing() kn = KneeLocator( x, y, curve="concave", direction="decreasing", interp_method=interp_method ) assert kn.knee == 7
def test_list_input(): """Indirectly test that flip works on lists as input""" x, y = dg.figure2() kl = KneeLocator( x.tolist(), y.tolist(), S=1.0, curve="concave", interp_method="polynomial" ) assert math.isclose(kl.knee, 0.22, rel_tol=0.05)
def test_convex_decreasing(interp_method): """test a convex decreasing function""" x, y = dg.convex_decreasing() kl = KneeLocator( x, y, curve="convex", direction="decreasing", interp_method=interp_method ) assert kl.knee == 2
def test_convex_decreasing_bumpy(interp_method, expected): """test a bumpy convex decreasing function""" x, y = dg.bumpy() kl = KneeLocator( x, y, curve="convex", direction="decreasing", interp_method=interp_method ) assert kl.knee == expected
def test_figure2(interp_method): """From the kneedle manuscript""" x, y = dg.figure2() kl = KneeLocator(x, y, S=1.0, curve="concave", interp_method=interp_method) assert math.isclose(kl.knee, 0.22, rel_tol=0.05) assert math.isclose(kl.elbow, 0.22, rel_tol=0.05) assert math.isclose(kl.norm_elbow, kl.knee, rel_tol=0.05)
def model_selector(self, model_dict, plot=False, save=True): # code for selecting optimal K-means model # code for selecting optimal cluster model returns the model errors = [] model_dict = OrderedDict(sorted(model_dict.items())) cluster_list = list(model_dict.keys()) for m in model_dict.values(): error = self.get_error(m) errors.append(error) # Calculate elbow kn = KneeLocator(cluster_list, errors, direction='decreasing', curve='convex') elbow = kn.knee print('[ClusterModel:model_selector]: recommended K = {}'.format(elbow)) # Plot Elbow fig, ax = plt.subplots(1) ax.plot(cluster_list, errors, color='b', marker='o') ax.vlines(x=elbow, linestyles='--', color='black', ymin=0, ymax=max(errors)+10) ax.set_title('Cluster Model Elbow Plot - Optimal K = {}'.format(elbow)) ax.set_xlabel('Cluster K') ax.set_ylabel('Model Error (Inertia)') if save: if self.model_folder: if not os.path.exists(self.model_folder+'/results'): os.makedirs(self.model_folder+'/results') print('[ClusterModel:model_selector]: saving Elbow Plot to {} dir.'.format(self.model_folder +'/results/')) plt.savefig(self.model_folder +'/results/Elbow_Plot.png') if plot: plt.show() return model_dict[elbow]
def test_convex_increasing_truncated(interp_method): """test a truncated convex increasing function""" x, y = dg.convex_increasing() kl = KneeLocator( x[:-3] / 10, y[:-3] / 10, curve="convex", interp_method=interp_method ) assert kl.knee == 0.4
def test_concave_increasing_truncated(interp_method): """test a truncated concave increasing function""" kn = KneeLocator(x[:-3] / 10, y_concave_inc[:-3] / 10, curve='concave', interp_method=interp_method) assert kn.knee == 0.2
def test_convex_increasing(interp_method): """test a convex increasing function""" kn = KneeLocator(x, y_convex_inc, curve='convex', interp_method=interp_method) assert kn.knee == 7
def test_gamma(): np.random.seed(23) n = 1000 x = range(1, n + 1) y = sorted(np.random.gamma(0.5, 1.0, n), reverse=True) kl = KneeLocator(x, y, curve='convex', direction='decreasing') assert kl.knee == 43
def test_convex_decreasing_bumpy(interp_method, expected): """test a bumpy convex decreasing function""" kn = KneeLocator(x_bumpy, y_bumpy, curve='convex', direction='decreasing', interp_method=interp_method) assert kn.knee == expected
def test_y(): """Test the y value""" x, y = dg.figure2() kl = KneeLocator(x, y, S=1.0, curve="concave", interp_method="interp1d") assert math.isclose(kl.knee_y, 1.897, rel_tol=0.03) assert math.isclose(kl.all_knees_y[0], 1.897, rel_tol=0.03) assert math.isclose(kl.norm_knee_y, 0.758, rel_tol=0.03) assert math.isclose(kl.all_norm_knees_y[0], 0.758, rel_tol=0.03)
def test_convex_decreasing(interp_method): """test a convex decreasing function""" kn = KneeLocator(x, y_convex_dec, curve='convex', direction='decreasing', interp_method=interp_method) assert kn.knee == 2
def test_convex_decreasing_truncated(interp_method): """test a truncated convex decreasing function""" kn = KneeLocator(x[:-3] / 10, y_convex_dec[:-3] / 10, curve='convex', direction='decreasing', interp_method=interp_method) assert kn.knee == 0.2
def test_concave_decreasing_truncated(interp_method): """test a truncated concave decreasing function""" x, y = dg.concave_decreasing() kl = KneeLocator(x[:-3] / 10, y[:-3] / 10, curve='concave', direction='decreasing', interp_method=interp_method) assert kl.knee == 0.4
def test_gamma_online_offline(online, expected): """Tests online and offline knee detection. Notable that a large number of samples are highly sensitive to S parameter """ np.random.seed(23) n = 1000 x = range(1, n + 1) y = sorted(np.random.gamma(0.5, 1.0, n), reverse=True) kl = KneeLocator(x, y, curve="convex", direction="decreasing", online=online) assert kl.knee == expected
def test_all_knees(): x, y = dg.bumpy() kl = KneeLocator(x, y, curve='convex', direction='decreasing', online=True) kl.all_elbows == set([41, 46, 53, 26, 31]) kl.all_norm_elbows == set([ 0.2921348314606742, 0.348314606741573, 0.5955056179775281, 0.4606741573033708, 0.5168539325842696, ])
def test_sensitivity(): """Test the S parameter -- where S is the number of flat points to identify before calling a knee""" np.random.seed(23) sensitivity = [1, 3, 5, 10, 100, 200, 400] detected_knees = [] expected_knees = [43, 137, 178, 258, 305, 482, 482] n = 1000 x = range(1, n + 1) y = sorted(np.random.gamma(0.5, 1.0, n), reverse=True) for s, expected_knee in zip(sensitivity, expected_knees): kl = KneeLocator(x, y, curve='convex', direction='decreasing', S=s) detected_knees.append(kl.knee) assert kl.knee, expected_knee
def test_NoisyGaussian(): """From the Kneedle manuscript""" x, y = dg.noisy_gaussian(mu=50, sigma=10, N=1000, seed=42) kl = KneeLocator( x, y, S=1.0, curve="concave", interp_method="polynomial", polynomial_degree=11, online=True, ) assert math.isclose(kl.knee, 63.0, rel_tol=1e-02)
def test_y_no_knee(): """Test the y value, if there is no knee found.""" kl = KneeLocator( np.array([1, 2, 3]), np.array([0.90483742, 0.81873075, 0.74081822]), S=1.0, curve="convex", direction="decreasing", interp_method="interp1d", online=False, ) assert kl.knee_y is None assert kl.norm_knee_y is None
def test_all_knees(): x, y = dg.bumpy() kl = KneeLocator(x, y, curve="convex", direction="decreasing", online=True) assert np.isclose(sorted(kl.all_elbows), [26, 31, 41, 46, 53]).all() assert np.isclose( sorted(kl.all_norm_elbows), [ 0.2921348314606742, 0.348314606741573, 0.4606741573033708, 0.5168539325842696, 0.5955056179775281, ], ).all()
def test_sine(): x = np.arange(0, 10, 0.1) y_sin = np.sin(x) sine_combos = [ ('decreasing', 'convex'), ('increasing', 'convex'), ('increasing', 'concave'), ('decreasing', 'concave') ] expected_knees = [4.5, 4.9, 1.4, 7.7, 8.1, 1.8] detected_knees = [] for direction, curve in sine_combos: kl_sine = KneeLocator(x, y_sin, direction=direction, curve=curve, S=1) detected_knees.extend(kl_sine.all_knees) assert np.isclose(expected_knees, detected_knees).all()
def test_sine(): x = np.arange(0, 10, 0.1) y_sin = np.sin(x) sine_combos = [ ("decreasing", "convex"), ("increasing", "convex"), ("increasing", "concave"), ("decreasing", "concave"), ] expected_knees = [4.5, 4.9, 7.7, 1.8] detected_knees = [] for direction, curve in sine_combos: kl_sine = KneeLocator( x, y_sin, direction=direction, curve=curve, S=1, online=True ) detected_knees.append(kl_sine.knee) assert np.isclose(expected_knees, detected_knees).all()
def test_convex_increasing(interp_method): """test a convex increasing function""" x, y = dg.convex_increasing() kl = KneeLocator(x, y, curve='convex', interp_method=interp_method) assert kl.knee == 7
def test_concave_increasing(interp_method): """test a concave increasing function""" x, y = dg.concave_increasing() kn = KneeLocator(x, y, curve='concave', interp_method=interp_method) assert kn.knee == 2
def test_NoisyGaussian(interp_method): """From the Kneedle manuscript""" x, y = dg.noisy_gaussian(mu=50, sigma=10, N=10000) kl = KneeLocator(x, y, S=1.0, curve='concave', interp_method=interp_method) assert math.isclose(kl.knee, 60.5, rel_tol=7.0)
def test_logistic(): y = np.array( [ 2.00855493e-45, 1.10299045e-43, 4.48168384e-42, 1.22376580e-41, 5.10688883e-40, 1.18778110e-38, 5.88777891e-35, 4.25317895e-34, 4.06507035e-33, 6.88084518e-32, 2.99321831e-31, 1.13291723e-30, 1.05244482e-28, 2.67578448e-27, 1.22522190e-26, 2.36517846e-26, 8.30369408e-26, 1.24303033e-25, 2.27726918e-25, 1.06330422e-24, 5.55017673e-24, 1.92068553e-23, 3.31361011e-23, 1.13575247e-22, 1.75386416e-22, 6.52680518e-22, 2.05106011e-21, 6.37285545e-21, 4.16125535e-20, 1.12709507e-19, 5.75853420e-19, 1.73333796e-18, 2.70099890e-18, 7.53254646e-18, 1.38139433e-17, 3.60081965e-17, 8.08419977e-17, 1.86378584e-16, 5.36224556e-16, 8.89404640e-16, 2.34045104e-15, 4.72168880e-15, 6.84378992e-15, 2.26898430e-14, 3.10087652e-14, 2.78081199e-13, 1.06479577e-12, 2.81002203e-12, 4.22067092e-12, 9.27095863e-12, 1.54519738e-11, 4.53347819e-11, 1.35564441e-10, 2.35242087e-10, 4.45253545e-10, 9.78613696e-10, 1.53140922e-09, 2.81648560e-09, 6.70890436e-09, 1.49724785e-08, 5.59553565e-08, 1.39510811e-07, 7.64761811e-07, 1.40723957e-06, 4.97638863e-06, 2.12817943e-05, 3.26471410e-05, 1.02599591e-04, 3.18774179e-04, 5.67297630e-04, 9.22732716e-04, 1.17445643e-03, 3.59279384e-03, 3.61936491e-02, 6.39493416e-02, 1.29304829e-01, 1.72272215e-01, 3.46945901e-01, 5.02826602e-01, 6.24800042e-01, 7.38412957e-01, 7.59931663e-01, 7.73374421e-01, 7.91421897e-01, 8.29325597e-01, 8.57718637e-01, 8.73286061e-01, 8.77056835e-01, 8.93173768e-01, 9.05435646e-01, 9.17217910e-01, 9.19119179e-01, 9.24810910e-01, 9.26306908e-01, 9.28621233e-01, 9.33855835e-01, 9.37263027e-01, 9.41651642e-01, ] ) x = np.array( [ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0, 43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0, 57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0, 64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0, 71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0, 78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0, 85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0, 92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0, ] ) kl = KneeLocator(x, y, curve="convex", direction="increasing", online=True,) assert kl.knee == 73