AIM-PIbd-31-Ievlewa-M-D/lab2/lab2.ipynb

1.5 MiB
Raw Blame History

1. Датасет: Диабет у индейцев Пима

https://www.kaggle.com/datasets/uciml/pima-indians-diabetes-database

О наборе данных:

Этот набор данных был получен из Национального института диабета, болезней органов пищеварения и почек. Цель набора данных - диагностически предсказать, есть ли у пациента сахарный диабет, на основе определенных диагностических измерений, включенных в набор данных. На выбор этих образцов из более обширной базы данных было наложено несколько ограничений. В частности, все пациенты были женщинами в возрасте не менее 21 года, родом из племени пима.

Таким образом:
  • Объект наблюдения - женщины племени пима, возрастом от 21 года.
  • Атрибуты: Pregnancies, Glucose, BloodPressure, SkinThickness, Insulin, BMI, DiabetesPedigreeFunction, Age, Outcome.
  • Проблемная область: Предсказание диабета у пациента на основе измерений.
In [1]:
import pandas as pd
df = pd.read_csv(".//static//csv//diabetes.csv", sep=",")
print('Количество колонок: ' + str(df.columns.size))  
print('Колонки: ' + ', '.join(df.columns)+'\n')

df.info()
df.head()
Количество колонок: 9
Колонки: Pregnancies, Glucose, BloodPressure, SkinThickness, Insulin, BMI, DiabetesPedigreeFunction, Age, Outcome

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 768 entries, 0 to 767
Data columns (total 9 columns):
 #   Column                    Non-Null Count  Dtype  
---  ------                    --------------  -----  
 0   Pregnancies               768 non-null    int64  
 1   Glucose                   768 non-null    int64  
 2   BloodPressure             768 non-null    int64  
 3   SkinThickness             768 non-null    int64  
 4   Insulin                   768 non-null    int64  
 5   BMI                       768 non-null    float64
 6   DiabetesPedigreeFunction  768 non-null    float64
 7   Age                       768 non-null    int64  
 8   Outcome                   768 non-null    int64  
dtypes: float64(2), int64(7)
memory usage: 54.1 KB
Out[1]:
Pregnancies Glucose BloodPressure SkinThickness Insulin BMI DiabetesPedigreeFunction Age Outcome
0 6 148 72 35 0 33.6 0.627 50 1
1 1 85 66 29 0 26.6 0.351 31 0
2 8 183 64 0 0 23.3 0.672 32 1
3 1 89 66 23 94 28.1 0.167 21 0
4 0 137 40 35 168 43.1 2.288 33 1

Получение сведений о пропущенных данных

Типы пропущенных данных:

  • None - представление пустых данных в Python
  • NaN - представление пустых данных в Pandas
  • '' - пустая строка
In [2]:
# Количество пустых значений признаков
print(df.isnull().sum())
print()

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

# Процент пустых значений признаков
for i in df.columns:
    null_rate = df[i].isnull().sum() / len(df) * 100
    print(f"{i} процент пустых значений: %{null_rate:.2f}")
Pregnancies                 0
Glucose                     0
BloodPressure               0
SkinThickness               0
Insulin                     0
BMI                         0
DiabetesPedigreeFunction    0
Age                         0
Outcome                     0
dtype: int64

Pregnancies                 False
Glucose                     False
BloodPressure               False
SkinThickness               False
Insulin                     False
BMI                         False
DiabetesPedigreeFunction    False
Age                         False
Outcome                     False
dtype: bool

Pregnancies процент пустых значений: %0.00
Glucose процент пустых значений: %0.00
BloodPressure процент пустых значений: %0.00
SkinThickness процент пустых значений: %0.00
Insulin процент пустых значений: %0.00
BMI процент пустых значений: %0.00
DiabetesPedigreeFunction процент пустых значений: %0.00
Age процент пустых значений: %0.00
Outcome процент пустых значений: %0.00
Проверим выбросы и устраним их:
In [3]:
numeric_columns = ['Pregnancies', 'Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI', 'DiabetesPedigreeFunction', 'Age']
for column in numeric_columns:
    if pd.api.types.is_numeric_dtype(df[column]):  # Проверяем, является ли колонка числовой
        q1 = df[column].quantile(0.25)  # Находим 1-й квартиль (Q1)
        q3 = df[column].quantile(0.75)  # Находим 3-й квартиль (Q3)
        iqr = q3 - q1  # Вычисляем межквартильный размах (IQR)

        # Определяем границы для выбросов
        lower_bound = q1 - 1.5 * iqr  # Нижняя граница
        upper_bound = q3 + 1.5 * iqr  # Верхняя граница

        # Подсчитываем количество выбросов
        outliers = df[(df[column] < lower_bound) | (df[column] > upper_bound)]
        outlier_count = outliers.shape[0]

        # Устраняем выбросы: заменяем значения ниже нижней границы на саму нижнюю границу, а выше верхней — на верхнюю
        df[column] = df[column].apply(lambda x: lower_bound if x < lower_bound else upper_bound if x > upper_bound else x)

        print(f"Колонка {column}:")
        print(f"  Есть выбросы: {'Да' if outlier_count > 0 else 'Нет'}")
        print(f"  Количество выбросов: {outlier_count}")
        print(f"  Минимальное значение: {df[column].min()}")
        print(f"  Максимальное значение: {df[column].max()}")
        print(f"  1-й квартиль (Q1): {q1}")
        print(f"  3-й квартиль (Q3): {q3}\n")
Колонка Pregnancies:
  Есть выбросы: Да
  Количество выбросов: 4
  Минимальное значение: 0.0
  Максимальное значение: 13.5
  1-й квартиль (Q1): 1.0
  3-й квартиль (Q3): 6.0

Колонка Glucose:
  Есть выбросы: Да
  Количество выбросов: 5
  Минимальное значение: 37.125
  Максимальное значение: 199.0
  1-й квартиль (Q1): 99.0
  3-й квартиль (Q3): 140.25

Колонка BloodPressure:
  Есть выбросы: Да
  Количество выбросов: 45
  Минимальное значение: 35.0
  Максимальное значение: 107.0
  1-й квартиль (Q1): 62.0
  3-й квартиль (Q3): 80.0

Колонка SkinThickness:
  Есть выбросы: Да
  Количество выбросов: 1
  Минимальное значение: 0.0
  Максимальное значение: 80.0
  1-й квартиль (Q1): 0.0
  3-й квартиль (Q3): 32.0

Колонка Insulin:
  Есть выбросы: Да
  Количество выбросов: 34
  Минимальное значение: 0.0
  Максимальное значение: 318.125
  1-й квартиль (Q1): 0.0
  3-й квартиль (Q3): 127.25

Колонка BMI:
  Есть выбросы: Да
  Количество выбросов: 19
  Минимальное значение: 13.35
  Максимальное значение: 50.550000000000004
  1-й квартиль (Q1): 27.3
  3-й квартиль (Q3): 36.6

