AIM-PIbd-31-Potapov-N-S/lab_2/lab2.ipynb

737 KiB
Raw Blame History

Датасет 1. Зарплата специалистов по обработке данных в 2023 году

https://www.kaggle.com/datasets/henryshan/2023-data-scientists-salary

Анализ сведений

Краткое описание

Этот датасет посвящен анализу факторов, влияющих на уровень заработных плат специалистов в области Data Science. Включенные данные позволяют исследовать взаимосвязь между различными характеристиками сотрудников и их доходами.

Проблемная область

Датасет касается анализа факторов, влияющих на заработную плату специалистов в области Data Science, что является важным аспектом для понимания экономических и профессиональных тенденций на рынке труда в этой сфере. Проблемная область включает:

  • Анализ влияния опыта, типа занятости, географического положения и других факторов на размер заработной платы специалистов.
  • Определение ключевых факторов, влияющих на рост зарплаты в профессии Data Scientist.
  • Выявление тенденций, которые могут помочь работодателям и специалистам принимать решения о карьере, зарплате и условиях работы.

Актуальность

  • Рост профессии: Data Science — это одна из самых востребованных и динамично развивающихся областей на рынке труда. Понимание факторов, влияющих на зарплату, важно для профессионалов и компаний.
  • Тенденции на рынке труда: В условиях глобализации и удаленной работы важно понять, как тип занятости и местоположение компании влияют на оплату труда.
  • Оптимизация карьерных решений: Анализ данных поможет специалистам принимать обоснованные решения при выборе карьерных путей, а работодателям — разрабатывать конкурентоспособные предложения по зарплате и условиям работы.

Объекты наблюдений

Объектами наблюдения являются Data Scientists, то есть специалисты, занимающиеся анализом данных. Каждый объект представляет собой запись, которая отражает характеристики работы конкретного специалиста в определенный год.

Атрибуты объектов

Каждый объект имеет следующие атрибуты:

  • work_year — год, в котором была выплачена зарплата. Позволяет отслеживать изменения зарплат в разные годы.
  • experience_level — уровень опыта сотрудника (Entry-level, Mid-level, Senior-level, Executive-level). Это важный атрибут, который влияет на зарплату.
  • employment_type — тип занятости (Part-time, Full-time, Contract, Freelance). Определяет, является ли работа постоянной или временной.
  • job_title — должность, занимаемая сотрудником. Важно для анализа различий между зарплатами для разных специализаций.
  • salary — общая сумма заработной платы.
  • salary_currency — валюта, в которой выплачена зарплата.
  • salaryinusd — зарплата в долларах США. Этот атрибут используется для стандартизации данных.
  • employee_residence — страна проживания сотрудника. Влияет на размер зарплаты и может быть важным для анализа глобальных различий.
  • remote_ratio — доля работы, выполняемой удаленно. Важно для анализа влияния удаленной работы на уровень зарплаты.
  • company_location — страна, где находится основная офисная локация компании. Это атрибут, который позволяет анализировать региональные различия в зарплатах.
  • company_size — размер компании, выраженный через медиану числа сотрудников. Размер компании может влиять на оплату труда, так как крупные компании часто предлагают более высокие зарплаты.

Связь между объектами

Связь между объектами заключается в том, что все атрибуты в совокупности описывают профессиональную деятельность и условия работы каждого специалиста. Например:

  • experience_level и job_title могут быть взаимосвязаны, так как более высокие должности (например, Senior или Executive) соответствуют большему опыту.
  • salary напрямую зависит от experience_level, employment_type, employee_residence, company_location, и company_size, а также от уровня удаленности работы (remote_ratio).
  • salaryinusd служит для нормализации и сопоставления зарплат между различными странами и валютами.
  • employee_residence и company_location могут быть связаны с различиями в заработной плате, так как зарплаты могут варьироваться в зависимости от страны проживания и местоположения компании.

Качество набора данных

Информативность

Датасет содержит разнообразные атрибуты, которые предоставляют полезную информацию для анализа факторов, влияющих на зарплату специалистов в области Data Science. Включенные переменные, такие как уровень опыта, тип занятости, зарплата, географическое расположение и удаленная работа, позволяют провести многогранный анализ и выявить значимые закономерности. Однако, отсутствие информации о дополнительной квалификации или навыках специалистов (например, знание конкретных технологий или инструментов) может ограничить глубину анализа.

Степень покрытия

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

  • Данные охватывают только одну профессиональную категорию (Data Science), что не позволяет делать выводы о других областях.
  • Пропущенные данные по некоторым атрибутам могут снизить полноту информации (например, отсутствие данных по размеру компании или типу работы для некоторых записей).

Соответствие реальным данным

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

Согласованность меток

Метки в датасете, такие как experience_level (уровень опыта), employment_type (тип занятости), и company_size (размер компании), имеют четкие и логичные категории, что способствует легкости их интерпретации. Однако для некоторых меток могут возникнуть проблемы с точностью классификации, например:

  • В разных странах или компаниях могут существовать различные способы определения уровней опыта, и это может не всегда совпадать с метками в датасете.
  • Некоторые метки могут требовать дополнительного пояснения, например, категориальные значения для remote_ratio или job_title могут быть варьироваться в зависимости от контекста.

Бизнес-цели

1. Определение конкурентоспособных уровней зарплат для специалистов в области Data Science

Эффект на бизнес: Датасет поможет компаниям, работающим в сфере Data Science, определять конкурентоспособные уровни зарплат для специалистов в зависимости от уровня опыта, типа занятости и географического положения. Это способствует привлечению и удержанию талантливых специалистов, улучшая стратегию найма и оптимизируя расходы на оплату труда.

