Data Science на автопилоте: 10 промтов, которые превращают Jupyter Notebook в конвейер от сырых данных до обученной модели
Большинство Data Science проектов умирает не из-за плохих алгоритмов, а из-за рутины. По оценкам, которые приводит Anaconda в отчёте «State of Data Science», дата-сайентисты тратят на очистку и подготовку данных больше времени, чем на моделирование — и это устойчивая тенденция уже несколько лет подряд. Pandas-цепочки, pd.merge с несовпадающими ключами, ValueError: could not convert string to float, забытый train_test_split(stratify=y), переобученный RandomForest с accuracy 0.99 на утечке данных — вот реальный рабочий день.
LLM здесь работают не как «заменитель аналитика», а как ускоритель рутины. Они отлично справляются с шаблонным кодом (boilerplate), переводом формулировки задачи в вызов sklearn, интерпретацией sklearn.metrics.classification_report и генерацией визуализаций. Там, где нужно понимать домен, ставить гипотезы и защищать результаты перед бизнесом — решает человек.
Ниже — 10 промтов, которые я реально использую в работе. Каждый — с описанием, примером и работающим кодом. Все библиотеки настоящие: pandas 2.x, scikit-learn 1.4+, matplotlib 3.8, seaborn 0.13, XGBoost 2.x, SHAP 0.45+.
1. EDA по одному описанию датасета
Задача: быстро получить первичную разведку без ручного перебора df.describe(), df.info() и .isna().
Промт:
Ты senior data scientist. Вот структура датасета: колонки и их типы (вставь вывод
df.dtypes). Сгенерируй Python-код для EDA: пропуски, распределения числовых признаков, cardinality категориальных, корреляционная матрица (только числовые, метод Spearman), поиск константных и почти константных колонок. Используй pandas 2.x и seaborn. Выведи отчёт в виде словаря с ключами: missing, distributions, correlations, constant_cols.
Пример применения:
import pandas as pd, seaborn as sns, numpy as np
def quick_eda(df: pd.DataFrame) -> dict:
report = {}
report["missing"] = df.isna().mean().sort_values(ascending=False)
num = df.select_dtypes(include=np.number)
report["correlations"] = num.corr(method="spearman")
nunique = df.nunique()
report["constant_cols"] = nunique[nunique <= 1].index.tolist()
report["near_constant"] = [
c for c in df.columns
if df[c].value_counts(normalize=True, dropna=False).iloc[0] > 0.99
]
return report
Почему работает: LLM знает, что spearman устойчивее к выбросам и нелинейностям, чем pearson, и не забудет про near-constant признаки — типичную причину бесполезных фичей в моделях.
2. Умная обработка пропусков с обоснованием стратегии
Задача: не заливать всё средним «на автомате», а выбирать стратегию по типу признака и механизму пропусков.
Промт:
Для каждой колонки датасета предложи стратегию обработки пропусков: удаление, mean/median, mode, KNN-imputer или индикатор пропуска. Обоснуй выбор типом признака и долей пропусков. Учти разницу между MCAR, MAR и MNAR. Выдай код с sklearn
SimpleImputerиKNNImputer, и отдельно — колонки, которые стоит просто дропнуть.
Пример:
from sklearn.impute import SimpleImputer, KNNImputer
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
num_cols = ["age", "income", "tenure"]
cat_cols = ["city", "plan"]
num_pipe = Pipeline([("imp", SimpleImputer(strategy="median", add_indicator=True))])
cat_pipe = Pipeline([("imp", SimpleImputer(strategy="most_frequent"))])
pre = ColumnTransformer([
("num", num_pipe, num_cols),
("cat", cat_pipe, cat_cols),
])
add_indicator=True создаёт бинарную колонку «был ли пропуск» — это ловит MNAR-паттерн, когда сам факт отсутствия значения информативен (например, клиент не указал доход, потому что он нулевой).
3. Feature engineering из сырых колонок
Задача: из signup_date и last_login получить признаки, которые реально влияют на отток.
Промт:
У меня есть колонки created_at, last_activity_at, amount, category. Сгенерируй feature engineering: recency (дней с последней активности), tenure (дней с регистрации), частотные признаки по category, агрегаты amount за 7/30/90 дней. Избегай target leakage: не используй данные после даты предсказания. Верни функцию, принимающую DataFrame и возвращающую DataFrame с новыми фичами.
Пример:
def build_features(df: pd.DataFrame, ref_date: pd.Timestamp) -> pd.DataFrame:
out = df.copy()
out["tenure_days"] = (ref_date - out["created_at"]).dt.days
out["recency_days"] = (ref_date - out["last_activity_at"]).dt.days
for w in (7, 30, 90):
mask = (ref_date - out["created_at"]).dt.days <= w
out[f"amount_{w}d"] = out["amount"].where(mask, 0)
return out
Ключевой момент: параметр ref_date вместо pd.Timestamp.now() — это защита от data leakage. При обучении ref_date берётся из train-периода, при инференсе — из момента предсказания.
4. Baseline-модель за один промт
Задача: получить честный baseline до того, как вы начнёте тюнить гиперпараметры.
Промт:
Задача — бинарная классификация. Целевая колонка: churn. Сгенерируй baseline-пайплайн: train_test_split со stratify и random_state=42, ColumnTransformer (StandardScaler для числовых, OneHotEncoder(handle_unknown="ignore") для категориальных), две модели — LogisticRegression и RandomForestClassifier. Выведи classification_report, confusion_matrix и ROC-AUC. Сравни и скажи, какая модель — baseline.
Пример:
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, roc_auc_score
X_tr, X_te, y_tr, y_te = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42
)
for name, clf in [
("logreg", LogisticRegression(max_iter=1000, class_weight="balanced")),
("rf", RandomForestClassifier(n_estimators=400, n_jobs=-1, random_state=42)),
]:
pipe = Pipeline([("pre", pre), ("clf", clf)])
pipe.fit(X_tr, y_tr)
proba = pipe.predict_proba(X_te)[:, 1]
print(name, "ROC-AUC:", round(roc_auc_score(y_te, proba), 4))
print(classification_report(y_te, pipe.predict(X_te)))
class_weight="balanced" важен, если классы несбалансированы — иначе accuracy будет вводить в заблуждение, а модель просто выучит majority class.
5. Диагностика переобучения и утечек
Задача: понять, почему на train метрика 0.99, а на test — 0.72.
Промт:
У меня ROC-AUC на train = 0.99, на test = 0.72. Дай чек-лист диагностики: 1) признаки с утечкой (корреляция с таргетом > 0.9), 2) временная утечка (фичи, вычисленные после target), 3) дубликаты строк между train и test, 4) слишком сложная модель. Выдай код, который проверяет каждый пункт.
Пример проверки утечечной корреляции:
corrs = X_tr.select_dtypes(include="number").corrwith(y_tr).abs().sort_values(ascending=False)
print(corrs.head(10))
# поиск дублей между train и test по ключевым фичам
key_cols = ["user_id", "created_at"]
leak = set(map(tuple, X_tr[key_cols].values)) & set(map(tuple, X_te[key_cols].values))
print("overlap rows:", len(leak))
На практике чаще всего виновата именно временная утечка: фича вида total_purchases содержит покупку, которая случилась после оттока. Лечится пересчётом фичей на дату среза.
6. Кросс-валидация и подбор гиперпараметров
Промт:
Подбери гиперпараметры для XGBoost с помощью Optuna. Задача — бинарная классификация с дисбалансом 1:20. Используй StratifiedKFold(n_splits=5, shuffle=True, random_state=42), оптимизируй ROC-AUC, 50 trials. Верни код с study.optimize и выводом best_params.
Пример:
import optuna
from sklearn.model_selection import StratifiedKFold, cross_val_score
from xgboost import XGBClassifier
def objective(trial):
params = {
"n_estimators": trial.suggest_int("n_estimators", 200, 800),
"max_depth": trial.suggest_int("max_depth", 3, 10),
"learning_rate": trial.suggest_float("learning_rate", 0.01, 0.3, log=True),
"subsample": trial.suggest_float("subsample", 0.6, 1.0),
"scale_pos_weight": 20,
"eval_metric": "auc",
"random_state": 42,
}
clf = XGBClassifier(**params)
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
return cross_val_score(clf, X_tr, y_tr, cv=cv, scoring="roc_auc", n_jobs=-1).mean()
study = optuna.create_study(direction="maximize")
study.optimize(objective, n_trials=50)
print(study.best_params)
Optuna использует TPE (Tree-structured Parzen Estimator) — байесовскую оптимизацию, которая сходится заметно быстрее grid search. Документация: optuna.readthedocs.io.
7. Интерпретация модели через SHAP
Задача: объяснить бизнесу, почему модель пометила клиента как уходящего.
Промт:
Построй SHAP-объяснение для обученной XGBoost-модели. Нужны: summary_plot (beeswarm), bar-plot важности, force_plot для одного конкретного клиента с index=42. Используй shap.TreeExplainer. Поясни, как читать каждый график.
Пример:
import shap
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_te)
shap.summary_plot(shap_values, X_te, plot_type="dot")
shap.plots.waterfall(shap.Explanation(
values=shap_values[42],
base_values=explainer.expected_value,
data=X_te.iloc[42],
feature_names=X_te.columns.tolist()
))
SHAP-значения аддитивны: вклад каждой фичи складывается с базовым значением (средним предсказанием). Красные точки — высокие значения фичи, синие — низкие. Сдвиг вправо = повышает вероятность оттока. Оригинальная статья: Lundberg & Lee, «A Unified Approach to Interpreting Model Predictions», NeurIPS 2017.
8. Визуализация: графики, которые не стыдно показать
Промт:
Сгенерируй matplotlib/seaborn-код для отчёта: 1) ROC-кривая с AUC в легенде, 2) precision-recall curve, 3) калибровочная кривая (reliability diagram), 4) матрица ошибок с нормализацией. Стиль — минималистичный, читаемые подписи, единая палитра. Сохрани в figsize (12, 8) через subplots.
Пример:
import matplotlib.pyplot as plt
from sklearn.metrics import RocCurveDisplay, PrecisionRecallDisplay, ConfusionMatrixDisplay
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
RocCurveDisplay.from_predictions(y_te, proba, ax=axes[0, 0])
PrecisionRecallDisplay.from_predictions(y_te, proba, ax=axes[0, 1])
ConfusionMatrixDisplay.from_predictions(y_te, pred, normalize="true", ax=axes[1, 0])
# калибровка
from sklearn.calibration import CalibrationDisplay
CalibrationDisplay.from_predictions(y_te, proba, n_bins=10, ax=axes[1, 1])
plt.tight_layout()
plt.savefig("report.png", dpi=150)
Калибровочная кривая — недооценённый график. Если модель выдаёт probability 0.8, а фактическая доля положительных в этом бине — 0.5, то калибровка сломана, и порог для бизнеса надо подбирать иначе.
9. Ревью ноутбука как код-ревью
Промт:
Проведи ревью моего ноутбука как senior DS. Найди: 1) data leakage, 2) фитинг препроцессоров на всём датасете вместо train, 3) отсутствие random_state, 4) магические числа в коде, 5) неинтерпретируемые названия переменных. Для каждого пункта — конкретная строка и как исправить.
Типичная находка:
# ПЛОХО: scaler обучен на всём X — утечка
scaler = StandardScaler().fit(X)
X_scaled = scaler.transform(X)
# ХОРОШО: scaler строго внутри пайплайна
pipe = Pipeline([("pre", pre), ("clf", clf)])
pipe.fit(X_tr, y_tr) # препроцессинг учится только на train
Это самая частая ошибка в student-ноутбуках: fit на полном датасете до train_test_split. Метрика завышается, а в продакшене модель разваливается.
10. Сохранение модели и воспроизводимость
Промт:
Сгенерируй код для сохранения обученного пайплайна через joblib, логирования метрик и параметров в JSON, фиксации версий библиотек. Добавь проверку входных данных при инференсе (набор и типы колонок).
Пример:
import joblib, json, sklearn
joblib.dump(pipe, "model_v1.joblib")
meta = {
"sklearn": sklearn.__version__,
"features": X_tr.columns.tolist(),
"target": "churn",
"roc_auc_test": float(roc_auc_score(y_te, pipe.predict_proba(X_te)[:, 1])),
}
with open("model_v1_meta.json", "w") as f:
json.dump(meta, f, indent=2)
def predict(df: pd.DataFrame):
missing = set(meta["features"]) - set(df.columns)
if missing:
raise ValueError(f"Missing features: {missing}")
return pipe.predict_proba(df[meta["features"]])[:, 1]
Что не стоит делегировать модели
LLM уверенно генерируют код, но плохо понимают контекст вашего бизнеса. Не отдавайте на откуп модели:
| Задача | Кому доверить |
|---|---|
| Выбор метрики (ROC-AUC vs PR-AUC vs бизнес-метрика) | Человек + согласование со стейкхолдером |
| Определение порога для продакшена | Человек (зависит от стоимости FP/FN) |
| Проверка причинно-следственных связей | Человек (LLM путает корреляцию с причинностью) |
| Написание boilerplate, пайплайнов, графиков | LLM |
| Чтение метрик, интерпретация SHAP | LLM + проверка человеком |
Отдельно про галлюцинации в коде: LLM иногда предлагает несуществующие параметры sklearn (например, class_weight там, где его нет, или устаревшие sklearn.cross_validation вместо sklearn.model_selection). Всегда проверяйте сигнатуры по официальной документации scikit-learn и запускайте код на маленьком сэмпле перед полным прогоном.
Итог
Промты не заменяют понимание статистики и домена — они снимают рутину между «понял задачу» и «получил baseline». Десять промтов выше покрывают 80% типового цикла: EDA, очистка, фичи, baseline, диагностика утечек, тюнинг, интерпретация, визуализация, ревью, деплой. Освоив их, вы будете тратить на механический код часы вместо дней, а время высвободится на то, что действительно требует экспертизы: постановку гипотез, дизайн эксперимента и разговор с бизнесом о цене ошибки.
Попробуйте начать с промтов №1 и №4 прямо сейчас — они дают самый быстрый эффект на любом новом датасете. А промты №5 и №9 держите под рукой: они спасают от публикации модели, которая красиво выглядит в ноутбуке и разваливается в продакшене.
Полезные источники:
- scikit-learn User Guide: https://scikit-learn.org/stable/user_guide.html
- pandas documentation: https://pandas.pydata.org/docs/
- XGBoost docs: https://xgboost.readthedocs.io/
- Optuna: https://optuna.readthedocs.io/
- SHAP: https://shap.readthedocs.io/
- Lundberg & Lee, NeurIPS 2017: https://papers.nips.cc/paper/2017/hash/8a20a8621978632d76c43dfd28b67767-Abstract.html
- Anaconda State of Data Science: https://www.anaconda.com/state-of-data-science
Comments