2024-12-13 21:57:58 +04:00

120 KiB
Raw Blame History

In [52]:
import pandas as pd

df = pd.read_csv("..//static//csv//ds_salaries.csv")

df.head()
Out[52]:
work_year experience_level employment_type job_title salary salary_currency salary_in_usd employee_residence remote_ratio company_location company_size
0 2023 SE FT Principal Data Scientist 80000 EUR 85847 ES 100 ES L
1 2023 MI CT ML Engineer 30000 USD 30000 US 100 US S
2 2023 MI CT ML Engineer 25500 USD 25500 US 100 US S
3 2023 SE FT Data Scientist 175000 USD 175000 CA 100 CA M
4 2023 SE FT Data Scientist 120000 USD 120000 CA 100 CA M

Бизнес-цели

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

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

In [53]:
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder

# Удаление выбросов из столбца `salary_in_usd` с использованием IQR
Q1 = df['salary_in_usd'].quantile(0.25)
Q3 = df['salary_in_usd'].quantile(0.75)
IQR = Q3 - Q1
df = df[(df['salary_in_usd'] >= Q1 - 1.5 * IQR) & (df['salary_in_usd'] <= Q3 + 1.5 * IQR)]

# Преобразование категориальных данных в числовые (если потребуется)
if 'remote_ratio' in df.columns:
    df['remote_ratio'] = df['remote_ratio'].astype(int)

# Удаление дубликатов
df.drop_duplicates(inplace=True)

# Определение целевой переменной и признаков
X = df.drop(columns=['salary_in_usd', 'salary_currency', 'job_title'])  # Признаки
y = df['salary_in_usd']  # Целевая переменная для регрессии

# Определение числовых и категориальных признаков
numeric_features = ['work_year', 'remote_ratio']
categorical_features = ['experience_level', 'employment_type', 
                        'employee_residence', 'company_location', 'company_size']

# Обработка числовых данных
numeric_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='median')),  # Заполнение пропусков медианой
    ('scaler', StandardScaler())                   # Нормализация данных
])

# Обработка категориальных данных
categorical_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='most_frequent')),  # Заполнение пропусков модой
    ('onehot', OneHotEncoder(handle_unknown='ignore'))     # Преобразование в One-Hot Encoding
])

# Комбинированный трансформер
preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, numeric_features),   # Применяем числовую обработку
        ('cat', categorical_transformer, categorical_features)  # Применяем категориальную обработку
    ]
)

# Разделение данных на обучающую и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Применение пайплайна
X_train_transformed = preprocessor.fit_transform(X_train)
X_test_transformed = preprocessor.transform(X_test)

# Проверка результата трансформации
print(f"Transformed feature shape (train): {X_train_transformed.shape}")
print(f"Transformed feature shape (test): {X_test_transformed.shape}")
Transformed feature shape (train): (2029, 151)
Transformed feature shape (test): (508, 151)

Выведим результаты

In [54]:
# Получим имена категориальных признаков после OneHotEncoder
categorical_feature_names = preprocessor.named_transformers_['cat']['onehot'].get_feature_names_out(categorical_features)

# Объединим их с именами числовых признаков
feature_names = list(numeric_features) + list(categorical_feature_names)

# Создадим DataFrame для преобразованных данных
X_train_transformed_df = pd.DataFrame(X_train_transformed.toarray() if hasattr(X_train_transformed, 'toarray') else X_train_transformed, columns=feature_names)

# Выведем первые 5 строк обработанного набора данных
X_train_transformed_df.head()
Out[54]:
work_year remote_ratio experience_level_EN experience_level_EX experience_level_MI experience_level_SE employment_type_CT employment_type_FL employment_type_FT employment_type_PT ... company_location_SI company_location_SK company_location_TH company_location_TR company_location_UA company_location_US company_location_VN company_size_L company_size_M company_size_S
0 -1.747172 1.016983 0.0 0.0 0.0 1.0 0.0 0.0 1.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0
1 -1.747172 1.016983 0.0 0.0 0.0 1.0 0.0 0.0 1.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 1.0 0.0 1.0 0.0 0.0
2 0.943539 -1.057887 0.0 0.0 0.0 1.0 0.0 0.0 1.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 1.0 0.0
3 -0.401816 1.016983 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 1.0 0.0
4 -0.401816 -0.020452 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0

