142 KiB
Лабораторная 3¶
Датасет: Информация об онлайн обучении учеников
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
import featuretools as ft
from imblearn.over_sampling import RandomOverSampler
from sklearn.model_selection import train_test_split
import time
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
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)
from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error
import math
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()