Колонка DiabetesPedigreeFunction:
  Есть выбросы: Да
  Количество выбросов: 29
  Минимальное значение: 0.078
  Максимальное значение: 1.2
  1-й квартиль (Q1): 0.24375
  3-й квартиль (Q3): 0.62625

Колонка Age:
  Есть выбросы: Да
  Количество выбросов: 9
  Минимальное значение: 21.0
  Максимальное значение: 66.5
  1-й квартиль (Q1): 24.0
  3-й квартиль (Q3): 41.0

Постараемся выявить зависимости Outcome от остальных колонок:

In [4]:
import matplotlib.pyplot as plt
    # Создание диаграмм зависимости
for column in numeric_columns:
    plt.figure(figsize=(8, 6))  # Установка размера графика
    if pd.api.types.is_numeric_dtype(df[column]):  # Проверяем, является ли колонка числовой
        # Проверяем, содержит ли колонка только два уникальных значения (0 и 1)
        if df[column].nunique() == 2 and set(df[column].unique()).issubset({0, 1}):
            # Если да, то строим столбчатую диаграмму
            counts = df[column].value_counts()  # Считаем количество повторений каждого значения
            counts.plot(kind='bar')  # Создаем столбчатую диаграмму
            plt.title(f'Количество значений для {column}')
            plt.xlabel(column)
            plt.ylabel('Количество повторений')
        else:
            # Если колонка числовая, создаем диаграмму рассеяния
            plt.scatter(df['Outcome'], df[column], alpha=0.5)  # Создаем диаграмму рассеяния
            plt.title(f'Зависимость {column} от Outcome')
            plt.xlabel('Outcome (0 = нет, 1 = да)')
            plt.ylabel(column)
            plt.xticks([0, 1])  # Установка меток по оси X
            plt.grid()  # Добавление сетки для удобства восприятия
    else:
        # Если колонка не числовая, строим столбчатую диаграмму
        counts = df[column].value_counts()  # Считаем количество повторений каждого значения
        counts.plot(kind='bar')  # Создаем столбчатую диаграмму
        plt.title(f'Количество значений для {column}')
        plt.xlabel(column)
        plt.ylabel('Количество повторений')

    plt.show()  # Отображение графика
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

Разобьем наш набор на выборки относительно параметра Outcome:

In [5]:
# Функция для создания выборок
from sklearn.model_selection import train_test_split

def split_stratified_into_train_val_test(
    df_input,
    stratify_colname="y",
    frac_train=0.6,
    frac_val=0.15,
    frac_test=0.25,
    random_state=None,
):

    if frac_train + frac_val + frac_test != 1.0:
        raise ValueError(
            "fractions %f, %f, %f do not add up to 1.0"
            % (frac_train, frac_val, frac_test)
        )

    if stratify_colname not in df_input.columns:
        raise ValueError("%s is not a column in the dataframe" % (stratify_colname))

    X = df_input  # Contains all columns.
    y = df_input[
        [stratify_colname]
    ]  # Dataframe of just the column on which to stratify.

    # Split original dataframe into train and temp dataframes.
    df_train, df_temp, y_train, y_temp = train_test_split(
        X, y, stratify=y, test_size=(1.0 - frac_train), random_state=random_state
    )

    # Split the temp dataframe into val and test dataframes.
    relative_frac_test = frac_test / (frac_val + frac_test)
    df_val, df_test, y_val, y_test = train_test_split(
        df_temp,
        y_temp,
        stratify=y_temp,
        test_size=relative_frac_test,
        random_state=random_state,
    )

    assert len(df_input) == len(df_train) + len(df_val) + len(df_test)

    return df_train, df_val, df_test
In [6]:
# Вывод распределения количества наблюдений по меткам (классам)
print(df.Outcome.value_counts())
print()

data = df.copy()

df_train, df_val, df_test = split_stratified_into_train_val_test(
   data, stratify_colname="Outcome", frac_train=0.60, frac_val=0.20, frac_test=0.20
)

print("Обучающая выборка: ", df_train.shape)
print(df_train.Outcome.value_counts())
counts = df_train['Outcome'].value_counts()
plt.figure(figsize=(2, 2))# Установка размера графика
plt.pie(counts, labels=counts.index, autopct='%1.1f%%', startangle=90)# Построение круговой диаграммы
plt.title('Распределение классов Outcome в обучающей выборке')# Добавление заголовка
plt.show()# Отображение графика

print("Контрольная выборка: ", df_val.shape)
print(df_val.Outcome.value_counts())
counts = df_val['Outcome'].value_counts()
plt.figure(figsize=(2, 2))
plt.pie(counts, labels=counts.index, autopct='%1.1f%%', startangle=90)
plt.title('Распределение классов Outcome в контрольной выборке')
plt.show()

print("Тестовая выборка: ", df_test.shape)
print(df_test.Outcome.value_counts())
counts = df_test['Outcome'].value_counts()
plt.figure(figsize=(2, 2))
plt.pie(counts, labels=counts.index, autopct='%1.1f%%', startangle=90)
plt.title('Распределение классов Outcome в тестовой выборке')
plt.show()
Outcome
0    500
1    268
Name: count, dtype: int64

Обучающая выборка:  (460, 9)
Outcome
0    299
1    161
Name: count, dtype: int64
No description has been provided for this image
Контрольная выборка:  (154, 9)
Outcome
0    101
1     53
Name: count, dtype: int64
No description has been provided for this image
Тестовая выборка:  (154, 9)
Outcome
0    100
1     54
Name: count, dtype: int64
No description has been provided for this image

Сбалансируем распределение:

  1. Балансировка данных оверсемплингом. Это метод, увеличивающий число наблюдений в меньшинственном классе для достижения более равномерного распределения классов.
In [7]:
from imblearn.over_sampling import ADASYN

ada = ADASYN()

print("Обучающая выборка: ", df_train.shape)
print(df_train.Outcome.value_counts())

X_resampled, y_resampled = ada.fit_resample(df_train, df_train["Outcome"])
df_train_adasyn = pd.DataFrame(X_resampled)

print("Обучающая выборка после oversampling: ", df_train_adasyn.shape)
print(df_train_adasyn.Outcome.value_counts())

counts = df_train_adasyn['Outcome'].value_counts()
plt.figure(figsize=(2, 2))
plt.pie(counts, labels=counts.index, autopct='%1.1f%%', startangle=90)
plt.title('Распределение классов Outcome в тренировочной выборке после ADASYN')
plt.show()

