Files
AIM-PIbd-31-Rodionov-I-A/lab_5/lab5.ipynb
2024-11-26 15:42:15 +04:00

4.0 MiB
Raw Blame History

Данные по инсультам

Выводим информацию о датасете:

In [352]:
import pandas as pd

df = pd.read_csv("..//..//static//csv//healthcare-dataset-stroke-data.csv")

df
Out[352]:
<style scoped=""> .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } </style>
id gender age hypertension heart_disease ever_married work_type Residence_type avg_glucose_level bmi smoking_status stroke
0 9046 Male 67.0 0 1 Yes Private Urban 228.69 36.6 formerly smoked 1
1 51676 Female 61.0 0 0 Yes Self-employed Rural 202.21 NaN never smoked 1
2 31112 Male 80.0 0 1 Yes Private Rural 105.92 32.5 never smoked 1
3 60182 Female 49.0 0 0 Yes Private Urban 171.23 34.4 smokes 1
4 1665 Female 79.0 1 0 Yes Self-employed Rural 174.12 24.0 never smoked 1
... ... ... ... ... ... ... ... ... ... ... ... ...
5105 18234 Female 80.0 1 0 Yes Private Urban 83.75 NaN never smoked 0
5106 44873 Female 81.0 0 0 Yes Self-employed Urban 125.20 40.0 never smoked 0
5107 19723 Female 35.0 0 0 Yes Self-employed Rural 82.99 30.6 never smoked 0
5108 37544 Male 51.0 0 0 Yes Private Rural 166.29 25.6 formerly smoked 0
5109 44679 Female 44.0 0 0 Yes Govt_job Urban 85.28 26.2 Unknown 0

5110 rows × 12 columns

Атрибуты:

  • id уникальный идентификатор пациента;
  • gender пол пациента: может быть "Male" (мужчина), "Female" (женщина) или "Other" (другой);
  • age возраст пациента (в годах);
  • hypertension наличие гипертонии: 0 гипертонии нет, 1 гипертония есть;
  • heart_disease наличие сердечных заболеваний: 0 заболеваний нет, 1 заболевание присутствует;
  • ever_married семейный статус пациента: "No" (не состоял в браке) или "Yes" (состоял в браке);
  • work_type тип занятости пациента: "children" (дети), "Govt_job" (государственная служба), "Never_worked" (никогда не работал), "Private" (частная компания) или "Self-employed" (самозанятый);
  • Residence_type место проживания пациента: "Rural" (сельская местность) или "Urban" (город);
  • avg_glucose_level средний уровень глюкозы в крови (в ммоль/л);
  • bmi индекс массы тела пациента;
  • smoking_status статус курения пациента: "formerly smoked" (курил ранее), "never smoked" (никогда не курил), "smokes" (курит), "Unknown" (информация недоступна);
  • stroke факт наличия инсульта: 1 пациент перенес инсульт, 0 инсульта не было.

Бизнес-цель: кластеризация пациентов для выявления групп с схожими характеристиками здоровья и рисками инсульта. Что, к примеру, может использоваться для следующего:

  • Определение групп пациентов для целенаправленных профилактических мероприятий.
  • Оптимизация распределения медицинских ресурсов и создания индивидуализированных программ наблюдения.

Для начала избавимся от пустых значений:

In [353]:
# Количество пустых значений признаков
print(df.isnull().sum())

print()

# Есть ли пустые значения признаков
print(df.isnull().any())

print()

# Процент пустых значений признаков
for i in df.columns:
    null_rate = df[i].isnull().sum() / len(df) * 100
    if null_rate > 0:
        print(f"{i} процент пустых значений: %{null_rate:.2f}")
id                     0
gender                 0
age                    0
hypertension           0
heart_disease          0
ever_married           0
work_type              0
Residence_type         0
avg_glucose_level      0
bmi                  201
smoking_status         0
stroke                 0
dtype: int64

id                   False
gender               False
age                  False
hypertension         False
heart_disease        False
ever_married         False
work_type            False
Residence_type       False
avg_glucose_level    False
bmi                   True
smoking_status       False
stroke               False
dtype: bool

bmi процент пустых значений: %3.93
In [354]:
# Замена значений
df["bmi"] = df["bmi"].fillna(df["bmi"].median())

Визуализация взаимосвязей

Для визуализации и выполнения задачи в целом были выбраны столбцы age, avg_glucose_level, bmi, hypertension.

In [355]:
from typing import Any, List
import matplotlib.pyplot as plt

def draw_data_2d(
    df: pd.DataFrame,
    col1: int,
    col2: int,
    y: List | None = None,
    classes: List | None = None,
    subplot: Any | None = None,
):
    ax = None
    if subplot is None:
        _, ax = plt.subplots()
    else:
        ax = subplot
    scatter = ax.scatter(df[df.columns[col1]], df[df.columns[col2]], c=y)
    ax.set(xlabel=df.columns[col1], ylabel=df.columns[col2])
    if classes is not None:
        ax.legend(
            scatter.legend_elements()[0], classes, loc="lower right", title="Classes"
        )
In [356]:
columns = ['age', 'avg_glucose_level', 'bmi', 'hypertension']
df_reduced = df[columns]

plt.figure(figsize=(16, 12))

draw_data_2d(df_reduced, 0, 1, subplot=plt.subplot(2, 2, 1))  # age vs avg_glucose_level
draw_data_2d(df_reduced, 0, 2, subplot=plt.subplot(2, 2, 2))  # age vs bmi
draw_data_2d(df_reduced, 0, 3, subplot=plt.subplot(2, 2, 3))  # age vs hypertension
draw_data_2d(df_reduced, 1, 2, subplot=plt.subplot(2, 2, 4))  # avg_glucose_level vs bmi
No description has been provided for this image

Перед кластеризацией стандартизируем данные:

In [357]:
from sklearn.preprocessing import StandardScaler

columns_to_scale = df_reduced.drop(columns=["hypertension"]).columns
columns_to_keep = ["hypertension"]

scaler = StandardScaler()
data_scaled = scaler.fit_transform(df_reduced[columns_to_scale])

df_scaled = pd.DataFrame(data_scaled, columns=columns_to_scale, index=df_reduced.index)

df_scaled[columns_to_keep] = df_reduced[columns_to_keep]

Иерархическая агломеративная кластеризация

Также выведем дендрограмму

In [358]:
import numpy as np
from sklearn import cluster
from scipy.cluster import hierarchy

def run_agglomerative(
    df: pd.DataFrame, num_clusters: int | None = 2
) -> cluster.AgglomerativeClustering:
    agglomerative = cluster.AgglomerativeClustering(
        n_clusters=num_clusters,
        compute_distances=True,
    )
    return agglomerative.fit(df)


def get_linkage_matrix(model: cluster.AgglomerativeClustering) -> np.ndarray:
    counts = np.zeros(model.children_.shape[0])  # type: ignore
    n_samples = len(model.labels_)
    for i, merge in enumerate(model.children_):  # type: ignore
        current_count = 0
        for child_idx in merge:
            if child_idx < n_samples:
                current_count += 1
            else:
                current_count += counts[child_idx - n_samples]
        counts[i] = current_count

    return np.column_stack([model.children_, model.distances_, counts]).astype(float)

def draw_dendrogram(linkage_matrix: np.ndarray):
    hierarchy.dendrogram(linkage_matrix, truncate_mode="level", p=3)
    plt.xticks(fontsize=10, rotation=45)
    plt.tight_layout()
In [359]:
tree = run_agglomerative(df_scaled)
linkage_matrix = get_linkage_matrix(tree)
draw_dendrogram(linkage_matrix)
No description has been provided for this image

Попробуем разделить данные на 2 больших кластера, поэтому зададим порог расстояния в 90 единиц.

И визуализируем сами результаты иерархической кластеризации, т.е. распределение кластеров:

In [360]:
result = hierarchy.fcluster(linkage_matrix, 90, criterion="distance")
y_names = ['1', '2']

plt.figure(figsize=(16, 12))

draw_data_2d(df_reduced, 0, 1, result, y_names, plt.subplot(2, 2, 1))  # age vs avg_glucose_level
draw_data_2d(df_reduced, 0, 2, result, y_names, plt.subplot(2, 2, 2))  # age vs bmi
draw_data_2d(df_reduced, 0, 3, result, y_names, plt.subplot(2, 2, 3))  # age vs hypertension
draw_data_2d(df_reduced, 1, 2, result, y_names, plt.subplot(2, 2, 4))  # avg_glucose_level vs bmi
No description has been provided for this image

KMeans (неиерархическая четкая кластеризация) для сравнения

In [361]:
from typing import Tuple

def print_cluster_result(
    df: pd.DataFrame, clusters_num: int, labels: np.ndarray, separator: str = ", "
):
    for cluster_id in range(clusters_num):
        cluster_indices = np.where(labels == cluster_id)[0]
        print(f"Cluster {cluster_id + 1} ({len(cluster_indices)}):")
        rules = [str(df.index[idx]) for idx in cluster_indices]
        print(separator.join(rules))
        print("")
        print("--------")


