Microsegment.ru
  • Главная страница
  • О проекте
  • Портфолио
  • Блог
Системы

Кластерный анализ модели предоставления разрешений в Tessa

Кластерный анализ модели предоставления разрешений в Tessa
Системы

Для анализа существующей модели предоставления разрешений в информационной системе (далее ИС) предлагается провести кластеризацию данных, используемых для предоставления разрешений. В кластеризации может принять участие около 100 числовых и текстовых параметров, представляющих собой категориальные признаки с количеством категорий от 2 до нескольких тысяч. Далее представлена основа выполнения кластеризации данных с большим количеством текстовых и числовых показателей на Python.

Кластерный анализ предлагается проводить методом DBSCAN. В целях этого кластерного анализа совместно с DeepSeek был разработан код генерации ролевой модели и модели предоставления разрешений в Tessa.

Кластерный анализ модели предоставления разрешений в Tessa¶

Для анализа существующей модели предоставления разрешений в информационной системе (далее ИС) предлагается провести кластеризацию данных, используемых для предоставления разрешений. В кластеризации может принять участие около 100 числовых и текстовых параметров, представляющих собой категориальные признаки с количеством категорий от 2 до нескольких тысяч. Далее представлена основа выполнения кластеризации данных с большим количеством текстовых и числовых показателей на Python.

Кластерный анализ предлагается проводить методом DBSCAN. В целях этого кластерного анализа совместно с DeepSeek был разработан код генерации ролевой модели и модели предоставления разрешений в Tessa.

Кластерный анализ тестовой модели предоставления разрешений¶

Загрузка библиотек¶

In [5]:
# Загрузка библиотек
import pandas as pd
import numpy as np

from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.neighbors import NearestNeighbors
from sklearn.cluster import DBSCAN
from sklearn.metrics import silhouette_score, silhouette_samples

import matplotlib.pyplot as plt

Загрузка и предобработка данных¶

In [7]:
# Загрузка и подготовка данных
data = pd.read_csv('df_permission.csv', encoding='utf-8-sig', sep=';')

print('Анализ данных:')
print(f'Размер: {data.shape}')
data.head(3)
Анализ данных:
Размер: (756, 28)
Out[7]:
Название роли Тип роли Скрытая роль Тип карточки Состояние карточки Создание карточки Чтение карточки Редактирование карточки Удаление карточки Добавление файлов … Права супермодератора Подписка на уведомления Расширенные настройки прав доступа Создание шаблона и копирование Пропуск этапов Редактирование своих сообщений Редактирование всех сообщений Добавление обсуждений Чтение обсуждений Чтение и отправка сообщений
0 Смирнов Иван Сотрудник 0 Входящий документ Проект 1 1 1 1 1 … 1 1 1 1 1 1 1 1 1 1
1 Смирнов Иван Сотрудник 0 Входящий документ На согласовании 1 1 1 1 1 … 1 1 1 1 1 1 1 1 1 1
2 Смирнов Иван Сотрудник 0 Входящий документ Утвержден 1 1 1 1 1 … 1 1 1 1 1 1 1 1 1 1

3 rows × 28 columns

In [8]:
# Преобразуем все в строки и заполняем пропуски
data = data.astype(str).fillna('Unknown')

# Убираем логически не нужные поля
#data = data.drop(['rule_id', 'rule_name', 'role_id', 'document_type_id', 'condition'], axis=1)

# Удаляем полные дубликаты объектов
data = data.drop_duplicates()

print(data.info())
print()
display(data.describe())
print()
data.head(5)
<class 'pandas.core.frame.DataFrame'>
Index: 624 entries, 0 to 755
Data columns (total 28 columns):
 #   Column                                      Non-Null Count  Dtype 
---  ------                                      --------------  ----- 
 0   Название роли                               624 non-null    object
 1   Тип роли                                    624 non-null    object
 2   Скрытая роль                                624 non-null    object
 3   Тип карточки                                624 non-null    object
 4   Состояние карточки                          624 non-null    object
 5   Создание карточки                           624 non-null    object
 6   Чтение карточки                             624 non-null    object
 7   Редактирование карточки                     624 non-null    object
 8   Удаление карточки                           624 non-null    object
 9   Добавление файлов                           624 non-null    object
 10  Редактирование всех файлов                  624 non-null    object
 11  Удаление всех файлов                        624 non-null    object
 12  Редактирование собственных файлов           624 non-null    object
 13  Удаление собственных файлов                 624 non-null    object
 14  Инициация типового процесса отправки задач  624 non-null    object
 15  Ручное редактирование номера                624 non-null    object
 16  Редактирование маршрута                     624 non-null    object
 17  Полный пересчет маршрута                    624 non-null    object
 18  Права супермодератора                       624 non-null    object
 19  Подписка на уведомления                     624 non-null    object
 20  Расширенные настройки прав доступа          624 non-null    object
 21  Создание шаблона и копирование              624 non-null    object
 22  Пропуск этапов                              624 non-null    object
 23  Редактирование своих сообщений              624 non-null    object
 24  Редактирование всех сообщений               624 non-null    object
 25  Добавление обсуждений                       624 non-null    object
 26  Чтение обсуждений                           624 non-null    object
 27  Чтение и отправка сообщений                 624 non-null    object
dtypes: object(28)
memory usage: 141.4+ KB
None

Название роли Тип роли Скрытая роль Тип карточки Состояние карточки Создание карточки Чтение карточки Редактирование карточки Удаление карточки Добавление файлов … Права супермодератора Подписка на уведомления Расширенные настройки прав доступа Создание шаблона и копирование Пропуск этапов Редактирование своих сообщений Редактирование всех сообщений Добавление обсуждений Чтение обсуждений Чтение и отправка сообщений
count 624 624 624 624 624 624 624 624 624 624 … 624 624 624 624 624 624 624 624 624 624
unique 37 3 1 5 4 2 1 2 2 2 … 2 2 2 2 2 2 2 2 2 2
top Смирнов Иван Сотрудник 0 Договор Проект 1 1 1 0 1 … 0 1 0 1 0 1 0 1 1 1
freq 56 500 624 152 156 395 624 401 370 476 … 424 554 424 312 424 481 424 470 567 566