5 rows × 151 columns

Обучим три модели

In [55]:
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import RandomizedSearchCV
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer

# Задание случайного состояния
random_state = 42

# Модели и параметры
models_regression = {
    "LinearRegression": LinearRegression(),
    "RandomForestRegressor": RandomForestRegressor(random_state=random_state),
    "GradientBoostingRegressor": GradientBoostingRegressor(random_state=random_state)
}

param_grids_regression = {
    "LinearRegression": {},
    "RandomForestRegressor": {
        'model__n_estimators': [50, 100, 200],
        'model__max_depth': [None, 10, 20],
        'model__min_samples_split': [2, 5, 10]
    },
    "GradientBoostingRegressor": {
        'model__n_estimators': [50, 100, 200],
        'model__learning_rate': [0.01, 0.1, 0.2],
        'model__max_depth': [3, 5, 10]
    }
}

# Результаты
results_regression = {}

# Перебор моделей
for name, model in models_regression.items():
    print(f"Training {name}...")
    
    # Создание пайплайна
    pipeline = Pipeline(steps=[
        ('preprocessor', preprocessor),  # Используем уже созданный preprocessor
        ('model', model)
    ])
    
    # Определение параметров для RandomizedSearchCV
    param_grid = param_grids_regression[name]
    search = RandomizedSearchCV(pipeline, param_distributions=param_grid, 
                                cv=5, scoring='neg_mean_absolute_error', 
                                n_jobs=-1, random_state=random_state, n_iter=20)
    search.fit(X_train, y_train)
    
    # Лучшая модель
    best_model = search.best_estimator_
    y_pred = best_model.predict(X_test)
    
    # Метрики
    mae = mean_absolute_error(y_test, y_pred)
    rmse = np.sqrt(mean_squared_error(y_test, y_pred))
    r2 = r2_score(y_test, y_pred)
    
    # Сохранение результатов
    results_regression[name] = {
        "Best Params": search.best_params_,
        "MAE": mae,
        "RMSE": rmse,
        "R2": r2
    }

# Печать результатов
for name, metrics in results_regression.items():
    print(f"\nModel: {name}")
    for metric, value in metrics.items():
        print(f"{metric}: {value}")
Training LinearRegression...
c:\Users\salih\OneDrive\Рабочий стол\3 курас\МИИ\laba1\AIM-PIbd-31-Yaruskin-S-A\aimenv\Lib\site-packages\sklearn\model_selection\_search.py:320: UserWarning: The total space of parameters 1 is smaller than n_iter=20. Running 1 iterations. For exhaustive searches, use GridSearchCV.
  warnings.warn(
Training RandomForestRegressor...
Training GradientBoostingRegressor...

Model: LinearRegression
Best Params: {}
MAE: 35903.74761235383
RMSE: 45746.92374132039
R2: 0.41681042958060477

Model: RandomForestRegressor
Best Params: {'model__n_estimators': 100, 'model__min_samples_split': 10, 'model__max_depth': 20}
MAE: 35382.49447920311
RMSE: 45711.49865435396
R2: 0.41771328994747514

Model: GradientBoostingRegressor
Best Params: {'model__n_estimators': 50, 'model__max_depth': 5, 'model__learning_rate': 0.2}
MAE: 35404.55042553757
RMSE: 45669.354449671955
R2: 0.41878648590699374
In [56]:
# Формирование таблицы метрик из результатов регрессионных моделей
reg_metrics = pd.DataFrame.from_dict(results_regression, orient="index")[
    ["MAE", "RMSE", "R2"]
]

# Визуализация результатов с помощью стилизации
styled_metrics = (
    reg_metrics.sort_values(by="RMSE")
    .style.background_gradient(cmap="viridis", low=1, high=0.3, subset=["RMSE", "MAE"])
    .background_gradient(cmap="plasma", low=0.3, high=1, subset=["R2"])
)

# Отобразим таблицу
styled_metrics
Out[56]:
  MAE RMSE R2
GradientBoostingRegressor 35404.550426 45669.354450 0.418786
RandomForestRegressor 35382.494479 45711.498654 0.417713
LinearRegression 35903.747612 45746.923741 0.416810

Чтото слабоватые модели получились. Даже 50% нет, нужно попробовать улучшить данные.

In [57]:
# Функция для приведения выбросов к среднему значению
def handle_outliers(df, column):
    Q1 = df[column].quantile(0.25)
    Q3 = df[column].quantile(0.75)
    IQR = Q3 - Q1
    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR
    
    mean_value = df[column].mean()
    df[column] = np.where((df[column] < lower_bound) | (df[column] > upper_bound), mean_value, df[column])
    return df

# Приведение выбросов в столбце `salary_in_usd` к среднему значению
df = handle_outliers(df, 'salary_in_usd')

# Преобразование категориальных данных в строковые для корректной обработки
if 'remote_ratio' in df.columns:
    df['remote_ratio'] = df['remote_ratio'].astype(str)

# Удаление дубликатов
df.drop_duplicates(inplace=True)

# Определение целевой переменной и признаков
X = df.drop(columns=['salary_in_usd', 'salary_currency', 'job_title'])  # Признаки
y = df['salary_in_usd']  # Целевая переменная для регрессии

# Определение числовых и категориальных признаков
numeric_features = ['work_year']  # Убрали 'remote_ratio', так как это категориальный признак
categorical_features = ['experience_level', 'employment_type', 
                        'employee_residence', 'company_location', 'company_size', 'remote_ratio']

# Обработка числовых данных
numeric_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='median')),  # Заполнение пропусков медианой
    ('scaler', StandardScaler())                   # Нормализация данных
])