def run_kmeans(
    df: pd.DataFrame, num_clusters: int, random_state: int
) -> Tuple[np.ndarray, np.ndarray]:
    kmeans = cluster.KMeans(n_clusters=num_clusters, random_state=random_state)
    labels = kmeans.fit_predict(df)
    return labels, kmeans.cluster_centers_
In [362]:
random_state = 9

labels, centers = run_kmeans(df_scaled, 2, random_state) # также указываем 2 кластера
print_cluster_result(df_scaled, 2, labels)
display(centers)
Cluster 1 (2979):
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 211, 212, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 246, 247, 248, 250, 252, 254, 256, 258, 259, 260, 261, 263, 264, 268, 269, 270, 271, 272, 273, 275, 277, 278, 280, 281, 283, 289, 293, 295, 296, 297, 298, 299, 300, 301, 303, 305, 307, 309, 311, 314, 315, 316, 317, 318, 323, 325, 326, 329, 330, 331, 332, 333, 334, 336, 337, 338, 341, 343, 344, 346, 347, 350, 351, 353, 355, 358, 360, 362, 364, 365, 366, 368, 370, 372, 374, 375, 379, 381, 382, 386, 387, 388, 389, 393, 396, 397, 398, 399, 402, 404, 405, 406, 407, 409, 411, 412, 413, 415, 417, 420, 422, 423, 424, 426, 428, 429, 430, 432, 433, 434, 435, 436, 438, 439, 442, 444, 447, 448, 449, 450, 452, 453, 454, 456, 457, 462, 463, 465, 466, 467, 468, 469, 475, 477, 478, 479, 481, 482, 484, 489, 490, 491, 492, 493, 494, 495, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 514, 515, 517, 518, 520, 521, 523, 524, 526, 527, 528, 530, 533, 536, 537, 539, 542, 543, 544, 549, 551, 552, 553, 555, 557, 559, 561, 563, 565, 567, 568, 569, 570, 571, 572, 573, 575, 577, 579, 580, 583, 585, 586, 590, 591, 592, 594, 595, 596, 599, 601, 606, 608, 610, 612, 613, 614, 615, 616, 617, 618, 620, 621, 622, 623, 624, 626, 627, 629, 630, 631, 632, 633, 635, 636, 637, 639, 642, 643, 645, 646, 647, 648, 649, 651, 652, 653, 654, 659, 660, 661, 662, 664, 665, 666, 668, 670, 671, 672, 673, 675, 677, 688, 689, 690, 691, 693, 700, 703, 704, 705, 706, 708, 710, 712, 713, 714, 715, 719, 720, 724, 725, 726, 727, 729, 731, 732, 735, 737, 738, 739, 741, 744, 745, 746, 747, 749, 751, 752, 755, 759, 760, 761, 763, 765, 766, 767, 769, 772, 773, 777, 778, 781, 782, 784, 785, 786, 787, 788, 789, 800, 801, 802, 803, 805, 809, 810, 815, 816, 819, 821, 822, 823, 825, 826, 828, 829, 831, 832, 834, 835, 836, 838, 841, 845, 846, 848, 849, 851, 853, 859, 860, 861, 863, 864, 865, 867, 868, 870, 871, 873, 874, 876, 879, 880, 881, 882, 884, 885, 887, 888, 889, 890, 891, 894, 895, 896, 897, 898, 899, 900, 901, 902, 905, 906, 908, 910, 911, 914, 916, 917, 918, 920, 923, 924, 927, 928, 929, 930, 931, 933, 934, 935, 940, 941, 942, 943, 944, 945, 946, 948, 954, 958, 960, 964, 968, 970, 971, 973, 975, 976, 978, 983, 990, 992, 993, 994, 997, 1000, 1001, 1002, 1003, 1004, 1007, 1010, 1011, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1034, 1035, 1036, 1038, 1042, 1043, 1044, 1046, 1048, 1050, 1052, 1053, 1054, 1055, 1057, 1058, 1059, 1061, 1062, 1064, 1065, 1067, 1068, 1070, 1071, 1072, 1075, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1089, 1092, 1096, 1097, 1098, 1100, 1103, 1104, 1105, 1106, 1109, 1110, 1112, 1113, 1114, 1116, 1117, 1118, 1119, 1121, 1125, 1126, 1128, 1129, 1130, 1131, 1133, 1136, 1138, 1140, 1141, 1146, 1148, 1151, 1152, 1153, 1157, 1158, 1160, 1161, 1162, 1165, 1166, 1167, 1168, 1170, 1171, 1172, 1173, 1175, 1176, 1178, 1179, 1180, 1183, 1184, 1186, 1187, 1188, 1189, 1193, 1195, 1198, 1199, 1200, 1201, 1202, 1207, 1211, 1213, 1214, 1215, 1216, 1224, 1225, 1226, 1227, 1228, 1229, 1230, 1233, 1236, 1239, 1240, 1241, 1245, 1247, 1250, 1254, 1257, 1259, 1260, 1262, 1264, 1266, 1267, 1268, 1269, 1273, 1274, 1276, 1279, 1280, 1282, 1283, 1284, 1285, 1286, 1288, 1289, 1290, 1291, 1293, 1294, 1295, 1296, 1297, 1299, 1300, 1302, 1303, 1304, 1306, 1308, 1309, 1311, 1313, 1314, 1315, 1318, 1319, 1320, 1322, 1323, 1324, 1325, 1328, 1330, 1332, 1333, 1335, 1336, 1337, 1339, 1340, 1341, 1342, 1343, 1344, 1345, 1346, 1347, 1348, 1350, 1351, 1352, 1353, 1354, 1355, 1357, 1360, 1361, 1362, 1363, 1365, 1367, 1368, 1373, 1374, 1376, 1378, 1379, 1381, 1382, 1383, 1384, 1385, 1388, 1391, 1393, 1395, 1397, 1398, 1399, 1400, 1401, 1404, 1407, 1409, 1410, 1411, 1412, 1413, 1414, 1415, 1416, 1418, 1420, 1423, 1425, 1428, 1431, 1433, 1434, 1435, 1436, 1437, 1438, 1439, 1440, 1443, 1446, 1447, 1448, 1451, 1452, 1453, 1454, 1456, 1457, 1458, 1466, 1468, 1472, 1473, 1474, 1477, 1478, 1479, 1480, 1481, 1482, 1485, 1487, 1489, 1490, 1491, 1495, 1496, 1497, 1498, 1500, 1501, 1503, 1506, 1508, 1509, 1511, 1515, 1516, 1517, 1520, 1521, 1522, 1523, 1524, 1525, 1526, 1527, 1529, 1531, 1532, 1533, 1534, 1535, 1538, 1540, 1541, 1543, 1544, 1546, 1547, 1549, 1550, 1552, 1553, 1554, 1559, 1561, 1562, 1563, 1564, 1566, 1568, 1569, 1571, 1574, 1575, 1576, 1577, 1578, 1582, 1584, 1585, 1588, 1592, 1594, 1595, 1596, 1598, 1599, 1602, 1603, 1604, 1605, 1606, 1607, 1608, 1611, 1613, 1615, 1616, 1617, 1620, 1621, 1625, 1627, 1628, 1630, 1634, 1637, 1638, 1639, 1640, 1641, 1642, 1643, 1646, 1650, 1651, 1652, 1653, 1654, 1655, 1656, 1657, 1658, 1659, 1660, 1661, 1662, 1663, 1664, 1667, 1669, 1670, 1671, 1674, 1675, 1677, 1678, 1679, 1680, 1681, 1682, 1685, 1687, 1688, 1690, 1691, 1692, 1694, 1695, 1696, 1697, 1698, 1699, 1704, 1705, 1706, 1711, 1715, 1717, 1718, 1723, 1727, 1729, 1730, 1732, 1733, 1735, 1737, 1738, 1740, 1741, 1745, 1747, 1749, 1750, 1751, 1752, 1753, 1755, 1756, 1759, 1760, 1761, 1764, 1769, 1770, 1775, 1776, 1778, 1780, 1781, 1782, 1785, 1786, 1787, 1788, 1790, 1793, 1795, 1796, 1798, 1801, 1804, 1810, 1813, 1814, 1815, 1816, 1818, 1819, 1822, 1823, 1825, 1827, 1828, 1829, 1830, 1832, 1835, 1837, 1838, 1839, 1840, 1842, 1843, 1850, 1852, 1853, 1854, 1855, 1856, 1857, 1858, 1859, 1860, 1861, 1862, 1864, 1867, 1870, 1871, 1872, 1873, 1875, 1876, 1878, 1879, 1880, 1881, 1883, 1884, 1887, 1888, 1889, 1891, 1892, 1893, 1895, 1898, 1903, 1904, 1905, 1906, 1908, 1909, 1912, 1915, 1916, 1918, 1920, 1921, 1923, 1924, 1925, 1926, 1933, 1934, 1938, 1943, 1944, 1945, 1946, 1951, 1952, 1953, 1954, 1955, 1961, 1962, 1967, 1968, 1969, 1970, 1971, 1972, 1974, 1978, 1979, 1981, 1984, 1988, 1989, 1992, 1993, 1995, 1998, 2001, 2002, 2004, 2007, 2010, 2011, 2014, 2016, 2017, 2018, 2021, 2025, 2026, 2027, 2028, 2029, 2031, 2032, 2033, 2034, 2037, 2038, 2039, 2041, 2044, 2046, 2047, 2048, 2049, 2053, 2055, 2057, 2058, 2059, 2060, 2061, 2063, 2064, 2067, 2069, 2070, 2071, 2072, 2075, 2077, 2078, 2081, 2082, 2083, 2084, 2085, 2086, 2088, 2089, 2093, 2094, 2095, 2096, 2099, 2100, 2102, 2104, 2105, 2107, 2108, 2110, 2111, 2115, 2118, 2119, 2120, 2121, 2122, 2124, 2126, 2128, 2129, 2130, 2132, 2134, 2135, 2136, 2138, 2139, 2143, 2144, 2145, 2149, 2153, 2155, 2156, 2158, 2159, 2160, 2161, 2162, 2164, 2168, 2170, 2172, 2173, 2174, 2176, 2179, 2180, 2182, 2184, 2185, 2188, 2189, 2190, 2192, 2197, 2198, 2199, 2200, 2201, 2202, 2203, 2204, 2205, 2206, 2208, 2209, 2210, 2211, 2214, 2215, 2218, 2220, 2221, 2223, 2224, 2225, 2227, 2228, 2229, 2231, 2232, 2233, 2235, 2237, 2238, 2239, 2244, 2246, 2250, 2251, 2252, 2253, 2255, 2256, 2257, 2260, 2261, 2264, 2265, 2266, 2267, 2269, 2271, 2272, 2273, 2275, 2277, 2278, 2280, 2281, 2282, 2283, 2284, 2285, 2286, 2287, 2288, 2290, 2292, 2293, 2294, 2296, 2297, 2298, 2301, 2303, 2306, 2307, 2309, 2312, 2314, 2315, 2316, 2317, 2318, 2319, 2320, 2321, 2322, 2324, 2328, 2330, 2331, 2332, 2334, 2335, 2336, 2338, 2341, 2342, 2343, 2351, 2352, 2355, 2356, 2357, 2359, 2360, 2362, 2363, 2365, 2366, 2369, 2373, 2374, 2375, 2376, 2378, 2383, 2390, 2392, 2393, 2395, 2396, 2398, 2399, 2400, 2401, 2402, 2403, 2405, 2407, 2408, 2410, 2411, 2412, 2413, 2414, 2416, 2418, 2420, 2422, 2424, 2428, 2430, 2433, 2435, 2438, 2439, 2440, 2441, 2444, 2446, 2447, 2449, 2451, 2453, 2454, 2456, 2458, 2459, 2460, 2461, 2462, 2466, 2467, 2468, 2469, 2470, 2473, 2484, 2486, 2487, 2492, 2493, 2494, 2496, 2497, 2499, 2501, 2502, 2503, 2504, 2507, 2508, 2509, 2510, 2511, 2512, 2515, 2517, 2518, 2519, 2520, 2521, 2523, 2524, 2527, 2529, 2532, 2533, 2534, 2537, 2540, 2541, 2544, 2545, 2547, 2548, 2549, 2550, 2553, 2555, 2556, 2559, 2561, 2562, 2563, 2564, 2565, 2567, 2568, 2569, 2570, 2571, 2572, 2575, 2576, 2577, 2578, 2582, 2583, 2584, 2585, 2587, 2589, 2591, 2592, 2596, 2598, 2599, 2600, 2601, 2602, 2603, 2604, 2606, 2608, 2609, 2610, 2612, 2614, 2617, 2621, 2622, 2623, 2624, 2626, 2629, 2631, 2632, 2633, 2635, 2636, 2637, 2639, 2640, 2641, 2642, 2643, 2645, 2646, 2648, 2650, 2651, 2654, 2655, 2656, 2657, 2658, 2661, 2664, 2666, 2668, 2669, 2673, 2674, 2675, 2676, 2677, 2678, 2680, 2682, 2683, 2685, 2686, 2688, 2690, 2691, 2693, 2696, 2697, 2698, 2699, 2700, 2701, 2702, 2703, 2705, 2706, 2707, 2709, 2710, 2713, 2715, 2718, 2719, 2720, 2722, 2723, 2724, 2727, 2731, 2732, 2734, 2735, 2737, 2738, 2743, 2746, 2748, 2749, 2750, 2751, 2752, 2754, 2757, 2758, 2759, 2761, 2763, 2764, 2765, 2766, 2768, 2769, 2771, 2772, 2774, 2775, 2777, 2782, 2784, 2786, 2789, 2792, 2793, 2795, 2796, 2797, 2798, 2799, 2800, 2802, 2804, 2806, 2809, 2810, 2813, 2814, 2815, 2816, 2817, 2818, 2821, 2822, 2823, 2825, 2826, 2829, 2830, 2831, 2833, 2839, 2840, 2841, 2846, 2847, 2848, 2849, 2851, 2853, 2855, 2857, 2862, 2863, 2864, 2865, 2866, 2867, 2868, 2870, 2872, 2873, 2876, 2878, 2880, 2881, 2883, 2884, 2886, 2888, 2890, 2891, 2893, 2895, 2896, 2897, 2899, 2900, 2901, 2902, 2903, 2904, 2905, 2907, 2908, 2910, 2913, 2914, 2918, 2922, 2930, 2933, 2934, 2935, 2937, 2938, 2940, 2945, 2947, 2948, 2951, 2953, 2957, 2959, 2960, 2961, 2962, 2963, 2965, 2966, 2968, 2969, 2974, 2975, 2976, 2977, 2978, 2979, 2980, 2981, 2982, 2985, 2986, 2991, 2992, 2993, 2995, 2997, 2998, 3001, 3002, 3005, 3006, 3007, 3008, 3011, 3012, 3013, 3014, 3015, 3017, 3019, 3021, 3024, 3026, 3027, 3028, 3029, 3030, 3032, 3033, 3045, 3046, 3048, 3049, 3050, 3052, 3054, 3055, 3056, 3057, 3060, 3061, 3062, 3065, 3066, 3068, 3069, 3070, 3071, 3074, 3075, 3077, 3080, 3081, 3082, 3083, 3084, 3085, 3088, 3089, 3090, 3091, 3098, 3100, 3101, 3102, 3103, 3106, 3108, 3111, 3112, 3113, 3114, 3115, 3122, 3123, 3124, 3126, 3128, 3130, 3134, 3135, 3136, 3138, 3140, 3142, 3144, 3146, 3147, 3148, 3149, 3150, 3151, 3152, 3154, 3155, 3157, 3159, 3161, 3162, 3164, 3166, 3167, 3168, 3169, 3172, 3174, 3181, 3182, 3186, 3192, 3194, 3195, 3196, 3197, 3198, 3201, 3202, 3203, 3204, 3206, 3207, 3209, 3210, 3213, 3215, 3216, 3217, 3218, 3219, 3220, 3221, 3222, 3223, 3224, 3225, 3227, 3230, 3232, 3234, 3235, 3236, 3238, 3241, 3243, 3245, 3246, 3248, 3249, 3254, 3257, 3258, 3260, 3263, 3264, 3265, 3269, 3272, 3274, 3275, 3276, 3278, 3285, 3288, 3290, 3291, 3293, 3294, 3296, 3298, 3299, 3302, 3303, 3305, 3306, 3308, 3312, 3314, 3316, 3317, 3318, 3320, 3322, 3326, 3327, 3328, 3329, 3330, 3333, 3335, 3336, 3337, 3339, 3341, 3342, 3344, 3345, 3346, 3347, 3349, 3350, 3351, 3352, 3353, 3358, 3360, 3361, 3362, 3363, 3364, 3365, 3367, 3369, 3371, 3372, 3375, 3376, 3377, 3378, 3380, 3381, 3383, 3384, 3387, 3388, 3389, 3391, 3393, 3396, 3399, 3400, 3404, 3405, 3406, 3407, 3408, 3410, 3416, 3417, 3419, 3421, 3422, 3424, 3425, 3427, 3428, 3429, 3431, 3432, 3433, 3436, 3439, 3441, 3442, 3443, 3445, 3448, 3449, 3450, 3452, 3454, 3455, 3456, 3459, 3461, 3462, 3465, 3466, 3469, 3470, 3471, 3474, 3475, 3476, 3478, 3479, 3480, 3487, 3489, 3490, 3492, 3494, 3495, 3499, 3500, 3502, 3503, 3504, 3505, 3507, 3508, 3509, 3512, 3514, 3515, 3516, 3517, 3521, 3522, 3524, 3525, 3526, 3528, 3529, 3531, 3534, 3536, 3537, 3541, 3547, 3548, 3549, 3553, 3556, 3557, 3558, 3559, 3561, 3563, 3564, 3566, 3567, 3568, 3569, 3570, 3571, 3578, 3579, 3580, 3582, 3585, 3587, 3588, 3590, 3591, 3592, 3593, 3594, 3595, 3596, 3597, 3598, 3599, 3600, 3602, 3603, 3604, 3606, 3607, 3613, 3614, 3622, 3625, 3627, 3628, 3630, 3631, 3632, 3633, 3634, 3637, 3638, 3639, 3641, 3643, 3646, 3649, 3650, 3652, 3656, 3658, 3659, 3660, 3663, 3664, 3665, 3666, 3667, 3668, 3669, 3672, 3673, 3674, 3677, 3679, 3680, 3682, 3683, 3686, 3687, 3688, 3689, 3690, 3691, 3693, 3696, 3697, 3699, 3700, 3701, 3702, 3707, 3708, 3709, 3710, 3711, 3716, 3718, 3720, 3722, 3723, 3724, 3725, 3727, 3729, 3730, 3732, 3735, 3736, 3737, 3738, 3741, 3742, 3750, 3752, 3753, 3756, 3757, 3759, 3760, 3764, 3768, 3770, 3771, 3772, 3775, 3779, 3780, 3781, 3783, 3784, 3788, 3790, 3792, 3794, 3795, 3799, 3802, 3803, 3804, 3805, 3811, 3812, 3813, 3814, 3815, 3820, 3821, 3822, 3824, 3825, 3826, 3827, 3828, 3830, 3831, 3833, 3834, 3838, 3839, 3841, 3842, 3845, 3847, 3852, 3853, 3854, 3855, 3856, 3860, 3862, 3863, 3865, 3866, 3867, 3869, 3871, 3878, 3879, 3880, 3881, 3882, 3884, 3889, 3890, 3891, 3892, 3898, 3899, 3900, 3901, 3902, 3903, 3905, 3906, 3907, 3909, 3913, 3914, 3915, 3916, 3918, 3919, 3920, 3922, 3923, 3925, 3926, 3927, 3930, 3931, 3933, 3935, 3939, 3943, 3945, 3947, 3948, 3949, 3951, 3953, 3955, 3956, 3959, 3960, 3961, 3962, 3963, 3965, 3967, 3969, 3971, 3974, 3975, 3976, 3977, 3979, 3980, 3981, 3982, 3983, 3984, 3987, 3988, 3989, 3991, 3993, 3994, 3995, 3997, 3998, 4008, 4014, 4015, 4017, 4022, 4023, 4024, 4026, 4028, 4029, 4032, 4033, 4034, 4035, 4036, 4037, 4038, 4044, 4045, 4046, 4047, 4048, 4049, 4051, 4052, 4054, 4055, 4060, 4061, 4062, 4066, 4070, 4071, 4072, 4073, 4074, 4076, 4078, 4080, 4085, 4086, 4088, 4092, 4093, 4094, 4095, 4096, 4097, 4098, 4099, 4101, 4102, 4104, 4105, 4107, 4109, 4110, 4113, 4118, 4120, 4121, 4122, 4123, 4125, 4126, 4129, 4132, 4134, 4135, 4136, 4138, 4139, 4140, 4142, 4143, 4144, 4145, 4146, 4147, 4149, 4152, 4153, 4154, 4155, 4161, 4162, 4163, 4164, 4165, 4166, 4168, 4169, 4171, 4176, 4178, 4179, 4180, 4183, 4185, 4187, 4188, 4189, 4197, 4198, 4199, 4200, 4201, 4204, 4205, 4207, 4208, 4209, 4211, 4212, 4214, 4217, 4219, 4223, 4224, 4225, 4226, 4228, 4231, 4237, 4239, 4240, 4241, 4242, 4249, 4253, 4258, 4259, 4260, 4263, 4264, 4265, 4266, 4267, 4270, 4271, 4274, 4278, 4281, 4282, 4283, 4284, 4285, 4288, 4289, 4290, 4291, 4292, 4295, 4296, 4297, 4299, 4300, 4301, 4304, 4305, 4308, 4309, 4311, 4312, 4313, 4314, 4315, 4316, 4318, 4321, 4324, 4325, 4326, 4327, 4329, 4331, 4334, 4335, 4336, 4337, 4338, 4339, 4340, 4343, 4344, 4345, 4347, 4348, 4349, 4350, 4351, 4352, 4354, 4355, 4356, 4357, 4360, 4362, 4365, 4366, 4367, 4368, 4369, 4371, 4375, 4376, 4377, 4380, 4381, 4385, 4387, 4390, 4395, 4397, 4400, 4401, 4402, 4404, 4405, 4406, 4407, 4408, 4411, 4417, 4419, 4426, 4427, 4429, 4430, 4431, 4434, 4436, 4437, 4440, 4442, 4443, 4445, 4446, 4447, 4448, 4449, 4450, 4451, 4452, 4453, 4456, 4457, 4459, 4460, 4461, 4463, 4464, 4470, 4471, 4473, 4475, 4476, 4477, 4479, 4480, 4481, 4482, 4483, 4491, 4493, 4494, 4497, 4498, 4499, 4500, 4504, 4505, 4511, 4512, 4513, 4514, 4515, 4516, 4518, 4519, 4521, 4522, 4523, 4524, 4526, 4528, 4529, 4530, 4531, 4532, 4533, 4534, 4536, 4537, 4539, 4541, 4542, 4543, 4547, 4548, 4551, 4552, 4554, 4555, 4557, 4558, 4559, 4560, 4561, 4562, 4564, 4565, 4566, 4571, 4573, 4575, 4576, 4577, 4578, 4579, 4580, 4582, 4586, 4588, 4589, 4590, 4598, 4604, 4605, 4607, 4608, 4609, 4611, 4614, 4615, 4616, 4617, 4619, 4620, 4624, 4625, 4627, 4628, 4629, 4631, 4632, 4633, 4634, 4635, 4636, 4637, 4638, 4639, 4641, 4642, 4644, 4646, 4647, 4648, 4650, 4651, 4652, 4653, 4654, 4658, 4660, 4661, 4665, 4666, 4668, 4670, 4672, 4673, 4677, 4678, 4679, 4680, 4681, 4682, 4683, 4684, 4685, 4686, 4687, 4691, 4692, 4695, 4696, 4697, 4699, 4700, 4701, 4702, 4703, 4704, 4706, 4707, 4708, 4711, 4712, 4713, 4715, 4716, 4719, 4721, 4722, 4724, 4725, 4727, 4732, 4733, 4734, 4737, 4738, 4740, 4741, 4742, 4743, 4745, 4746, 4749, 4750, 4751, 4753, 4754, 4755, 4757, 4759, 4762, 4763, 4765, 4769, 4773, 4777, 4778, 4779, 4780, 4784, 4789, 4791, 4795, 4796, 4797, 4800, 4801, 4803, 4805, 4809, 4810, 4811, 4816, 4817, 4818, 4819, 4820, 4822, 4823, 4824, 4827, 4829, 4834, 4835, 4836, 4838, 4840, 4843, 4845, 4847, 4849, 4851, 4852, 4855, 4858, 4859, 4861, 4862, 4866, 4867, 4868, 4869, 4873, 4874, 4875, 4877, 4880, 4881, 4882, 4883, 4884, 4888, 4889, 4892, 4894, 4895, 4896, 4897, 4899, 4905, 4906, 4907, 4908, 4909, 4912, 4914, 4915, 4916, 4917, 4918, 4920, 4921, 4922, 4925, 4931, 4932, 4933, 4937, 4938, 4939, 4941, 4942, 4946, 4949, 4952, 4953, 4954, 4955, 4956, 4958, 4960, 4962, 4966, 4967, 4971, 4973, 4974, 4976, 4977, 4979, 4983, 4984, 4986, 4987, 4988, 4995, 5001, 5002, 5006, 5007, 5008, 5009, 5010, 5011, 5015, 5016, 5017, 5018, 5019, 5023, 5024, 5026, 5029, 5034, 5035, 5036, 5038, 5041, 5046, 5047, 5048, 5051, 5056, 5057, 5059, 5060, 5061, 5062, 5063, 5064, 5065, 5068, 5071, 5072, 5074, 5075, 5081, 5082, 5084, 5085, 5086, 5087, 5088, 5091, 5092, 5093, 5096, 5097, 5100, 5103, 5105, 5106, 5108