Примеры целей технического проекта:

  • Цель проекта: Создание модели для предсказания конкурентоспособных зарплат для специалистов по Data Science в зависимости от их уровня опыта и местоположения.
    • Что поступает на вход: Данные о годе работы, уровне опыта, типе занятости, местоположении компании и специалиста.
    • Целевой признак: Прогнозируемая зарплата (в долларах США или эквивалент в локальной валюте).

2. Определение факторов, влияющих на рост зарплат в сфере Data Science

Эффект на бизнес: Анализ факторов, влияющих на рост зарплат, позволит компаниям лучше понимать, какие характеристики (например, удаленная работа, опыт работы в крупных компаниях) способствуют повышению заработной платы. Это может помочь в построении программ карьерного роста и мотивации для сотрудников.

Примеры целей технического проекта:

  • Цель проекта: Разработка модели для анализа факторов, которые влияют на рост зарплат в сфере Data Science.
    • Что поступает на вход: Данные о годе работы, уровне опыта, типе занятости, удаленной работе, размере компании и других характеристиках.
    • Целевой признак: Изменение зарплаты за год (прибавка к зарплате или её снижение).

3. Улучшение стратегии удаленной работы и гибких условий занятости

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

Примеры целей технического проекта:

  • Цель проекта: Создание модели для анализа влияния удаленной работы и типа занятости на уровень зарплаты в сфере Data Science.
    • Что поступает на вход: Данные о проценте удаленной работы, типе занятости (фриланс, контракт, полная или частичная занятость).
    • Целевой признак: Зарплата в зависимости от удаленности работы и типа занятости (фиксированная сумма или разница в зарплатах для разных типов занятости).

Выполним все необходимые импорты

In [146]:
from typing import Any
from math import ceil

import pandas as pd
from pandas import DataFrame, Series
from sklearn.model_selection import train_test_split
from imblearn.over_sampling import ADASYN
from imblearn.under_sampling import RandomUnderSampler
import matplotlib.pyplot as plt

Считаем данные для первого датасета

In [147]:
df = pd.read_csv('csv/8.ds_salaries.csv')
df.info()
df.describe().transpose()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3755 entries, 0 to 3754
Data columns (total 11 columns):
 #   Column              Non-Null Count  Dtype 
---  ------              --------------  ----- 
 0   work_year           3755 non-null   int64 
 1   experience_level    3755 non-null   object
 2   employment_type     3755 non-null   object
 3   job_title           3755 non-null   object
 4   salary              3755 non-null   int64 
 5   salary_currency     3755 non-null   object
 6   salary_in_usd       3755 non-null   int64 
 7   employee_residence  3755 non-null   object
 8   remote_ratio        3755 non-null   int64 
 9   company_location    3755 non-null   object
 10  company_size        3755 non-null   object
dtypes: int64(4), object(7)
memory usage: 322.8+ KB
Out[147]:
count mean std min 25% 50% 75% max
work_year 3755.0 2022.373635 0.691448 2020.0 2022.0 2022.0 2023.0 2023.0
salary 3755.0 190695.571771 671676.500508 6000.0 100000.0 138000.0 180000.0 30400000.0
salary_in_usd 3755.0 137570.389880 63055.625278 5132.0 95000.0 135000.0 175000.0 450000.0
remote_ratio 3755.0 46.271638 48.589050 0.0 0.0 0.0 100.0 100.0

Метод проверки пустых значений в датафрейме

In [148]:
# Проверка пропущенных данных
def check_null_columns(dataframe: DataFrame) -> None:
    print('Присутствуют ли пустые значения признаков в колонке:')
    print(dataframe.isnull().any(), '\n')

    if any(dataframe.isnull().any()):
        print('Количество пустых значений признаков в колонке:')
        print(dataframe.isnull().sum(), '\n')

        print('Процент пустых значений признаков в колонке:')
        for column in dataframe.columns:
            null_rate: float = dataframe[column].isnull().sum() / len(dataframe) * 100
            if null_rate > 0:
                print(f"{column} процент пустых значений: {null_rate:.2f}%")

Проверим на пустые значения в колонках

In [149]:
check_null_columns(df)
Присутствуют ли пустые значения признаков в колонке:
work_year             False
experience_level      False
employment_type       False
job_title             False
salary                False
salary_currency       False
salary_in_usd         False
employee_residence    False
remote_ratio          False
company_location      False
company_size          False
dtype: bool 

Проверка на наличие выборосов и зашумленности данных

Зашумленность это наличие случайных ошибок или вариаций в данных, которые могут затруднить выявление истинных закономерностей.

Выбросы это значения, которые значительно отличаются от остальных наблюдений в наборе данных.

Функция возвращает список числовых колонок датафрейма

In [150]:
def get_numeric_columns(dataframe: DataFrame) -> list[str]:
    w = []
    for column in dataframe.columns:
        if not pd.api.types.is_numeric_dtype(dataframe[column]):
            continue
        w.append(column)
    return w

Метод для проверки датафрейма на наличие выбросов

In [151]:
def check_outliers(dataframe: DataFrame) -> list[str]:
    w = []
    for column in get_numeric_columns(dataframe):
        Q1: float = dataframe[column].quantile(0.25)
        Q3: float = dataframe[column].quantile(0.75)
        IQR: float = Q3 - Q1

        lower_bound: float = Q1 - 1.5 * IQR
        upper_bound: float = Q3 + 1.5 * IQR

        outliers: DataFrame = dataframe[(dataframe[column] < lower_bound) | (dataframe[column] > upper_bound)]
        outlier_count: int = outliers.shape[0]

        if outlier_count > 0:
            w.append(column)

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

Метод для визуализации выбросов