4 rows × 28 columns


Out[8]:
Название роли Тип роли Скрытая роль Тип карточки Состояние карточки Создание карточки Чтение карточки Редактирование карточки Удаление карточки Добавление файлов … Права супермодератора Подписка на уведомления Расширенные настройки прав доступа Создание шаблона и копирование Пропуск этапов Редактирование своих сообщений Редактирование всех сообщений Добавление обсуждений Чтение обсуждений Чтение и отправка сообщений
0 Смирнов Иван Сотрудник 0 Входящий документ Проект 1 1 1 1 1 … 1 1 1 1 1 1 1 1 1 1
1 Смирнов Иван Сотрудник 0 Входящий документ На согласовании 1 1 1 1 1 … 1 1 1 1 1 1 1 1 1 1
2 Смирнов Иван Сотрудник 0 Входящий документ Утвержден 1 1 1 1 1 … 1 1 1 1 1 1 1 1 1 1
3 Смирнов Иван Сотрудник 0 Входящий документ Архив 1 1 1 1 1 … 1 1 1 1 1 1 1 1 1 1
4 Смирнов Иван Сотрудник 0 Договор Проект 1 1 1 1 1 … 1 1 1 1 1 1 1 1 1 1

5 rows × 28 columns

In [9]:
# Анализ уникальных значений
print("Уникальные значения по полям:")
for col in data.columns:
    unique_count = data[col].nunique()
    print(f"{col}: {unique_count} уникальных значений")
Уникальные значения по полям:
Название роли: 37 уникальных значений
Тип роли: 3 уникальных значений
Скрытая роль: 1 уникальных значений
Тип карточки: 5 уникальных значений
Состояние карточки: 4 уникальных значений
Создание карточки: 2 уникальных значений
Чтение карточки: 1 уникальных значений
Редактирование карточки: 2 уникальных значений
Удаление карточки: 2 уникальных значений
Добавление файлов: 2 уникальных значений
Редактирование всех файлов: 2 уникальных значений
Удаление всех файлов: 2 уникальных значений
Редактирование собственных файлов: 2 уникальных значений
Удаление собственных файлов: 2 уникальных значений
Инициация типового процесса отправки задач: 2 уникальных значений
Ручное редактирование номера: 2 уникальных значений
Редактирование маршрута: 2 уникальных значений
Полный пересчет маршрута: 2 уникальных значений
Права супермодератора: 2 уникальных значений
Подписка на уведомления: 2 уникальных значений
Расширенные настройки прав доступа: 2 уникальных значений
Создание шаблона и копирование: 2 уникальных значений
Пропуск этапов: 2 уникальных значений
Редактирование своих сообщений: 2 уникальных значений
Редактирование всех сообщений: 2 уникальных значений
Добавление обсуждений: 2 уникальных значений
Чтение обсуждений: 2 уникальных значений
Чтение и отправка сообщений: 2 уникальных значений
In [10]:
# Упрощенное кодирование категориальных признаков
def simple_category_encoding(data):
    """Упрощенное кодирование категориальных признаков"""
    encoded_data = pd.DataFrame()
    
    for col in data.columns:
        unique_count = data[col].nunique()
        
        if unique_count <= 20:
            # One-Hot для малого количества категорий
            dummies = pd.get_dummies(data[col], prefix=col)
            encoded_data = pd.concat([encoded_data, dummies], axis=1)
        else:
            # Frequency encoding для большого количества категорий
            freq_map = data[col].value_counts().to_dict()
            encoded_data[col] = data[col].map(freq_map)
    
    return encoded_data

print("Кодирование признаков...")
encoded_data = simple_category_encoding(data)
print(f"После кодирования: {encoded_data.shape} признаков")
encoded_data.head(5)
Кодирование признаков...
После кодирования: (624, 59) признаков
Out[10]:
Название роли Тип роли_Подразделение Тип роли_Сотрудник Тип роли_Статическая роль Скрытая роль_0 Тип карточки_Входящий документ Тип карточки_Договор Тип карточки_Приказ Тип карточки_Служебная записка Тип карточки_Счет … Редактирование своих сообщений_0 Редактирование своих сообщений_1 Редактирование всех сообщений_0 Редактирование всех сообщений_1 Добавление обсуждений_0 Добавление обсуждений_1 Чтение обсуждений_0 Чтение обсуждений_1 Чтение и отправка сообщений_0 Чтение и отправка сообщений_1
0 56 False True False True True False False False False … False True False True False True False True False True
1 56 False True False True True False False False False … False True False True False True False True False True
2 56 False True False True True False False False False … False True False True False True False True False True
3 56 False True False True True False False False False … False True False True False True False True False True
4 56 False True False True False True False False False … False True False True False True False True False True

5 rows × 59 columns

In [11]:
# Стандартизация
scaler = StandardScaler()
scaled_data = scaler.fit_transform(encoded_data)

print('Размерность:', scaled_data.shape)
scaled_data
Размерность: (624, 59)
Out[11]:
array([[ 2.41478203, -0.41585133,  0.49799598, ...,  0.31706324,
        -0.32011482,  0.32011482],
       [ 2.41478203, -0.41585133,  0.49799598, ...,  0.31706324,
        -0.32011482,  0.32011482],
       [ 2.41478203, -0.41585133,  0.49799598, ...,  0.31706324,
        -0.32011482,  0.32011482],
       ...,
       [-1.15589092, -0.41585133, -2.00804832, ...,  0.31706324,
         3.12387911, -3.12387911],
       [-1.15589092, -0.41585133, -2.00804832, ...,  0.31706324,
         3.12387911, -3.12387911],
       [-1.15589092, -0.41585133, -2.00804832, ...,  0.31706324,
         3.12387911, -3.12387911]])