--------
Cluster 2 (2131):
31, 94, 118, 133, 162, 182, 210, 213, 245, 249, 251, 253, 255, 257, 262, 265, 266, 267, 274, 276, 279, 282, 284, 285, 286, 287, 288, 290, 291, 292, 294, 302, 304, 306, 308, 310, 312, 313, 319, 320, 321, 322, 324, 327, 328, 335, 339, 340, 342, 345, 348, 349, 352, 354, 356, 357, 359, 361, 363, 367, 369, 371, 373, 376, 377, 378, 380, 383, 384, 385, 390, 391, 392, 394, 395, 400, 401, 403, 408, 410, 414, 416, 418, 419, 421, 425, 427, 431, 437, 440, 441, 443, 445, 446, 451, 455, 458, 459, 460, 461, 464, 470, 471, 472, 473, 474, 476, 480, 483, 485, 486, 487, 488, 496, 497, 498, 513, 516, 519, 522, 525, 529, 531, 532, 534, 535, 538, 540, 541, 545, 546, 547, 548, 550, 554, 556, 558, 560, 562, 564, 566, 574, 576, 578, 581, 582, 584, 587, 588, 589, 593, 597, 598, 600, 602, 603, 604, 605, 607, 609, 611, 619, 625, 628, 634, 638, 640, 641, 644, 650, 655, 656, 657, 658, 663, 667, 669, 674, 676, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 692, 694, 695, 696, 697, 698, 699, 701, 702, 707, 709, 711, 716, 717, 718, 721, 722, 723, 728, 730, 733, 734, 736, 740, 742, 743, 748, 750, 753, 754, 756, 757, 758, 762, 764, 768, 770, 771, 774, 775, 776, 779, 780, 783, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 804, 806, 807, 808, 811, 812, 813, 814, 817, 818, 820, 824, 827, 830, 833, 837, 839, 840, 842, 843, 844, 847, 850, 852, 854, 855, 856, 857, 858, 862, 866, 869, 872, 875, 877, 878, 883, 886, 892, 893, 903, 904, 907, 909, 912, 913, 915, 919, 921, 922, 925, 926, 932, 936, 937, 938, 939, 947, 949, 950, 951, 952, 953, 955, 956, 957, 959, 961, 962, 963, 965, 966, 967, 969, 972, 974, 977, 979, 980, 981, 982, 984, 985, 986, 987, 988, 989, 991, 995, 996, 998, 999, 1005, 1006, 1008, 1009, 1012, 1020, 1021, 1022, 1032, 1033, 1037, 1039, 1040, 1041, 1045, 1047, 1049, 1051, 1056, 1060, 1063, 1066, 1069, 1073, 1074, 1076, 1087, 1088, 1090, 1091, 1093, 1094, 1095, 1099, 1101, 1102, 1107, 1108, 1111, 1115, 1120, 1122, 1123, 1124, 1127, 1132, 1134, 1135, 1137, 1139, 1142, 1143, 1144, 1145, 1147, 1149, 1150, 1154, 1155, 1156, 1159, 1163, 1164, 1169, 1174, 1177, 1181, 1182, 1185, 1190, 1191, 1192, 1194, 1196, 1197, 1203, 1204, 1205, 1206, 1208, 1209, 1210, 1212, 1217, 1218, 1219, 1220, 1221, 1222, 1223, 1231, 1232, 1234, 1235, 1237, 1238, 1242, 1243, 1244, 1246, 1248, 1249, 1251, 1252, 1253, 1255, 1256, 1258, 1261, 1263, 1265, 1270, 1271, 1272, 1275, 1277, 1278, 1281, 1287, 1292, 1298, 1301, 1305, 1307, 1310, 1312, 1316, 1317, 1321, 1326, 1327, 1329, 1331, 1334, 1338, 1349, 1356, 1358, 1359, 1364, 1366, 1369, 1370, 1371, 1372, 1375, 1377, 1380, 1386, 1387, 1389, 1390, 1392, 1394, 1396, 1402, 1403, 1405, 1406, 1408, 1417, 1419, 1421, 1422, 1424, 1426, 1427, 1429, 1430, 1432, 1441, 1442, 1444, 1445, 1449, 1450, 1455, 1459, 1460, 1461, 1462, 1463, 1464, 1465, 1467, 1469, 1470, 1471, 1475, 1476, 1483, 1484, 1486, 1488, 1492, 1493, 1494, 1499, 1502, 1504, 1505, 1507, 1510, 1512, 1513, 1514, 1518, 1519, 1528, 1530, 1536, 1537, 1539, 1542, 1545, 1548, 1551, 1555, 1556, 1557, 1558, 1560, 1565, 1567, 1570, 1572, 1573, 1579, 1580, 1581, 1583, 1586, 1587, 1589, 1590, 1591, 1593, 1597, 1600, 1601, 1609, 1610, 1612, 1614, 1618, 1619, 1622, 1623, 1624, 1626, 1629, 1631, 1632, 1633, 1635, 1636, 1644, 1645, 1647, 1648, 1649, 1665, 1666, 1668, 1672, 1673, 1676, 1683, 1684, 1686, 1689, 1693, 1700, 1701, 1702, 1703, 1707, 1708, 1709, 1710, 1712, 1713, 1714, 1716, 1719, 1720, 1721, 1722, 1724, 1725, 1726, 1728, 1731, 1734, 1736, 1739, 1742, 1743, 1744, 1746, 1748, 1754, 1757, 1758, 1762, 1763, 1765, 1766, 1767, 1768, 1771, 1772, 1773, 1774, 1777, 1779, 1783, 1784, 1789, 1791, 1792, 1794, 1797, 1799, 1800, 1802, 1803, 1805, 1806, 1807, 1808, 1809, 1811, 1812, 1817, 1820, 1821, 1824, 1826, 1831, 1833, 1834, 1836, 1841, 1844, 1845, 1846, 1847, 1848, 1849, 1851, 1863, 1865, 1866, 1868, 1869, 1874, 1877, 1882, 1885, 1886, 1890, 1894, 1896, 1897, 1899, 1900, 1901, 1902, 1907, 1910, 1911, 1913, 1914, 1917, 1919, 1922, 1927, 1928, 1929, 1930, 1931, 1932, 1935, 1936, 1937, 1939, 1940, 1941, 1942, 1947, 1948, 1949, 1950, 1956, 1957, 1958, 1959, 1960, 1963, 1964, 1965, 1966, 1973, 1975, 1976, 1977, 1980, 1982, 1983, 1985, 1986, 1987, 1990, 1991, 1994, 1996, 1997, 1999, 2000, 2003, 2005, 2006, 2008, 2009, 2012, 2013, 2015, 2019, 2020, 2022, 2023, 2024, 2030, 2035, 2036, 2040, 2042, 2043, 2045, 2050, 2051, 2052, 2054, 2056, 2062, 2065, 2066, 2068, 2073, 2074, 2076, 2079, 2080, 2087, 2090, 2091, 2092, 2097, 2098, 2101, 2103, 2106, 2109, 2112, 2113, 2114, 2116, 2117, 2123, 2125, 2127, 2131, 2133, 2137, 2140, 2141, 2142, 2146, 2147, 2148, 2150, 2151, 2152, 2154, 2157, 2163, 2165, 2166, 2167, 2169, 2171, 2175, 2177, 2178, 2181, 2183, 2186, 2187, 2191, 2193, 2194, 2195, 2196, 2207, 2212, 2213, 2216, 2217, 2219, 2222, 2226, 2230, 2234, 2236, 2240, 2241, 2242, 2243, 2245, 2247, 2248, 2249, 2254, 2258, 2259, 2262, 2263, 2268, 2270, 2274, 2276, 2279, 2289, 2291, 2295, 2299, 2300, 2302, 2304, 2305, 2308, 2310, 2311, 2313, 2323, 2325, 2326, 2327, 2329, 2333, 2337, 2339, 2340, 2344, 2345, 2346, 2347, 2348, 2349, 2350, 2353, 2354, 2358, 2361, 2364, 2367, 2368, 2370, 2371, 2372, 2377, 2379, 2380, 2381, 2382, 2384, 2385, 2386, 2387, 2388, 2389, 2391, 2394, 2397, 2404, 2406, 2409, 2415, 2417, 2419, 2421, 2423, 2425, 2426, 2427, 2429, 2431, 2432, 2434, 2436, 2437, 2442, 2443, 2445, 2448, 2450, 2452, 2455, 2457, 2463, 2464, 2465, 2471, 2472, 2474, 2475, 2476, 2477, 2478, 2479, 2480, 2481, 2482, 2483, 2485, 2488, 2489, 2490, 2491, 2495, 2498, 2500, 2505, 2506, 2513, 2514, 2516, 2522, 2525, 2526, 2528, 2530, 2531, 2535, 2536, 2538, 2539, 2542, 2543, 2546, 2551, 2552, 2554, 2557, 2558, 2560, 2566, 2573, 2574, 2579, 2580, 2581, 2586, 2588, 2590, 2593, 2594, 2595, 2597, 2605, 2607, 2611, 2613, 2615, 2616, 2618, 2619, 2620, 2625, 2627, 2628, 2630, 2634, 2638, 2644, 2647, 2649, 2652, 2653, 2659, 2660, 2662, 2663, 2665, 2667, 2670, 2671, 2672, 2679, 2681, 2684, 2687, 2689, 2692, 2694, 2695, 2704, 2708, 2711, 2712, 2714, 2716, 2717, 2721, 2725, 2726, 2728, 2729, 2730, 2733, 2736, 2739, 2740, 2741, 2742, 2744, 2745, 2747, 2753, 2755, 2756, 2760, 2762, 2767, 2770, 2773, 2776, 2778, 2779, 2780, 2781, 2783, 2785, 2787, 2788, 2790, 2791, 2794, 2801, 2803, 2805, 2807, 2808, 2811, 2812, 2819, 2820, 2824, 2827, 2828, 2832, 2834, 2835, 2836, 2837, 2838, 2842, 2843, 2844, 2845, 2850, 2852, 2854, 2856, 2858, 2859, 2860, 2861, 2869, 2871, 2874, 2875, 2877, 2879, 2882, 2885, 2887, 2889, 2892, 2894, 2898, 2906, 2909, 2911, 2912, 2915, 2916, 2917, 2919, 2920, 2921, 2923, 2924, 2925, 2926, 2927, 2928, 2929, 2931, 2932, 2936, 2939, 2941, 2942, 2943, 2944, 2946, 2949, 2950, 2952, 2954, 2955, 2956, 2958, 2964, 2967, 2970, 2971, 2972, 2973, 2983, 2984, 2987, 2988, 2989, 2990, 2994, 2996, 2999, 3000, 3003, 3004, 3009, 3010, 3016, 3018, 3020, 3022, 3023, 3025, 3031, 3034, 3035, 3036, 3037, 3038, 3039, 3040, 3041, 3042, 3043, 3044, 3047, 3051, 3053, 3058, 3059, 3063, 3064, 3067, 3072, 3073, 3076, 3078, 3079, 3086, 3087, 3092, 3093, 3094, 3095, 3096, 3097, 3099, 3104, 3105, 3107, 3109, 3110, 3116, 3117, 3118, 3119, 3120, 3121, 3125, 3127, 3129, 3131, 3132, 3133, 3137, 3139, 3141, 3143, 3145, 3153, 3156, 3158, 3160, 3163, 3165, 3170, 3171, 3173, 3175, 3176, 3177, 3178, 3179, 3180, 3183, 3184, 3185, 3187, 3188, 3189, 3190, 3191, 3193, 3199, 3200, 3205, 3208, 3211, 3212, 3214, 3226, 3228, 3229, 3231, 3233, 3237, 3239, 3240, 3242, 3244, 3247, 3250, 3251, 3252, 3253, 3255, 3256, 3259, 3261, 3262, 3266, 3267, 3268, 3270, 3271, 3273, 3277, 3279, 3280, 3281, 3282, 3283, 3284, 3286, 3287, 3289, 3292, 3295, 3297, 3300, 3301, 3304, 3307, 3309, 3310, 3311, 3313, 3315, 3319, 3321, 3323, 3324, 3325, 3331, 3332, 3334, 3338, 3340, 3343, 3348, 3354, 3355, 3356, 3357, 3359, 3366, 3368, 3370, 3373, 3374, 3379, 3382, 3385, 3386, 3390, 3392, 3394, 3395, 3397, 3398, 3401, 3402, 3403, 3409, 3411, 3412, 3413, 3414, 3415, 3418, 3420, 3423, 3426, 3430, 3434, 3435, 3437, 3438, 3440, 3444, 3446, 3447, 3451, 3453, 3457, 3458, 3460, 3463, 3464, 3467, 3468, 3472, 3473, 3477, 3481, 3482, 3483, 3484, 3485, 3486, 3488, 3491, 3493, 3496, 3497, 3498, 3501, 3506, 3510, 3511, 3513, 3518, 3519, 3520, 3523, 3527, 3530, 3532, 3533, 3535, 3538, 3539, 3540, 3542, 3543, 3544, 3545, 3546, 3550, 3551, 3552, 3554, 3555, 3560, 3562, 3565, 3572, 3573, 3574, 3575, 3576, 3577, 3581, 3583, 3584, 3586, 3589, 3601, 3605, 3608, 3609, 3610, 3611, 3612, 3615, 3616, 3617, 3618, 3619, 3620, 3621, 3623, 3624, 3626, 3629, 3635, 3636, 3640, 3642, 3644, 3645, 3647, 3648, 3651, 3653, 3654, 3655, 3657, 3661, 3662, 3670, 3671, 3675, 3676, 3678, 3681, 3684, 3685, 3692, 3694, 3695, 3698, 3703, 3704, 3705, 3706, 3712, 3713, 3714, 3715, 3717, 3719, 3721, 3726, 3728, 3731, 3733, 3734, 3739, 3740, 3743, 3744, 3745, 3746, 3747, 3748, 3749, 3751, 3754, 3755, 3758, 3761, 3762, 3763, 3765, 3766, 3767, 3769, 3773, 3774, 3776, 3777, 3778, 3782, 3785, 3786, 3787, 3789, 3791, 3793, 3796, 3797, 3798, 3800, 3801, 3806, 3807, 3808, 3809, 3810, 3816, 3817, 3818, 3819, 3823, 3829, 3832, 3835, 3836, 3837, 3840, 3843, 3844, 3846, 3848, 3849, 3850, 3851, 3857, 3858, 3859, 3861, 3864, 3868, 3870, 3872, 3873, 3874, 3875, 3876, 3877, 3883, 3885, 3886, 3887, 3888, 3893, 3894, 3895, 3896, 3897, 3904, 3908, 3910, 3911, 3912, 3917, 3921, 3924, 3928, 3929, 3932, 3934, 3936, 3937, 3938, 3940, 3941, 3942, 3944, 3946, 3950, 3952, 3954, 3957, 3958, 3964, 3966, 3968, 3970, 3972, 3973, 3978, 3985, 3986, 3990, 3992, 3996, 3999, 4000, 4001, 4002, 4003, 4004, 4005, 4006, 4007, 4009, 4010, 4011, 4012, 4013, 4016, 4018, 4019, 4020, 4021, 4025, 4027, 4030, 4031, 4039, 4040, 4041, 4042, 4043, 4050, 4053, 4056, 4057, 4058, 4059, 4063, 4064, 4065, 4067, 4068, 4069, 4075, 4077, 4079, 4081, 4082, 4083, 4084, 4087, 4089, 4090, 4091, 4100, 4103, 4106, 4108, 4111, 4112, 4114, 4115, 4116, 4117, 4119, 4124, 4127, 4128, 4130, 4131, 4133, 4137, 4141, 4148, 4150, 4151, 4156, 4157, 4158, 4159, 4160, 4167, 4170, 4172, 4173, 4174, 4175, 4177, 4181, 4182, 4184, 4186, 4190, 4191, 4192, 4193, 4194, 4195, 4196, 4202, 4203, 4206, 4210, 4213, 4215, 4216, 4218, 4220, 4221, 4222, 4227, 4229, 4230, 4232, 4233, 4234, 4235, 4236, 4238, 4243, 4244, 4245, 4246, 4247, 4248, 4250, 4251, 4252, 4254, 4255, 4256, 4257, 4261, 4262, 4268, 4269, 4272, 4273, 4275, 4276, 4277, 4279, 4280, 4286, 4287, 4293, 4294, 4298, 4302, 4303, 4306, 4307, 4310, 4317, 4319, 4320, 4322, 4323, 4328, 4330, 4332, 4333, 4341, 4342, 4346, 4353, 4358, 4359, 4361, 4363, 4364, 4370, 4372, 4373, 4374, 4378, 4379, 4382, 4383, 4384, 4386, 4388, 4389, 4391, 4392, 4393, 4394, 4396, 4398, 4399, 4403, 4409, 4410, 4412, 4413, 4414, 4415, 4416, 4418, 4420, 4421, 4422, 4423, 4424, 4425, 4428, 4432, 4433, 4435, 4438, 4439, 4441, 4444, 4454, 4455, 4458, 4462, 4465, 4466, 4467, 4468, 4469, 4472, 4474, 4478, 4484, 4485, 4486, 4487, 4488, 4489, 4490, 4492, 4495, 4496, 4501, 4502, 4503, 4506, 4507, 4508, 4509, 4510, 4517, 4520, 4525, 4527, 4535, 4538, 4540, 4544, 4545, 4546, 4549, 4550, 4553, 4556, 4563, 4567, 4568, 4569, 4570, 4572, 4574, 4581, 4583, 4584, 4585, 4587, 4591, 4592, 4593, 4594, 4595, 4596, 4597, 4599, 4600, 4601, 4602, 4603, 4606, 4610, 4612, 4613, 4618, 4621, 4622, 4623, 4626, 4630, 4640, 4643, 4645, 4649, 4655, 4656, 4657, 4659, 4662, 4663, 4664, 4667, 4669, 4671, 4674, 4675, 4676, 4688, 4689, 4690, 4693, 4694, 4698, 4705, 4709, 4710, 4714, 4717, 4718, 4720, 4723, 4726, 4728, 4729, 4730, 4731, 4735, 4736, 4739, 4744, 4747, 4748, 4752, 4756, 4758, 4760, 4761, 4764, 4766, 4767, 4768, 4770, 4771, 4772, 4774, 4775, 4776, 4781, 4782, 4783, 4785, 4786, 4787, 4788, 4790, 4792, 4793, 4794, 4798, 4799, 4802, 4804, 4806, 4807, 4808, 4812, 4813, 4814, 4815, 4821, 4825, 4826, 4828, 4830, 4831, 4832, 4833, 4837, 4839, 4841, 4842, 4844, 4846, 4848, 4850, 4853, 4854, 4856, 4857, 4860, 4863, 4864, 4865, 4870, 4871, 4872, 4876, 4878, 4879, 4885, 4886, 4887, 4890, 4891, 4893, 4898, 4900, 4901, 4902, 4903, 4904, 4910, 4911, 4913, 4919, 4923, 4924, 4926, 4927, 4928, 4929, 4930, 4934, 4935, 4936, 4940, 4943, 4944, 4945, 4947, 4948, 4950, 4951, 4957, 4959, 4961, 4963, 4964, 4965, 4968, 4969, 4970, 4972, 4975, 4978, 4980, 4981, 4982, 4985, 4989, 4990, 4991, 4992, 4993, 4994, 4996, 4997, 4998, 4999, 5000, 5003, 5004, 5005, 5012, 5013, 5014, 5020, 5021, 5022, 5025, 5027, 5028, 5030, 5031, 5032, 5033, 5037, 5039, 5040, 5042, 5043, 5044, 5045, 5049, 5050, 5052, 5053, 5054, 5055, 5058, 5066, 5067, 5069, 5070, 5073, 5076, 5077, 5078, 5079, 5080, 5083, 5089, 5090, 5094, 5095, 5098, 5099, 5101, 5102, 5104, 5107, 5109