df_train_adasyn
Обучающая выборка:  (460, 9)
Outcome
0    299
1    161
Name: count, dtype: int64
Обучающая выборка после oversampling:  (586, 9)
Outcome
0    299
1    287
Name: count, dtype: int64
No description has been provided for this image
Out[7]:
Pregnancies Glucose BloodPressure SkinThickness Insulin BMI DiabetesPedigreeFunction Age Outcome
0 4.000000 171.000000 72.000000 0.000000 0.000000 43.600000 0.479000 26.000000 1
1 4.000000 110.000000 76.000000 20.000000 100.000000 28.400000 0.118000 27.000000 0
2 3.000000 106.000000 72.000000 0.000000 0.000000 25.800000 0.207000 27.000000 0
3 0.000000 141.000000 35.000000 0.000000 0.000000 42.400000 0.205000 29.000000 1
4 1.000000 100.000000 66.000000 15.000000 56.000000 23.600000 0.666000 26.000000 0
... ... ... ... ... ... ... ... ... ...
581 4.223037 113.446073 77.243141 0.000000 0.000000 30.687204 0.404846 46.797068 1
582 3.000000 78.972919 65.566707 31.513540 79.243727 32.556671 0.711110 26.486460 1
583 3.000000 79.630698 76.091174 31.184651 73.323715 33.609117 1.024212 26.815349 1
584 2.984921 95.708532 66.462302 28.552777 159.015079 36.005535 0.685819 27.432143 1
585 10.312967 142.374065 77.082295 41.915212 124.164589 37.666584 0.495783 40.875312 1

586 rows × 9 columns

  1. Балансировка данных андерсемплингом. Этот метод помогает сбалансировать выборку, уменьшая количество экземпляров класса большинства, чтобы привести его в соответствие с классом меньшинства.
In [8]:
from imblearn.under_sampling import RandomUnderSampler

rus = RandomUnderSampler()# Создание экземпляра RandomUnderSampler

# Применение RandomUnderSampler
X_resampled, y_resampled = rus.fit_resample(df_train.drop(columns=['Outcome']), df_train['Outcome'])

# Создание нового DataFrame
df_train_undersampled = pd.DataFrame(X_resampled)
df_train_undersampled['Outcome'] = y_resampled  # Добавление целевой переменной

# Вывод информации о новой выборке
print("Обучающая выборка после undersampling: ", df_train_undersampled.shape)
print(df_train_undersampled['Outcome'].value_counts())

# Визуализация распределения классов
counts = df_train_undersampled['Outcome'].value_counts()
plt.figure(figsize=(2, 2))
plt.pie(counts, labels=counts.index, autopct='%1.1f%%', startangle=90)
plt.title('Распределение классов Outcome в тренировочной выборке после Undersampling')
plt.show()
Обучающая выборка после undersampling:  (322, 9)
Outcome
0    161
1    161
Name: count, dtype: int64
No description has been provided for this image

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

https://www.kaggle.com/datasets/fedesoriano/stroke-prediction-dataset

О наборе данных:

По данным Всемирной организации здравоохранения (ВОЗ), инсульт является второй по значимости причиной смертности во всем мире, на его долю приходится примерно 11% от общего числа смертей. Этот набор данных используется для прогнозирования вероятности инсульта у пациента на основе входных параметров, таких как пол, возраст, различные заболевания и статус курильщика. Каждая строка в данных содержит соответствующую информацию о пациенте.

Атрибуты:

  1. id: уникальный идентификатор
  2. gender: "Male", "Female" или "Other"
  3. age: возраст пациента
  4. hypertension: 0, если у пациента нет артериальной гипертензии, 1, если у пациента есть артериальная гипертензия
  5. heart_disease: 0, если у пациента нет сердечных заболеваний, 1, если у пациента есть сердечные заболевания
  6. ever_married: "No" или "Yes"
  7. work_type: "children", "Govt_jov", "Never_worked", "Private" or "Self-employed"
  8. Residence_type: "Rural" or "Urban"
  9. avg_glucose_level: средний уровень глюкозы в крови
  10. bmi: индекс массы тела
  11. smoking_status: "formerly smoked", "never smoked", "smokes" или "Unknown"*
  12. stroke: 1, если у пациента был инсульт, или 0, если нет.
Таким образом:
  • Объект наблюдения - Реальные пациенты.
  • Атрибуты: id, gender, age, hypertension, heart_disease, ever_married, work_type, Residence_type, avg_glucose_level, bmi, smoking_status, stroke.
  • Проблемная область: Прогнозирование вероятности инсульта у пациента.
In [11]:
import pandas as pd
df = pd.read_csv(".//static//csv//stroke.csv", sep=",")
print('Количество колонок: ' + str(df.columns.size))  
print('Колонки: ' + ', '.join(df.columns)+'\n')

df.info()
df.head()
Количество колонок: 12
Колонки: id, gender, age, hypertension, heart_disease, ever_married, work_type, Residence_type, avg_glucose_level, bmi, smoking_status, stroke

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5110 entries, 0 to 5109
Data columns (total 12 columns):
 #   Column             Non-Null Count  Dtype  
---  ------             --------------  -----  
 0   id                 5110 non-null   int64  
 1   gender             5110 non-null   object 
 2   age                5110 non-null   float64
 3   hypertension       5110 non-null   int64  
 4   heart_disease      5110 non-null   int64  
 5   ever_married       5110 non-null   object 
 6   work_type          5110 non-null   object 
 7   Residence_type     5110 non-null   object 
 8   avg_glucose_level  5110 non-null   float64
 9   bmi                4909 non-null   float64
 10  smoking_status     5110 non-null   object 
 11  stroke             5110 non-null   int64  
dtypes: float64(3), int64(4), object(5)
memory usage: 479.2+ KB
Out[11]:
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

Получение сведений о пропущенных данных

Типы пропущенных данных:

  • None - представление пустых данных в Python
  • NaN - представление пустых данных в Pandas
  • '' - пустая строка
In [10]:
# Количество пустых значений признаков
print(df.isnull().sum())
print()

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

# Процент пустых значений признаков
for i in df.columns:
    null_rate = df[i].isnull().sum() / len(df) * 100
    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

id процент пустых значений: %0.00
gender процент пустых значений: %0.00
age процент пустых значений: %0.00
hypertension процент пустых значений: %0.00
heart_disease процент пустых значений: %0.00
ever_married процент пустых значений: %0.00
work_type процент пустых значений: %0.00
Residence_type процент пустых значений: %0.00
avg_glucose_level процент пустых значений: %0.00
bmi процент пустых значений: %3.93
smoking_status процент пустых значений: %0.00
stroke процент пустых значений: %0.00
Пропущенные данные существуют. Необходимо заполнить пропуски медианными значениями.

Заполнение пропущенных данных:

In [12]:
fillna_df = df.fillna(0)

print(fillna_df.shape)

print(fillna_df.isnull().any())

# Замена пустых данных на 0
df["bmi"] = df["bmi"].fillna(0)

# Вычисляем медиану для колонки "bmi"
median_bmi = df["bmi"].median()

# Заменяем значения 0 на медиану
df.loc[df["bmi"] == 0, "bmi"] = median_bmi