In [12]:
# Уменьшение размерности с проверкой
if scaled_data.shape[1] > 2:
    # Используем PCA только если много признаков
    pca = PCA(n_components=0.9)  # 90% дисперсии
    reduced_data = pca.fit_transform(scaled_data)
    print(f"После PCA: {reduced_data.shape[1]} компонент")
else:
    reduced_data = scaled_data
    print("PCA не применен - мало признаков")

print('Размерность:', reduced_data.shape)
reduced_data
После PCA: 14 компонент
Размерность: (624, 14)
Out[12]:
array([[-6.66944381e+00,  1.73699452e+00, -2.77415616e-01, ...,
        -7.89983622e-01, -1.46725876e-03,  4.07035509e-01],
       [-6.64921634e+00,  1.72833260e+00, -2.88282044e-01, ...,
        -1.01157229e+00, -2.84718758e-01, -1.52178472e-01],
       [-6.64777272e+00,  1.71797606e+00, -2.61027343e-01, ...,
        -1.11808482e+00, -1.07316438e-01, -3.07728761e-01],
       ...,
       [ 5.91365519e+00,  1.56469945e+00,  3.63010672e+00, ...,
        -1.74072581e-02, -2.03900835e+00,  1.50558501e+00],
       [ 4.04313778e+00, -2.28800299e+00,  4.25269136e+00, ...,
         8.32828951e-01, -1.21114122e+00,  2.38354682e+00],
       [ 4.79466646e+00,  1.02528981e-01,  1.70128985e+00, ...,
        -1.10297823e+00,  4.14896794e-01, -7.86012767e-02]])

Подбор параметров модели DBSCAN¶

In [14]:
# Аналитический подбор параметров DBSCAN на основе статистики расстояний
def analytical_dbscan_params(X, auto_min_samples=True):
    """
    Аналитический подбор параметров DBSCAN на основе статистики расстояний
    """
    n_samples, n_features = X.shape
    
    # Автоматический подбор min_samples
    # Эвристика: min_samples = 2 * размерность данных
    min_samples = max(2, 2 * n_features)
    # Корректировка для больших наборов данных
    if auto_min_samples & n_samples > 1000:
        min_samples = min(min_samples, 20)
    
    # Анализ распределения расстояний до k-го соседа
    k = min_samples
    nbrs = NearestNeighbors(n_neighbors=k)
    nbrs.fit(X)
    distances, indices = nbrs.kneighbors(X)
    
    # Берем расстояние до k-го соседа (последний в массиве)
    k_distances = distances[:, k-1]
    
    # Статистический анализ для определения eps
    # Метод 1: Анализ квантилей и выбросов
    # (наиболее устойчив к выбросам и подходит к любому распределению)
    Q1 = np.percentile(k_distances, 25)
    Q3 = np.percentile(k_distances, 75)
    IQR = Q3 - Q1
    standard_method = Q3 + 1.5 * IQR

    # Метод 2: Анализ стандартного отклонения от среднего
    # (наименее устойчив к выбросам, подходит только к стандартному распределению)
    std_near_mean = np.mean(k_distances) + 1.5 * np.std(k_distances)
    
    # Метод 3: Поиск точки перегиба в распределении
    # (колено в распределении)
    sorted_distances = np.sort(k_distances)
    gradients = np.gradient(sorted_distances)
    
    # Находим точку максимального изменения градиента
    # (вторая производная)
    inflection_idx = np.argmax(np.abs(np.gradient(gradients)))
    eps_inflection = sorted_distances[inflection_idx]
    
    # Метод 4: Адаптивный порог на основе статистики
    eps_adaptive = np.percentile(k_distances, 85)  # 85-й перцентиль
    
    # Комбинированный подход
    eps_candidates = [
        standard_method,  # Стандартный метод выбросов через квантили
        std_near_mean, # Метод с использованием стандартного отклонения от среднего
        eps_inflection, # 
        eps_adaptive
    ]
    
    # Выбираем наиболее стабильное значение
    #optimal_eps = np.median(eps_candidates) # Рискованный вариант
    optimal_eps = np.min(eps_candidates) # Консервативный вариант (учитывает все подходы)
    
    # Визуализация для валидации
    plt.figure(figsize=(12, 4))
    
    plt.subplot(1, 2, 1)
    plt.hist(k_distances, bins=50, alpha=0.7, edgecolor='black')
    plt.axvline(optimal_eps, color='red', linestyle='--', 
                label=f'Optimal eps: {optimal_eps:.3f}')
    plt.xlabel(f'Distance to {k}-th neighbor')
    plt.ylabel('Frequency')
    plt.legend()
    plt.title('Distribution of k-distances')
    
    plt.subplot(1, 2, 2)
    plt.plot(sorted_distances, 'b-', alpha=0.7)
    plt.axvline(inflection_idx, color='red', linestyle='--', 
                label=f'Inflection point')
    plt.axhline(optimal_eps, color='green', linestyle='--', 
                label=f'Optimal eps')
    plt.xlabel('Points (sorted)')
    plt.ylabel(f'Distance to {k}-th neighbor')
    plt.legend()
    plt.title('Sorted k-distances with inflection point')
    
    plt.tight_layout()
    plt.show()
    
    print(f"Рекомендуемые параметры метода DBSCAN:")
    print(f"  eps: {optimal_eps:.4f}")
    print(f"  min_samples: {min_samples}")
    print(f"  Обоснование: статистический анализ {k}-расстояний")
    
    return optimal_eps, min_samples

# Использование
optimal_eps, min_samples = analytical_dbscan_params(reduced_data)
No description has been provided for this image
Рекомендуемые параметры метода DBSCAN:
  eps: 5.3630
  min_samples: 28
  Обоснование: статистический анализ 28-расстояний

Кластеризация методом DBSCAN¶

In [16]:
# Кластеризация методом DBSCAN
dbscan = DBSCAN(eps=optimal_eps, min_samples=min_samples)
clusters = dbscan.fit_predict(reduced_data)
In [17]:
# Присваивание объектам номеров кластеров
data_with_clusters = data.copy()
data_with_clusters['cluster'] = clusters