--------
array([[ 0.6485448 ,  0.23763868,  0.4646737 ,  0.16123614],
       [-0.90516543, -0.33166917, -0.64853897,  0.00843882]])

Также визуализируем результаты:

In [363]:
def draw_cluster_results(
    df: pd.DataFrame,
    col1: int,
    col2: int,
    labels: np.ndarray,
    cluster_centers: np.ndarray,
    subplot: Any | None = None,
):
    ax = None
    if subplot is None:
        ax = plt
    else:
        ax = subplot

    centroids = cluster_centers
    u_labels = np.unique(labels)

    for i in u_labels:
        ax.scatter(
            df[labels == i][df.columns[col1]],
            df[labels == i][df.columns[col2]],
            label=i,
        )

    ax.scatter(centroids[:, col1], centroids[:, col2], s=80, color="k")
In [364]:
plt.figure(figsize=(16, 12))
draw_cluster_results(df_scaled, 0, 1, labels, centers, plt.subplot(2, 2, 1)) # age vs avg_glucose_level
draw_cluster_results(df_scaled, 0, 2, labels, centers, plt.subplot(2, 2, 2)) # age vs bmi
draw_cluster_results(df_scaled, 0, 3, labels, centers, plt.subplot(2, 2, 3)) # age vs hypertension
draw_cluster_results(df_scaled, 1, 2, labels, centers, plt.subplot(2, 2, 4)) # avg_glucose_level vs bmi
No description has been provided for this image