df.tail()
(5110, 12)
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                  False
smoking_status       False
stroke               False
dtype: bool
Out[12]:
id gender age hypertension heart_disease ever_married work_type Residence_type avg_glucose_level bmi smoking_status stroke
5105 18234 Female 80.0 1 0 Yes Private Urban 83.75 27.7 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

Удалим наблюдения с пропусками:

In [13]:
dropna_df = df.dropna()

print(dropna_df.shape)

print(fillna_df.isnull().any())
df.tail()
(5110, 12)
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                  False
smoking_status       False
stroke               False
dtype: bool
Out[13]:
id gender age hypertension heart_disease ever_married work_type Residence_type avg_glucose_level bmi smoking_status stroke
5105 18234 Female 80.0 1 0 Yes Private Urban 83.75 27.7 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
Проверим выбросы и усредним их:
In [15]:
numeric_columns = ['age', 'avg_glucose_level', 'bmi']
for column in numeric_columns:
    if pd.api.types.is_numeric_dtype(df[column]):  # Проверяем, является ли колонка числовой
        q1 = df[column].quantile(0.25)  # Находим 1-й квартиль (Q1)
        q3 = df[column].quantile(0.75)  # Находим 3-й квартиль (Q3)
        iqr = q3 - q1  # Вычисляем межквартильный размах (IQR)

        # Определяем границы для выбросов
        lower_bound = q1 - 1.5 * iqr  # Нижняя граница
        upper_bound = q3 + 1.5 * iqr  # Верхняя граница

        # Подсчитываем количество выбросов
        outliers = df[(df[column] < lower_bound) | (df[column] > upper_bound)]
        outlier_count = outliers.shape[0]

        print(f"Колонка {column}:")
        print(f"  Есть выбросы: {'Да' if outlier_count > 0 else 'Нет'}")
        print(f"  Количество выбросов: {outlier_count}")
        print(f"  Минимальное значение: {df[column].min()}")
        print(f"  Максимальное значение: {df[column].max()}")
        print(f"  1-й квартиль (Q1): {q1}")
        print(f"  3-й квартиль (Q3): {q3}\n")

        # Устраняем выбросы: заменяем значения ниже нижней границы на саму нижнюю границу, а выше верхней — на верхнюю
        df[column] = df[column].apply(lambda x: lower_bound if x < lower_bound else upper_bound if x > upper_bound else x)
Колонка age:
  Есть выбросы: Нет
  Количество выбросов: 0
  Минимальное значение: 0.08
  Максимальное значение: 82.0
  1-й квартиль (Q1): 25.0
  3-й квартиль (Q3): 61.0

Колонка avg_glucose_level:
  Есть выбросы: Нет
  Количество выбросов: 0
  Минимальное значение: 55.12
  Максимальное значение: 169.35750000000002
  1-й квартиль (Q1): 77.245
  3-й квартиль (Q3): 114.09

Колонка bmi:
  Есть выбросы: Нет
  Количество выбросов: 0
  Минимальное значение: 10.300000000000006
  Максимальное значение: 46.29999999999999
  1-й квартиль (Q1): 23.8
  3-й квартиль (Q3): 32.8

Постараемся выявить зависимости Stroke от остальных колонок:

Разобьем наш набор на выборки относительно параметра Stroke:

In [19]:
import matplotlib.pyplot as plt
# Список колонок для построения графиков
columns = ['gender', 'age', 'hypertension', 'heart_disease', 'ever_married',
           'work_type', 'Residence_type', 'avg_glucose_level', 'bmi',
           'smoking_status']

# Создание диаграмм зависимости
for column in columns:
    plt.figure(figsize=(8, 6))  # Установка размера графика
    if pd.api.types.is_numeric_dtype(df[column]):  # Проверяем, является ли колонка числовой
        # Проверяем, содержит ли колонка только два уникальных значения (0 и 1)
        if df[column].nunique() == 2 and set(df[column].unique()).issubset({0, 1}):
            # Если да, то строим столбчатую диаграмму
            counts = df[column].value_counts()  # Считаем количество повторений каждого значения
            counts.plot(kind='bar')  # Создаем столбчатую диаграмму
            plt.title(f'Количество значений для {column}')
            plt.xlabel(column)
            plt.ylabel('Количество повторений')
        else:
            # Если колонка числовая, создаем диаграмму рассеяния
            plt.scatter(df['stroke'], df[column], alpha=0.5)  # Создаем диаграмму рассеяния
            plt.title(f'Зависимость {column} от stroke')
            plt.xlabel('stroke (0 = нет, 1 = да)')
            plt.ylabel(column)
            plt.xticks([0, 1])  # Установка меток по оси X
            plt.grid()  # Добавление сетки для удобства восприятия
    else:
        # Если колонка не числовая, строим столбчатую диаграмму
        counts = df[column].value_counts()  # Считаем количество повторений каждого значения
        counts.plot(kind='bar')  # Создаем столбчатую диаграмму
        plt.title(f'Количество значений для {column}')
        plt.xlabel(column)
        plt.ylabel('Количество повторений')

    plt.show()  # Отображение графика
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
In [15]:
# Функция для создания выборок
from sklearn.model_selection import train_test_split

def split_stratified_into_train_val_test(
    df_input,
    stratify_colname="y",
    frac_train=0.6,
    frac_val=0.15,
    frac_test=0.25,
    random_state=None,
):

    if frac_train + frac_val + frac_test != 1.0:
        raise ValueError(
            "fractions %f, %f, %f do not add up to 1.0"
            % (frac_train, frac_val, frac_test)
        )

    if stratify_colname not in df_input.columns:
        raise ValueError("%s is not a column in the dataframe" % (stratify_colname))

    X = df_input  # Contains all columns.
    y = df_input[
        [stratify_colname]
    ]  # Dataframe of just the column on which to stratify.

    # Split original dataframe into train and temp dataframes.
    df_train, df_temp, y_train, y_temp = train_test_split(
        X, y, stratify=y, test_size=(1.0 - frac_train), random_state=random_state
    )

    # Split the temp dataframe into val and test dataframes.
    relative_frac_test = frac_test / (frac_val + frac_test)
    df_val, df_test, y_val, y_test = train_test_split(
        df_temp,
        y_temp,
        stratify=y_temp,
        test_size=relative_frac_test,
        random_state=random_state,
    )

    assert len(df_input) == len(df_train) + len(df_val) + len(df_test)

    return df_train, df_val, df_test
In [16]:
# Вывод распределения количества наблюдений по меткам (классам)
print(df.stroke.value_counts())
print()

data = df.copy()

df_train, df_val, df_test = split_stratified_into_train_val_test(
   data, stratify_colname="stroke", frac_train=0.60, frac_val=0.20, frac_test=0.20
)