In [152]:
def visualize_outliers(dataframe: DataFrame) -> None:
    columns = get_numeric_columns(dataframe)
    plt.figure(figsize=(15, 10))
    rows: int = ceil(len(columns) / 3)
    for index, column in enumerate(columns, 1):
        plt.subplot(rows, 3, index)
        plt.boxplot(dataframe[column], vert=True, patch_artist=True)
        plt.title(f"Диаграмма размахов для \"{column}\"")
        plt.xlabel(column)
            
    plt.tight_layout()
    plt.show()

Проверим на наличие выбросов

In [153]:
columns_with_outliers = check_outliers(df)
Колонка work_year:
	Есть выбросы: Да
	Количество выбросов: 76
	Минимальное значение: 2020
	Максимальное значение: 2023
	1-й квартиль (Q1): 2022.0
	3-й квартиль (Q3): 2023.0

Колонка salary:
	Есть выбросы: Да
	Количество выбросов: 113
	Минимальное значение: 6000
	Максимальное значение: 30400000
	1-й квартиль (Q1): 100000.0
	3-й квартиль (Q3): 180000.0

Колонка salary_in_usd:
	Есть выбросы: Да
	Количество выбросов: 63
	Минимальное значение: 5132
	Максимальное значение: 450000
	1-й квартиль (Q1): 95000.0
	3-й квартиль (Q3): 175000.0

Колонка remote_ratio:
	Есть выбросы: Нет
	Количество выбросов: 0
	Минимальное значение: 0
	Максимальное значение: 100
	1-й квартиль (Q1): 0.0
	3-й квартиль (Q3): 100.0

Визуализируем выбросы

In [154]:
visualize_outliers(df)
No description has been provided for this image

Метод устраняет выбросы в заданных колонках, задавая значениям выше максимального значение максимума, а ниже минимального - значение минимума.

In [155]:
def remove_outliers(dataframe: DataFrame, columns: list[str]) -> DataFrame:
    print('Колонки с выбросами:', *columns, sep='\n')
    for column in columns:
        Q1: float = dataframe[column].quantile(0.25)
        Q3: float = dataframe[column].quantile(0.75)
        IQR: float = Q3 - Q1

        lower_bound: float = Q1 - 1.5 * IQR
        upper_bound: float = Q3 + 1.5 * IQR

        dataframe[column] = dataframe[column].apply(lambda x: lower_bound if x < lower_bound else upper_bound if x > upper_bound else x)
    
    return dataframe

Устраняем выбросы, если они имеются

In [156]:
df = remove_outliers(df, columns_with_outliers)
Колонки с выбросами:
work_year
salary
salary_in_usd

Проверим наличие выбросов и визуализируем

In [157]:
check_outliers(df)
visualize_outliers(df)
Колонка work_year:
	Есть выбросы: Нет
	Количество выбросов: 0
	Минимальное значение: 2020.5
	Максимальное значение: 2023.0
	1-й квартиль (Q1): 2022.0
	3-й квартиль (Q3): 2023.0

Колонка salary:
	Есть выбросы: Нет
	Количество выбросов: 0
	Минимальное значение: 6000.0
	Максимальное значение: 300000.0
	1-й квартиль (Q1): 100000.0
	3-й квартиль (Q3): 180000.0

Колонка salary_in_usd:
	Есть выбросы: Нет
	Количество выбросов: 0
	Минимальное значение: 5132.0
	Максимальное значение: 295000.0
	1-й квартиль (Q1): 95000.0
	3-й квартиль (Q3): 175000.0

Колонка remote_ratio:
	Есть выбросы: Нет
	Количество выбросов: 0
	Минимальное значение: 0
	Максимальное значение: 100
	1-й квартиль (Q1): 0.0
	3-й квартиль (Q3): 100.0

No description has been provided for this image

Разбиение набора данных на выборки:¶

Групповое разбиение данных это метод разделения данных на несколько групп или подмножеств на основе определенного признака или характеристики. При этом наблюдения для одного объекта должны попасть только в одну выборку.

Основные виды выборки данных:

  • Обучающая выборка (60-80%). Обучение модели (подбор коэффициентов некоторой математической функции для аппроксимации).
  • Контрольная выборка (10-20%). Выбор метода обучения, настройка гиперпараметров.
  • Тестовая выборка (10-20% или 20-30%). Оценка качества модели перед передачей заказчику.

Разделим выборку данных на 3 группы и проанализируем качество распределения данных.

Функция для создания выборок

In [158]:
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,
) -> tuple[Any, Any, Any]:
    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: DataFrame = df_input
    y: DataFrame = df_input[
        [stratify_colname]
    ]

    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
    )

    relative_frac_test: float = 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 [159]:
def check_balance(dataframe: DataFrame, dataframe_name: str, column: str) -> None:
    counts: Series[int] = dataframe[column].value_counts()
    print(dataframe_name + ": ", dataframe.shape)
    print(f"Распределение выборки данных по классам в колонке \"{column}\":\n", counts)
    total_count: int = len(dataframe)
    for value in counts.index:
        percentage: float = counts[value] / total_count * 100
        print(f"Процент объектов класса \"{value}\": {percentage:.2f}%")
    print()

Функция определения необходимости аугментации данных

In [160]:
def need_augmentation(dataframe: DataFrame,
                      column: str, 
                      first_value: Any, second_value: Any) -> bool:
    counts: Series[int] = dataframe[column].value_counts()
    ratio: float = counts[first_value] / counts[second_value]
    return ratio > 1.5 or ratio < 0.67

Метод визуализации сбалансированности классов