Теперь понизим размерность данных до двух компонент и еще раз осуществим неиерархическую кластеризацию

In [365]:
from sklearn.decomposition import PCA

pca_data = PCA(n_components=2).fit_transform(df_scaled)
pca_data
Out[365]:
array([[ 2.60768539,  1.53464701],
       [ 1.49992616,  1.6776011 ],
       [ 1.29355664, -0.53864765],
       ...,
       [-0.36264968, -0.48106455],
       [ 0.63114968,  1.26696549],
       [-0.42000577, -0.20709469]])

Визуализация данных после понижения размерности:

In [366]:
plt.figure(figsize=(8, 6))
draw_data_2d(
    pd.DataFrame({"Column1": pca_data[:, 0], "Column2": pca_data[:, 1]}),
    0,
    1
)
<Figure size 800x600 with 0 Axes>
No description has been provided for this image

Визуализация результатов неиерархической кластеризации для двух кластеров с учетом понижения размерности:

In [367]:
from sklearn.cluster import KMeans

def fit_kmeans(
    reduced_data: np.ndarray, num_clusters: int, random_state: int
) -> cluster.KMeans:
    kmeans = cluster.KMeans(n_clusters=num_clusters, random_state=random_state)
    kmeans.fit(reduced_data)
    return kmeans