print("Обучающая выборка: ", df_train.shape)
print(df_train.stroke.value_counts())
counts = df_train['stroke'].value_counts()
plt.figure(figsize=(2, 2))# Установка размера графика
plt.pie(counts, labels=counts.index, autopct='%1.1f%%', startangle=90)# Построение круговой диаграммы
plt.title('Распределение классов stroke в обучающей выборке')# Добавление заголовка
plt.show()# Отображение графика

print("Контрольная выборка: ", df_val.shape)
print(df_val.stroke.value_counts())
counts = df_val['stroke'].value_counts()
plt.figure(figsize=(2, 2))
plt.pie(counts, labels=counts.index, autopct='%1.1f%%', startangle=90)
plt.title('Распределение классов stroke в контрольной выборке')
plt.show()

print("Тестовая выборка: ", df_test.shape)
print(df_test.stroke.value_counts())
counts = df_test['stroke'].value_counts()
plt.figure(figsize=(2, 2))
plt.pie(counts, labels=counts.index, autopct='%1.1f%%', startangle=90)
plt.title('Распределение классов stroke в тестовой выборке')
plt.show()
stroke
0    4861
1     249
Name: count, dtype: int64

Обучающая выборка:  (3066, 12)
stroke
0    2917
1     149
Name: count, dtype: int64
No description has been provided for this image
Контрольная выборка:  (1022, 12)
stroke
0    972
1     50
Name: count, dtype: int64
No description has been provided for this image
Тестовая выборка:  (1022, 12)
stroke
0    972
1     50
Name: count, dtype: int64
No description has been provided for this image

Сбалансируем распределение:

  1. Балансировка данных оверсемплингом. Это метод, увеличивающий число наблюдений в меньшинственном классе для достижения более равномерного распределения классов.
In [17]:
from imblearn.over_sampling import ADASYN
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder

categorical_features = ['gender', 'ever_married', 'work_type', 'Residence_type']  # Ваши категориальные признаки
numeric_features = ['age', 'hypertension', 'heart_disease', 'avg_glucose_level', 'bmi']  # Ваши числовые признаки

# Создание пайплайна для обработки категориальных данных
preprocessor = ColumnTransformer(
    transformers=[
        ('cat', OneHotEncoder(), categorical_features),  # OneHotEncoder для категориальных данных
        ('num', 'passthrough', numeric_features)  # Оставляем числовые колонки без изменений
    ]
)

# Создание экземпляра ADASYN
ada = ADASYN()

# Преобразование данных с помощью пайплайна
X = preprocessor.fit_transform(df_train.drop(columns=['stroke']))
y = df_train['stroke']

# Применение ADASYN
X_resampled, y_resampled = ada.fit_resample(X, y)

# Создание нового DataFrame
df_train_adasyn = pd.DataFrame(X_resampled)
# Восстанавливаем названия столбцов для DataFrame
ohe_columns = preprocessor.named_transformers_['cat'].get_feature_names_out(categorical_features)
new_column_names = list(ohe_columns) + numeric_features
df_train_adasyn.columns = new_column_names

# Добавление целевой переменной
df_train_adasyn['stroke'] = y_resampled

# Вывод информации о новой выборке
print("Обучающая выборка после oversampling: ", df_train_adasyn.shape)
print(df_train_adasyn['stroke'].value_counts())

# Визуализация
counts = df_train_adasyn['stroke'].value_counts()
plt.figure(figsize=(6, 6))
plt.pie(counts, labels=counts.index, autopct='%1.1f%%', startangle=90)
plt.title('Распределение классов Stroke в тренировочной выборке после ADASYN')
plt.show()
Обучающая выборка после oversampling:  (5811, 18)
stroke
0    2917
1    2894
Name: count, dtype: int64
No description has been provided for this image
  1. Балансировка данных андерсемплингом. Этот метод помогает сбалансировать выборку, уменьшая количество экземпляров класса большинства, чтобы привести его в соответствие с классом меньшинства.
In [18]:
from imblearn.under_sampling import RandomUnderSampler

rus = RandomUnderSampler()# Создание экземпляра RandomUnderSampler

# Применение RandomUnderSampler
X_resampled, y_resampled = rus.fit_resample(df_train.drop(columns=['stroke']), df_train['stroke'])

# Создание нового DataFrame
df_train_undersampled = pd.DataFrame(X_resampled)
df_train_undersampled['stroke'] = y_resampled  # Добавление целевой переменной

# Вывод информации о новой выборке
print("Обучающая выборка после undersampling: ", df_train_undersampled.shape)
print(df_train_undersampled['stroke'].value_counts())

# Визуализация распределения классов
counts = df_train_undersampled['stroke'].value_counts()
plt.figure(figsize=(2, 2))
plt.pie(counts, labels=counts.index, autopct='%1.1f%%', startangle=90)
plt.title('Распределение классов stroke в тренировочной выборке после Undersampling')
plt.show()
Обучающая выборка после undersampling:  (298, 12)
stroke
0    149
1    149
Name: count, dtype: int64
No description has been provided for this image

3. Датасет: Набор данных для анализа и прогнозирования сердечного приступа

https://www.kaggle.com/datasets/kamilpytlak/personal-key-indicators-of-heart-disease

О наборе данных:

По данным CDC, болезни сердца являются основной причиной смерти представителей большинства рас в США (афроамериканцев, американских индейцев и коренных жителей Аляски, а также белых). Около половины всех американцев (47%) имеют по крайней мере 1 из 3 основных факторов риска сердечно-сосудистых заболеваний: высокое кровяное давление, высокий уровень холестерина и курение. Другие ключевые показатели включают сахарный диабет, ожирение (высокий ИМТ), недостаточную физическую активность или чрезмерное употребление алкоголя. Выявление и профилактика факторов, оказывающих наибольшее влияние на сердечно-сосудистые заболевания, очень важны в здравоохранении. В свою очередь, достижения в области вычислительной техники позволяют применять методы машинного обучения для выявления "закономерностей" в данных, которые позволяют предсказать состояние пациента.

Таким образом:
  • Объект наблюдения - представители большинства рас в США
  • Атрибуты: HeartDisease, BMI, Smoking, AlcoholDrinking, Stroke, PhysicalHealth(как много дней за месяц вы чувствовали себя плохо), MentalHealth(как много дней за месяц вы чувствовали себя ментально плохо), DiffWalking, Sex, AgeCategory, Race, Diabetic, PhysicalActivity, GenHealth, SleepTime, Asthma, KidneyDisease, SkinCancer.
  • Проблемная область: прогнозирование сердечного приступа у человека.
In [20]:
import pandas as pd
df = pd.read_csv(".//static//csv//heart.csv", sep=",")
print('Количество колонок: ' + str(df.columns.size))  
print('Колонки: ' + ', '.join(df.columns)+'\n')