# Обработка категориальных данных
categorical_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='most_frequent')),  # Заполнение пропусков модой
    ('onehot', OneHotEncoder(handle_unknown='ignore'))     # Преобразование в One-Hot Encoding
])

# Комбинированный трансформер
preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, numeric_features),   # Применяем числовую обработку
        ('cat', categorical_transformer, categorical_features)  # Применяем категориальную обработку
    ]
)

# Разделение данных на обучающую и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Применение пайплайна
X_train_transformed = preprocessor.fit_transform(X_train)
X_test_transformed = preprocessor.transform(X_test)

# Проверка результата трансформации
print(f"Transformed feature shape (train): {X_train_transformed.shape}")
print(f"Transformed feature shape (test): {X_test_transformed.shape}")

# Определение моделей и их параметров
models = {
    "LinearRegression": LinearRegression(),
    "RandomForestRegressor": RandomForestRegressor(random_state=42),
    "GradientBoostingRegressor": GradientBoostingRegressor(random_state=42)
}

param_grids = {
    "LinearRegression": {},
    "RandomForestRegressor": {
        'model__n_estimators': [100, 200, 300],
        'model__max_depth': [10, 20, None],
        'model__min_samples_split': [2, 5, 10]
    },
    "GradientBoostingRegressor": {
        'model__n_estimators': [100, 200, 300],
        'model__learning_rate': [0.01, 0.1, 0.2],
        'model__max_depth': [3, 5, 7]
    }
}

# Результаты
results = {}

# Обучение моделей с подбором гиперпараметров
for name, model in models.items():
    print(f"Training {name}...")
    pipeline = Pipeline(steps=[
        ('preprocessor', preprocessor),
        ('model', model)
    ])
    param_grid = param_grids[name]
    search = RandomizedSearchCV(pipeline, param_distributions=param_grid, cv=5, 
                                scoring='neg_mean_absolute_error', n_jobs=-1, 
                                random_state=42, n_iter=20)
    search.fit(X_train, y_train)
    
    # Лучшая модель
    best_model = search.best_estimator_
    y_pred = best_model.predict(X_test)
    
    # Метрики
    mae = mean_absolute_error(y_test, y_pred)
    rmse = np.sqrt(mean_squared_error(y_test, y_pred))
    r2 = r2_score(y_test, y_pred)
    
    # Сохранение результатов
    results[name] = {
        "Best Params": search.best_params_,
        "MAE": mae,
        "RMSE": rmse,
        "R2": r2
    }

# Печать результатов
for name, metrics in results.items():
    print(f"\nModel: {name}")
    for metric, value in metrics.items():
        print(f"{metric}: {value}")