def draw_clusters(reduced_data: np.ndarray, kmeans: KMeans):
    h = 0.02

    x_min, x_max = reduced_data[:, 0].min() - 1, reduced_data[:, 0].max() + 1
    y_min, y_max = reduced_data[:, 1].min() - 1, reduced_data[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))

    Z = kmeans.predict(np.c_[xx.ravel(), yy.ravel()])

    Z = Z.reshape(xx.shape)
    plt.figure(1)
    plt.clf()
    plt.imshow(
        Z,
        interpolation="nearest",
        extent=(xx.min(), xx.max(), yy.min(), yy.max()),
        cmap=plt.cm.Paired,  # type: ignore
        aspect="auto",
        origin="lower",
    )

    plt.plot(reduced_data[:, 0], reduced_data[:, 1], "k.", markersize=2)
    centroids = kmeans.cluster_centers_
    plt.scatter(
        centroids[:, 0],
        centroids[:, 1],
        marker="x",
        s=169,
        linewidths=3,
        color="w",
        zorder=10,
    )
    plt.title(
        "K-means clustering (PCA-reduced data)\n"
        "Centroids are marked with white cross"
    )
    plt.xlim(x_min, x_max)
    plt.ylim(y_min, y_max)
    plt.xticks(())
    plt.yticks(())