# Дополнение данных объектов индивидуальными значениями метода силуэта
silhouette_vals = silhouette_samples(reduced_data, clusters)
data_with_clusters['silhouette_vals'] = silhouette_vals

# Сохранение результатов кластеризации
data_with_clusters.to_excel('data_with_clusters.xlsx')

# Предварительный просмотр результатов кластерного анализа
print('Предварительный просмотр результатов кластерного анализа:')
#pd.DataFrame({'clusters': clusters, 'silhouette_vals': silhouette_vals})
data_with_clusters.head(5)
Предварительный просмотр результатов кластерного анализа:
Out[17]:
Название роли Тип роли Скрытая роль Тип карточки Состояние карточки Создание карточки Чтение карточки Редактирование карточки Удаление карточки Добавление файлов … Расширенные настройки прав доступа Создание шаблона и копирование Пропуск этапов Редактирование своих сообщений Редактирование всех сообщений Добавление обсуждений Чтение обсуждений Чтение и отправка сообщений cluster silhouette_vals
0 Смирнов Иван Сотрудник 0 Входящий документ Проект 1 1 1 1 1 … 1 1 1 1 1 1 1 1 0 0.595508
1 Смирнов Иван Сотрудник 0 Входящий документ На согласовании 1 1 1 1 1 … 1 1 1 1 1 1 1 1 0 0.588349
2 Смирнов Иван Сотрудник 0 Входящий документ Утвержден 1 1 1 1 1 … 1 1 1 1 1 1 1 1 0 0.582772
3 Смирнов Иван Сотрудник 0 Входящий документ Архив 1 1 1 1 1 … 1 1 1 1 1 1 1 1 0 0.586075
4 Смирнов Иван Сотрудник 0 Договор Проект 1 1 1 1 1 … 1 1 1 1 1 1 1 1 0 0.577844

5 rows × 30 columns

Анализ кластеров¶

In [19]:
# Анализ кластеров

# Количество кластеров (+1, т.к. нумерация с нуля)
clusters_num = max(clusters) + 1
emissions_share = data_with_clusters[data_with_clusters['cluster'] < 0]['cluster'].count() / data_with_clusters['cluster'].count()

print(f'Количество кластеров: {clusters_num}')
print(f'Доля выбросов: {emissions_share:.2%}\n')

# Визуализация результатов
plt.figure(figsize=(15, 5))

# Визуализация кластеров (первые 2 компоненты)
plt.subplot(1, 3, 1)
if reduced_data.shape[1] >= 2:
    plt.scatter(reduced_data[:, 0], reduced_data[:, 1], c=clusters, cmap='tab10', alpha=0.6)
    plt.xlabel('Компонента 1')
    plt.ylabel('Компонента 2')
else:
    # Если одна компонента - гистограмма
    for cluster in range(clusters_nums):
        cluster_data = reduced_data[clusters == cluster, 0]
        plt.hist(cluster_data, alpha=0.6, label=f'Cluster {cluster}')
    plt.xlabel('Значение компоненты')
    plt.ylabel('Частота')
    plt.legend()

plt.title('Визуализация кластеров')

# Размеры кластеров
plt.subplot(1, 3, 2)
cluster_sizes = pd.Series(clusters).value_counts().sort_index()
plt.bar(cluster_sizes.index, cluster_sizes.values)
plt.xlabel('Кластер')
plt.ylabel('Количество объектов')
plt.title('Размеры кластеров')

for i, size in enumerate(cluster_sizes.values):
    plt.text(i, size, str(size), ha='center', va='bottom')