df.info()
df.head()
Количество колонок: 18
Колонки: HeartDisease, BMI, Smoking, AlcoholDrinking, Stroke, PhysicalHealth, MentalHealth, DiffWalking, Sex, AgeCategory, Race, Diabetic, PhysicalActivity, GenHealth, SleepTime, Asthma, KidneyDisease, SkinCancer

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 319795 entries, 0 to 319794
Data columns (total 18 columns):
 #   Column            Non-Null Count   Dtype  
---  ------            --------------   -----  
 0   HeartDisease      319795 non-null  object 
 1   BMI               319795 non-null  float64
 2   Smoking           319795 non-null  object 
 3   AlcoholDrinking   319795 non-null  object 
 4   Stroke            319795 non-null  object 
 5   PhysicalHealth    319795 non-null  float64
 6   MentalHealth      319795 non-null  float64
 7   DiffWalking       319795 non-null  object 
 8   Sex               319795 non-null  object 
 9   AgeCategory       319795 non-null  object 
 10  Race              319795 non-null  object 
 11  Diabetic          319795 non-null  object 
 12  PhysicalActivity  319795 non-null  object 
 13  GenHealth         319795 non-null  object 
 14  SleepTime         319795 non-null  float64
 15  Asthma            319795 non-null  object 
 16  KidneyDisease     319795 non-null  object 
 17  SkinCancer        319795 non-null  object 
dtypes: float64(4), object(14)
memory usage: 43.9+ MB
Out[20]:
HeartDisease BMI Smoking AlcoholDrinking Stroke PhysicalHealth MentalHealth DiffWalking Sex AgeCategory Race Diabetic PhysicalActivity GenHealth SleepTime Asthma KidneyDisease SkinCancer
0 No 16.60 Yes No No 3.0 30.0 No Female 55-59 White Yes Yes Very good 5.0 Yes No Yes
1 No 20.34 No No Yes 0.0 0.0 No Female 80 or older White No Yes Very good 7.0 No No No
2 No 26.58 Yes No No 20.0 30.0 No Male 65-69 White Yes Yes Fair 8.0 Yes No No
3 No 24.21 No No No 0.0 0.0 No Female 75-79 White No No Good 6.0 No No Yes
4 No 23.71 No No No 28.0 0.0 Yes Female 40-44 White No Yes Very good 8.0 No No No

Получение сведений о пропущенных данных

Типы пропущенных данных:

  • None - представление пустых данных в Python
  • NaN - представление пустых данных в Pandas
  • '' - пустая строка
In [3]:
# Количество пустых значений признаков
print(df.isnull().sum())
print()

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

# Процент пустых значений признаков
for i in df.columns:
    null_rate = df[i].isnull().sum() / len(df) * 100
    print(f"{i} процент пустых значений: %{null_rate:.2f}")
HeartDisease        0
BMI                 0
Smoking             0
AlcoholDrinking     0
Stroke              0
PhysicalHealth      0
MentalHealth        0
DiffWalking         0
Sex                 0
AgeCategory         0
Race                0
Diabetic            0
PhysicalActivity    0
GenHealth           0
SleepTime           0
Asthma              0
KidneyDisease       0
SkinCancer          0
dtype: int64

HeartDisease        False
BMI                 False
Smoking             False
AlcoholDrinking     False
Stroke              False
PhysicalHealth      False
MentalHealth        False
DiffWalking         False
Sex                 False
AgeCategory         False
Race                False
Diabetic            False
PhysicalActivity    False
GenHealth           False
SleepTime           False
Asthma              False
KidneyDisease       False
SkinCancer          False
dtype: bool

HeartDisease процент пустых значений: %0.00
BMI процент пустых значений: %0.00
Smoking процент пустых значений: %0.00
AlcoholDrinking процент пустых значений: %0.00
Stroke процент пустых значений: %0.00
PhysicalHealth процент пустых значений: %0.00
MentalHealth процент пустых значений: %0.00
DiffWalking процент пустых значений: %0.00
Sex процент пустых значений: %0.00
AgeCategory процент пустых значений: %0.00
Race процент пустых значений: %0.00
Diabetic процент пустых значений: %0.00
PhysicalActivity процент пустых значений: %0.00
GenHealth процент пустых значений: %0.00
SleepTime процент пустых значений: %0.00
Asthma процент пустых значений: %0.00
KidneyDisease процент пустых значений: %0.00
SkinCancer процент пустых значений: %0.00
Пропущенные данные отсутствуют.
Проверим выбросы и усредним их:
In [22]:
numeric_columns = ['BMI', 'PhysicalHealth', 'MentalHealth', 'AgeCategory', 'SleepTime']
for column in numeric_columns:
    if pd.api.types.is_numeric_dtype(df[column]):  # Проверяем, является ли колонка числовой
        q1 = df[column].quantile(0.25)  # Находим 1-й квартиль (Q1)
        q3 = df[column].quantile(0.75)  # Находим 3-й квартиль (Q3)
        iqr = q3 - q1  # Вычисляем межквартильный размах (IQR)

        # Определяем границы для выбросов
        lower_bound = q1 - 1.5 * iqr  # Нижняя граница
        upper_bound = q3 + 1.5 * iqr  # Верхняя граница

        # Подсчитываем количество выбросов
        outliers = df[(df[column] < lower_bound) | (df[column] > upper_bound)]
        outlier_count = outliers.shape[0]

        print(f"Колонка {column}:")
        print(f"  Есть выбросы: {'Да' if outlier_count > 0 else 'Нет'}")
        print(f"  Количество выбросов: {outlier_count}")
        print(f"  Минимальное значение: {df[column].min()}")
        print(f"  Максимальное значение: {df[column].max()}")
        print(f"  1-й квартиль (Q1): {q1}")
        print(f"  3-й квартиль (Q3): {q3}\n")

        # Устраняем выбросы: заменяем значения ниже нижней границы на саму нижнюю границу, а выше верхней — на верхнюю
        df[column] = df[column].apply(lambda x: lower_bound if x < lower_bound else upper_bound if x > upper_bound else x)
Колонка BMI:
  Есть выбросы: Да
  Количество выбросов: 10396
  Минимальное значение: 12.02
  Максимальное значение: 94.85
  1-й квартиль (Q1): 24.03
  3-й квартиль (Q3): 31.42

Колонка PhysicalHealth:
  Есть выбросы: Да
  Количество выбросов: 47146
  Минимальное значение: 0.0
  Максимальное значение: 30.0
  1-й квартиль (Q1): 0.0
  3-й квартиль (Q3): 2.0

Колонка MentalHealth:
  Есть выбросы: Да
  Количество выбросов: 51576
  Минимальное значение: 0.0
  Максимальное значение: 30.0
  1-й квартиль (Q1): 0.0
  3-й квартиль (Q3): 3.0

Колонка SleepTime:
  Есть выбросы: Да
  Количество выбросов: 4543
  Минимальное значение: 1.0
  Максимальное значение: 24.0
  1-й квартиль (Q1): 6.0
  3-й квартиль (Q3): 8.0