In [368]:
kmeans = fit_kmeans(pca_data, 2, random_state)
draw_clusters(pca_data, kmeans)
No description has been provided for this image

Анализ оценки инерции для метода локтя (метод оценки суммы квадратов расстояний)

In [369]:
import math

def _get_kmeans_range(
    df: pd.DataFrame | np.ndarray, random_state: int
) -> Tuple[List, range]:
    max_clusters = int(math.sqrt(len(df)))
    clusters_range = range(2, max_clusters + 1)
    kmeans_per_k = [
        cluster.KMeans(n_clusters=k, random_state=random_state).fit(df)
        for k in clusters_range
    ]
    return kmeans_per_k, clusters_range

def get_clusters_inertia(df: pd.DataFrame, random_state: int) -> Tuple[List, range]:
    kmeans_per_k, clusters_range = _get_kmeans_range(df, random_state)
    return [model.inertia_ for model in kmeans_per_k], clusters_range

def _draw_cluster_scores(
    data: List,
    clusters_range: range,
    score_name: str,
    title: str,
):
    plt.figure(figsize=(8, 5))
    plt.plot(clusters_range, data, "bo-")
    plt.xlabel("$k$", fontsize=8)
    plt.ylabel(score_name, fontsize=8)
    plt.title(title)
    
def draw_elbow_diagram(inertias: List, clusters_range: range):
    _draw_cluster_scores(inertias, clusters_range, "Inertia", "The Elbow Diagram")
In [370]:
inertias, clusters_range = get_clusters_inertia(df_scaled, random_state)
display(clusters_range)
display(inertias)
draw_elbow_diagram(inertias, clusters_range)
range(2, 72)
[10807.951785407908,
 7230.469448316978,
 5557.52543138914,
 4797.979759685805,
 4423.644627489069,
 4111.274040074362,
 3835.9107274654943,
 3470.5709394276055,
 3214.9508312393855,
 3055.948649061301,
 2917.4624294525993,
 2775.6958508684957,
 2666.7517424114767,
 2583.67129721405,
 2454.734133533792,
 2366.97034902638,
 2300.243540824471,
 2238.6558968087775,
 2198.859569168518,
 2105.264113236806,
 2047.1365550171872,
 1974.5089970560662,
 1929.7059701072799,
 1871.56533195742,
 1822.1024899611,
 1789.851518648345,
 1755.412423308707,
 1723.9872684190675,
 1667.5634523563954,
 1636.4369435503352,
 1608.6680809373954,
 1578.8323940677194,
 1552.8989367572233,
 1520.491137014789,
 1490.2403910960472,
 1472.1227947451098,
 1441.2470981595752,
 1420.0414881358802,
 1389.6252737600823,
 1367.3892464059263,
 1335.2873410440359,
 1318.5070068269697,
 1299.4557514619496,
 1279.04141250672,
 1269.4607342885668,
 1241.4829425893622,
 1236.9364320273746,
 1220.5757555830407,
 1199.4787721885064,
 1186.2215896452149,
 1170.5811727707412,
 1158.9217988447597,
 1142.7886283279302,
 1130.6280655439107,
 1112.4530760381049,
 1107.3693682651183,
 1099.851461837676,
 1076.4412692721194,
 1061.4615354232028,
 1045.4243908226806,
 1035.036118670891,
 1022.8113452915354,
 1014.745803846552,
 1005.9123944421705,
 991.4155267495079,
 987.8048104794525,
 971.6510070731988,
 964.0072117403057,
 953.4181697151636,
 951.7655191323424]
No description has been provided for this image

