Загрузка данных
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from itertools import product
from IPython.display import display
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 OneHotEncoder
from sklearn.tree import (
DecisionTreeClassifier,
DecisionTreeRegressor,
plot_tree,
)
from sklearn.metrics import (
accuracy_score, precision_score, recall_score, f1_score,
classification_report, ConfusionMatrixDisplay,
mean_absolute_error, mean_squared_error, r2_score,
)
sns.set_theme(style="whitegrid")
pd.set_option("display.max_columns", 100)
pd.set_option("display.float_format", lambda v: f"{v:.4f}")
# ============================================================
# 0. Загрузка датасетов
# ============================================================
USE_SAVED_FILES = False # True, если есть CSV из ЛР №2
from sklearn.datasets import fetch_openml
def load_dataset(name, version="active", fallback_names=None):
"""Пытается загрузить датасет по имени, при ошибке перебирает fallback."""
candidates = [name] + (fallback_names or [])
last_err = None
for cand in candidates:
try:
print(f"Пробуем загрузить '{cand}'...")
data = fetch_openml(name=cand, version=version, as_frame=True, parser="auto")
df = data.frame.copy()
target_columns = list(data.target_names)
X = df.drop(columns=target_columns)
y = df[target_columns[0]] if len(target_columns) == 1 else df[target_columns]
print(f" OK: {cand}, shape={df.shape}, target={target_columns}")
return X, y, df, target_columns
except Exception as e:
print(f" Не удалось: {cand} -> {type(e).__name__}: {e}")
last_err = e
raise RuntimeError(f"Не удалось загрузить ни один из {candidates}") from last_err
# --- Регрессия: Bike Sharing ---
if USE_SAVED_FILES:
reg_df = pd.read_csv("regression_tree_dataset.csv")
REG_TARGET = "cnt" # замените на имя целевой переменной из ЛР №2
X_reg = reg_df.drop(columns=[REG_TARGET])
y_reg = reg_df[REG_TARGET].copy()
else:
X_reg, y_reg, reg_df, reg_target_cols = load_dataset(
"bike-sharing",
fallback_names=["Bike_Sharing_Demand", "bike_sharing", "Bike-Sharing-Demand"],
)
REG_TARGET = reg_target_cols[0]
# Целевая переменная — количество арендованных велосипедов.
# В Bike Sharing обычно есть cnt (общее), casual + registered.
# Оставляем cnt как target; casual/registered убираем из X, чтобы не было утечки.
for leak in ["casual", "registered", "cnt", "count"]:
if leak in X_reg.columns and leak != REG_TARGET:
X_reg = X_reg.drop(columns=[leak])
# Если target называется иначе, попробуем найти cnt
if REG_TARGET not in y_reg.name and "cnt" in reg_df.columns:
REG_TARGET = "cnt"
y_reg = reg_df[REG_TARGET].copy()
X_reg = reg_df.drop(columns=[c for c in ["cnt", "casual", "registered"] if c in reg_df.columns])
# --- Классификация: ILPD ---
if USE_SAVED_FILES:
cls_df = pd.read_csv("classification_tree_dataset.csv")
CLS_TARGET = "target" # замените на имя целевой переменной из ЛР №2
X_cls = cls_df.drop(columns=[CLS_TARGET])
y_cls = cls_df[CLS_TARGET].copy()
else:
X_cls, y_cls, cls_df, cls_target_cols = load_dataset(
"ilpd",
fallback_names=["ILPD", "Indian_Liver_Patient_Dataset", "liver-disorders", "liver_disorders"],
)
CLS_TARGET = cls_target_cols[0]
print("\n=== Регрессия: размер ===", reg_df.shape if 'reg_df' in dir() else X_reg.shape)
print("=== Классификация: размер ===", cls_df.shape if 'cls_df' in dir() else X_cls.shape)
# ============================================================
# ЧАСТЬ I. КЛАССИФИКАЦИЯ (ILPD)
# ============================================================
print("\n" + "="*60)
print("ЧАСТЬ I. КЛАССИФИКАЦИЯ — ILPD")
print("="*60)
CLS_DROP_COLUMNS = [] # при необходимости добавьте id-подобные столбцы
# Убираем строки без target
classification_df = pd.concat([X_cls, y_cls.rename(CLS_TARGET)], axis=1) \
if CLS_TARGET not in X_cls.columns else cls_df.copy()
classification_df = classification_df.dropna(subset=[CLS_TARGET]).copy()
print("Размер датасета:", classification_df.shape)
display(classification_df.head())
display(classification_df.dtypes.rename("dtype").to_frame())
missing_report = pd.DataFrame({
"missing_count": classification_df.isna().sum(),
"missing_percent": (classification_df.isna().mean() * 100).round(2),
})
display(missing_report.sort_values("missing_count", ascending=False))
print("\nРаспределение классов:")
display(classification_df[CLS_TARGET].value_counts(dropna=False))
display(
classification_df[CLS_TARGET]
.value_counts(normalize=True, dropna=False)
.mul(100).round(2)
)
plt.figure(figsize=(8, 4))
sns.countplot(data=classification_df, x=CLS_TARGET)
plt.title("Распределение классов (ILPD)")
plt.xlabel("Класс"); plt.ylabel("Количество объектов")
plt.tight_layout(); plt.show()
# --- X, y ---
X_cls = classification_df.drop(
columns=[CLS_TARGET, *CLS_DROP_COLUMNS], errors="ignore"
)
y_cls = classification_df[CLS_TARGET].copy()
numeric_features_cls = X_cls.select_dtypes(include=np.number).columns.tolist()
categorical_features_cls = X_cls.select_dtypes(
include=["object", "category", "bool"]
).columns.tolist()
print("Числовые признаки:", numeric_features_cls)
print("Категориальные признаки:", categorical_features_cls)
# --- Train / Val / Test ---
X_train_val_cls, X_test_cls, y_train_val_cls, y_test_cls = train_test_split(
X_cls, y_cls, test_size=0.20, random_state=42, stratify=y_cls
)
X_train_cls, X_val_cls, y_train_cls, y_val_cls = train_test_split(
X_train_val_cls, y_train_val_cls, test_size=0.25, random_state=42,
stratify=y_train_val_cls
)
print("Train:", X_train_cls.shape)
print("Validation:", X_val_cls.shape)
print("Test:", X_test_cls.shape)
# --- Препроцессор ---
numeric_pipeline_cls = Pipeline([
("imputer", SimpleImputer(strategy="median")),
])
categorical_pipeline_cls = Pipeline([
("imputer", SimpleImputer(strategy="most_frequent")),
("encoder", OneHotEncoder(handle_unknown="ignore", sparse_output=False)),
])
preprocessor_cls = ColumnTransformer([
("numeric", numeric_pipeline_cls, numeric_features_cls),
("categorical", categorical_pipeline_cls, categorical_features_cls),
], verbose_feature_names_out=False)
X_train_cls_processed = preprocessor_cls.fit_transform(X_train_cls)
X_val_cls_processed = preprocessor_cls.transform(X_val_cls)
X_test_cls_processed = preprocessor_cls.transform(X_test_cls)
feature_names_cls = preprocessor_cls.get_feature_names_out()
print("Признаков после обработки:", len(feature_names_cls))
# --- Базовое дерево ---
base_classifier = DecisionTreeClassifier(criterion="entropy", random_state=42)
base_classifier.fit(X_train_cls_processed, y_train_cls)
base_val_pred = base_classifier.predict(X_val_cls_processed)
print("Глубина:", base_classifier.get_depth())
print("Листьев:", base_classifier.get_n_leaves())
print("Узлов:", base_classifier.tree_.node_count)
print("\nМетрики на Validation:")
print("Accuracy:", round(accuracy_score(y_val_cls, base_val_pred), 4))
print("Precision:", round(precision_score(y_val_cls, base_val_pred, average="weighted", zero_division=0), 4))
print("Recall:", round(recall_score(y_val_cls, base_val_pred, average="weighted", zero_division=0), 4))
print("F1:", round(f1_score(y_val_cls, base_val_pred, average="weighted", zero_division=0), 4))
print("\n", classification_report(y_val_cls, base_val_pred, zero_division=0))
ConfusionMatrixDisplay.from_predictions(y_val_cls, base_val_pred)
plt.title("Матрица ошибок (базовое дерево, ILPD)")
plt.tight_layout(); plt.show()
# --- Train vs Val ---
train_pred = base_classifier.predict(X_train_cls_processed)
train_accuracy = accuracy_score(y_train_cls, train_pred)
val_accuracy = accuracy_score(y_val_cls, base_val_pred)
print("Accuracy Train:", round(train_accuracy, 4))
print("Accuracy Validation:", round(val_accuracy, 4))
# --- Визуализация ---
plt.figure(figsize=(26, 14))
plot_tree(
base_classifier,
feature_names=feature_names_cls,
class_names=[str(c) for c in base_classifier.classes_],
filled=True, rounded=True, max_depth=3, fontsize=8,
)
plt.title("Базовое дерево классификации (ILPD, первые 3 уровня)")
plt.savefig("classification_tree_base.png", dpi=150, bbox_inches="tight")
plt.show()
# --- Перебор гиперпараметров ---
max_depth_values = [1, 2, 3, 4, 5, 7, 10, None]
min_leaf_values = [1, 2, 5, 10, 20]
min_split_values = [2, 5, 10, 20]
classification_results = []
for max_depth, min_leaf, min_split in product(
max_depth_values, min_leaf_values, min_split_values
):
model = DecisionTreeClassifier(
criterion="entropy",
max_depth=max_depth,
min_samples_leaf=min_leaf,
min_samples_split=min_split,
random_state=42,
)
model.fit(X_train_cls_processed, y_train_cls)
tr_pred = model.predict(X_train_cls_processed)
va_pred = model.predict(X_val_cls_processed)
classification_results.append({
"max_depth": max_depth,
"min_samples_leaf": min_leaf,
"min_samples_split": min_split,
"train_accuracy": accuracy_score(y_train_cls, tr_pred),
"val_accuracy": accuracy_score(y_val_cls, va_pred),
"train_f1": f1_score(y_train_cls, tr_pred, average="weighted", zero_division=0),
"val_f1": f1_score(y_val_cls, va_pred, average="weighted", zero_division=0),
"actual_depth": model.get_depth(),
"leaves": model.get_n_leaves(),
"nodes": model.tree_.node_count,
})
classification_results = pd.DataFrame(classification_results)
display(classification_results.head())
classification_results_sorted = classification_results \
.sort_values("val_f1", ascending=False).reset_index(drop=True)
print("\nТОП-10 по val_f1:")
display(classification_results_sorted.head(10))
print("ХУДШИЕ-10 по val_f1:")
display(classification_results_sorted.tail(10))
classification_results["overfit_gap"] = (
classification_results["train_f1"] - classification_results["val_f1"]
)
print("\nНаибольший разрыв train-val (переобучение):")
display(
classification_results
.sort_values("overfit_gap", ascending=False)
.head(10)
)
# --- Влияние глубины ---
depth_summary_cls = (
classification_results
.groupby("actual_depth", as_index=False)
.agg(train_f1=("train_f1", "mean"), val_f1=("val_f1", "mean"))
)
plt.figure(figsize=(9, 5))
plt.plot(depth_summary_cls["actual_depth"], depth_summary_cls["train_f1"], marker="o", label="Train")
plt.plot(depth_summary_cls["actual_depth"], depth_summary_cls["val_f1"], marker="o", label="Validation")
plt.xlabel("Фактическая глубина дерева"); plt.ylabel("Средний F1-score")
plt.title("Качество в зависимости от глубины (ILPD)")
plt.legend(); plt.tight_layout(); plt.show()
# --- Лучшее и худшее дерево ---
def restore_max_depth(v):
return None if pd.isna(v) else int(v)
best_cls_row = classification_results.sort_values("val_f1", ascending=False).iloc[0]
worst_cls_row = classification_results.sort_values("val_f1", ascending=True).iloc[0]
print("\nЛУЧШЕЕ дерево (ILPD):")
display(best_cls_row.to_frame("value"))
print("НАИХУДШЕЕ дерево (ILPD):")
display(worst_cls_row.to_frame("value"))
best_classifier = DecisionTreeClassifier(
criterion="entropy",
max_depth=restore_max_depth(best_cls_row["max_depth"]),
min_samples_leaf=int(best_cls_row["min_samples_leaf"]),
min_samples_split=int(best_cls_row["min_samples_split"]),
random_state=42,
)
best_classifier.fit(X_train_cls_processed, y_train_cls)
worst_classifier = DecisionTreeClassifier(
criterion="entropy",
max_depth=restore_max_depth(worst_cls_row["max_depth"]),
min_samples_leaf=int(worst_cls_row["min_samples_leaf"]),
min_samples_split=int(worst_cls_row["min_samples_split"]),
random_state=42,
)
worst_classifier.fit(X_train_cls_processed, y_train_cls)
best_test_pred = best_classifier.predict(X_test_cls_processed)
worst_test_pred = worst_classifier.predict(X_test_cls_processed)
final_cls_comparison = pd.DataFrame([
{
"model": "Лучшее дерево",
"accuracy": accuracy_score(y_test_cls, best_test_pred),
"f1": f1_score(y_test_cls, best_test_pred, average="weighted", zero_division=0),
"depth": best_classifier.get_depth(),
"leaves": best_classifier.get_n_leaves(),
},
{
"model": "Наихудшее дерево",
"accuracy": accuracy_score(y_test_cls, worst_test_pred),
"f1": f1_score(y_test_cls, worst_test_pred, average="weighted", zero_division=0),
"depth": worst_classifier.get_depth(),
"leaves": worst_classifier.get_n_leaves(),
},
])
print("\nСравнение на TEST (ILPD):")
display(final_cls_comparison)
# Визуализация лучшего/худшего
for name, model, fname in [
("Лучшее дерево классификации", best_classifier, "classification_tree_best.png"),
("Наихудшее дерево классификации", worst_classifier, "classification_tree_worst.png"),
]:
plt.figure(figsize=(28, 15))
plot_tree(
model, feature_names=feature_names_cls,
class_names=[str(c) for c in model.classes_],
filled=True, rounded=True, max_depth=4, fontsize=8,
)
plt.title(f"{name} (ILPD, первые 4 уровня)")
plt.savefig(fname, dpi=150, bbox_inches="tight")
plt.show()
# ============================================================
# ЧАСТЬ II. РЕГРЕССИЯ (Bike Sharing)
# ============================================================
print("\n" + "="*60)
print("ЧАСТЬ II. РЕГРЕССИЯ — Bike Sharing")
print("="*60)
REGRESSION_DROP_COLUMNS = [] # при необходимости
if USE_SAVED_FILES:
regression_df = pd.read_csv("regression_tree_dataset.csv")
else:
regression_df = pd.concat([X_reg, y_reg.rename(REG_TARGET)], axis=1) \
if REG_TARGET not in X_reg.columns else reg_df.copy()
regression_df = regression_df.dropna(subset=[REG_TARGET]).copy()
print("Размер датасета:", regression_df.shape)
display(regression_df.head())
display(regression_df.dtypes.rename("dtype").to_frame())
reg_missing_report = pd.DataFrame({
"missing_count": regression_df.isna().sum(),
"missing_percent": (regression_df.isna().mean() * 100).round(2),
})
display(reg_missing_report.sort_values("missing_count", ascending=False))
print("\nОписание target:")
display(regression_df[REG_TARGET].describe())
plt.figure(figsize=(9, 5))
sns.histplot(data=regression_df, x=REG_TARGET, kde=True)
plt.title("Распределение целевой переменной (Bike Sharing)")
plt.tight_layout(); plt.show()
# --- X, y ---
X_reg = regression_df.drop(columns=[REG_TARGET, *REGRESSION_DROP_COLUMNS], errors="ignore")
y_reg = regression_df[REG_TARGET].copy()
numeric_features_reg = X_reg.select_dtypes(include=np.number).columns.tolist()
categorical_features_reg = X_reg.select_dtypes(
include=["object", "category", "bool"]
).columns.tolist()
print("Числовые признаки:", numeric_features_reg)
print("Категориальные признаки:", categorical_features_reg)
# --- Train / Val / Test ---
X_train_val_reg, X_test_reg, y_train_val_reg, y_test_reg = train_test_split(
X_reg, y_reg, test_size=0.20, random_state=42
)
X_train_reg, X_val_reg, y_train_reg, y_val_reg = train_test_split(
X_train_val_reg, y_train_val_reg, test_size=0.25, random_state=42
)
print("Train:", X_train_reg.shape)
print("Validation:", X_val_reg.shape)
print("Test:", X_test_reg.shape)
# --- Препроцессор ---
numeric_pipeline_reg = Pipeline([
("imputer", SimpleImputer(strategy="median")),
])
categorical_pipeline_reg = Pipeline([
("imputer", SimpleImputer(strategy="most_frequent")),
("encoder", OneHotEncoder(handle_unknown="ignore", sparse_output=False)),
])
preprocessor_reg = ColumnTransformer([
("numeric", numeric_pipeline_reg, numeric_features_reg),
("categorical", categorical_pipeline_reg, categorical_features_reg),
], verbose_feature_names_out=False)
X_train_reg_processed = preprocessor_reg.fit_transform(X_train_reg)
X_val_reg_processed = preprocessor_reg.transform(X_val_reg)
X_test_reg_processed = preprocessor_reg.transform(X_test_reg)
feature_names_reg = preprocessor_reg.get_feature_names_out()
# --- Базовое дерево ---
base_regressor = DecisionTreeRegressor(criterion="squared_error", random_state=42)
base_regressor.fit(X_train_reg_processed, y_train_reg)
base_val_pred_reg = base_regressor.predict(X_val_reg_processed)
print("Глубина:", base_regressor.get_depth())
print("Листьев:", base_regressor.get_n_leaves())
base_reg_metrics = {
"MAE": mean_absolute_error(y_val_reg, base_val_pred_reg),
"RMSE": np.sqrt(mean_squared_error(y_val_reg, base_val_pred_reg)),
"R2": r2_score(y_val_reg, base_val_pred_reg),
}
print("\nМетрики базового дерева (Validation):")
display(pd.DataFrame([base_reg_metrics]))
train_pred_reg = base_regressor.predict(X_train_reg_processed)
train_rmse = np.sqrt(mean_squared_error(y_train_reg, train_pred_reg))
val_rmse = np.sqrt(mean_squared_error(y_val_reg, base_val_pred_reg))
print("Train RMSE:", round(train_rmse, 4))
print("Validation RMSE:", round(val_rmse, 4))
plt.figure(figsize=(7, 7))
plt.scatter(y_val_reg, base_val_pred_reg, alpha=0.6)
mn = min(y_val_reg.min(), base_val_pred_reg.min())
mx = max(y_val_reg.max(), base_val_pred_reg.max())
plt.plot([mn, mx], [mn, mx], linestyle="--")
plt.xlabel("Фактическое"); plt.ylabel("Предсказанное")
plt.title("Фактические и предсказанные (Bike Sharing)")
plt.tight_layout(); plt.show()
# --- Визуализация базового дерева регрессии ---
plt.figure(figsize=(28, 15))
plot_tree(
base_regressor, feature_names=feature_names_reg,
filled=True, rounded=True, max_depth=3, fontsize=8,
)
plt.title("Базовое дерево регрессии (Bike Sharing, первые 3 уровня)")
plt.savefig("regression_tree_base.png", dpi=150, bbox_inches="tight")
plt.show()
# --- Перебор гиперпараметров ---
regression_results = []
for max_depth, min_leaf, min_split in product(
max_depth_values, min_leaf_values, min_split_values
):
model = DecisionTreeRegressor(
criterion="squared_error",
max_depth=max_depth,
min_samples_leaf=min_leaf,
min_samples_split=min_split,
random_state=42,
)
model.fit(X_train_reg_processed, y_train_reg)
tr_pred = model.predict(X_train_reg_processed)
va_pred = model.predict(X_val_reg_processed)
regression_results.append({
"max_depth": max_depth,
"min_samples_leaf": min_leaf,
"min_samples_split": min_split,
"train_rmse": np.sqrt(mean_squared_error(y_train_reg, tr_pred)),
"val_rmse": np.sqrt(mean_squared_error(y_val_reg, va_pred)),
"val_mae": mean_absolute_error(y_val_reg, va_pred),
"val_r2": r2_score(y_val_reg, va_pred),
"actual_depth": model.get_depth(),
"leaves": model.get_n_leaves(),
"nodes": model.tree_.node_count,
})
regression_results = pd.DataFrame(regression_results)
display(regression_results.head())
regression_results_sorted = regression_results \
.sort_values("val_rmse", ascending=True).reset_index(drop=True)
print("\nТОП-10 по val_rmse:")
display(regression_results_sorted.head(10))
print("ХУДШИЕ-10 по val_rmse:")
display(regression_results_sorted.tail(10))
regression_results["overfit_gap"] = (
regression_results["val_rmse"] - regression_results["train_rmse"]
)
print("\nНаибольший разрыв train-val RMSE (переобучение):")
display(
regression_results
.sort_values("overfit_gap", ascending=False)
.head(10)
)
depth_summary_reg = (
regression_results
.groupby("actual_depth", as_index=False)
.agg(train_rmse=("train_rmse", "mean"), val_rmse=("val_rmse", "mean"))
)
plt.figure(figsize=(9, 5))
plt.plot(depth_summary_reg["actual_depth"], depth_summary_reg["train_rmse"], marker="o", label="Train")
plt.plot(depth_summary_reg["actual_depth"], depth_summary_reg["val_rmse"], marker="o", label="Validation")
plt.xlabel("Фактическая глубина дерева"); plt.ylabel("Средний RMSE")
plt.title("Ошибка в зависимости от глубины (Bike Sharing)")
plt.legend(); plt.tight_layout(); plt.show()
# --- Лучшее и худшее дерево регрессии ---
best_reg_row = regression_results.sort_values("val_rmse", ascending=True).iloc[0]
worst_reg_row = regression_results.sort_values("val_rmse", ascending=False).iloc[0]
print("\nЛУЧШЕЕ дерево (Bike Sharing):")
display(best_reg_row.to_frame("value"))
print("НАИХУДШЕЕ дерево (Bike Sharing):")
display(worst_reg_row.to_frame("value"))
best_regressor = DecisionTreeRegressor(
criterion="squared_error",
max_depth=restore_max_depth(best_reg_row["max_depth"]),
min_samples_leaf=int(best_reg_row["min_samples_leaf"]),
min_samples_split=int(best_reg_row["min_samples_split"]),
random_state=42,
)
best_regressor.fit(X_train_reg_processed, y_train_reg)
worst_regressor = DecisionTreeRegressor(
criterion="squared_error",
max_depth=restore_max_depth(worst_reg_row["max_depth"]),
min_samples_leaf=int(worst_reg_row["min_samples_leaf"]),
min_samples_split=int(worst_reg_row["min_samples_split"]),
random_state=42,
)
worst_regressor.fit(X_train_reg_processed, y_train_reg)
best_test_pred_reg = best_regressor.predict(X_test_reg_processed)
worst_test_pred_reg = worst_regressor.predict(X_test_reg_processed)
final_reg_comparison = pd.DataFrame([
{
"model": "Лучшее дерево",
"MAE": mean_absolute_error(y_test_reg, best_test_pred_reg),
"RMSE": np.sqrt(mean_squared_error(y_test_reg, best_test_pred_reg)),
"R2": r2_score(y_test_reg, best_test_pred_reg),
"depth": best_regressor.get_depth(),
"leaves": best_regressor.get_n_leaves(),
},
{
"model": "Наихудшее дерево",
"MAE": mean_absolute_error(y_test_reg, worst_test_pred_reg),
"RMSE": np.sqrt(mean_squared_error(y_test_reg, worst_test_pred_reg)),
"R2": r2_score(y_test_reg, worst_test_pred_reg),
"depth": worst_regressor.get_depth(),
"leaves": worst_regressor.get_n_leaves(),
},
])
print("\nСравнение на TEST (Bike Sharing):")
display(final_reg_comparison)
for name, model, fname in [
("Лучшее дерево регрессии", best_regressor, "regression_tree_best.png"),
("Наихудшее дерево регрессии", worst_regressor, "regression_tree_worst.png"),
]:
plt.figure(figsize=(28, 15))
plot_tree(
model, feature_names=feature_names_reg,
filled=True, rounded=True, max_depth=4, fontsize=8,
)
plt.title(f"{name} (Bike Sharing, первые 4 уровня)")
plt.savefig(fname, dpi=150, bbox_inches="tight")
plt.show()