# Качество кластеризации по кластерам
plt.subplot(1, 3, 3)
y_lower = 10
for i in range(clusters_num):
    cluster_silhouette_vals = silhouette_vals[clusters == i]
    cluster_silhouette_vals.sort()
    
    plt.barh(range(y_lower, y_lower + len(cluster_silhouette_vals)),
             cluster_silhouette_vals, height=1)
    plt.text(-0.05, y_lower + len(cluster_silhouette_vals) // 2, str(i))
    y_lower += len(cluster_silhouette_vals) + 10

plt.xlabel('Silhouette Score')
plt.ylabel('Кластер')
plt.title('Silhouette Analysis')

plt.tight_layout()
plt.show()
Количество кластеров: 3
Доля выбросов: 13.14%

No description has been provided for this image
In [20]:
# Анализ параметров кластеров
def analyzing_cluster_parameters(data_with_clusters, clusters_nums, minimum_cluster_share):
    ''' Анализ параметров кластеров '''
    # data_with_clusters - датафрейм с указанием принадлежности к кластерам
    # cluster_nums - функция типа range(clusters_num) 
    #                для задания номеров кластеров для их перебора циклом
    # minimum_cluster_share - минимальная доля категории внутри кластера для ее учета

    # Анализируем распределение по самым информативным признакам
    # Выбираем признаки с разумным количеством категорий
    analysis_cols = []
    for col in data_with_clusters.columns:
        if 2 <= data_with_clusters[col].nunique() <= 50: # признаки с разумным количеством категорий
            analysis_cols.append(col)
        if len(analysis_cols) >= 30: # максимальное количество информативных признаков
            break
    
    if not analysis_cols:
        # Если не нашли подходящие, берем первые 3 с наименьшим количеством категорий
        col_stats = [(col, data_with_clusters[col].nunique()) for col in data_with_clusters.columns]
        col_stats.sort(key=lambda x: x[1])
        analysis_cols = [col for col, count in col_stats[:3]]
    
    for cluster_num in clusters_nums:
        cluster_data = data_with_clusters[data_with_clusters['cluster'] == cluster_num]
        print(f"\n\n--- Кластер {cluster_num} ({len(cluster_data)} объектов, {len(cluster_data)/len(data_with_clusters):.1%}) ---")
        
        print('\nСилуэтная оценка качества объектов кластера:')
        display(pd.DataFrame({'Min':[data_with_clusters.loc[data_with_clusters['cluster'] == cluster_num, 'silhouette_vals'].min()], 
                              'Mean':[data_with_clusters.loc[data_with_clusters['cluster'] == cluster_num, 'silhouette_vals'].mean()], 
                              'Median':[data_with_clusters.loc[data_with_clusters['cluster'] == cluster_num, 'silhouette_vals'].median()], 
                              'Max':[data_with_clusters.loc[data_with_clusters['cluster'] == cluster_num, 'silhouette_vals'].max()]}).round(3))
        
        col = []
        value = []
        count = []
        percentage = []
        for c in analysis_cols:
            top_value = cluster_data[c].value_counts().head(1)
            if len(top_value) > 0:
                col.append(c)
                value.append(top_value.index[0])
                count.append(top_value.values[0])
                percentage.append(count[-1] / len(cluster_data))
        print('\nКлючевые параметры, характеризующие кластер:')
        display_claster = pd.DataFrame({'Параметр':col, 'Модальное значение':value, 'Количество':count, 'Доля в кластере':percentage})
        display(display_claster[display_claster['Доля в кластере'] > minimum_cluster_share])
    
        print('\nПримеры объектов кластера:')
        display(data_with_clusters[data_with_clusters['cluster'] == cluster_num].head(5))

analyzing_cluster_parameters(data_with_clusters, range(clusters_num), .75)

--- Кластер 0 (200 объектов, 32.1%) ---

Силуэтная оценка качества объектов кластера:
Min Mean Median Max
0 0.563 0.586 0.584 0.608
Ключевые параметры, характеризующие кластер:
Параметр Модальное значение Количество Доля в кластере
1 Тип роли Сотрудник 200 1.0
4 Создание карточки 1 200 1.0
5 Редактирование карточки 1 200 1.0
6 Удаление карточки 1 200 1.0
7 Добавление файлов 1 200 1.0
8 Редактирование всех файлов 1 200 1.0
9 Удаление всех файлов 1 200 1.0
10 Редактирование собственных файлов 1 200 1.0
11 Удаление собственных файлов 1 200 1.0
12 Инициация типового процесса отправки задач 1 200 1.0
13 Ручное редактирование номера 1 200 1.0
14 Редактирование маршрута 1 200 1.0
15 Полный пересчет маршрута 1 200 1.0
16 Права супермодератора 1 200 1.0
17 Подписка на уведомления 1 200 1.0
18 Расширенные настройки прав доступа 1 200 1.0
19 Создание шаблона и копирование 1 200 1.0
20 Пропуск этапов 1 200 1.0
21 Редактирование своих сообщений 1 200 1.0
22 Редактирование всех сообщений 1 200 1.0
23 Добавление обсуждений 1 200 1.0
24 Чтение обсуждений 1 200 1.0
25 Чтение и отправка сообщений 1 200 1.0
26 cluster 0 200 1.0
Примеры объектов кластера:
Название роли Тип роли Скрытая роль Тип карточки Состояние карточки Создание карточки Чтение карточки Редактирование карточки Удаление карточки Добавление файлов … Расширенные настройки прав доступа Создание шаблона и копирование Пропуск этапов Редактирование своих сообщений Редактирование всех сообщений Добавление обсуждений Чтение обсуждений Чтение и отправка сообщений cluster silhouette_vals
0 Смирнов Иван Сотрудник 0 Входящий документ Проект 1 1 1 1 1 … 1 1 1 1 1 1 1 1 0 0.595508
1 Смирнов Иван Сотрудник 0 Входящий документ На согласовании 1 1 1 1 1 … 1 1 1 1 1 1 1 1 0 0.588349
2 Смирнов Иван Сотрудник 0 Входящий документ Утвержден 1 1 1 1 1 … 1 1 1 1 1 1 1 1 0 0.582772
3 Смирнов Иван Сотрудник 0 Входящий документ Архив 1 1 1 1 1 … 1 1 1 1 1 1 1 1 0 0.586075
4 Смирнов Иван Сотрудник 0 Договор Проект 1 1 1 1 1 … 1 1 1 1 1 1 1 1 0 0.577844

5 rows × 30 columns


--- Кластер 1 (230 объектов, 36.9%) ---

Силуэтная оценка качества объектов кластера:
Min Mean Median Max
0 -0.184 0.121 0.123 0.309
Ключевые параметры, характеризующие кластер:
Параметр Модальное значение Количество Доля в кластере
1 Тип роли Сотрудник 183 0.795652
4 Создание карточки 0 182 0.791304
6 Удаление карточки 0 204 0.886957
8 Редактирование всех файлов 0 230 1.000000
9 Удаление всех файлов 0 230 1.000000
13 Ручное редактирование номера 0 230 1.000000
14 Редактирование маршрута 0 230 1.000000
15 Полный пересчет маршрута 0 230 1.000000
16 Права супермодератора 0 230 1.000000
17 Подписка на уведомления 1 207 0.900000
18 Расширенные настройки прав доступа 0 230 1.000000
19 Создание шаблона и копирование 0 230 1.000000
20 Пропуск этапов 0 230 1.000000
22 Редактирование всех сообщений 0 230 1.000000
24 Чтение обсуждений 1 229 0.995652
25 Чтение и отправка сообщений 1 221 0.960870
26 cluster 1 230 1.000000
Примеры объектов кластера:
Название роли Тип роли Скрытая роль Тип карточки Состояние карточки Создание карточки Чтение карточки Редактирование карточки Удаление карточки Добавление файлов … Расширенные настройки прав доступа Создание шаблона и копирование Пропуск этапов Редактирование своих сообщений Редактирование всех сообщений Добавление обсуждений Чтение обсуждений Чтение и отправка сообщений cluster silhouette_vals
180 Обработчик входящих документов Статическая роль 0 Входящий документ Проект 1 1 1 1 1 … 0 0 0 1 0 1 1 1 1 -0.072081
181 Обработчик входящих документов Статическая роль 0 Входящий документ На согласовании 0 1 1 0 1 … 0 0 0 1 0 1 1 1 1 0.028767
182 Обработчик входящих документов Статическая роль 0 Входящий документ Утвержден 0 1 0 0 1 … 0 0 0 1 0 1 1 1 1 0.072624
183 Обработчик входящих документов Статическая роль 0 Входящий документ Архив 0 1 0 0 1 … 0 0 0 1 0 1 1 1 1 0.070186
184 Обработчик входящих документов Статическая роль 0 Служебная записка Проект 1 1 1 1 1 … 0 0 0 1 0 1 1 1 1 -0.070408

5 rows × 30 columns


--- Кластер 2 (112 объектов, 17.9%) ---

Силуэтная оценка качества объектов кластера:
Min Mean Median Max
0 0.343 0.518 0.511 0.563
Ключевые параметры, характеризующие кластер:
Параметр Модальное значение Количество Доля в кластере
1 Тип роли Сотрудник 104 0.928571
4 Создание карточки 1 112 1.000000
7 Добавление файлов 1 112 1.000000
8 Редактирование всех файлов 1 112 1.000000
9 Удаление всех файлов 0 112 1.000000
10 Редактирование собственных файлов 1 112 1.000000
11 Удаление собственных файлов 1 112 1.000000
12 Инициация типового процесса отправки задач 1 112 1.000000
13 Ручное редактирование номера 0 112 1.000000
14 Редактирование маршрута 1 112 1.000000
15 Полный пересчет маршрута 0 112 1.000000
16 Права супермодератора 0 112 1.000000
17 Подписка на уведомления 1 112 1.000000
18 Расширенные настройки прав доступа 0 112 1.000000
19 Создание шаблона и копирование 1 112 1.000000
20 Пропуск этапов 0 112 1.000000
21 Редактирование своих сообщений 1 112 1.000000
22 Редактирование всех сообщений 0 112 1.000000
23 Добавление обсуждений 1 112 1.000000
24 Чтение обсуждений 1 112 1.000000
25 Чтение и отправка сообщений 1 112 1.000000
26 cluster 2 112 1.000000
Примеры объектов кластера:
Название роли Тип роли Скрытая роль Тип карточки Состояние карточки Создание карточки Чтение карточки Редактирование карточки Удаление карточки Добавление файлов … Расширенные настройки прав доступа Создание шаблона и копирование Пропуск этапов Редактирование своих сообщений Редактирование всех сообщений Добавление обсуждений Чтение обсуждений Чтение и отправка сообщений cluster silhouette_vals
308 Ответственный за договоры Статическая роль 0 Договор Проект 1 1 1 1 1 … 0 1 0 1 0 1 1 1 2 0.360071
309 Ответственный за договоры Статическая роль 0 Договор На согласовании 1 1 1 0 1 … 0 1 0 1 0 1 1 1 2 0.381258
310 Ответственный за договоры Статическая роль 0 Договор Утвержден 1 1 1 0 1 … 0 1 0 1 0 1 1 1 2 0.382574
311 Ответственный за договоры Статическая роль 0 Договор Архив 1 1 0 0 1 … 0 1 0 1 0 1 1 1 2 0.343410
312 Ответственный за договоры Статическая роль 0 Счет Проект 1 1 1 1 1 … 0 1 0 1 0 1 1 1 2 0.360925

5 rows × 30 columns

In [21]:
# Табличное представление параметров кластеров
cluster_parameters = data_with_clusters[data_with_clusters['cluster'] >= 0].groupby(['cluster']).agg(lambda x: x.mode().iloc[0]).reset_index()
cluster_parameters['cluster_size'] = data_with_clusters.groupby(['cluster']).size()
cluster_parameters['percentage_cluster_size'] = cluster_parameters['cluster_size'] / len(data_with_clusters)

# Сохранение полученного табличного представления ключевых параметров кластеров
cluster_parameters.to_excel('cluster_parameters.xlsx')

print('Табличное представление медианных значений ключевых параметров кластеров:')
cluster_parameters
Табличное представление медианных значений ключевых параметров кластеров:
Out[21]:
cluster Название роли Тип роли Скрытая роль Тип карточки Состояние карточки Создание карточки Чтение карточки Редактирование карточки Удаление карточки … Создание шаблона и копирование Пропуск этапов Редактирование своих сообщений Редактирование всех сообщений Добавление обсуждений Чтение обсуждений Чтение и отправка сообщений silhouette_vals cluster_size percentage_cluster_size
0 0 Кузнецов Елена Сотрудник 0 Входящий документ Архив 1 1 1 1 … 1 1 1 1 1 1 1 0.566934 200 0.320513
1 1 Смирнов Иван Сотрудник 0 Служебная записка Проект 0 1 0 0 … 0 0 1 0 1 1 1 -0.086274 230 0.368590
2 2 Кузнецов Иван Сотрудник 0 Договор Архив 1 1 1 0 … 1 0 1 0 1 1 1 0.501731 112 0.179487

3 rows × 32 columns

In [22]:
# Итоговая информация
silhouette_avg = data_with_clusters['silhouette_vals'].mean() #silhouette_score(reduced_data, clusters)
print(f"\n=== ИТОГИ ===")
print(f"Исходные данные: {data.shape[1]} категориальных признаков")
print(f"После кодирования: {encoded_data.shape[1]} признаков")
print(f"Для кластеризации: {reduced_data.shape[1]} компонент")
print(f"Число кластеров: {clusters_num}")
print(f"Распределение по кластерам:")
for cluster_num in range(clusters_num):
    count = (clusters == clusters_num).sum()
    print(f"  Кластер {clusters_num}: {count} объектов ({count/len(clusters):.1%})")
=== ИТОГИ ===
Исходные данные: 28 категориальных признаков
После кодирования: 59 признаков
Для кластеризации: 14 компонент
Число кластеров: 3
Распределение по кластерам:
  Кластер 3: 0 объектов (0.0%)
  Кластер 3: 0 объектов (0.0%)
  Кластер 3: 0 объектов (0.0%)

Анализ выбросов¶

In [24]:
# Анализ выбросов, полученных на основе силуэтной оценки объектов сущствующих кластеров
print('Анализ выбросов, полученных на основе силуэтной оценки объектов сущствующих кластеров:')
analyzing_cluster_parameters(data_with_clusters[data_with_clusters['silhouette_vals'] < 0], 
                             data_with_clusters[(data_with_clusters['silhouette_vals'] < 0) & (data_with_clusters['cluster'] >= 0)]['cluster'].unique(), 
                             .75)
Анализ выбросов, полученных на основе силуэтной оценки объектов сущствующих кластеров:


--- Кластер 1 (42 объектов, 84.0%) ---

Силуэтная оценка качества объектов кластера:
Min Mean Median Max
0 -0.184 -0.085 -0.083 -0.0
Ключевые параметры, характеризующие кластер:
Параметр Модальное значение Количество Доля в кластере
4 Создание карточки 1 37 0.880952
5 Редактирование карточки 1 34 0.809524
7 Добавление файлов 1 32 0.761905
8 Редактирование собственных файлов 1 37 0.880952
9 Удаление собственных файлов 1 33 0.785714
14 Чтение обсуждений 1 41 0.976190
15 Чтение и отправка сообщений 1 34 0.809524
16 cluster 1 42 1.000000
Примеры объектов кластера:
Название роли Тип роли Скрытая роль Тип карточки Состояние карточки Создание карточки Чтение карточки Редактирование карточки Удаление карточки Добавление файлов … Расширенные настройки прав доступа Создание шаблона и копирование Пропуск этапов Редактирование своих сообщений Редактирование всех сообщений Добавление обсуждений Чтение обсуждений Чтение и отправка сообщений cluster silhouette_vals
180 Обработчик входящих документов Статическая роль 0 Входящий документ Проект 1 1 1 1 1 … 0 0 0 1 0 1 1 1 1 -0.072081
184 Обработчик входящих документов Статическая роль 0 Служебная записка Проект 1 1 1 1 1 … 0 0 0 1 0 1 1 1 1 -0.070408
188 Смирнов Иван Сотрудник 0 Входящий документ Проект 1 1 1 1 1 … 0 0 0 1 0 1 1 1 1 -0.083878
192 Смирнов Иван Сотрудник 0 Служебная записка Проект 1 1 1 1 1 … 0 0 0 1 0 1 1 1 1 -0.081775
196 Петров Мария Сотрудник 0 Входящий документ Проект 1 1 1 1 1 … 0 0 0 1 0 1 1 1 1 -0.084201

5 rows × 30 columns

In [25]:
# Анализ выбросов, полученных в результате кластеризации методом DBSCAN

print('Анализ выбросов, полученных в результате кластеризации методом DBSCAN:')

cluster_data = data_with_clusters[data_with_clusters['cluster'] < 0]
print(f"\n\n--- Кластер выбросов ({len(cluster_data)} объектов, {len(cluster_data)/len(data_with_clusters):.1%}) ---")
        
print('\nСилуэтная оценка качества объектов кластера выбросов:')
display(pd.DataFrame({'Min':[cluster_data['silhouette_vals'].min()], 
                      'Mean':[cluster_data['silhouette_vals'].mean()], 
                      'Median':[cluster_data['silhouette_vals'].median()], 
                      'Max':[cluster_data['silhouette_vals'].max()]}).round(3))

analysis_cols = []
for col in cluster_data.columns:
    if 2 <= cluster_data[col].nunique() <= 50:
        analysis_cols.append(col)
    if len(analysis_cols) >= 30: 
        break

if not analysis_cols:
    # Если не нашли подходящие, берем первые 3 с наименьшим количеством категорий
    col_stats = [(col, cluster_data[col].nunique()) for col in data.columns]
    col_stats.sort(key=lambda x: x[1])
    analysis_cols = [col for col, count in col_stats[:3]]

col = []
value = []
count = []
percentage = []
for c in analysis_cols:
    top_value = cluster_data[c].value_counts().head(1)
    if len(top_value) > 0:
        col.append(c)
        value.append(top_value.index[0])
        count.append(top_value.values[0])
        percentage.append(count[-1] / len(cluster_data))
print('\nКлючевые параметры, характеризующие кластер:')
display_claster = pd.DataFrame({'Параметр':col, 'Модальное значение':value, 'Количество':count, 'Доля в кластере':percentage})
display(display_claster[display_claster['Доля в кластере'] > .75])

print('\nПримеры объектов кластера:')
display(cluster_data.head(5))
Анализ выбросов, полученных в результате кластеризации методом DBSCAN:


--- Кластер выбросов (82 объектов, 13.1%) ---

Силуэтная оценка качества объектов кластера выбросов:
Min Mean Median Max
0 -0.127 0.157 0.188 0.315
Ключевые параметры, характеризующие кластер:
Параметр Модальное значение Количество Доля в кластере
Примеры объектов кластера:
Название роли Тип роли Скрытая роль Тип карточки Состояние карточки Создание карточки Чтение карточки Редактирование карточки Удаление карточки Добавление файлов … Расширенные настройки прав доступа Создание шаблона и копирование Пропуск этапов Редактирование своих сообщений Редактирование всех сообщений Добавление обсуждений Чтение обсуждений Чтение и отправка сообщений cluster silhouette_vals
628 Руководство Подразделение 0 Договор Проект 0 1 0 0 1 … 0 0 0 1 0 0 1 0 -1 0.062176
629 Руководство Подразделение 0 Договор На согласовании 0 1 1 0 0 … 0 0 0 1 0 1 0 0 -1 0.250915
630 Руководство Подразделение 0 Договор Утвержден 0 1 1 0 1 … 0 0 0 1 0 1 0 1 -1 0.170804
634 Руководство Подразделение 0 Служебная записка Утвержден 1 1 1 0 1 … 0 0 0 0 0 1 0 1 -1 0.191462
635 Руководство Подразделение 0 Служебная записка Архив 0 1 0 0 1 … 0 0 0 0 0 1 0 0 -1 0.255555

5 rows × 30 columns

Альтернативный вариант OPTICS для больших датафреймов с кластерами сложной формы

import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import OPTICS
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons, make_blobs

# 1. ЗАГРУЗКА ДАННЫХ
X = pd.read_csv('df_permission.csv', encoding='utf-8-sig', sep=';')

# 2. МАСШТАБИРОВАНИЕ ПРИЗНАКОВ (критично для метрик расстояния)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 3. ПОДБОР ПАРАМЕТРОВ НА ПОДВЫБОРКЕ (для экономии времени)
sample_size = min(20000, len(X_scaled))
sample_indices = np.random.choice(len(X_scaled), sample_size, replace=False)
X_sample = X_scaled[sample_indices]

# Визуализируем первые 2 признака для понимания структуры
plt.figure(figsize=(10, 6))
plt.scatter(X_sample[:, 0], X_sample[:, 1], s=1, alpha=0.6, c='gray')
plt.title("Структура данных (первые 2 признака)")
plt.show()

# 4. СОЗДАНИЕ И ОБУЧЕНИЕ МОДЕЛИ OPTICS
# Ключевые параметры:
# - min_samples: минимальный размер кластера (~1% от данных или от 50-100)
# - max_eps: максимальный радиус поиска (None для автоматического)
# - metric: метрика расстояния (евклидова для масштабированных данных)
# - algorithm: эффективная структура данных для больших наборов
# - n_jobs: распараллеливание

min_samples = max(50, int(0.01 * sample_size))  # 1% от подвыборки, но не менее 50

optics_model = OPTICS(
    min_samples=min_samples,
    max_eps=np.inf,  # Не ограничиваем заранее
    metric='euclidean',
    cluster_method='xi',  # Алгоритм выделения кластеров
    xi=0.05,             # Параметр для определения перепадов плотности
    min_cluster_size=0.05,  # Минимальная доля точек в кластере
    algorithm='ball_tree',  # Эффективная структура для многомерных данных
    n_jobs=-1,           # Использовать все ядра
    leaf_size=30         # Размер листа для ball_tree
)

# Обучаем на подвыборке для быстрой настройки
optics_model.fit(X_sample)

# 5. ПОЛУЧЕНИЕ И АНАЛИЗ РЕЗУЛЬТАТОВ
labels = optics_model.labels_
n_clusters = len(set(labels)) - (1 if -1 in labels else 0)

print(f"\nРЕЗУЛЬТАТЫ НА ПОДВЫБОРКЕ ({sample_size} объектов):")
print(f"Найдено кластеров: {n_clusters}")
print(f"Точек шума: {np.sum(labels == -1)}")
print(f"Размеры кластеров: {np.bincount(labels[labels >= 0])}")

# 6. ВИЗУАЛИЗАЦИЯ КЛАСТЕРОВ (по первым 2 признакам)
plt.figure(figsize=(12, 8))
unique_labels = set(labels)
colors = plt.cm.Spectral(np.linspace(0, 1, len(unique_labels)))

for k, col in zip(unique_labels, colors):
    if k == -1:
        col = 'k'  # Черный для шума
        alpha = 0.1
    else:
        alpha = 0.6

    class_members = labels == k
    plt.scatter(X_sample[class_members, 0], 
                X_sample[class_members, 1], 
                s=2, c=[col], alpha=alpha, label=f'Cluster {k}' if k != -1 else 'Noise')

plt.title(f'OPTICS кластеризация: {n_clusters} кластеров')
plt.legend()
plt.show()

# 7. ЗАПУСК НА ПОЛНОМ НАБОРЕ ДАННЫХ (если результаты на подвыборке хорошие)
if n_clusters > 1 and np.sum(labels == -1) < 0.5 * sample_size:  # Проверка качества
    print("\nЗапуск OPTICS на полном наборе данных...")

    # Увеличиваем min_samples пропорционально
    full_min_samples = max(100, int(0.01 * len(X_scaled)))

    optics_full = OPTICS(
        min_samples=full_min_samples,
        max_eps=np.inf,
        metric='euclidean',
        cluster_method='xi',
        xi=0.05,
        min_cluster_size=0.02,  # Можно уменьшить для большего числа кластеров
        algorithm='ball_tree',
        n_jobs=-1,
        leaf_size=40
    )

    # Обучаем на полных данных (может занять время)
    optics_full.fit(X_scaled)
    full_labels = optics_full.labels_

    print(f"\nРЕЗУЛЬТАТЫ НА ПОЛНЫХ ДАННЫХ ({len(X_scaled)} объектов):")
    print(f"Найдено кластеров: {len(set(full_labels)) - (1 if -1 in full_labels else 0)}")
    print(f"Точек шума: {np.sum(full_labels == -1)} ({np.sum(full_labels == -1)/len(full_labels)*100:.1f}%)")

    # Сохранение результатов в датафрейм
    df_result = pd.DataFrame(X, columns=[f'feature_{i}' for i in range(n_features)])
    df_result['cluster_label'] = full_labels
    df_result['is_noise'] = (full_labels == -1)

    print(f"\nПервые 5 строк результатов:")
    print(df_result[['feature_0', 'feature_1', 'cluster_label', 'is_noise']].head())
else:
    print("\nРезультаты на подвыборке неудовлетворительные. Настройте параметры.")

Python анализ информационная система корпоративная информационная система практика

Предыдущая статьяАнализ ошибок в Tessa (Тесса)Следующая статья DeepSeek сгенерировал ролевую модель предоставления разрешений в Tessa

Рубрики

Метки

abc abcd excel ms sql pandas Python sql tessa VBA xyz анализ виртуальный помощник данные знания информационная система информация искусственный интеллект кластерный анализ комбинаторика компетенции корпоративная информационная система маркетинг математика мудрость о проекте оптимизация ошибка практика программное обеспечение пэст ролевая модель теория теория вероятностей тесса тест юмор языки программирования

Политика конфиденциальности

Продолжая использовать данный сайт вы подтверждаете свое согласие с условиями его политики конфиденциальности. Подробнее…




Администрация и владельцы данного информационного ресурса не несут ответственности за возможные последствия, связанные с использованием информации, размещенной на нем.


Все права защищены. При копировании материалов сайта обязательно указывать ссылку на © Microsegment.ru (2020-2025)