На графике "Elbow Diagram" (метод локтя) оптимальное количество кластеров определяется точкой, где график начинает "сгибаться", то есть уменьшается прирост качества при добавлении новых кластеров (резкое снижение инерции становится более плавным).

На представленном выше варианте графика видно, что инерция резко падает от 2 до примерно 5 кластеров. После этого снижение инерции становится гораздо менее выраженным. Поэтому в этом случае не будет ошибкой выбрать число от 3 до 5, так как добавление большего количества кластеров уменьшает инерцию незначительно, что может не оправдывать усложнение модели.

Для выбранного же ранее варианта в 2 кластера (в процессе использования алгоритмов) инерция достаточно высокая, поэтому на таком значении, особенно если неизвестны особенности решаемой задачи, лучше не останавливаться.

Выбор количества кластеров на основе коэффициента силуэта

In [371]:
from sklearn.metrics import silhouette_score

def get_clusters_silhouette_scores(
    df: pd.DataFrame, random_state: int
) -> Tuple[List, range]:
    kmeans_per_k, clusters_range = _get_kmeans_range(df, random_state)
    return [
        float(silhouette_score(df, model.labels_)) for model in kmeans_per_k
    ], clusters_range

def draw_silhouettes_diagram(silhouette: List, clusters_range: range):
    _draw_cluster_scores(
        silhouette, clusters_range, "Silhouette score", "The Silhouette score"
    )
In [372]:
silhouette_scores, clusters_range = get_clusters_silhouette_scores(df_scaled, random_state)
display(clusters_range)
display(silhouette_scores)
draw_silhouettes_diagram(silhouette_scores, clusters_range)
range(2, 72)
[0.29075853608405966,
 0.357346280582275,
 0.3580028147866339,
 0.3060072539613545,
 0.2778622088721048,
 0.28235647440325495,
 0.26298446011032567,
 0.2519432670192678,
 0.25796332430571123,
 0.2595321756963313,
 0.25189577317992934,
 0.25716821055171396,
 0.2520497680241438,
 0.24353440201890503,
 0.24991746284790578,
 0.24532156215695916,
 0.24200645508428598,
 0.2458836870128432,
 0.24107604337960176,
 0.2394901399609211,
 0.24132182121975873,
 0.24200391337627483,
 0.2406841380203819,
 0.2399078448492621,
 0.24412970613974297,
 0.23836956197419176,
 0.2362035088131641,
 0.23661921022263044,
 0.23692749271772362,
 0.23000948461224915,
 0.23066661229530314,
 0.22745627516202846,
 0.23140639929847473,
 0.23210518261226853,
 0.23116884290692077,
 0.23097360362311076,
 0.23655235029308458,
 0.23544438069925508,
 0.23735392388178586,
 0.23905299077276798,
 0.24235545959509608,
 0.2406902608674703,
 0.239164952448719,
 0.24153292277088986,
 0.23730838097862156,
 0.23622612255542497,
 0.23178308967837488,
 0.23419780804936907,
 0.23451681572078148,
 0.23271567646200786,
 0.23308032412226642,
 0.23444237065890342,
 0.23535637084165628,
 0.23439210732139512,
 0.23533909329002442,
 0.2304452327666582,
 0.22877458477903484,
 0.2323010916454335,
 0.23542525447499896,
 0.23711126604283184,
 0.23694848474776883,
 0.23771230321441972,
 0.2370089858565995,
 0.23582819858269166,
 0.23842397638982815,
 0.23630756225043792,
 0.23982136167290108,
 0.23844722816042435,
 0.240919396127249,
 0.23844771154987698]
No description has been provided for this image

Коэффициент силуэта рассчитывается с использованием среднего расстояния внутри кластера (а) и среднего расстояния до ближайшего кластера (b) для каждого образца. Лучшее значение — 1, худшее — -1. Значения около 0 указывают на перекрывающиеся кластеры. Отрицательные значения обычно указывают на то, что образец был отнесен к неправильному кластеру.

На графике коэффициента силуэта оптимальное количество кластеров определяется пиком, где значение силуэта максимально, т.к. чем выше значение, тем лучше структура кластеров.

В данном случае из графика и предыдущего вывода списка оценок видно, что максимальное значение коэффициента силуэта наблюдается при 3 или 4 кластерах (около 0.36). Это говорит о том, что при таком количестве кластеров группы имеют наилучшее качество разделения.

Однако, если для задачи требуется большее количество кластеров, можно выбрать другое значение, где коэффициент силуэта все еще достаточно высокий (по сравнению с остальными вариантами). К примеру значения 5 или 7.

Для выбранного ранее варианта разделения на 2 кластера значение коэффициента силуэта равно примерно 0.2908, что указывает на то, что кластеры имеют нечеткую границу, а разделение данных является неоптимальным. Это может быть связано либо с недостаточным количеством кластеров, либо с особенностями самих данных, которые затрудняют их разделение на четко определенные группы.

Пример анализа силуэтов для разбиения от 2 до 12 кластеров

In [373]:
from typing import Dict
from sklearn.metrics import silhouette_samples
import matplotlib.cm as cm

def get_clusters_silhouettes(df: np.ndarray, random_state: int) -> Dict:
    kmeans_per_k, _ = _get_kmeans_range(df, random_state)
    clusters_silhouettes: Dict = {}
    for model in kmeans_per_k:
        silhouette_value = silhouette_score(df, model.labels_)
        sample_silhouette_values = silhouette_samples(df, model.labels_)
        clusters_silhouettes[model.n_clusters] = (
            silhouette_value,
            sample_silhouette_values,
            model,
        )
    return clusters_silhouettes

def _draw_silhouette(
    ax: Any,
    reduced_data: np.ndarray,
    n_clusters: int,
    silhouette_avg: float,
    sample_silhouette_values: List,
    cluster_labels: List,
):
    ax.set_xlim([-0.1, 1])
    ax.set_ylim([0, len(reduced_data) + (n_clusters + 1) * 10])

    y_lower = 10
    for i in range(n_clusters):
        ith_cluster_silhouette_values = sample_silhouette_values[cluster_labels == i]

        ith_cluster_silhouette_values.sort()

        size_cluster_i = ith_cluster_silhouette_values.shape[0]
        y_upper = y_lower + size_cluster_i

        color = cm.nipy_spectral(float(i) / n_clusters)  # type: ignore
        ax.fill_betweenx(
            np.arange(y_lower, y_upper),
            0,
            ith_cluster_silhouette_values,
            facecolor=color,
            edgecolor=color,
            alpha=0.7,
        )

        ax.text(-0.05, y_lower + 0.5 * size_cluster_i, str(i))

        y_lower = y_upper + 10  # 10 for the 0 samples

    ax.set_title("The silhouette plot for the various clusters.")
    ax.set_xlabel("The silhouette coefficient values")
    ax.set_ylabel("Cluster label")

    ax.axvline(x=silhouette_avg, color="red", linestyle="--")

    ax.set_yticks([])
    ax.set_xticks([-0.1, 0, 0.2, 0.4, 0.6, 0.8, 1])


def _draw_cluster_data(
    ax: Any,
    reduced_data: np.ndarray,
    n_clusters: int,
    cluster_labels: np.ndarray,
    cluster_centers: np.ndarray,
):
    colors = cm.nipy_spectral(cluster_labels.astype(float) / n_clusters)  # type: ignore
    ax.scatter(
        reduced_data[:, 0],
        reduced_data[:, 1],
        marker=".",
        s=30,
        lw=0,
        alpha=0.7,
        c=colors,
        edgecolor="k",
    )

    ax.scatter(
        cluster_centers[:, 0],
        cluster_centers[:, 1],
        marker="o",
        c="white",
        alpha=1,
        s=200,
        edgecolor="k",
    )

    for i, c in enumerate(cluster_centers):
        ax.scatter(c[0], c[1], marker="$%d$" % i, alpha=1, s=50, edgecolor="k")

    ax.set_title("The visualization of the clustered data.")
    ax.set_xlabel("Feature space for the 1st feature")
    ax.set_ylabel("Feature space for the 2nd feature")

def draw_silhouettes(reduced_data: np.ndarray, silhouettes: Dict):
    for key, value in silhouettes.items():
        if key > 12:
            return 
        fig, (ax1, ax2) = plt.subplots(1, 2)
        fig.set_size_inches(18, 7)

        n_clusters = key
        silhouette_avg = value[0]
        sample_silhouette_values = value[1]
        cluster_labels = value[2].labels_
        cluster_centers = value[2].cluster_centers_

        _draw_silhouette(
            ax1,
            reduced_data,
            n_clusters,
            silhouette_avg,
            sample_silhouette_values,
            cluster_labels,
        )

        _draw_cluster_data(
            ax2,
            reduced_data,
            n_clusters,
            cluster_labels,
            cluster_centers,
        )

        plt.suptitle(
            "Silhouette analysis for KMeans clustering on sample data with n_clusters = %d"
            % n_clusters,
            fontsize=14,
            fontweight="bold",
        )
In [374]:
silhouettes = get_clusters_silhouettes(pca_data, random_state)
draw_silhouettes(pca_data, silhouettes)
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image