# Формирование таблицы метрик из результатов регрессионных моделей
reg_metrics = pd.DataFrame.from_dict(results, orient="index")[
    ["MAE", "RMSE", "R2"]
]

# Визуализация результатов с помощью стилизации
styled_metrics = (
    reg_metrics.sort_values(by="RMSE")
    .style.background_gradient(cmap="viridis", low=1, high=0.3, subset=["RMSE", "MAE"])
    .background_gradient(cmap="plasma", low=0.3, high=1, subset=["R2"])
)

# Отобразим таблицу
styled_metrics
Transformed feature shape (train): (2029, 153)
Transformed feature shape (test): (508, 153)
Training LinearRegression...
Training RandomForestRegressor...
c:\Users\salih\OneDrive\Рабочий стол\3 курас\МИИ\laba1\AIM-PIbd-31-Yaruskin-S-A\aimenv\Lib\site-packages\sklearn\model_selection\_search.py:320: UserWarning: The total space of parameters 1 is smaller than n_iter=20. Running 1 iterations. For exhaustive searches, use GridSearchCV.
  warnings.warn(
Training GradientBoostingRegressor...

Model: LinearRegression
Best Params: {}
MAE: 35923.393167146765
RMSE: 45787.2465103007
R2: 0.41578189344376837

Model: RandomForestRegressor
Best Params: {'model__n_estimators': 100, 'model__min_samples_split': 10, 'model__max_depth': 20}
MAE: 35428.00841155441
RMSE: 45772.13311276274
R2: 0.41616750576805106

Model: GradientBoostingRegressor
Best Params: {'model__n_estimators': 100, 'model__max_depth': 3, 'model__learning_rate': 0.1}
MAE: 35575.964157916314
RMSE: 45645.593157690266
R2: 0.41939112731165484
Out[57]:
  MAE RMSE R2
GradientBoostingRegressor 35575.964158 45645.593158 0.419391
RandomForestRegressor 35428.008412 45772.133113 0.416168
LinearRegression 35923.393167 45787.246510 0.415782

Переписал не много код, стало чуть лучше, но не намного. Думаю для моей первой работы подойдет

Приступим к задаче классификации

In [58]:
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score, confusion_matrix, f1_score

# Удалить строки с пропусками в experience_level
df = df.dropna(subset=['experience_level'])

# Пересоздать y_class
y_class = df['experience_level'].map({'EN': 0, 'MI': 1, 'SE': 2, 'EX': 3})

# Повторное разделение данных
X_train_clf, X_test_clf, y_train_clf, y_test_clf = train_test_split(X, y_class, test_size=0.2, random_state=42)

# Определение моделей и их гиперпараметров
models_classification = {
    "LogisticRegression": LogisticRegression(max_iter=1000),
    "RandomForestClassifier": RandomForestClassifier(random_state=42),
    "KNN": KNeighborsClassifier()
}

param_grids_classification = {
    "LogisticRegression": {
        'model__C': [0.1, 1, 10]
    },
    "RandomForestClassifier": {
        "model__n_estimators": [100, 200, 300],
        "model__max_features": ["sqrt", "log2", None],
        "model__max_depth": [5, 10, 15, None],
        "model__criterion": ["gini", "entropy"]
    },
    "KNN": {
        'model__n_neighbors': [3, 5, 7, 9],
        'model__weights': ['uniform', 'distance']
    }
}

# Результаты
results_classification = {}

# Перебор моделей
for name, model in models_classification.items():
    print(f"Training {name}...")
    pipeline = Pipeline(steps=[
        ('preprocessor', preprocessor),
        ('model', model)
    ])
    param_grid = param_grids_classification[name]
    grid_search = RandomizedSearchCV(pipeline, param_distributions=param_grid, cv=5, scoring='f1_macro', n_jobs=-1, random_state=42)
    grid_search.fit(X_train_clf, y_train_clf)

    # Лучшая модель
    best_model = grid_search.best_estimator_
    y_pred = best_model.predict(X_test_clf)

    # Метрики
    acc = accuracy_score(y_test_clf, y_pred)
    f1 = f1_score(y_test_clf, y_pred, average='macro')

    # Вычисление матрицы ошибок
    c_matrix = confusion_matrix(y_test_clf, y_pred)

    # Сохранение результатов
    results_classification[name] = {
        "Best Params": grid_search.best_params_,
        "Accuracy": acc,
        "F1 Score": f1,
        "Confusion_matrix": c_matrix
    }

# Печать результатов
for name, metrics in results_classification.items():
    print(f"\nModel: {name}")
    for metric, value in metrics.items():
        print(f"{metric}: {value}")
Training LogisticRegression...
c:\Users\salih\OneDrive\Рабочий стол\3 курас\МИИ\laba1\AIM-PIbd-31-Yaruskin-S-A\aimenv\Lib\site-packages\sklearn\model_selection\_search.py:320: UserWarning: The total space of parameters 3 is smaller than n_iter=10. Running 3 iterations. For exhaustive searches, use GridSearchCV.
  warnings.warn(
Training RandomForestClassifier...
Training KNN...
c:\Users\salih\OneDrive\Рабочий стол\3 курас\МИИ\laba1\AIM-PIbd-31-Yaruskin-S-A\aimenv\Lib\site-packages\sklearn\model_selection\_search.py:320: UserWarning: The total space of parameters 8 is smaller than n_iter=10. Running 8 iterations. For exhaustive searches, use GridSearchCV.
  warnings.warn(
Model: LogisticRegression
Best Params: {'model__C': 0.1}
Accuracy: 1.0
F1 Score: 1.0
Confusion_matrix: [[ 50   0   0   0]
 [  0 129   0   0]
 [  0   0 316   0]
 [  0   0   0  13]]

Model: RandomForestClassifier
Best Params: {'model__n_estimators': 300, 'model__max_features': None, 'model__max_depth': 15, 'model__criterion': 'entropy'}
Accuracy: 1.0
F1 Score: 1.0
Confusion_matrix: [[ 50   0   0   0]
 [  0 129   0   0]
 [  0   0 316   0]
 [  0   0   0  13]]

Model: KNN
Best Params: {'model__weights': 'distance', 'model__n_neighbors': 9}
Accuracy: 0.9940944881889764
F1 Score: 0.9641274132899506
Confusion_matrix: [[ 50   0   0   0]
 [  0 129   0   0]
 [  0   0 316   0]
 [  1   0   2  10]]

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

Нарисуем матрицу ошибок

In [59]:
from sklearn.metrics import ConfusionMatrixDisplay

# Визуализация матриц ошибок
num_models = len(results_classification)
num_rows = (num_models // 2) + (num_models % 2)  # Количество строк для подграфиков
fig, ax = plt.subplots(num_rows, 2, figsize=(12, 10), sharex=False, sharey=False)

for index, (name, metrics) in enumerate(results_classification.items()):
    c_matrix = metrics["Confusion_matrix"]
    disp = ConfusionMatrixDisplay(
        confusion_matrix=c_matrix, display_labels=['EN', 'MI', 'SE', 'EX']
    ).plot(ax=ax.flat[index])
    disp.ax_.set_title(name)

# Корректировка расположения графиков
plt.subplots_adjust(top=0.9, bottom=0.1, hspace=0.4, wspace=0.3)
plt.show()
No description has been provided for this image

Формируем таблицу метрик классификации

In [60]:
# Формируем таблицу метрик классификации
clf_metrics = pd.DataFrame.from_dict(results_classification, orient="index")[["Accuracy", "F1 Score"]]

# Визуализация результатов с помощью стилизации
styled_metrics_clf = (
    clf_metrics.sort_values(by="F1 Score", ascending=False)  # Сортировка по F1 Score
    .style.background_gradient(cmap="viridis", low=0, high=1, subset=["F1 Score", "Accuracy"])  # Стилизация столбцов
    .background_gradient(cmap="plasma", low=0.3, high=1, subset=["Accuracy"])
)

styled_metrics_clf
Out[60]:
  Accuracy F1 Score
LogisticRegression 1.000000 1.000000
RandomForestClassifier 1.000000 1.000000
KNN 0.994094 0.964127

В итоге RandomForestClassifier и LogisticRegression выдали точность в 100% что я считаю это очень хорошо, но я не уверен что это правда. KNN очень близко.