In [161]:
def visualize_balance(dataframe: DataFrame,
                      column: str) -> None:
   fig, axes = plt.subplots(1, 1, figsize=(15, 5))

   counts_train: Series[int] = dataframe[column].value_counts()
   axes.pie(counts_train, labels=counts_train.index, autopct='%1.1f%%', startangle=90)
   axes.set_title(f"Распределение классов \"{column}\"\n")

   plt.tight_layout()
   plt.show()

Разделим выборку данных на 3 группы и проанализируем качество распределения данных.

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

Чтобы решить эту проблему введём категории для значения зарплаты. Вместо того, чтобы использовать точные значения зарплаты для стратификации, мы создадим категории зарплат, основываясь на квартилях (25%, 50%, 75%) и минимальном и максимальном значении зарплаты. Это позволит создать более крупные классы, что устранит проблему с редкими значениями

Категории для разбиения зарплат:

  • Низкая зарплата: зарплаты ниже первого квартиля (25%) — это значения меньше 95000.
  • Средняя зарплата: зарплаты между первым квартилем (25%) и третьим квартилем (75%) — это зарплаты от 95000 до 175000.
  • Высокая зарплата: зарплаты выше третьего квартиля (75%) и до максимального значения — это зарплаты выше 175000.
In [162]:
# Вывод распределения количества наблюдений по меткам (классам)
print('Распределение количества наблюдений по меткам (классам):')
print(df.salary_in_usd.value_counts(), '\n')

# Статистическое описание целевого признака
print('Статистическое описание целевого признака:')
print(df['salary_in_usd'].describe().transpose(), '\n')

# Определим границы для каждой категории зарплаты
bins: list[float] = [df['salary_in_usd'].min() - 1, 
                     df['salary_in_usd'].quantile(0.25), 
                     df['salary_in_usd'].quantile(0.75), 
                     df['salary_in_usd'].max() + 1]
labels: list[str] = ['low', 'medium', 'high']

# Создаем новую колонку с категориями зарплат#
df['salary_category'] = pd.cut(df['salary_in_usd'], bins=bins, labels=labels)

# Вывод распределения количества наблюдений по меткам (классам)
print('Распределение количества наблюдений по меткам (классам):')
print(df['salary_category'].value_counts(), '\n')

# Проверка сбалансированности
print('Проверка сбалансированности:')
check_balance(df, 'Весь датасет', 'salary_category')

# Проверка необходимости аугментации
print('Проверка необходимости аугментации:')
print(f"Для датасета аугментация данных {'НЕ ' if not need_augmentation(df, 'salary_category', 'low', 'medium') else ''}ТРЕБУЕТСЯ")
   
# Визуализация сбалансированности классов
visualize_balance(df, 'salary_category')
Распределение количества наблюдений по меткам (классам):
salary_in_usd
100000.0    99
150000.0    98
120000.0    91
160000.0    84
130000.0    82
            ..
39916.0      1
26005.0      1
22611.0      1
5679.0       1
40038.0      1
Name: count, Length: 1002, dtype: int64 

Статистическое описание целевого признака:
count      3755.000000
mean     136959.779760
std       61098.121137
min        5132.000000
25%       95000.000000
50%      135000.000000
75%      175000.000000
max      295000.000000
Name: salary_in_usd, dtype: float64 

Распределение количества наблюдений по меткам (классам):
salary_category
medium    1867
low        956
high       932
Name: count, dtype: int64 

Проверка сбалансированности:
Весь датасет:  (3755, 12)
Распределение выборки данных по классам в колонке "salary_category":
 salary_category
medium    1867
low        956
high       932
Name: count, dtype: int64
Процент объектов класса "medium": 49.72%
Процент объектов класса "low": 25.46%
Процент объектов класса "high": 24.82%

Проверка необходимости аугментации:
Для датасета аугментация данных ТРЕБУЕТСЯ
No description has been provided for this image

Данные обладают значительным дисбалансом между классами. Это может быть проблемой при обучении модели, так как она может иметь тенденцию игнорировать низкие или высокие зарплаты (low или high), что следует учитывать при дальнейшем анализе и выборе методов обработки данных.

Для получения более сбалансированных данных необходимо воспользоваться методами приращения (аугментации) данных, а именно методами oversampling и undersampling.

Метод приращения с избытком (oversampling)

In [163]:
def oversample(df: DataFrame, column: str) -> DataFrame:
    X: DataFrame = pd.get_dummies(df.drop(column, axis=1))
    y: DataFrame = df[column] # type: ignore
    
    adasyn = ADASYN()
    X_resampled, y_resampled = adasyn.fit_resample(X, y) # type: ignore
    
    df_resampled: DataFrame = pd.concat([X_resampled, y_resampled], axis=1)
    return df_resampled
In [164]:
# Приращение данных (oversampling)
df_oversampled: DataFrame = oversample(df, 'salary_category')

# Проверка сбалансированности
print('Проверка сбалансированности выборок после применения метода oversampling:')
check_balance(df_oversampled, 'Весь датасет', 'salary_category')

# Проверка необходимости аугментации
print('Проверка необходимости аугментации выборок после применения метода oversampling:')
print(f"Для всего датасета аугментация данных {'НЕ ' if not need_augmentation(df_oversampled, 'salary_category', 'low', 'medium') else ''}ТРЕБУЕТСЯ")
   
# Визуализация сбалансированности классов
visualize_balance(df_oversampled, 'salary_category')
Проверка сбалансированности выборок после применения метода oversampling:
Весь датасет:  (5601, 279)
Распределение выборки данных по классам в колонке "salary_category":
 salary_category
high      1868
medium    1867
low       1866
Name: count, dtype: int64
Процент объектов класса "high": 33.35%
Процент объектов класса "medium": 33.33%
Процент объектов класса "low": 33.32%