Постараемся выявить зависимости HeartDisease от остальных колонок:

Разобьем наш набор на выборки относительно параметра HeartDisease:

In [23]:
import matplotlib.pyplot as plt
# Список колонок для построения графиков
columns = ['BMI', 'Smoking', 'AlcoholDrinking', 'Stroke',
           'PhysicalHealth', 'MentalHealth', 'DiffWalking', 'Sex',
           'AgeCategory', 'Race', 'Diabetic', 'PhysicalActivity', 'GenHealth', 'SleepTime', 'Asthma', 'KidneyDisease', 'SkinCancer']

# Создание диаграмм зависимости
for column in columns:
    plt.figure(figsize=(8, 6))  # Установка размера графика
    if pd.api.types.is_numeric_dtype(df[column]):  # Проверяем, является ли колонка числовой
        # Проверяем, содержит ли колонка только два уникальных значения (0 и 1)
        if df[column].nunique() == 2 and set(df[column].unique()).issubset({0, 1}):
            # Если да, то строим столбчатую диаграмму
            counts = df[column].value_counts()  # Считаем количество повторений каждого значения
            counts.plot(kind='bar')  # Создаем столбчатую диаграмму
            plt.title(f'Количество значений для {column}')
            plt.xlabel(column)
            plt.ylabel('Количество повторений')
        else:
            # Если колонка числовая, создаем диаграмму рассеяния
            plt.scatter(df['HeartDisease'], df[column], alpha=0.5)  # Создаем диаграмму рассеяния
            plt.title(f'Зависимость HeartDisease от {column}')
            plt.xlabel('HeartDisease (0 = нет, 1 = да)')
            plt.ylabel(column)
            plt.xticks([0, 1])  # Установка меток по оси X
            plt.grid()  # Добавление сетки для удобства восприятия
    else:
        # Если колонка не числовая, строим столбчатую диаграмму
        counts = df[column].value_counts()  # Считаем количество повторений каждого значения
        counts.plot(kind='bar')  # Создаем столбчатую диаграмму
        plt.title(f'Количество значений для {column}')
        plt.xlabel(column)
        plt.ylabel('Количество повторений')

    plt.show()  # Отображение графика
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
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
In [23]:
# Функция для создания выборок
from sklearn.model_selection import train_test_split

def split_stratified_into_train_val_test(
    df_input,
    stratify_colname="y",
    frac_train=0.6,
    frac_val=0.15,
    frac_test=0.25,
    random_state=None,
):

    if frac_train + frac_val + frac_test != 1.0:
        raise ValueError(
            "fractions %f, %f, %f do not add up to 1.0"
            % (frac_train, frac_val, frac_test)
        )

    if stratify_colname not in df_input.columns:
        raise ValueError("%s is not a column in the dataframe" % (stratify_colname))

    X = df_input  # Contains all columns.
    y = df_input[
        [stratify_colname]
    ]  # Dataframe of just the column on which to stratify.

    # Split original dataframe into train and temp dataframes.
    df_train, df_temp, y_train, y_temp = train_test_split(
        X, y, stratify=y, test_size=(1.0 - frac_train), random_state=random_state
    )

    # Split the temp dataframe into val and test dataframes.
    relative_frac_test = frac_test / (frac_val + frac_test)
    df_val, df_test, y_val, y_test = train_test_split(
        df_temp,
        y_temp,
        stratify=y_temp,
        test_size=relative_frac_test,
        random_state=random_state,
    )

    assert len(df_input) == len(df_train) + len(df_val) + len(df_test)

    return df_train, df_val, df_test
In [24]:
# Вывод распределения количества наблюдений по меткам (классам)
print(df.HeartDisease.value_counts())
print()

data = df.copy()

df_train, df_val, df_test = split_stratified_into_train_val_test(
   data, stratify_colname="HeartDisease", frac_train=0.60, frac_val=0.20, frac_test=0.20
)

print("Обучающая выборка: ", df_train.shape)
print(df_train.HeartDisease.value_counts())
counts = df_train['HeartDisease'].value_counts()
plt.figure(figsize=(2, 2))# Установка размера графика
plt.pie(counts, labels=counts.index, autopct='%1.1f%%', startangle=90)# Построение круговой диаграммы
plt.title('Распределение классов HeartDisease в обучающей выборке')# Добавление заголовка
plt.show()# Отображение графика

print("Контрольная выборка: ", df_val.shape)
print(df_val.HeartDisease.value_counts())
counts = df_val['HeartDisease'].value_counts()
plt.figure(figsize=(2, 2))
plt.pie(counts, labels=counts.index, autopct='%1.1f%%', startangle=90)
plt.title('Распределение классов HeartDisease в контрольной выборке')
plt.show()

print("Тестовая выборка: ", df_test.shape)
print(df_test.HeartDisease.value_counts())
counts = df_test['HeartDisease'].value_counts()
plt.figure(figsize=(2, 2))
plt.pie(counts, labels=counts.index, autopct='%1.1f%%', startangle=90)
plt.title('Распределение классов HeartDisease в тестовой выборке')
plt.show()
HeartDisease
0    53
1    47
Name: count, dtype: int64

Обучающая выборка:  (60, 2)
HeartDisease
0    32
1    28
Name: count, dtype: int64
No description has been provided for this image
Контрольная выборка:  (20, 2)
HeartDisease
0    11
1     9
Name: count, dtype: int64
No description has been provided for this image
Тестовая выборка:  (20, 2)
HeartDisease
1    10
0    10
Name: count, dtype: int64
No description has been provided for this image

Сбалансируем распределение:

  1. Балансировка данных оверсемплингом. Это метод, увеличивающий число наблюдений в меньшинственном классе для достижения более равномерного распределения классов.
In [25]:
from imblearn.over_sampling import ADASYN
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder

categorical_features = ['Smoking', 'AlcoholDrinking', 'Stroke', 'DiffWalking', 'Sex', 'AgeCategory', 'Race', 'Diabetic', 'PhysicalActivity', 'GenHealth', 'Asthma', 'KidneyDisease', 'SkinCancer']  # Ваши категориальные признаки
numeric_features = ['BMI', 'PhysicalHealth', 'MentalHealth', 'SleepTime']  # Ваши числовые признаки

# Создание пайплайна для обработки категориальных данных
preprocessor = ColumnTransformer(
    transformers=[
        ('cat', OneHotEncoder(), categorical_features),  # OneHotEncoder для категориальных данных
        ('num', 'passthrough', numeric_features)  # Оставляем числовые колонки без изменений
    ]
)

# Создание экземпляра ADASYN
ada = ADASYN()

# Преобразование данных с помощью пайплайна
X = preprocessor.fit_transform(df_train.drop(columns=['HeartDisease']))
y = df_train['HeartDisease']

# Применение ADASYN
X_resampled, y_resampled = ada.fit_resample(X, y)

