142 KiB
Лабораторная 3¶
Датасет: Информация об онлайн обучении учеников
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
import featuretools as ft
import time
import math
from imblearn.over_sampling import RandomOverSampler
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error
df = pd.read_csv("..\\static\\csv\\students_adaptability_level_online_education.csv")
print(df.columns)
Столбцы:
Education Level - уровень образования
Institution Type - тип учреждения
Gender - пол
Age - возраст
Device - устройство
IT Student - ученик IT направления или нет
Location - локация
Financial Condition - финансовое состояние
Internet Type - тип доступа к сети
Network Type - уровень сети
Flexibility Level - уровень приспособления
df.info()
df.head()
Примеры бизнес-целей для датасета:
- Улучшение доступа к онлайн-образованию для учеников с низким уровнем финансового обеспечения.
- Повышение удовлетворенности учеников онлайн-обучением на основе их устройств, типу соединения, местоположения.
Цели технического проекта:
- Провести анализ зависимости учеников от уровня интернет-соединения и устройств
- Провести анализ влияния различных факторов (тип устройства, интернет-соединение, финансовое положение) на уровень приспособленности.
Проверяем на выбросы.
null_values = df.isnull().sum()
print("Пустые значения по столбцам:")
print(null_values)
duplicates = df.duplicated().sum()
print(f"\nКоличество дубликатов: {duplicates}")
print("\nСтатистический обзор данных:")
df.describe()
for column in df.select_dtypes(include=[np.number]).columns:
skewness = df[column].skew()
print(f"\nКоэффициент асимметрии для столбца '{column}': {skewness}")
Выбросы незначительны, дубликаты есть. Удаляем дубликаты и очищаем от шумов.
cleaned_df = df.drop_duplicates()
Q1 = df["Age"].quantile(0.25)
Q3 = df["Age"].quantile(0.75)
IQR = Q3 - Q1
threshold = 1.5 * IQR
lower_bound = Q1 - threshold
upper_bound = Q3 + threshold
outliers = (df["Age"] < lower_bound) | (df["Age"] > upper_bound)
print("Шумы в датасете:")
print(df[outliers])
median_score = df["Age"].median()
df.loc[outliers, "Age"] = median_score
Преобразуем строковые значение в столбце "Уровень приспособления" в числовые значения. Это понадобится для расчёта качества набора признаков.
map_flexibility_to_int = {'Low': 0, 'Moderate': 1, 'High': 2}
df['Flexibility Level'] = df['Flexibility Level'].map(map_flexibility_to_int).astype('int32')
Шумов в датасете нет. Разбиваем датасет на три выборки: обучающую, контрольную и тестовую.
X = df.drop(columns=['Flexibility Level'])
Y = df['Flexibility Level']
X_train_df, X_test_df, Y_train_df, Y_test_df = train_test_split(X, Y, test_size=0.2, random_state=42)
X_train_df, X_val_df, Y_train_df, Y_val_df = train_test_split(X_train_df, Y_train_df, test_size=0.25, random_state=42)
print("Размер обучающей выборки:", X_train_df.shape)
print("Размер контрольной выборки:",X_val_df.shape)
print("Размер тестовой выборки:", X_test_df.shape)
Проверка сбалансированности данных.
def analyze_balance(y_train, y_val, y_test, y_name):
print("Распределение классов в обучающей выборке:")
print(y_train.value_counts(normalize=True))
print("\nРаспределение классов в контрольной выборке:")
print(y_val.value_counts(normalize=True))
print("\nРаспределение классов в тестовой выборке:")
print(y_test.value_counts(normalize=True))
fig, axes = plt.subplots(1, 3, figsize=(18, 5), sharey=True)
fig.suptitle('Распределение в различных выборках')
sns.barplot(x=y_train.value_counts().index, y=y_train.value_counts(normalize=True), ax=axes[0])
axes[0].set_title('Обучающая выборка')
axes[0].set_xlabel(y_name)
axes[0].set_ylabel('Доля')
sns.barplot(x=y_val.value_counts().index, y=y_val.value_counts(normalize=True), ax=axes[1])
axes[1].set_title('Контрольная выборка')
axes[1].set_xlabel(y_name)
sns.barplot(x=y_test.value_counts().index, y=y_test.value_counts(normalize=True), ax=axes[2])
axes[2].set_title('Тестовая выборка')
axes[2].set_xlabel(y_name)
plt.show()
analyze_balance(Y_train_df, Y_val_df, Y_test_df, 'Flexibility Level')
Выполним оверсемплинг для балансировки.
ros = RandomOverSampler(random_state=42)
X_train_resampled, Y_train_resampled = ros.fit_resample(X_train_df, Y_train_df)
X_val_resampled, Y_val_resampled = ros.fit_resample(X_val_df, Y_val_df)
analyze_balance(Y_train_resampled, Y_val_resampled, Y_test_df, 'Flexibility Level')
Конструирование признаков. Для начала применим унитарное кодирование категориальных признаков (one-hot encoding), переведя их в бинарные вектора.
cat_features = ['Education Level', 'Institution Type', 'Gender', 'Device', 'IT Student', 'Location', 'Financial Condition', 'Internet Type', 'Network Type']
train_encoded = pd.get_dummies(X_train_resampled, columns=cat_features, drop_first=True)
val_encoded = pd.get_dummies(X_val_resampled, columns=cat_features, drop_first=True)
test_encoded = pd.get_dummies(X_test_df, columns=cat_features, drop_first=True)
train_encoded.head()
Применим дискретизацию к числовым признакам.
num_features = ['Age']
def discretize_features(df, features, bins, labels):
for feature in features:
df[f'{feature}_Bin'] = pd.cut(df[feature], bins=bins, labels=labels)
df.drop(columns=[feature], inplace=True)
return df
age_bins = [0, 25, 55, 100]
age_labels = ["young", "middle-aged", "old"]
train_encoded = discretize_features(train_encoded, num_features, bins=age_bins, labels=age_labels)
val_encoded = discretize_features(val_encoded, num_features, bins=age_bins, labels=age_labels)
test_encoded = discretize_features(test_encoded, num_features, bins=age_bins, labels=age_labels)
train_encoded.head()
Применим ручной синтез признаков. К примеру, для этого датасета, сделаем признак "соотвествие устройства для обучения". Мобильные устройства часто менее удобны для учебы по сравнению с планшетами.
train_encoded['Device Suitability'] = train_encoded['Device_Tab'].apply(lambda x: "High" if x == True else "Low")
val_encoded['Device Suitability'] = val_encoded['Device_Tab'].apply(lambda x: "High" if x == True else "Low")
test_encoded['Device Suitability'] = test_encoded['Device_Tab'].apply(lambda x: "High" if x == True else "Low")
train_encoded.head()
Конструирование признаков с помощью фреймворка Featuretools.
ft_data = train_encoded.copy()
es = ft.EntitySet(id="students")
es = es.add_dataframe(dataframe_name="students_data", dataframe=ft_data, index="id", make_index=True)
feature_matrix, feature_defs = ft.dfs(
entityset=es,
target_dataframe_name="students_data",
max_depth=1
)
feature_matrix.head()
Featuretools не смог сделать новые признаки.
Оценка качества набора признаков.
train_encoded = pd.get_dummies(train_encoded, drop_first=True)
val_encoded = pd.get_dummies(val_encoded, drop_first=True)
test_encoded = pd.get_dummies(test_encoded, drop_first=True)
cols = train_encoded.columns
train_encoded = train_encoded.reindex(columns=cols, fill_value=0)
val_encoded = val_encoded.reindex(columns=cols, fill_value=0)
test_encoded = test_encoded.reindex(columns=cols, fill_value=0)
model = RandomForestClassifier(n_estimators=100, random_state=42)
start = time.time()
model.fit(train_encoded, Y_train_resampled)
train_time = time.time() - start
print(f'Время обучения модели: {train_time:.2f} секунд')
# Получение важности признаков
importances = model.feature_importances_
feature_names = train_encoded.columns
# Сортировка признаков по важности
feature_importance = pd.DataFrame({'feature': feature_names, 'importance': importances})
feature_importance = feature_importance.sort_values(by='importance', ascending=False)
print("Feature Importance:")
print(feature_importance)
y_pred = model.predict(test_encoded)
# Анализ важности признаков
feature_importances = model.feature_importances_
feature_names = train_encoded.columns
importance_df = pd.DataFrame({'Feature': feature_names, 'Importance': feature_importances})
importance_df = importance_df.sort_values(by='Importance', ascending=False)
rmse = mean_squared_error(Y_test_df, y_pred, squared=False)
r2 = r2_score(Y_test_df, y_pred)
mae = mean_absolute_error(Y_test_df, y_pred)
print()
print(f"RMSE: {rmse}")
print(f"R²: {r2}")
print(f"MAE: {mae} \n")
# Кросс-валидация
scores = cross_val_score(model, train_encoded, Y_train_resampled, cv=5, scoring='neg_mean_squared_error')
rmse_cv = math.sqrt((-scores.mean()))
print(f"Кросс-валидация RMSE: {rmse_cv} \n")
# Проверка на переобучение
y_train_pred = model.predict(train_encoded)
rmse_train = mean_squared_error(Y_train_resampled, y_train_pred, squared=False)
r2_train = r2_score(Y_train_resampled, y_train_pred)
mae_train = mean_absolute_error(Y_train_resampled, y_train_pred)
print(f"Train RMSE: {rmse_train}")
print(f"Train R²: {r2_train}")
print(f"Train MAE: {mae_train}")
print()