Проверка необходимости аугментации выборок после применения метода oversampling:
Для всего датасета аугментация данных НЕ ТРЕБУЕТСЯ
No description has been provided for this image

Разделим датасет на выборки

In [165]:
df_train, df_val, df_test = split_stratified_into_train_val_test(
   df_oversampled,
   stratify_colname="salary_category", 
   frac_train=0.60, 
   frac_val=0.20, 
   frac_test=0.20
)

print('Тренировочная выборка')
visualize_balance(df_train, 'salary_category')
print('Контрольная выборка')
visualize_balance(df_val, 'salary_category')
print('Тестовая выборка')
visualize_balance(df_test, 'salary_category')
Тренировочная выборка
No description has been provided for this image
Контрольная выборка
No description has been provided for this image
Тестовая выборка
No description has been provided for this image

Датасет 2. Анализ продаж филиалов супермаркетов

https://www.kaggle.com/datasets/surajjha101/stores-area-and-sales-data

Анализ сведений о датасете

Проблемная область

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

  • Оценку производительности магазинов;
  • Поиск факторов, которые могут улучшить прибыль и эффективность;
  • Определение взаимосвязи между различными характеристиками магазинов.

Актуальность

Анализ эффективности супермаркетов актуален в сфере розничной торговли, поскольку помогает:

  • Повышать прибыльность магазинов;
  • Улучшать распределение ресурсов (например, товаров или пространства);
  • Оптимизировать маркетинговые и операционные стратегии;
  • Оценивать влияние внешних факторов (например, площади магазина или ассортимента товаров) на продажи.

Объекты наблюдений

Объектами наблюдения являются магазины супермаркетов, каждый из которых представлен в датасете через уникальный идентификатор (Store ID). Для каждого магазина представлены различные параметры, которые отражают его физическую структуру и экономическую деятельность.

Атрибуты объектов

Каждое наблюдение (магазин) имеет следующие атрибуты:

  • Store ID — уникальный идентификатор магазина (индекс);
  • Store_Area — физическая площадь магазина в квадратных ярдах (меряет размер магазина);
  • Items_Available — количество различных товаров, доступных в магазине (ассортимент);
  • Daily_Customer_Count — среднее количество клиентов, посещающих магазин ежедневно (популярность);
  • Store_Sales — объем продаж магазина в долларах США (экономическая эффективность).

Связь между объектами

Связь между атрибутами объектов (магазинов) может быть следующей:

  • Store_Area ↔ Items_Available: Большее количество товаров может требовать большей площади для их размещения.
  • Store_Area ↔ Store_Sales: Большая площадь магазина может свидетельствовать о большем объеме продаж, поскольку позволяет разместить больше товаров и обслуживать больше клиентов.
  • Items_Available ↔ Daily_Customer_Count: Магазины с большим ассортиментом товаров могут привлекать больше клиентов, особенно если товары соответствуют потребительским ожиданиям.
  • Daily_Customer_Count ↔ Store_Sales: Прямая зависимость — большее количество клиентов может привести к большему объему продаж.

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

Качество набора данных

  1. Информативность:
    Датасет содержит несколько ключевых атрибутов, которые отражают как физические характеристики магазинов, так и их экономическую эффективность. Эти атрибуты (площадь, ассортимент товаров, количество клиентов и продажи) достаточно информативны для начального анализа производительности супермаркетов.

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

  3. Соответствие реальным данным:
    Данные, представленные в датасете, соответствуют реальной практической ситуации, поскольку информация о площади магазинов, количестве товаров и клиентском потоке довольно типична для анализа розничных торговых точек.

  4. Согласованность меток:
    Метки данных (например, Store ID, Store_Area, Items_Available и т.д.) хорошо согласованы и имеют понятные и логичные наименования. Однако для полной уверенности в корректности данных потребуется проверка на наличие пропусков или аномалий (например, если площадь магазина или количество товаров кажется необычно низким или высоким).

Бизнес цели, которые может решить датасет:

  1. Оптимизация ассортимента товаров и пространства
    Цель: Разработать стратегию по оптимальному размещению товаров и выбору ассортимента в зависимости от площади магазина и его клиентской базы.
    Эффект на бизнес: Поможет увеличить продажи путем улучшения доступности популярных товаров и оптимизации использования пространства в магазинах.

    Цели технического проекта:

    • Входные данные: Площадь магазина, количество товаров, ежедневное количество клиентов.
    • Целевой признак: Объем продаж (Store_Sales).
  2. Увеличение продаж через улучшение привлечения клиентов
    Цель: Разработать стратегию по увеличению потока клиентов в магазины на основе текущего количества покупателей и их корреляции с объемом продаж.
    Эффект на бизнес: Увеличение количества клиентов может прямо повлиять на рост продаж и прибыльность, особенно если будет применена стратегия привлечения дополнительного потока потребителей.

    Цели технического проекта:

    • Входные данные: Количество товаров в магазине, площадь магазина, среднее количество клиентов.
    • Целевой признак: Объем продаж (Store_Sales).
  3. Предсказание и управление производительностью магазинов
    Цель: Оценить, какие факторы (площадь, ассортимент, количество клиентов) влияют на эффективность магазина и как прогнозировать его продажи в будущем.
    Эффект на бизнес: Ожидаемый результат — повышение точности прогнозов продаж и улучшение стратегического планирования для различных магазинов сети.

    Цели технического проекта:

    • Входные данные: Площадь магазина, количество товаров, ежедневное количество клиентов.
    • Целевой признак: Объем продаж (Store_Sales).

