Selecting the number of clusters k
Here is an example using exclusively PyCVI in order to guess the number of clusters in a dataset. The preprocessing steps and the clustering steps can be integrated into the PyCVI pipeline by providing sklearn-like classes of clustering models (e.g. KMeans) and data preprocessor (e.g. StandardScaler).
In this example, we use time-series data and non-time-series data. In addition we use classes from scikit-learn, scikit-learn extra and aeon in order to illustrate the compatibility of PyCVI with sklearn-like libraries.
Here we assume that we are in real conditions, which means that we don’t have access to the true labels (except that we plot the true data for illustrating purpose). We then don’t use the features included in the pycvi.vi module.
If you wish to run the example scripts on your own computer, please first follow the instructions detailed in Running example scripts on your computer.
import numpy as np
import time
from sklearn.cluster import AgglomerativeClustering, KMeans
from sklearn.preprocessing import StandardScaler
from sklearn_extra.cluster import KMedoids
from aeon.clustering import TimeSeriesKMeans
from pycvi.cluster import generate_all_clusterings, get_clustering
from pycvi.cvi import CVIs
from pycvi.compute_scores import compute_all_scores
from pycvi.vi import variation_information
from pycvi.datasets.benchmark import load_data
from pycvi.exceptions import SelectionError
from pycvi_examples_utils import plot_true_best, plot_hist_selected, plot_only_selected
def pipeline(
X: np.ndarray,
y: np.ndarray,
model_class,
model_kw: dict,
k_max: int = 25,
scaler = StandardScaler(),
DTW:bool = False,
fig_title: str = "",
fig_name: str = "",
) -> None:
"""
This function gives an example of typical pipeline using PyCVI.
In this example we assume that we are in real conditions, which
means that we don't have access to the true labels (except for the
final figure). We then don't use the features included in the
:mod:`pycvi.vi` module. In this function we:
- Standardize the data
- Generate all clusterings for a given range of number of clusters.
- For each CVI available in PyCVI, compute their values for all
generated clusterings
- For each CVI, select the best clustering according to its CVI
values
- Create a summary plot containing the true clustering, the
clustering assuming the correct number of clusters, for each
potential number of clusters :math:`k`, the number of CVI that
selected :math:`k` and the selected clusterings of each CVI.
"""
print(f'\n ***** {fig_title} ***** \n')
k_range = range(k_max)
# ------------------------------------------------------------------
# ------------------ Define true clustering -----------------------
# ------------------------------------------------------------------
# From the label for each datapoint to a list of
# datapoints for each cluster.
# true clusters: List[List[int]]
true_clusters = get_clustering(y)
k_true = len(true_clusters)
# ------------------------------------------------------------------
# ------------------ Generate clusterings -------------------------
# ------------------------------------------------------------------
t_start = time.time()
clusterings = generate_all_clusterings(
X,
model_class,
model_kw=model_kw,
n_clusters_range = k_range,
DTW = DTW,
scaler=scaler,
)
t_end = time.time()
dt = t_end - t_start
print(f"Clusterings generated in: {dt:.2f}s")
# ------------------------------------------------------------------
# ------------ Compute CVI values and select k ---------------------
# ------------------------------------------------------------------
summary = {}
for cvi_class in CVIs:
# Instantiate a CVI model
cvi = cvi_class()
t_start = time.time()
print(f" ====== {cvi} ====== ")
# Compute CVI values for all clusterings
scores = compute_all_scores(
cvi,
X,
clusterings,
DTW=DTW,
scaler=StandardScaler(),
)
t_end = time.time()
dt = t_end - t_start
# Print CVI values and selected k
for k in clusterings:
print(k, scores[k])
print('Code executed in %.2f s' %(dt))
# Select k
try:
k_selected = cvi.select(scores)
# If no k could be selected with this CVI don't do anything...
except SelectionError as e:
k_selected = None
# Otherwise update summary info related to the selected clusterings
else:
if k_selected not in summary:
summary[k_selected] = {}
summary[k_selected]["clustering"] = clusterings[k_selected]
summary[k_selected]["#CVI"] = summary[k_selected].pop("#CVI", 0) + 1
ax_title = f'k={k_selected}, #CVI={summary[k_selected]["#CVI"]}'
summary[k_selected]["ax_title"] = ax_title
finally:
print(f"Selected k: {k_selected} | True k: {k_true}")
# ------------------------------------------------------------------
# --------------------- Plot true clustering -----------------------
# ------------------------------------------------------------------
best_clusters = clusterings[k_true]
# -- Plot true clusters & clusters when assuming k_true clusters ---
vi = variation_information(true_clusters, clusterings[k_true])
fig = plot_true_best(
X, y, best_clusters, n_plots=len(summary) + 2, VI_best=vi
)
# ------------------------------------------------------------------
# ----------------------- Summary plot -----------------------------
# ------------------------------------------------------------------
fig_hist = plot_hist_selected(summary)
fig_hist.savefig(fig_name + "-histogram.png")
fig = plot_only_selected(X, summary, fig)
fig.suptitle(fig_title)
fig.savefig(fig_name + ".png")
# ======================================================================
# PyCVI on non time-series data
# ======================================================================
# ------------- KMeans ------------------------
X, y = load_data("zelnik1", "barton")
DTW = False
k_max = 10
model_class = KMeans
model_kw = {}
scaler = StandardScaler()
fig_title = "Non time-series data with KMeans"
fig_name = "Barton_data_KMeans"
pipeline(X, y, model_class, model_kw, k_max, scaler, DTW, fig_title, fig_name)
# --------- AgglomerativeClustering ----------
X, y = load_data("zelnik1", "barton")
DTW = False
k_max = 10
model_class = AgglomerativeClustering
# sklearn kwargs for AgglomerativeClustering
model_kw = {"linkage" : "single"}
scaler = StandardScaler()
fig_title = "Non time-series data with AgglomerativeClustering-Single"
fig_name = "Barton_data_AgglomerativeClustering_Single"
pipeline(X, y, model_class, model_kw, k_max, scaler, DTW, fig_title, fig_name)
# ======================================================================
# PyCVI on time series data
# ======================================================================
X, y = load_data("Trace", "UCR")
# ==========================
# PyCVI using DTW
# ==========================
DTW = True
model_class = TimeSeriesKMeans
# aeon kwargs for TimeSeriesKMeans
model_kw = {
"distance" : "dtw",
"distance_params" : {"window": 0.2},
}
scaler = StandardScaler()
fig_title = "Time-series data using DTW with TimeSeriesKMeans"
fig_name = "UCR_data_DTW_TimeSeriesKMeans"
pipeline(X, y, model_class, model_kw, k_max, scaler, DTW, fig_title, fig_name)
# ==========================
# PyCVI not using DTW
# ==========================
DTW = False
model_class = KMeans
model_kw = {}
scaler = StandardScaler()
fig_title = "Time-series data without DTW with KMeans"
fig_name = "UCR_data_no_DTW_KMeans"
pipeline(X, y, model_class, model_kw, k_max, scaler, DTW, fig_title, fig_name)
***** Non time-series data with KMeans *****
Clusterings generated in: 3.43s
====== Hartigan_monotonous ======
0 232.0654934825328
1 119.35970324964389
2 117.48376087987144
3 74.81376333379224
4 94.02113199762944
5 60.063410719491124
6 49.13114054653303
7 56.107966521302906
8 66.54603998947124
9 None
Code executed in 0.01 s
Selected k: 1 | True k: 3
====== CalinskiHarabasz_original ======
0 None
1 0.0
2 119.35970324964381
3 141.82824238803025
4 143.0686329683243
5 164.64168872243084
6 170.186712530179
7 173.30794137460683
8 184.6002587570539
9 206.2260894858037
Code executed in 0.00 s
Selected k: 9 | True k: 3
====== GapStatistic_original ======
0 None
1 -0.004049261254301939
2 -0.1466943029518859
3 -0.2574519787770262
4 -0.4959379867722822
5 -0.4310111508002352
6 -0.4288992680368535
7 -0.4172060677867959
8 -0.437764359777038
9 -0.34072244388128015
Code executed in 1.94 s
Selected k: 1 | True k: 3
====== Silhouette ======
0 None
1 None
2 0.294881135722376
3 0.31894744669253744
4 0.29798080612545413
5 0.3736008216988594
6 0.4072776141787857
7 0.3909850999953891
8 0.41543412383314715
9 0.45983052584836287
Code executed in 0.26 s
Selected k: 9 | True k: 3
====== ScoreFunction ======
0 None
1 0.2674380756639073
2 0.1550843042064396
3 0.08685111282044777
4 0.03901086119350805
5 0.026213360239625283
6 0.019719302806356365
7 0.012903014077872865
8 0.00987125075483175
9 0.01112695458513302
Code executed in 0.00 s
Selected k: 1 | True k: 3
====== MaulikBandyopadhyay_absolute ======
0 None
1 0.0
2 0.9242634903533967
3 1.0106391331124567
4 1.3505793773465988
5 1.6458222824529551
6 1.813807646556902
7 1.6046370113161568
8 1.5619573904343338
9 1.6482442168829055
Code executed in 0.01 s
Selected k: 6 | True k: 3
====== SD ======
0 None
1 None
2 427816.25027301314
3 308234.3571623011
4 313015.50628717366
5 250421.20168155606
6 183794.19931335672
7 169283.23242283796
8 156683.26457756112
9 118676.32815086907
Code executed in 0.04 s
Selected k: 9 | True k: 3
====== SDbw ======
0 None
1 None
2 1.8047594672974212
3 1.7877900464755645
4 1.2051396009214541
5 0.8345880806349644
6 0.6477230749500456
7 0.5015294373056953
8 0.41376167883816367
9 0.35172605811689184
Code executed in 0.01 s
Selected k: 9 | True k: 3
====== Dunn ======
0 None
1 None
2 0.010065909313172913
3 0.018916130828700615
4 0.00751287010931274
5 0.016669929567251652
6 0.02853045897920662
7 0.01824796418470226
8 0.027144323226010098
9 0.036514605274897145
Code executed in 0.01 s
Selected k: 9 | True k: 3
====== XB ======
0 None
1 None
2 0.5618878462751026
3 0.3657671096000677
4 0.3951571923232496
5 0.2783179932690058
6 0.22019688470658177
7 0.5252023730102705
8 0.41789448017214176
9 0.3151156731844618
Code executed in 0.00 s
Selected k: 6 | True k: 3
====== XB_star ======
0 None
1 None
2 0.5864092730147941
3 0.3906190180469987
4 0.7043865003489642
5 0.42117315987587967
6 0.2974300292633844
7 0.8014218198612433
8 0.7756854199930513
9 0.5847464903703681
Code executed in 0.00 s
Selected k: 6 | True k: 3
====== DB ======
0 None
1 None
2 1.5067200560308753
3 1.206541474525784
4 1.1171928742233472
5 0.9983551376765537
6 1.0228354114824059
7 1.1399422585025645
8 1.06738958126988
9 0.9892289573663334
Code executed in 0.01 s
Selected k: 9 | True k: 3
====== Inertia_sum ======
0 524.0762519879294
1 349.0214135130419
2 305.7029315837352
3 266.4690882230733
4 234.6614988429572
5 209.70258725695268
6 193.6134906751688
7 177.91884932855606
8 159.1013650487448
9 145.2200548406606
Code executed in 0.00 s
Selected k: 1 | True k: 3
====== Diameter_max ======
0 6.225889127666499
1 4.737154354950527
2 4.658670749537977
3 3.8435782753171948
4 3.7739107956814633
5 3.0681102527384976
6 2.54835120455792
7 2.615671521101394
8 2.668337213378385
9 2.3049716692711906
Code executed in 0.00 s
Selected k: 1 | True k: 3
***** Non time-series data with AgglomerativeClustering-Single *****
Clusterings generated in: 0.03s
====== Hartigan_monotonous ======
0 243.17059353383993
1 0.017211966847463733
2 0.011381644034122118
3 34.58290182504769
4 35.42587757523952
5 1.2950951116888347
6 60.33785949064848
7 114.59147910894787
8 1.8539186101937388
9 None
Code executed in 0.01 s
Selected k: 1 | True k: 3
====== CalinskiHarabasz_original ======
0 None
1 0.0
2 0.01721196684745756
3 0.0142681588644381
4 11.538225250696614
5 18.52000323839848
6 15.089892803326242
7 25.17787001256915
8 46.34652110326673
9 40.903946263374344
Code executed in 0.00 s
Selected k: 8 | True k: 3
====== GapStatistic_original ======
0 None
1 -0.006801696816472358
2 -0.48145315500214636
3 -0.979375016445184
4 -1.3110344218429697
5 -1.3586144241351032
6 -1.5336292499126905
7 -1.5296817128467648
8 -1.3710958512315936
9 -1.488746370935087
Code executed in 2.06 s
Selected k: 1 | True k: 3
====== Silhouette ======
0 None
1 None
2 0.19510829001256025
3 0.12276877018507622
4 0.2666718402395447
5 0.31348555456323224
6 0.36558953430010227
7 0.3731558417803549
8 0.4027680246588693
9 0.4166374121873863
Code executed in 0.26 s
Selected k: 9 | True k: 3
====== ScoreFunction ======
0 None
1 0.2674380756639073
2 0.05526989678867322
3 0.03930535158636095
4 0.02794482468974735
5 0.022399044305932536
6 0.02434609671721777
7 0.01898781804203109
8 0.015903727593373773
9 0.016224624922212327
Code executed in 0.00 s
Selected k: 1 | True k: 3
====== MaulikBandyopadhyay_absolute ======
0 None
1 0.0
2 0.0001624885689488273
3 0.00011507955476191883
4 0.5826084938588393
5 0.5140899521509295
6 0.37313655054752143
7 0.43569610764369654
8 0.6956504829553489
9 0.5612754701918737
Code executed in 0.01 s
Selected k: 8 | True k: 3
====== SD ======
0 None
1 None
2 783980.3997208492
3 549586.6578302318
4 432027.73873390944
5 341231.9036186975
6 275681.20322029595
7 240429.69366993732
8 195142.12738698898
9 169718.94353532573
Code executed in 0.04 s
Selected k: 9 | True k: 3
====== SDbw ======
0 None
1 None
2 2.350951786083218
3 2.1462945100388757
4 2.7385019142539297
5 inf
6 inf
7 inf
8 inf
9 inf
Code executed in 0.01 s
Selected k: 3 | True k: 3
====== Dunn ======
0 None
1 None
2 0.22248894960242496
3 0.09930825342423276
4 0.07694370083375403
5 0.06829859591450839
6 0.06533380025087833
7 0.06397229612167823
8 0.060820685569083446
9 0.06081222025210591
Code executed in 0.01 s
Selected k: 2 | True k: 3
====== XB ======
0 None
1 None
2 3821.6310955892613
3 5646.566350558779
4 3300.5590447734985
5 2945.622749207921
6 2932.6600403937264
7 2430.4419997127675
8 1743.770908279943
9 1732.6941019304875
Code executed in 0.00 s
Selected k: 9 | True k: 3
====== XB_star ======
0 None
1 None
2 9446.229005417188
3 13957.603039842357
4 8989.974686370095
5 8641.081615403018
6 8641.081615403018
7 7751.014168430341
8 5854.809633100446
9 5854.809633100446
Code executed in 0.00 s
Selected k: 8 | True k: 3
====== DB ======
0 None
1 None
2 129.40126293281918
3 125.11207732852509
4 43.39706303619012
5 43.39706303619012
6 43.39706303619011
7 43.39706303619011
8 43.39706303619012
9 43.39706303619011
Code executed in 0.01 s
Selected k: 6 | True k: 3
====== Inertia_sum ======
0 534.4874209327967
1 349.0214135130419
2 349.0095479285938
3 349.0536409924574
4 325.43055668310694
5 303.72575042981157
6 300.60978707111775
7 272.065674157918
8 233.85530390170882
9 231.56485922473567
Code executed in 0.00 s
Selected k: 1 | True k: 3
====== Diameter_max ======
0 6.141355878094843
1 4.737154354950527
2 4.737154354950527
3 4.737154354950527
4 4.737154354950527
5 4.737154354950527
6 4.737154354950527
7 4.737154354950527
8 4.665215171139693
9 4.665215171139693
Code executed in 0.00 s
Selected k: 1 | True k: 3
***** Time-series data using DTW with TimeSeriesKMeans *****
Clusterings generated in: 42889.95s
====== Hartigan_monotonous ======
0 -56.90067754163524
1 1754.8645616800438
2 76.24579045110623
3 37.733877486854595
4 79.29719802625675
5 80.19390551149755
6 6.632087307791902
7 39.45874430530717
8 -9.893252058686645
9 None
Code executed in 88.08 s
Selected k: 2 | True k: 4
====== CalinskiHarabasz_original ======
0 None
1 0.0
2 909.4945115463156
3 526.7065848162986
4 2624.508286546201
5 1808.2341285102382
6 4155.991992753323
7 3384.9911989323527
8 2675.381491131559
9 2296.5345916843085
Code executed in 235.78 s
Selected k: 6 | True k: 4
====== GapStatistic_original ======
0 None
1 -0.8812998026013208
2 2.055425016808117
3 2.631300777719721
4 2.957189035750253
5 3.554722214302635
6 4.166915188848764
7 4.222276716713786
8 4.571892321305585
9 4.445586154127582
Code executed in 197.15 s
Selected k: 8 | True k: 4
====== Silhouette ======
0 None
1 None
2 0.970434561006521
3 0.8527254054088229
4 0.6752801531849165
5 0.6054094076302349
6 0.6986554587591675
7 0.6084665091940706
8 0.5682508855370472
9 0.5702841836807133
Code executed in 57.02 s
Selected k: 2 | True k: 4
====== ScoreFunction ======
0 None
1 0.0
2 1.0
3 0.9861827986301104
4 1.0
5 1.0
6 1.0
7 1.0
8 1.0
9 1.0
Code executed in 284.87 s
Selected k: 2 | True k: 4
====== MaulikBandyopadhyay_absolute ======
0 None
1 0.0
2 9764539.528487094
3 40628502.56714549
4 51421023.038382955
5 145286852.28984886
6 322023315.3611904
7 285679012.34581053
8 466216936.7233079
9 267771379.1979349
Code executed in 89.63 s
Selected k: 8 | True k: 4
====== SD ======
0 None
1 None
2 1.040885406580014
3 2.099751582581919
4 2.0553369914806225
5 5.263439716295043
6 6.406778871067406
7 11.597586686414818
8 10.4819669787031
9 10.008078847273994
Code executed in 376.15 s
Selected k: 2 | True k: 4
====== SDbw ======
0 None
1 None
2 0.037893359297291124
3 0.07126170982692476
4 0.020893886894811076
5 0.07725431215919357
6 0.10625698590064792
7 inf
8 inf
9 0.024635073347551672
Code executed in 348.08 s
Selected k: 4 | True k: 4
====== Dunn ======
0 None
1 None
2 0.8305380457449938
3 0.004488337269329974
4 0.004537116488733659
5 0.004486536318569252
6 0.015123424563564974
7 0.012839959782798446
8 0.029457702501142203
9 0.015622169774173958
Code executed in 27.92 s
Selected k: 2 | True k: 4
====== XB ======
0 None
1 None
2 0.009145963962476508
3 0.24034419423695982
4 0.5588606318246246
5 1.7501327685391477
6 1.0448318387800133
7 1.7548790482851948
8 1.2862056979174672
9 1.2866038981813879
Code executed in 108.34 s
Selected k: 2 | True k: 4
====== XB_star ======
0 None
1 None
2 0.013948302957666072
3 0.7251912448883574
4 1.935734525567115
5 5.708602227509266
6 3.2228784935451755
7 2.3668587025142993
8 2.741071252365762
9 2.741071252365762
Code executed in 108.16 s
Selected k: 2 | True k: 4
====== DB ======
0 None
1 None
2 0.023029071315210144
3 2.2826228053514726
4 2.7634803725723676
5 14.582140927746549
6 4.347684669505504
7 6.8076974700562705
8 4.392518276020974
9 4.392518276020974
Code executed in 108.51 s
Selected k: 2 | True k: 4
====== Inertia_sum ======
0 7702.02760727836
1 8093.267386810244
2 296.40332652261225
3 512.4017882745568
4 125.49379669179126
5 98.53516203174911
6 46.452852932463564
7 44.17765830607994
8 36.68686939632812
9 36.69822739380838
Code executed in 229.68 s
Selected k: 2 | True k: 4
====== Diameter_max ======
0 147.52587026993422
1 746.9674811658102
2 272.39277521031187
3 272.39277521031187
4 42.9663835289349
5 38.652638553616875
6 11.614410467507343
7 12.127901842859023
8 6.617742410930589
9 11.243614881589929
Code executed in 46.07 s
Selected k: 4 | True k: 4
***** Time-series data without DTW with KMeans *****
Clusterings generated in: 0.19s
====== Hartigan_monotonous ======
0 16.44418491910634
1 180.2820962759362
2 53.846852033119134
3 22.394820456074385
4 25.202760282907153
5 21.309513200043522
6 15.228534322387095
7 10.168166451037088
8 10.575307550082556
9 None
Code executed in 0.02 s
Selected k: 2 | True k: 4
====== CalinskiHarabasz_original ======
0 None
1 0.0
2 180.2820962759362
3 166.17448068089337
4 142.93732434587815
5 140.81535014558364
6 141.25368243775634
7 138.2587461813344
8 131.76983601770954
9 128.75260366784764
Code executed in 0.01 s
Selected k: 2 | True k: 4
====== GapStatistic_original ======
0 None
1 0.14959122676002323
2 1.1835881800036443
3 1.6089298517817427
4 1.800355794973413
5 2.029295395837196
6 2.217146464498101
7 2.3598893029715544
8 2.446996508838799
9 2.539016647051622
Code executed in 0.20 s
Selected k: None | True k: 4
====== Silhouette ======
0 None
1 None
2 0.5851724720291331
3 0.5053323519853271
4 0.4602712659960852
5 0.4570190106763704
6 0.41519039685198794
7 0.41005678542657314
8 0.4079333530816935
9 0.3890486437467804
Code executed in 0.11 s
Selected k: 2 | True k: 4
====== ScoreFunction ======
0 None
1 6.445205555927203e-07
2 1.0
3 1.0
4 1.0
5 1.0
6 0.9709617419307015
7 0.0007776143475627384
8 2.0324395942128426e-05
9 8.787509897523194e-08
Code executed in 0.01 s
Selected k: 2 | True k: 4
====== MaulikBandyopadhyay_absolute ======
0 None
1 0.0
2 425.1118921667764
3 349.1043610495471
4 293.3275902208881
5 238.83141407717827
6 209.78955038173314
7 183.46253543130317
8 169.52472800673607
9 154.10215969988073
Code executed in 0.01 s
Selected k: 2 | True k: 4
====== SD ======
0 None
1 None
2 3.0428152641301534
3 3.18555999614733
4 2.3873900047715817
5 2.275674041038432
6 2.453979427846945
7 2.624046549452272
8 2.261826823872787
9 2.173803637453388
Code executed in 0.17 s
Selected k: 9 | True k: 4
====== SDbw ======
0 None
1 None
2 inf
3 inf
4 inf
5 inf
6 inf
7 inf
8 inf
9 inf
Code executed in 0.24 s
Selected k: None | True k: 4
====== Dunn ======
0 None
1 None
2 0.6502295327816004
3 0.4183330532684955
4 0.05453741885973551
5 0.05453741885973551
6 0.06321374469697073
7 0.07400800378311924
8 0.06494750239192468
9 0.10601181798605641
Code executed in 0.01 s
Selected k: 2 | True k: 4
====== XB ======
0 None
1 None
2 0.133838927267939
3 0.2070968818131891
4 0.5675473082510523
5 0.44755211078152235
6 0.36411970567875473
7 0.31240763602838445
8 0.4738119600939409
9 0.4509076900133838
Code executed in 0.01 s
Selected k: 2 | True k: 4
====== XB_star ======
0 None
1 None
2 0.23315333559033175
3 0.3264007671262032
4 1.105363220173563
5 1.105363220173563
6 0.9130044140189886
7 0.7152928875287857
8 1.206065159615511
9 1.2826295897069153
Code executed in 0.01 s
Selected k: 2 | True k: 4
====== DB ======
0 None
1 None
2 0.7011289065432457
3 1.1189101040092817
4 1.1189101040092817
5 1.0398228359696284
6 1.4226602670081683
7 1.2262026392098047
8 1.2474667583315489
9 1.3179401633417192
Code executed in 0.01 s
Selected k: 2 | True k: 4
====== Inertia_sum ======
0 1550.7100623367241
1 1411.2211210804048
2 766.9830051666322
3 633.31553154121
4 525.9256968224321
5 463.6934090080416
6 429.2586106174678
7 407.1627289647065
8 366.692020199272
9 350.9654859871239
Code executed in 0.01 s
Selected k: 2 | True k: 4
====== Diameter_max ======
0 26.52376778327575
1 29.63943217185524
2 26.085661705315456
3 21.104625863172615
4 21.104625863172615
5 21.104625863172615
6 18.207936044532552
7 15.55226140068361
8 15.55226140068361
9 15.55226140068361
Code executed in 0.01 s
Selected k: 3 | True k: 4