# Создание нового DataFrame
df_train_adasyn = pd.DataFrame(X_resampled)
# Восстанавливаем названия столбцов для DataFrame
ohe_columns = preprocessor.named_transformers_['cat'].get_feature_names_out(categorical_features)
new_column_names = list(ohe_columns) + numeric_features
df_train_adasyn.columns = new_column_names

# Добавление целевой переменной
df_train_adasyn['HeartDisease'] = y_resampled

# Вывод информации о новой выборке
print("Обучающая выборка после oversampling: ", df_train_adasyn.shape)
print(df_train_adasyn['HeartDisease'].value_counts())

# Визуализация
counts = df_train_adasyn['HeartDisease'].value_counts()
plt.figure(figsize=(6, 6))
plt.pie(counts, labels=counts.index, autopct='%1.1f%%', startangle=90)
plt.title('Распределение классов HeartDisease в тренировочной выборке после ADASYN')
plt.show()
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File c:\Users\K\source\repos\AIM-PIbd-31-Ievlewa-M-D\aimenv\Lib\site-packages\pandas\core\indexes\base.py:3805, in Index.get_loc(self, key)
   3804 try:
-> 3805     return self._engine.get_loc(casted_key)
   3806 except KeyError as err:

File index.pyx:167, in pandas._libs.index.IndexEngine.get_loc()

File index.pyx:196, in pandas._libs.index.IndexEngine.get_loc()

File pandas\\_libs\\hashtable_class_helper.pxi:7081, in pandas._libs.hashtable.PyObjectHashTable.get_item()

File pandas\\_libs\\hashtable_class_helper.pxi:7089, in pandas._libs.hashtable.PyObjectHashTable.get_item()

KeyError: 'Smoking'

The above exception was the direct cause of the following exception:

KeyError                                  Traceback (most recent call last)
File c:\Users\K\source\repos\AIM-PIbd-31-Ievlewa-M-D\aimenv\Lib\site-packages\sklearn\utils\_indexing.py:361, in _get_column_indices(X, key)
    360 for col in columns:
--> 361     col_idx = all_columns.get_loc(col)
    362     if not isinstance(col_idx, numbers.Integral):

File c:\Users\K\source\repos\AIM-PIbd-31-Ievlewa-M-D\aimenv\Lib\site-packages\pandas\core\indexes\base.py:3812, in Index.get_loc(self, key)
   3811         raise InvalidIndexError(key)
-> 3812     raise KeyError(key) from err
   3813 except TypeError:
   3814     # If we have a listlike key, _check_indexing_error will raise
   3815     #  InvalidIndexError. Otherwise we fall through and re-raise
   3816     #  the TypeError.

KeyError: 'Smoking'

The above exception was the direct cause of the following exception:

ValueError                                Traceback (most recent call last)
Cell In[25], line 20
     17 ada = ADASYN()
     19 # Преобразование данных с помощью пайплайна
---> 20 X = preprocessor.fit_transform(df_train.drop(columns=['HeartDisease']))
     21 y = df_train['HeartDisease']
     23 # Применение ADASYN

File c:\Users\K\source\repos\AIM-PIbd-31-Ievlewa-M-D\aimenv\Lib\site-packages\sklearn\utils\_set_output.py:316, in _wrap_method_output.<locals>.wrapped(self, X, *args, **kwargs)
    314 @wraps(f)
    315 def wrapped(self, X, *args, **kwargs):
--> 316     data_to_wrap = f(self, X, *args, **kwargs)
    317     if isinstance(data_to_wrap, tuple):
    318         # only wrap the first output for cross decomposition
    319         return_tuple = (
    320             _wrap_data_with_container(method, data_to_wrap[0], X, self),
    321             *data_to_wrap[1:],
    322         )

File c:\Users\K\source\repos\AIM-PIbd-31-Ievlewa-M-D\aimenv\Lib\site-packages\sklearn\base.py:1473, in _fit_context.<locals>.decorator.<locals>.wrapper(estimator, *args, **kwargs)
   1466     estimator._validate_params()
   1468 with config_context(
   1469     skip_parameter_validation=(
   1470         prefer_skip_nested_validation or global_skip_validation
   1471     )
   1472 ):
-> 1473     return fit_method(estimator, *args, **kwargs)

File c:\Users\K\source\repos\AIM-PIbd-31-Ievlewa-M-D\aimenv\Lib\site-packages\sklearn\compose\_column_transformer.py:968, in ColumnTransformer.fit_transform(self, X, y, **params)
    965 self._validate_transformers()
    966 n_samples = _num_samples(X)
--> 968 self._validate_column_callables(X)
    969 self._validate_remainder(X)
    971 if _routing_enabled():

File c:\Users\K\source\repos\AIM-PIbd-31-Ievlewa-M-D\aimenv\Lib\site-packages\sklearn\compose\_column_transformer.py:536, in ColumnTransformer._validate_column_callables(self, X)
    534         columns = columns(X)
    535     all_columns.append(columns)
--> 536     transformer_to_input_indices[name] = _get_column_indices(X, columns)
    538 self._columns = all_columns
    539 self._transformer_to_input_indices = transformer_to_input_indices

File c:\Users\K\source\repos\AIM-PIbd-31-Ievlewa-M-D\aimenv\Lib\site-packages\sklearn\utils\_indexing.py:369, in _get_column_indices(X, key)
    366         column_indices.append(col_idx)
    368 except KeyError as e:
--> 369     raise ValueError("A given column is not a column of the dataframe") from e
    371 return column_indices

ValueError: A given column is not a column of the dataframe
  1. Балансировка данных андерсемплингом. Этот метод помогает сбалансировать выборку, уменьшая количество экземпляров класса большинства, чтобы привести его в соответствие с классом меньшинства.
In [56]:
from imblearn.under_sampling import RandomUnderSampler

rus = RandomUnderSampler()# Создание экземпляра RandomUnderSampler

# Применение RandomUnderSampler
X_resampled, y_resampled = rus.fit_resample(df_train.drop(columns=['HeartDisease']), df_train['HeartDisease'])

# Создание нового DataFrame
df_train_undersampled = pd.DataFrame(X_resampled)
df_train_undersampled['HeartDisease'] = y_resampled  # Добавление целевой переменной

# Вывод информации о новой выборке
print("Обучающая выборка после undersampling: ", df_train_undersampled.shape)
print(df_train_undersampled['HeartDisease'].value_counts())

# Визуализация распределения классов
counts = df_train_undersampled['HeartDisease'].value_counts()
plt.figure(figsize=(2, 2))
plt.pie(counts, labels=counts.index, autopct='%1.1f%%', startangle=90)
plt.title('Распределение классов HeartDisease в тренировочной выборке после Undersampling')
plt.show()
Обучающая выборка после undersampling:  (32848, 18)
HeartDisease
No     16424
Yes    16424
Name: count, dtype: int64
No description has been provided for this image