Примеры целей технического проекта для каждой бизнес-цели:

  1. Оптимизация ассортимента товаров и пространства

    • Задача: Построить модель, которая на основе площади магазина и ассортимента товаров будет предсказывать оптимальный объем продаж.
    • Вход: Площадь магазина (Store_Area), Количество товаров (Items_Available).
    • Цель: Прогнозировать объем продаж (Store_Sales).
  2. Увеличение продаж через улучшение привлечения клиентов

    • Задача: Разработать алгоритм, который будет анализировать связи между количеством клиентов и продажами для оценки эффективности маркетинговых усилий.
    • Вход: Среднее количество клиентов (Daily_Customer_Count), Количество товаров (Items_Available), Площадь магазина (Store_Area).
    • Цель: Прогнозировать объем продаж (Store_Sales).
  3. Предсказание и управление производительностью магазинов

    • Задача: Построить модель для предсказания объемов продаж на основе характеристик магазинов, чтобы заранее прогнозировать производительность и принимать меры по улучшению результатов.
    • Вход: Площадь магазина (Store_Area), Среднее количество клиентов (Daily_Customer_Count), Количество товаров (Items_Available).
    • Цель: Прогнозировать объем продаж (Store_Sales).
In [171]:
df = pd.read_csv('csv/9.Stores.csv')
df.info()
df.describe().transpose()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 896 entries, 0 to 895
Data columns (total 5 columns):
 #   Column                Non-Null Count  Dtype
---  ------                --------------  -----
 0   Store ID              896 non-null    int64
 1   Store_Area            896 non-null    int64
 2   Items_Available       896 non-null    int64
 3   Daily_Customer_Count  896 non-null    int64
 4   Store_Sales           896 non-null    int64
dtypes: int64(5)
memory usage: 35.1 KB
Out[171]:
count mean std min 25% 50% 75% max
Store ID 896.0 448.500000 258.797218 1.0 224.75 448.5 672.25 896.0
Store_Area 896.0 1485.409598 250.237011 775.0 1316.75 1477.0 1653.50 2229.0
Items_Available 896.0 1782.035714 299.872053 932.0 1575.50 1773.5 1982.75 2667.0
Daily_Customer_Count 896.0 786.350446 265.389281 10.0 600.00 780.0 970.00 1560.0
Store_Sales 896.0 59351.305804 17190.741895 14920.0 46530.00 58605.0 71872.50 116320.0

Удалим колонку с ID магазинов, она нам вряд ли понадобится

In [192]:
if "Store ID " in df.columns:
    df = df.drop(columns=["Store ID "])

df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 896 entries, 0 to 895
Data columns (total 4 columns):
 #   Column                Non-Null Count  Dtype
---  ------                --------------  -----
 0   Store_Area            896 non-null    int64
 1   Items_Available       896 non-null    int64
 2   Daily_Customer_Count  896 non-null    int64
 3   Store_Sales           896 non-null    int64
dtypes: int64(4)
memory usage: 28.1 KB
In [193]:
check_null_columns(df)
Присутствуют ли пустые значения признаков в колонке:
Store_Area              False
Items_Available         False
Daily_Customer_Count    False
Store_Sales             False
dtype: bool 

Проверим на наличие выбросов

In [194]:
columns_with_outliers = check_outliers(df)
Колонка Store_Area:
	Есть выбросы: Да
	Количество выбросов: 5
	Минимальное значение: 775
	Максимальное значение: 2229
	1-й квартиль (Q1): 1316.75
	3-й квартиль (Q3): 1653.5

Колонка Items_Available:
	Есть выбросы: Да
	Количество выбросов: 5
	Минимальное значение: 932
	Максимальное значение: 2667
	1-й квартиль (Q1): 1575.5
	3-й квартиль (Q3): 1982.75

Колонка Daily_Customer_Count:
	Есть выбросы: Да
	Количество выбросов: 3
	Минимальное значение: 10
	Максимальное значение: 1560
	1-й квартиль (Q1): 600.0
	3-й квартиль (Q3): 970.0

Колонка Store_Sales:
	Есть выбросы: Да
	Количество выбросов: 1
	Минимальное значение: 14920
	Максимальное значение: 116320
	1-й квартиль (Q1): 46530.0
	3-й квартиль (Q3): 71872.5

Визуализируем выбросы

In [195]:
visualize_outliers(df)
No description has been provided for this image

Устраняем выбросы, если они имеются

In [196]:
df = remove_outliers(df, columns_with_outliers)
Колонки с выбросами:
Store_Area
Items_Available
Daily_Customer_Count
Store_Sales

Проверим наличие выбросов и визуализируем

In [197]:
check_outliers(df)
visualize_outliers(df)
Колонка Store_Area:
	Есть выбросы: Нет
	Количество выбросов: 0
	Минимальное значение: 811.625
	Максимальное значение: 2158.625
	1-й квартиль (Q1): 1316.75
	3-й квартиль (Q3): 1653.5

Колонка Items_Available:
	Есть выбросы: Нет
	Количество выбросов: 0
	Минимальное значение: 964.625
	Максимальное значение: 2593.625
	1-й квартиль (Q1): 1575.5
	3-й квартиль (Q3): 1982.75

Колонка Daily_Customer_Count:
	Есть выбросы: Нет
	Количество выбросов: 0
	Минимальное значение: 45.0
	Максимальное значение: 1525.0
	1-й квартиль (Q1): 600.0
	3-й квартиль (Q3): 970.0

Колонка Store_Sales:
	Есть выбросы: Нет
	Количество выбросов: 0
	Минимальное значение: 14920.0
	Максимальное значение: 109886.25
	1-й квартиль (Q1): 46530.0
	3-й квартиль (Q3): 71872.5

No description has been provided for this image

Разделим выборку данных на 3 группы и проанализируем качество распределения данных.

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

