Введение
Кредитный скоринг играет ключевую роль в современной финансовой индустрии, позволяя банкам и кредитным организациям оценивать риск дефолта потенциальных заемщиков. Среди множества методов моделирования кредитного риска логистическая регрессия (логит-модель) остается одним из наиболее популярных инструментов благодаря своей интерпретируемости, прозрачности и достаточной точности.
В этой статье мы рассмотрим процесс построения логит-модели для кредитного скоринга с использованием Python, начиная от подготовки данных и заканчивая оценкой и интерпретацией результатов. Мы сфокусируемся на практических аспектах, которые важны для создания надежных и эффективных скоринговых моделей в реальных условиях, и подробно разберем весь рабочий процесс кредитного скоринга.
Ключевые концепции логистической модели кредитного скоринга
Логистическая регрессия (логит-модель)
Логистическая регрессия — это статистическая модель, широко используемая в кредитном скоринге для оценки вероятности дефолта заемщика. В основе модели лежит логистическая функция, которая связывает линейную комбинацию факторов риска (скоринговый балл) с вероятностью события (дефолта). Формально логит-модель задается уравнением:
$$\text{logit}(p_i) = \ln\left(\frac{p_i}{1-p_i}\right) = \beta_0 + \beta_1 x_{i1} + \beta_2 x_{i2} + \ldots + \beta_k x_{ik}$$
где $p_i$ — прогнозируемая вероятность дефолта для i-го клиента, $x_{ij}$ — значение j-го факторного признака (например, отношение долга к доходу, возраст счета, количество просрочек и т.д.), а $\beta_j$ — коэффициенты модели.
Обратная логистическая функция преобразует скоринговый балл $s_i = \beta_0 + \sum_j \beta_j x_{ij}$ в вероятность:
$$p_i = \frac{1}{1 + e^{-s_i}}$$
Иными словами, логит-модель ограничивает прогнозируемую вероятность значениями от 0 до 1, что идеально подходит для моделирования дефолта (событие/несобытие).
В Python логистическую регрессию можно легко реализовать с помощью библиотеки scikit-learn:
from sklearn.linear_model import LogisticRegression
# Создание и обучение модели
logit_model = LogisticRegression(penalty='none', max_iter=1000, random_state=42)
logit_model.fit(X_train, y_train)
# Получение вероятностей дефолта
y_prob = logit_model.predict_proba(X_test)[:, 1]
Комплексный рабочий процесс построения логит-модели
Предлагаемый код представляет собой полный конвейер для создания и оценки логит-модели кредитного скоринга. Рассмотрим подробно каждый этап этого процесса.
Функция логит-модельного рабочего процесса
Основная функция logit_model_workflow представляет собой полный цикл создания модели, от подготовки данных до оценки результатов. Давайте рассмотрим ее структуру и функциональность:
def logit_model_workflow(data, target_col, features=None, test_size=0.3, random_state=42):
"""
Полный рабочий процесс построения и оценки логит-модели для кредитного скоринга
Параметры:
-----------
data : pandas.DataFrame
Датафрейм с данными
target_col : str
Название столбца с целевой переменной (0/1)
features : list, default=None
Список признаков для использования (если None, используются все кроме целевой)
test_size : float, default=0.3
Доля тестовой выборки
random_state : int, default=42
Случайное зерно для воспроизводимости
"""
Эта функция принимает датафрейм с данными, название столбца с целевой переменной, опциональный список признаков, размер тестовой выборки и случайное зерно для воспроизводимости результатов.
Этап 1: Подготовка данных
На первом этапе выполняется подготовка данных для моделирования:
# 1. Подготовка данных
print("1. ПОДГОТОВКА ДАННЫХ")
print("-" * 50)
# Выбор признаков
if features is None:
features = [col for col in data.columns if col != target_col]
X = data[features]
y = data[target_col]
# Проверяем наличие пропущенных значений
missing_data = X.isnull().sum()
if missing_data.sum() > 0:
print("ОБНАРУЖЕНЫ ПРОПУЩЕННЫЕ ЗНАЧЕНИЯ:")
print(missing_data[missing_data > 0])
print("\nПропущенные значения необходимо обработать перед моделированием!")
return
# Разделение на обучающую и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=test_size, random_state=random_state, stratify=y
)
print(f"Размер обучающей выборки: {X_train.shape[0]} наблюдений")
print(f"Размер тестовой выборки: {X_test.shape[0]} наблюдений")
print(f"Доля положительного класса (общая): {y.mean():.2%}")
print(f"Доля положительного класса (обучающая): {y_train.mean():.2%}")
print(f"Доля положительного класса (тестовая): {y_test.mean():.2%}")
Здесь происходит:
- Выбор признаков для моделирования
- Проверка наличия пропущенных значений
- Разделение данных на обучающий и тестовый наборы с сохранением соотношения классов (stratify=y)
- Вывод основной информации о выборках
Ключевой момент: функция останавливается, если обнаружены пропущенные значения, поскольку они должны быть предварительно обработаны.
Этап 2: Обработка выбросов и преобразование признаков
На втором этапе происходит обработка выбросов и стандартизация признаков:
# 2. Обработка выбросов и преобразование признаков
print("\n2. ОБРАБОТКА ВЫБРОСОВ И ПРЕОБРАЗОВАНИЕ ПРИЗНАКОВ")
print("-" * 50)
# Обработка выбросов (усечение)
X_train_processed = winsorize_features(X_train, lower_quantile=0.01, upper_quantile=0.99)
X_test_processed = winsorize_features(X_test, lower_quantile=0.01, upper_quantile=0.99, reference_df=X_train)
# Стандартизация признаков
scaler = StandardScaler()
X_train_scaled = pd.DataFrame(
scaler.fit_transform(X_train_processed),
columns=X_train_processed.columns,
index=X_train_processed.index
)
X_test_scaled = pd.DataFrame(
scaler.transform(X_test_processed),
columns=X_test_processed.columns,
index=X_test_processed.index
)
print("Выбросы обработаны методом усечения (винсоризации)")
print("Признаки стандартизированы (среднее=0, стд=1)")
В этом разделе:
- Применяется функция winsorize_features для обработки выбросов методом усечения (винсоризации) – значения выше 99-го и ниже 1-го перцентиля заменяются на соответствующие перцентили
- Выполняется стандартизация признаков с помощью StandardScaler из scikit-learn
Важный момент: преобразования для тестовой выборки выполняются на основе параметров, полученных из обучающей выборки (reference_df=X_train), что предотвращает утечку данных
Функция winsorize_features определена отдельно:
def winsorize_features(df, lower_quantile=0.01, upper_quantile=0.99, reference_df=None):
"""
Обработка выбросов методом усечения (винсоризации)
Параметры:
-----------
df : pandas.DataFrame
Датафрейм для обработки
lower_quantile, upper_quantile : float
Нижний и верхний квантили для усечения
reference_df : pandas.DataFrame, default=None
Датафрейм для расчета квантилей (если None, используется df)
Возвращает:
-----------
pandas.DataFrame
Обработанный датафрейм
"""
df_processed = df.copy()
for col in df.columns:
if pd.api.types.is_numeric_dtype(df[col]):
if reference_df is not None:
# Используем квантили из reference_df
lower_val = reference_df[col].quantile(lower_quantile)
upper_val = reference_df[col].quantile(upper_quantile)
else:
# Рассчитываем квантили по текущему датафрейму
lower_val = df[col].quantile(lower_quantile)
upper_val = df[col].quantile(upper_quantile)
# Усечение
df_processed[col] = df[col].clip(lower_val, upper_val)
return df_processed
Эта функция выполняет винсоризацию значений, что является важным шагом для обеспечения устойчивости модели к выбросам.
Этап 3: Анализ мультиколлинеарности
Мультиколлинеарность может негативно влиять на качество и стабильность модели, поэтому на третьем этапе выполняется анализ корреляций между признаками:
# 3. Анализ мультиколлинеарности
print("\n3. АНАЛИЗ МУЛЬТИКОЛЛИНЕАРНОСТИ")
print("-" * 50)
# Расчет матрицы корреляций
corr_matrix = X_train_processed.corr()
# Нахождение сильно коррелированных пар
corr_threshold = 0.7
high_corr_pairs = []
for i in range(len(corr_matrix.columns)):
for j in range(i+1, len(corr_matrix.columns)):
if abs(corr_matrix.iloc[i, j]) > corr_threshold:
high_corr_pairs.append((
corr_matrix.columns[i],
corr_matrix.columns[j],
corr_matrix.iloc[i, j]
))
if high_corr_pairs:
print(f"Обнаружены сильно коррелированные признаки (|r| > {corr_threshold}):")
for feat1, feat2, corr in high_corr_pairs:
print(f" {feat1} и {feat2}: r = {corr:.4f}")
print("\nРекомендуется рассмотреть удаление одного из признаков в каждой паре")
else:
print(f"Сильно коррелированных признаков (|r| > {corr_threshold}) не обнаружено")
В этом разделе:
- Вычисляется матрица корреляций для обработанных признаков
- Определяется порог сильных корреляций (corr_threshold = 0.7)
- Выявляются пары признаков с корреляцией выше порога
- Выводится предупреждение, если такие пары обнаружены
Этот шаг позволяет выявить потенциальную мультиколлинеарность, которая может привести к нестабильности оценок коэффициентов и переобучению модели.
Этап 4: Построение модели с использованием scikit-learn
На четвертом этапе строится логистическая регрессия с использованием библиотеки scikit-learn и оцениваются основные метрики качества модели:
# 4. Построение модели с использованием sklearn
print("\n4. ПОСТРОЕНИЕ МОДЕЛИ С ИСПОЛЬЗОВАНИЕМ SKLEARN")
print("-" * 50)
# Обучение логистической регрессии
logit_model = LogisticRegression(penalty=None, max_iter=1000, random_state=random_state)
logit_model.fit(X_train_scaled, y_train)
# Прогнозы и вероятности
y_train_pred = logit_model.predict(X_train_scaled)
y_train_prob = logit_model.predict_proba(X_train_scaled)[:, 1]
y_test_pred = logit_model.predict(X_test_scaled)
y_test_prob = logit_model.predict_proba(X_test_scaled)[:, 1]
# Метрики качества
train_accuracy = logit_model.score(X_train_scaled, y_train)
test_accuracy = logit_model.score(X_test_scaled, y_test)
train_auc = roc_auc_score(y_train, y_train_prob)
test_auc = roc_auc_score(y_test, y_test_prob)
# Вывод метрик
print("Метрики на обучающей выборке:")
print(f" Accuracy: {train_accuracy:.4f}")
print(f" ROC AUC: {train_auc:.4f}")
print(f" Gini: {(2 * train_auc - 1):.4f}")
print("\nМетрики на тестовой выборке:")
print(f" Accuracy: {test_accuracy:.4f}")
print(f" ROC AUC: {test_auc:.4f}")
print(f" Gini: {(2 * test_auc - 1):.4f}")
В этом разделе:
- Обучается модель логистической регрессии без регуляризации (penalty=None)
- Вычисляются прогнозы класса и вероятности на обучающей и тестовой выборках
- Рассчитываются и выводятся основные метрики:
- Accuracy (точность классификации)
- ROC AUC (площадь под ROC-кривой)
- Коэффициент Джини (2*AUC-1)
Важно оценивать метрики как на обучающей, так и на тестовой выборке, чтобы выявить возможное переобучение модели.
Этап 5: Построение модели с использованием statsmodels
Пятый этап включает построение модели с использованием библиотеки statsmodels для более подробного статистического анализа:
# 5. Построение модели с использованием statsmodels для статистического анализа
print("\n5. ПОСТРОЕНИЕ МОДЕЛИ С ИСПОЛЬЗОВАНИЕМ STATSMODELS")
print("-" * 50)
# Добавляем константу для statsmodels
X_train_sm = sm.add_constant(X_train_scaled)
# Обучение логистической регрессии
logit_sm = sm.Logit(y_train, X_train_sm)
logit_results = logit_sm.fit(disp=0) # disp=0 отключает вывод итераций
# Вывод результатов
print(logit_results.summary2())
# Анализ значимости признаков
print("\nАнализ значимости признаков:")
coef_summary = pd.DataFrame({
'Feature': ['const'] + list(X_train_scaled.columns),
'Coefficient': logit_results.params,
'Std Error': logit_results.bse,
'z-value': logit_results.tvalues,
'p-value': logit_results.pvalues,
'Significant': logit_results.pvalues < 0.05
})
# Сортировка по значимости (по p-value)
coef_summary = coef_summary.sort_values('p-value')
print(coef_summary)
В отличие от scikit-learn, библиотека statsmodels предоставляет более детальную статистическую информацию:
- Добавляется константа для свободного члена (интерсепта)
- Обучается логистическая регрессия с выводом подробной статистики
- Формируется и выводится таблица коэффициентов со стандартными ошибками, z-значениями и p-значениями
- Отмечаются статистически значимые признаки (p < 0.05)
Этот анализ позволяет определить, какие факторы действительно влияют на вероятность дефолта, а какие можно исключить из модели.
Этап 6: Подробный анализ прогнозов и метрик
На шестом этапе выполняется более глубокий анализ прогнозов модели и расчет дополнительных метрик качества:
# 6. Подробный анализ прогнозов и метрик
print("\n6. ПОДРОБНЫЙ АНАЛИЗ ПРОГНОЗОВ И МЕТРИК")
print("-" * 50)
# Оптимальный порог отсечения (по F1-score)
from sklearn.metrics import f1_score
thresholds = np.linspace(0.01, 0.99, 99)
f1_scores = []
for threshold in thresholds:
y_pred_t = (y_test_prob >= threshold).astype(int)
f1_scores.append(f1_score(y_test, y_pred_t))
optimal_threshold = thresholds[np.argmax(f1_scores)]
y_test_pred_optimal = (y_test_prob >= optimal_threshold).astype(int)
print(f"Оптимальный порог отсечения (по F1-score): {optimal_threshold:.4f}")
# Отчет о классификации с оптимальным порогом
print("\nОтчет о классификации с оптимальным порогом:")
print(classification_report(y_test, y_test_pred_optimal))
# Матрица ошибок
print("\nМатрица ошибок:")
conf_matrix = confusion_matrix(y_test, y_test_pred_optimal)
print(conf_matrix)
# Расчет чувствительности, специфичности и других метрик
TP = conf_matrix[1, 1]
TN = conf_matrix[0, 0]
FP = conf_matrix[0, 1]
FN = conf_matrix[1, 0]
sensitivity = TP / (TP + FN)
specificity = TN / (TN + FP)
precision = TP / (TP + FP)
npv = TN / (TN + FN)
print("\nДополнительные метрики:")
print(f" Чувствительность (Recall): {sensitivity:.4f}")
print(f" Специфичность: {specificity:.4f}")
print(f" Точность (Precision): {precision:.4f}")
print(f" Негативная прогностическая ценность: {npv:.4f}")
В этом разделе:
- Определяется оптимальный порог для бинарной классификации путем максимизации F1-меры
- Вычисляются и выводятся детальные метрики классификации с использованием оптимального порога
- Строится и анализируется матрица ошибок (confusion matrix)
- Рассчитываются дополнительные метрики:
- Чувствительность (Recall) – доля правильно предсказанных положительных исходов
- Специфичность – доля правильно предсказанных отрицательных исходов
- Точность (Precision) – доля истинных положительных среди всех предсказанных положительных
- Негативная прогностическая ценность – доля истинных отрицательных среди всех предсказанных отрицательных
Эти метрики особенно важны в задачах кредитного скоринга, где цена ошибок разного типа может сильно различаться.
Этап 7: Преобразование в скоринговую шкалу
На седьмом этапе происходит преобразование вероятностей в привычную для кредитных организаций скоринговую шкалу:
# 7. Преобразование в скоринговую шкалу
print("\n7. ПРЕОБРАЗОВАНИЕ В СКОРИНГОВУЮ ШКАЛУ")
print("-" * 50)
# Параметры скоринговой шкалы
score_min = 300
score_max = 850
odds_min = 1/19 # Шансы 1:19 (вероятность ~0.05)
odds_max = 1/1 # Шансы 1:1 (вероятность 0.5)
# Преобразование логита в шкалу
def prob_to_score(prob, scale_min=score_min, scale_max=score_max,
odds_min=odds_min, odds_max=odds_max):
"""Преобразование вероятности дефолта в скоринговый балл"""
# Вероятность -> шансы (odds)
odds = prob / (1 - prob)
# Логарифмирование для линейной шкалы
log_odds = np.log(odds)
log_odds_min = np.log(odds_min)
log_odds_max = np.log(odds_max)
# Масштабирование на целевую шкалу (инверсия, выше балл = ниже риск)
score = scale_max - (log_odds - log_odds_min) / (log_odds_max - log_odds_min) * (scale_max - scale_min)
return score
# Вычисляем скоринговые баллы
y_test_scores = prob_to_score(y_test_prob)
# Статистика по скоринговым баллам
score_stats = pd.DataFrame({
'Статистика': ['Минимум', 'Максимум', 'Среднее', 'Медиана', 'Стд. отклонение'] +
[f'{p}-й процентиль' for p in [10, 25, 50, 75, 90, 95, 99]],
'Значение': [y_test_scores.min(), y_test_scores.max(), y_test_scores.mean(), np.median(y_test_scores),
y_test_scores.std()] +
[np.percentile(y_test_scores, p) for p in [10, 25, 50, 75, 90, 95, 99]]
})
print("Статистика скоринговых баллов:")
print(score_stats)
# Оптимальный порог в скоринговой шкале
score_threshold = prob_to_score(optimal_threshold)
print(f"\nОптимальный порог отсечения в скоринговой шкале: {score_threshold:.0f}")
В этом разделе:
- Определяются параметры скоринговой шкалы (минимальный и максимальный баллы, минимальные и максимальные шансы)
- Реализуется функция prob_to_score для преобразования вероятностей в скоринговую шкалу
- Вычисляются скоринговые баллы для тестовой выборки
- Выводится статистика распределения скоринговых баллов
- Определяется оптимальный порог отсечения в скоринговой шкале
Преобразование в скоринговую шкалу делает результаты модели более интуитивно понятными для кредитных специалистов и соответствует отраслевым стандартам (например, шкале FICO от 300 до 850 баллов).
Этап 8: Сохранение результатов и модели
Заключительный этап включает сохранение полученной модели и всех необходимых для ее использования параметров:
# 8. Сохранение результатов и модели
print("\n8. СОХРАНЕНИЕ РЕЗУЛЬТАТОВ И МОДЕЛИ")
print("-" * 50)
# Создаем словарь с результатами
results = {
'model': logit_model,
'scaler': scaler,
'features': list(X_train.columns),
'metrics': {
'train_accuracy': train_accuracy,
'test_accuracy': test_accuracy,
'train_auc': train_auc,
'test_auc': test_auc,
'optimal_threshold': optimal_threshold,
'score_threshold': score_threshold
},
'coef_summary': coef_summary,
'score_params': {
'score_min': score_min,
'score_max': score_max,
'odds_min': odds_min,
'odds_max': odds_max
}
}
print("Результаты работы модели сохранены в словаре 'results'")
print("\nДля сохранения модели в файл можно использовать pickle:")
print("import pickle")
print("with open('logit_model_results.pkl', 'wb') as f:")
print(" pickle.dump(results, f)")
return results
В результирующем словаре сохраняются:
- Обученная модель
- Объект стандартизации (scaler)
- Список использованных признаков
- Метрики качества модели
- Оптимальные пороги отсечения
- Сводка коэффициентов
- Параметры скоринговой шкалы
Этот подход обеспечивает возможность повторного использования модели и всех необходимых трансформаций.
Дополнительные функции для анализа
Трансформация WoE (Weight of Evidence)
Код также включает функцию create_woe_features для создания признаков на основе трансформации WoE (Weight of Evidence):
def create_woe_features(df, target, features, n_bins=10, smoothing=0.01, reference_df=None, reference_woe=None):
"""
Создание WoE-признаков
Параметры:
-----------
df : pandas.DataFrame
Датафрейм для обработки
target : str
Название столбца с целевой переменной
features : list
Список признаков для WoE-преобразования
n_bins : int, default=10
Количество бинов для разбиения
smoothing : float, default=0.01
Параметр сглаживания Лапласа
reference_df : pandas.DataFrame, default=None
Датафрейм для расчета бинов (если None, используется df)
reference_woe : dict, default=None
Словарь с предварительно рассчитанными WoE-значениями
Возвращает:
-----------
pandas.DataFrame
Датафрейм с WoE-признаками
dict
Словарь с информацией о бинах и WoE-значениях
"""
df_woe = df.copy()
woe_info = {}
for feature in features:
if reference_woe is not None and feature in reference_woe:
# Используем предварительно рассчитанные значения
bin_info = reference_woe[feature]
# Применяем бины к текущему датафрейму
if 'bins' in bin_info:
df_woe[f'{feature}_bin'] = pd.cut(
df[feature],
bins=bin_info['bins'],
labels=False,
include_lowest=True
)
else:
# Для категориальных признаков
df_woe[f'{feature}_bin'] = df[feature].astype('category').cat.codes
# Маппим WoE-значения
df_woe[f'{feature}_woe'] = df_woe[f'{feature}_bin'].map(bin_info['woe_map'])
# Сохраняем информацию
woe_info[feature] = bin_info
else:
# Рассчитываем новые бины и WoE-значения
if reference_df is not None:
# Используем reference_df для расчета бинов
ref = reference_df
else:
# Используем текущий датафрейм
ref = df
# Для числовых признаков используем qcut, для категориальных - category codes
if pd.api.types.is_numeric_dtype(ref[feature]):
# Рассчитываем бины
bins = pd.qcut(ref[feature], q=n_bins, duplicates='drop', retbins=True)[1]
# Применяем бины к текущему датафрейму
df_woe[f'{feature}_bin'] = pd.cut(
df[feature],
bins=bins,
labels=False,
include_lowest=True
)
bin_type = 'numeric'
else:
# Для категориальных признаков
df_woe[f'{feature}_bin'] = df[feature].astype('category').cat.codes
bins = None
bin_type = 'categorical'
# Рассчитываем WoE для каждого бина
woe_df = pd.DataFrame({
'feature': df[feature],
'bin': df_woe[f'{feature}_bin'],
'target': df[target]
})
# Группировка и расчет статистик
bin_stats = woe_df.groupby('bin').agg(
total=('target', 'count'),
events=('target', 'sum'),
non_events=lambda x: (x['target']==0).sum()
)
# Добавляем сглаживание Лапласа
total_events = bin_stats['events'].sum()
total_non_events = bin_stats['non_events'].sum()
bin_stats['event_rate'] = (bin_stats['events'] + smoothing) / (bin_stats['total'] + 2 * smoothing)
bin_stats['non_event_rate'] = (bin_stats['non_events'] + smoothing) / (bin_stats['total'] + 2 * smoothing)
bin_stats['event_dist'] = (bin_stats['events'] + smoothing) / (total_events + smoothing * len(bin_stats))
bin_stats['non_event_dist'] = (bin_stats['non_events'] + smoothing) / (total_non_events + smoothing * len(bin_stats))
# Расчет WoE и IV
bin_stats['woe'] = np.log(bin_stats['non_event_dist'] / bin_stats['event_dist'])
bin_stats['iv_component'] = (bin_stats['non_event_dist'] - bin_stats['event_dist']) * bin_stats['woe']
total_iv = bin_stats['iv_component'].sum()
# Создаем маппинг для WoE
woe_map = bin_stats['woe'].to_dict()
# Применяем WoE-трансформацию
df_woe[f'{feature}_woe'] = df_woe[f'{feature}_bin'].map(woe_map)
# Сохраняем информацию
woe_info[feature] = {
'bins': bins,
'bin_type': bin_type,
'woe_map': woe_map,
'bin_stats': bin_stats,
'iv': total_iv
}
return df_woe, woe_info
WoE-трансформация заключается в преобразовании исходных признаков в логарифм отношения шансов. Для каждого признака:
- Значения разбиваются на интервалы (бины)
- Для каждого бина рассчитывается доля “хороших” и “плохих” случаев
- Вычисляется логарифм отношения этих долей (WoE)
- Исходное значение признака заменяется соответствующим значением WoE
Особенности реализации:
- Применяется сглаживание Лапласа для обработки редких событий
- Поддерживается расчет бинов на одном датафрейме и применение к другому
- Возможность использования предварительно рассчитанных WoE-значений
- Расчет информационной ценности (IV) для оценки предиктивной силы признака
WoE-трансформация имеет несколько преимуществ:
* Обрабатывает нелинейные зависимости
* Обрабатывает выбросы и аномальные значения
* Делает связь с целевой переменной более линейной
* Позволяет включать категориальные переменные без дополнительного кодирования
Пример использования кода
В конце файла приведен пример использования созданного рабочего процесса на синтетических данных:
# Пример использования
if __name__ == "__main__":
# Создаем синтетические данные для примера
np.random.seed(42)
n_samples = 1000
# Признаки
age = np.random.normal(35, 10, n_samples) # Возраст
income = np.random.lognormal(10, 0.5, n_samples) # Доход
debt_ratio = np.random.gamma(2, 0.2, n_samples) # Отношение долга к доходу
credit_history = np.random.normal(60, 15, n_samples) # Кредитная история
# Вероятность дефолта на основе признаков
logit = (-1 + 0.02 * age - 0.0002 * income + 3.0 * debt_ratio - 0.05 * credit_history)
prob_default = 1 / (1 + np.exp(-logit))
default = np.random.binomial(1, prob_default)
# Создаем датафрейм
data = pd.DataFrame({
'age': age,
'income': income,
'debt_ratio': debt_ratio,
'credit_history': credit_history,
'default': default
})
# Запускаем рабочий процесс
results = logit_model_workflow(data, target_col='default')
В этом примере:
- Генерируются синтетические данные с четырьмя признаками: возраст, доход, отношение долга к доходу и кредитная история
- На основе линейной комбинации этих признаков с заданными коэффициентами вычисляется логит и вероятность дефолта
- Генерируются бинарные исходы (дефолт/недефолт) согласно вычисленным вероятностям
- Создается датафрейм с признаками и целевой переменной
- Запускается рабочий процесс построения и оценки модели
Этот пример позволяет протестировать весь конвейер обработки данных и построения модели на контролируемых данных.
Заключение
Представленный код предоставляет полный рабочий процесс для построения и оценки логит-модели кредитного скоринга. Он включает все необходимые этапы:
- Подготовка данных: проверка наличия пропущенных значений, разделение на обучающую и тестовую выборки.
- Обработка выбросов и преобразование признаков: винсоризация и стандартизация.
- Анализ мультиколлинеарности: выявление сильно коррелированных пар признаков.
- Построение модели: использование scikit-learn для быстрого прототипирования.
- Статистический анализ: использование statsmodels для детальной оценки параметров модели.
- Подробный анализ прогнозов: определение оптимального порога отсечения, расчет дополнительных метрик.
- Преобразование в скоринговую шкалу: перевод вероятностей в привычные баллы.
- Сохранение результатов: формирование полного набора артефактов модели для дальнейшего использования.
Дополнительно предоставляются функции для создания WoE-признаков и пример использования на синтетических данных.
Этот подход обеспечивает построение интерпретируемых, статистически обоснованных и практически применимых моделей кредитного скоринга, которые могут быть использованы финансовыми организациями для оценки кредитного риска заемщиков.
Приложение 1: Результаты работы модели
1. ПОДГОТОВКА ДАННЫХ
--------------------------------------------------
Размер обучающей выборки: 700 наблюдений
Размер тестовой выборки: 300 наблюдений
Доля положительного класса (общая): 0.90%
Доля положительного класса (обучающая): 0.86%
Доля положительного класса (тестовая): 1.00%
2. ОБРАБОТКА ВЫБРОСОВ И ПРЕОБРАЗОВАНИЕ ПРИЗНАКОВ
--------------------------------------------------
Выбросы обработаны методом усечения (винсоризации)
Признаки стандартизированы (среднее=0, стд=1)
3. АНАЛИЗ МУЛЬТИКОЛЛИНЕАРНОСТИ
--------------------------------------------------
Сильно коррелированных признаков (|r| > 0.7) не обнаружено
4. ПОСТРОЕНИЕ МОДЕЛИ С ИСПОЛЬЗОВАНИЕМ SKLEARN
--------------------------------------------------
Метрики на обучающей выборке:
Accuracy: 0.9914
ROC AUC: 0.9433
Gini: 0.8866
Метрики на тестовой выборке:
Accuracy: 0.9900
ROC AUC: 0.9270
Gini: 0.8541
5. ПОСТРОЕНИЕ МОДЕЛИ С ИСПОЛЬЗОВАНИЕМ STATSMODELS
--------------------------------------------------
Results: Logit
=================================================================
Model: Logit Method: MLE
Dependent Variable: default Pseudo R-squared: 0.346
Date: 2025-03-26 12:33 AIC: 55.1835
No. Observations: 700 BIC: 77.9389
Df Model: 4 Log-Likelihood: -22.592
Df Residuals: 695 LL-Null: -34.530
Converged: 1.0000 LLR p-value: 8.4547e-05
No. Iterations: 11.0000 Scale: 1.0000
-----------------------------------------------------------------
Coef. Std.Err. z P>|z| [0.025 0.975]
-----------------------------------------------------------------
const -7.5310 1.3652 -5.5165 0.0000 -10.2066 -4.8553
age 0.2649 0.4660 0.5685 0.5697 -0.6484 1.1782
income -2.6382 1.0912 -2.4178 0.0156 -4.7769 -0.4995
debt_ratio 1.1970 0.3649 3.2807 0.0010 0.4819 1.9122
credit_history -0.2692 0.5129 -0.5249 0.5997 -1.2745 0.7361
=================================================================
Анализ значимости признаков:
Feature Coefficient Std Error z-value p-value Significant
0 const -7.530956 1.365165 -5.516518 3.457831e-08 True
2 debt_ratio 1.197050 0.364879 3.280672 1.035601e-03 True
1 income -2.638209 1.091179 -2.417760 1.561637e-02 True
3 age 0.264888 0.465968 0.568470 5.697161e-01 False
4 credit_history -0.269205 0.512915 -0.524852 5.996861e-01 False
6. ПОДРОБНЫЙ АНАЛИЗ ПРОГНОЗОВ И МЕТРИК
--------------------------------------------------
Оптимальный порог отсечения (по F1-score): 0.0400
Отчет о классификации с оптимальным порогом:
precision recall f1-score support
0 1.00 0.97 0.98 297
1 0.20 0.67 0.31 3
accuracy 0.97 300
macro avg 0.60 0.82 0.65 300
weighted avg 0.99 0.97 0.98 300
Матрица ошибок:
[[289 8]
[ 1 2]]
Дополнительные метрики:
Чувствительность (Recall): 0.6667
Специфичность: 0.9731
Точность (Precision): 0.2000
Негативная прогностическая ценность: 0.9966
7. ПРЕОБРАЗОВАНИЕ В СКОРИНГОВУЮ ШКАЛУ
--------------------------------------------------
Статистика скоринговых баллов:
Статистика Значение
0 Минимум 531.655088
1 Максимум 3572.368825
2 Среднее 1718.100909
3 Медиана 1642.026167
4 Стд. отклонение 552.753535
5 10-й процентиль 1077.965199
6 25-й процентиль 1333.885719
7 50-й процентиль 1642.026167
8 75-й процентиль 2001.356014
9 90-й процентиль 2416.950034
10 95-й процентиль 2771.009211
11 99-й процентиль 3411.927627
Оптимальный порог отсечения в скоринговой шкале: 894
8. СОХРАНЕНИЕ РЕЗУЛЬТАТОВ И МОДЕЛИ
--------------------------------------------------
Результаты работы модели сохранены в словаре 'results'
Для сохранения модели в файл можно использовать pickle:
import pickle
with open('logit_model_results.pkl', 'wb') as f:
pickle.dump(results, f)