Чтобы решить эту проблему введём категории для значения объема продаж. Вместо того, чтобы использовать точные значения объема продаж для стратификации, мы создадим категории, основываясь на квартилях (25%, 50%, 75%) и минимальном и максимальном значении. Это позволит создать более крупные классы, что устранит проблему с редкими значениями

Категории для разбиения:

Низкая: значения ниже первого квартиля (25%) — это значения меньше 46530.0 Средняя: значения между первым квартилем (25%) и третьим квартилем (75%) — это зарплаты от 46530.0 до 71872.5 Высокая: значения выше третьего квартиля (75%) и до максимального значения — это зарплаты выше 71872.5

In [199]:
# Вывод распределения количества наблюдений по меткам (классам)
print('Распределение количества наблюдений по меткам (классам):')
print(df.Store_Sales.value_counts(), '\n')

# Статистическое описание целевого признака
print('Статистическое описание целевого признака:')
print(df['Store_Sales'].describe().transpose(), '\n')

# Определим границы для каждой категории зарплаты
bins: list[float] = [df['Store_Sales'].min() - 1, 
                     df['Store_Sales'].quantile(0.25), 
                     df['Store_Sales'].quantile(0.75), 
                     df['Store_Sales'].max() + 1]
labels: list[str] = ['low', 'medium', 'high']

# Создаем новую колонку с категориями
df['Store_Sales_category'] = pd.cut(df['Store_Sales'], bins=bins, labels=labels)

# Вывод распределения количества наблюдений по меткам (классам)
print('Распределение количества наблюдений по меткам (классам):')
print(df['Store_Sales_category'].value_counts(), '\n')

# Проверка сбалансированности
print('Проверка сбалансированности:')
check_balance(df, 'Весь датасет', 'Store_Sales_category')

# Проверка необходимости аугментации
print('Проверка необходимости аугментации:')
print(f"Для датасета аугментация данных {'НЕ ' if not need_augmentation(df, 'Store_Sales_category', 'low', 'medium') else ''}ТРЕБУЕТСЯ")
   
# Визуализация сбалансированности классов
visualize_balance(df, 'Store_Sales_category')
Распределение количества наблюдений по меткам (классам):
Store_Sales
63540.0    3
54590.0    3
76530.0    2
54370.0    2
82350.0    2
          ..
70620.0    1
82080.0    1
76440.0    1
96610.0    1
54340.0    1
Name: count, Length: 816, dtype: int64 

Статистическое описание целевого признака:
count       896.000000
mean      59344.125279
std       17168.248608
min       14920.000000
25%       46530.000000
50%       58605.000000
75%       71872.500000
max      109886.250000
Name: Store_Sales, dtype: float64 

Распределение количества наблюдений по меткам (классам):
Store_Sales_category
medium    448
low       224
high      224
Name: count, dtype: int64 

Проверка сбалансированности:
Весь датасет:  (896, 5)
Распределение выборки данных по классам в колонке "Store_Sales_category":
 Store_Sales_category
medium    448
low       224
high      224
Name: count, dtype: int64
Процент объектов класса "medium": 50.00%
Процент объектов класса "low": 25.00%
Процент объектов класса "high": 25.00%

Проверка необходимости аугментации:
Для датасета аугментация данных ТРЕБУЕТСЯ
No description has been provided for this image

Ради интереса можно сначала разбить на выборки, а потом провести аугментацию

In [201]:
def visualize_balance_three_pies(dataframe_train: DataFrame,
                      dataframe_val: DataFrame,
                      dataframe_test: DataFrame, 
                      column: str) -> None:
   fig, axes = plt.subplots(1, 3, figsize=(15, 5))

   counts_train: Series[int] = dataframe_train[column].value_counts()
   axes[0].pie(counts_train, labels=counts_train.index, autopct='%1.1f%%', startangle=90)
   axes[0].set_title(f"Распределение классов \"{column}\"\nв обучающей выборке")

   counts_val: Series[int] = dataframe_val[column].value_counts()
   axes[1].pie(counts_val, labels=counts_val.index, autopct='%1.1f%%', startangle=90)
   axes[1].set_title(f"Распределение классов \"{column}\"\nв контрольной выборке")

   counts_test: Series[int] = dataframe_test[column].value_counts()
   axes[2].pie(counts_test, labels=counts_test.index, autopct='%1.1f%%', startangle=90)
   axes[2].set_title(f"Распределение классов \"{column}\"\nв тренировочной выборке")

   plt.tight_layout()
   plt.show()
In [204]:
df_train, df_val, df_test = split_stratified_into_train_val_test(
   df,
   stratify_colname="Store_Sales_category", 
   frac_train=0.60, 
   frac_val=0.20, 
   frac_test=0.20
)

visualize_balance_three_pies(df_train, df_val, df_test, 'Store_Sales_category')
No description has been provided for this image

Произведем оверсэмплинг

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

In [207]:
df_train_oversampled = oversample(df_train, 'Store_Sales_category')
df_val_oversampled = oversample(df_val, 'Store_Sales_category')
df_test_oversampled = oversample(df_test, 'Store_Sales_category')

visualize_balance_three_pies(df_train_oversampled, df_val_oversampled, df_test_oversampled, 'Store_Sales_category')
No description has been provided for this image

Датасет 3. Прогнозирование стоимости медицинского страхования

https://www.kaggle.com/datasets/harishkumardatalab/medical-insurance-price-prediction

Анализ сведений о датасете

Проблемная область:

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

Актуальность:

Прогнозирование медицинских расходов является ключевым элементом для страховых компаний, чтобы правильно оценить риски, установить справедливые страховые взносы и обеспечить финансовую устойчивость компании. Актуальность такого анализа возрастает с увеличением потребности в персонализированном страховании.

Объекты наблюдений:

Каждый объект наблюдения представляет собой запись о человеке, который является клиентом медицинской страховой компании.

Атрибуты объектов:

  • Age (возраст) — числовой атрибут, показывает возраст клиента.
  • Sex (пол) — категориальный атрибут (мужчина/женщина), который может повлиять на тип медицинских услуг и расходы.
  • BMI (индекс массы тела) — числовой атрибут, который может быть важным для оценки здоровья клиента и возможных заболеваний.
  • Children (дети) — числовой атрибут, который может показывать потребность в медицинских услугах для детей.
  • Smoker (курящий) — булев атрибут, показывающий, является ли человек курильщиком, что влияет на его здоровье и расходы.
  • Region (регион) — текстовый атрибут, который может учитывать различия в стоимости медицинских услуг в разных регионах.
  • Charges (расходы) — целевой числовой атрибут, показывающий медицинские расходы, которые следует предсказать.

Связь между объектами:

Атрибуты данных взаимосвязаны. Например, возраст, ИМТ и курение могут быть связанными с увеличением медицинских расходов, так как старение и ожирение повышают риски заболеваний. Регион может определять базовый уровень расходов, а наличие детей может указывать на дополнительные расходы на медицинские услуги для детей.

Качество набора данных

Информативность:

Набор данных содержит важные параметры для оценки медицинских расходов, такие как возраст, ИМТ, статус курящего и наличие детей. Однако дополнительные параметры, такие как хронические заболевания, история медицинских визитов или история страховки, могут улучшить модель.

Степень покрытия:

Набор данных охватывает несколько ключевых факторов (возраст, пол, ИМТ, количество детей, курение, регион), которые являются важными для прогнозирования расходов. Однако для более точных прогнозов могут быть полезны дополнительные данные, такие как образ жизни или медицинская история.

Соответствие реальным данным:

Данные вполне могут соответствовать реальной ситуации в медицинском страховании, так как параметры, такие как курение, возраст и ИМТ, действительно влияют на здоровье и, следовательно, на расходы на лечение. Однако важно, чтобы данные были сбалансированы и не содержали искажений.

Согласованность меток:

Метки, такие как пол, курящий/не курящий, и регион, должны быть корректно представлены. Необходимо убедиться в отсутствии противоречий в данных (например, отсутствие значений для категориальных переменных или неверных числовых значений).

Бизнес-цели, которые может решить этот датасет

  1. Оптимизация ценообразования на медицинское страхование

    • Эффект на бизнес: Компании смогут более точно оценивать потенциальные расходы на медицинские услуги для клиентов, что позволит устанавливать адекватные страховые взносы, минимизируя риски и обеспечивая прибыльность.
  2. Оценка рисков клиентов

    • Эффект на бизнес: Страховые компании смогут выявлять группы клиентов с высоким риском, что поможет предсказать, какие клиенты могут потребовать больше затрат на лечение, и соответственно, предлагать им более высокие премии или дополнительные услуги.
  3. Разработка персонализированных предложений для клиентов

    • Эффект на бизнес: Возможность предложить клиентам индивидуальные страховые планы и дополнительные услуги, основанные на их рисках и потребностях, повысит их удовлетворенность и лояльность, а также улучшит финансовые результаты компании.

Примеры целей технического проекта для каждой бизнес-цели

  1. Оптимизация ценообразования на медицинское страхование

    • Цель технического проекта: Построить модель регрессии для прогнозирования медицинских расходов на основе демографических данных (возраст, пол, ИМТ, курение и т.д.).
    • Что поступает на вход: Возраст, пол, ИМТ, количество детей, курение, регион.
    • Целевой признак: Расходы (charges).
  2. Оценка рисков клиентов

    • Цель технического проекта: Разработать модель классификации для оценки уровня риска клиента (низкий, средний, высокий риск).
    • Что поступает на вход: Возраст, пол, ИМТ, количество детей, курение, регион.
    • Целевой признак: Риск (классификация на категории: низкий, средний, высокий).
  3. Разработка персонализированных предложений для клиентов

    • Цель технического проекта: Создать систему рекомендаций, которая будет предлагать персонализированные страховые планы и услуги на основе характеристик клиента.
    • Что поступает на вход: Все атрибуты клиента (возраст, пол, ИМТ, дети, курение, регион).
    • Целевой признак: Рекомендуемый план страхования или дополнительная услуга.

Каждый из этих проектов направлен на повышение прибыльности компании, улучшение персонализированного подхода к клиентам и снижение финансовых рисков.

In [168]:
df = pd.read_csv('csv/5.medical_insurance.csv')
df.info()
df.describe().transpose()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2772 entries, 0 to 2771
Data columns (total 7 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   age       2772 non-null   int64  
 1   sex       2772 non-null   object 
 2   bmi       2772 non-null   float64
 3   children  2772 non-null   int64  
 4   smoker    2772 non-null   object 
 5   region    2772 non-null   object 
 6   charges   2772 non-null   float64
dtypes: float64(2), int64(2), object(3)
memory usage: 151.7+ KB
Out[168]:
count mean std min 25% 50% 75% max
age 2772.0 39.109668 14.081459 18.0000 26.000 39.00000 51.0000 64.00000
bmi 2772.0 30.701349 6.129449 15.9600 26.220 30.44750 34.7700 53.13000
children 2772.0 1.101732 1.214806 0.0000 0.000 1.00000 2.0000 5.00000
charges 2772.0 13261.369959 12151.768945 1121.8739 4687.797 9333.01435 16577.7795 63770.42801
In [169]:
check_null_columns(df)
Присутствуют ли пустые значения признаков в колонке:
age         False
sex         False
bmi         False
children    False
smoker      False
region      False
charges     False
dtype: bool