269 lines
13 KiB
Python
269 lines
13 KiB
Python
# interpreter.py
|
|
"""
|
|
Модуль интерпретации результатов статистического анализа.
|
|
Выдаёт содержательные комментарии на русском языке о качестве модели,
|
|
значимости факторов, адекватности и рекомендациях по дальнейшим действиям.
|
|
"""
|
|
|
|
import numpy as np
|
|
|
|
|
|
def interpret_regression(r2, r2_adj, f_stat, f_pvalue, alpha):
|
|
"""
|
|
Интерпретирует качество регрессионной модели.
|
|
|
|
Аргументы:
|
|
r2 (float): коэффициент детерминации
|
|
r2_adj (float): скорректированный R²
|
|
f_stat (float): F-статистика
|
|
f_pvalue (float): p-value для F-критерия
|
|
alpha (float): уровень значимости
|
|
|
|
Возвращает:
|
|
str: текстовое заключение
|
|
"""
|
|
parts = []
|
|
|
|
# ----- Оценка значимости модели в целом -----
|
|
if f_pvalue < alpha:
|
|
parts.append("✅ Модель статистически значима (p < α).")
|
|
else:
|
|
parts.append("❌ Модель статистически незначима (p ≥ α).")
|
|
|
|
# ----- Оценка R² -----
|
|
if r2 >= 0.9:
|
|
parts.append(f"📊 R² = {r2:.3f} — очень высокий, модель объясняет более 90% вариации.")
|
|
elif r2 >= 0.7:
|
|
parts.append(f"📊 R² = {r2:.3f} — хороший, модель объясняет {r2*100:.1f}% вариации.")
|
|
elif r2 >= 0.5:
|
|
parts.append(f"📊 R² = {r2:.3f} — умеренный, модель объясняет около {r2*100:.1f}% вариации.")
|
|
else:
|
|
parts.append(f"📊 R² = {r2:.3f} — низкий, модель плохо объясняет данные.")
|
|
|
|
# ----- Сравнение R² и скорректированного R² -----
|
|
diff = r2 - r2_adj
|
|
if diff > 0.1:
|
|
parts.append(f"⚠️ Разница между R² и скорректированным R² велика ({diff:.3f}) — возможно, модель переобучена или содержит лишние факторы.")
|
|
elif diff > 0.05:
|
|
parts.append(f"ℹ️ Разница между R² и скорректированным R² составляет {diff:.3f} — стоит проверить, все ли факторы значимы.")
|
|
else:
|
|
parts.append(f"✅ R² и скорректированный R² близки — модель хорошо сбалансирована.")
|
|
|
|
return "\n".join(parts)
|
|
|
|
|
|
def interpret_coefficients(beta, t_stats, p_values, factor_names, alpha):
|
|
"""
|
|
Интерпретирует значимость коэффициентов регрессии.
|
|
|
|
Аргументы:
|
|
beta (list): коэффициенты (включая свободный член)
|
|
t_stats (list): t-статистики для каждого коэффициента
|
|
p_values (list или None): p-values для каждого коэффициента
|
|
factor_names (list): имена факторов (первый должен быть "Свободный член")
|
|
alpha (float): уровень значимости
|
|
|
|
Возвращает:
|
|
str: текстовое заключение
|
|
"""
|
|
parts = []
|
|
significant = []
|
|
not_significant = []
|
|
|
|
# Проверяем, что factor_names содержит "Свободный член"
|
|
if factor_names[0] != "Свободный член":
|
|
# Если нет, добавляем
|
|
factor_names = ["Свободный член"] + factor_names
|
|
|
|
for i, name in enumerate(factor_names):
|
|
if i == 0:
|
|
continue # пропускаем свободный член
|
|
if i >= len(t_stats):
|
|
break
|
|
if p_values is not None and p_values[i] < alpha:
|
|
significant.append(name)
|
|
elif p_values is None and abs(t_stats[i]) > 2.0: # грубая оценка
|
|
significant.append(name)
|
|
else:
|
|
not_significant.append(name)
|
|
|
|
if not significant:
|
|
parts.append("❌ Ни один фактор не является статистически значимым.")
|
|
else:
|
|
parts.append(f"✅ Значимые факторы: {', '.join(significant)}.")
|
|
|
|
if not_significant:
|
|
parts.append(f"⚠️ Незначимые факторы: {', '.join(not_significant)}. Рекомендуется исключить их из модели.")
|
|
|
|
# ----- Оценка силы влияния (по величине коэффициентов) -----
|
|
if len(significant) >= 2:
|
|
coefs = {name: abs(beta[i]) for i, name in enumerate(factor_names) if name in significant}
|
|
if coefs:
|
|
max_name = max(coefs, key=coefs.get)
|
|
max_val = coefs[max_name]
|
|
parts.append(f"💪 Самый сильный фактор — {max_name} (коэффициент {max_val:.4f}).")
|
|
|
|
return "\n".join(parts)
|
|
|
|
|
|
def interpret_anova(anova, alpha):
|
|
"""
|
|
Интерпретирует результаты дисперсионного анализа (ANOVA).
|
|
|
|
Аргументы:
|
|
anova (dict): результат функции anova_regression
|
|
alpha (float): уровень значимости
|
|
|
|
Возвращает:
|
|
str: текстовое заключение
|
|
"""
|
|
parts = []
|
|
|
|
# ----- Проверка адекватности (lack-of-fit) -----
|
|
if not np.isnan(anova.get("F_lof", np.nan)):
|
|
p_lof = anova["p_lof"]
|
|
if p_lof < alpha:
|
|
parts.append(f"⚠️ Модель НЕ АДЕКВАТНА (p_lof = {p_lof:.4f} < {alpha}). Линейная модель плохо описывает данные — вероятно, присутствует кривизна.")
|
|
parts.append(" 🔄 Рекомендуется перейти к квадратичной модели (ЦКП, Бокса-Бенкена).")
|
|
else:
|
|
parts.append(f"✅ Модель адекватна (p_lof = {p_lof:.4f} ≥ {alpha}). Линейное приближение достаточно.")
|
|
else:
|
|
parts.append("ℹ️ Недостаточно центральных точек для оценки адекватности (lack-of-fit).")
|
|
|
|
# ----- Сравнение дисперсий -----
|
|
if not np.isnan(anova.get("MS_pe", np.nan)) and not np.isnan(anova.get("MS_lof", np.nan)):
|
|
ms_pe = anova["MS_pe"]
|
|
ms_lof = anova["MS_lof"]
|
|
if ms_lof > ms_pe * 3:
|
|
parts.append(f"⚠️ Дисперсия неадекватности ({ms_lof:.6f}) намного превышает чистую ошибку ({ms_pe:.6f}) — модель систематически ошибается.")
|
|
elif ms_lof > ms_pe:
|
|
parts.append(f"ℹ️ Дисперсия неадекватности ({ms_lof:.6f}) больше чистой ошибки ({ms_pe:.6f}), но разница не критична.")
|
|
else:
|
|
parts.append(f"✅ Дисперсия неадекватности ({ms_lof:.6f}) меньше или сравнима с чистой ошибкой ({ms_pe:.6f}).")
|
|
|
|
return "\n".join(parts)
|
|
|
|
|
|
def interpret_center_points(center_mean, center_var, n_center, alpha):
|
|
"""
|
|
Интерпретирует анализ центральных точек.
|
|
|
|
Аргументы:
|
|
center_mean (float): среднее отклика в центре
|
|
center_var (float): дисперсия в центре
|
|
n_center (int): количество центральных точек
|
|
alpha (float): уровень значимости
|
|
|
|
Возвращает:
|
|
str: текстовое заключение
|
|
"""
|
|
parts = []
|
|
|
|
if n_center < 2:
|
|
parts.append("⚠️ Меньше двух центральных точек — оценка воспроизводимости невозможна.")
|
|
return "\n".join(parts)
|
|
|
|
std = np.sqrt(center_var) if center_var > 0 else 0.0
|
|
|
|
parts.append(f"📌 Центральных точек: {n_center}")
|
|
parts.append(f"📌 Среднее отклика в центре: {center_mean:.6f}")
|
|
parts.append(f"📌 Стандартное отклонение в центре: {std:.6f}")
|
|
|
|
# ----- Оценка воспроизводимости -----
|
|
if center_var < 1e-6:
|
|
parts.append("✅ Дисперсия в центре очень мала — эксперимент высоковоспроизводим.")
|
|
elif center_var < 1e-4:
|
|
parts.append("✅ Дисперсия в центре мала — воспроизводимость хорошая.")
|
|
elif center_var < 1e-3:
|
|
parts.append("ℹ️ Дисперсия в центре умеренная — стоит обратить внимание на точность измерений.")
|
|
else:
|
|
parts.append("⚠️ Дисперсия в центре велика — возможны проблемы с воспроизводимостью эксперимента.")
|
|
|
|
# ----- Относительная оценка (по сравнению со средним) -----
|
|
if center_mean != 0:
|
|
rel_std = std / abs(center_mean) * 100
|
|
if rel_std < 1:
|
|
parts.append(f"📊 Относительное стандартное отклонение: {rel_std:.2f}% — отличная точность.")
|
|
elif rel_std < 5:
|
|
parts.append(f"📊 Относительное стандартное отклонение: {rel_std:.2f}% — хорошая точность.")
|
|
elif rel_std < 10:
|
|
parts.append(f"📊 Относительное стандартное отклонение: {rel_std:.2f}% — удовлетворительная точность.")
|
|
else:
|
|
parts.append(f"⚠️ Относительное стандартное отклонение: {rel_std:.2f}% — низкая точность, возможно, есть выбросы или систематическая ошибка.")
|
|
|
|
return "\n".join(parts)
|
|
|
|
|
|
def interpret_model_adequacy(anova, alpha):
|
|
"""
|
|
Даёт общую рекомендацию: переходить на квадратичную модель или нет.
|
|
|
|
Аргументы:
|
|
anova (dict): результат ANOVA
|
|
alpha (float): уровень значимости
|
|
|
|
Возвращает:
|
|
str: краткая рекомендация
|
|
"""
|
|
if not np.isnan(anova.get("p_lof", np.nan)):
|
|
p_lof = anova["p_lof"]
|
|
if p_lof < alpha:
|
|
return (
|
|
"⚠️ РЕКОМЕНДАЦИЯ: модель НЕ АДЕКВАТНА.\n"
|
|
" Линейное приближение недостаточно. Перейдите к квадратичной модели\n"
|
|
" (Центральный композиционный план или план Бокса-Бенкена).\n"
|
|
" Это позволит точно определить положение экстремума."
|
|
)
|
|
else:
|
|
return (
|
|
"✅ РЕКОМЕНДАЦИЯ: модель АДЕКВАТНА.\n"
|
|
" Линейное приближение достаточно. Можете использовать крутое восхождение\n"
|
|
" для движения к оптимуму или уточнять модель дальше."
|
|
)
|
|
else:
|
|
return (
|
|
"ℹ️ Недостаточно данных для проверки адекватности.\n"
|
|
" Добавьте центральные точки в план, чтобы оценить воспроизводимость\n"
|
|
" и проверить, нужна ли квадратичная модель."
|
|
)
|
|
|
|
|
|
def full_interpretation(
|
|
r2, r2_adj, f_stat, f_pvalue,
|
|
beta, t_stats, p_values, factor_names,
|
|
anova, center_mean, center_var, n_center,
|
|
alpha
|
|
):
|
|
"""
|
|
Собирает все интерпретации в единый отчёт.
|
|
|
|
Аргументы:
|
|
все вышеперечисленные
|
|
|
|
Возвращает:
|
|
str: полное заключение
|
|
"""
|
|
lines = []
|
|
lines.append("=" * 60)
|
|
lines.append("📋 ИНТЕРПРЕТАЦИЯ РЕЗУЛЬТАТОВ")
|
|
lines.append("=" * 60)
|
|
|
|
lines.append("\n▶ 1. КАЧЕСТВО МОДЕЛИ")
|
|
lines.append(interpret_regression(r2, r2_adj, f_stat, f_pvalue, alpha))
|
|
|
|
lines.append("\n▶ 2. ЗНАЧИМОСТЬ ФАКТОРОВ")
|
|
lines.append(interpret_coefficients(beta, t_stats, p_values, factor_names, alpha))
|
|
|
|
lines.append("\n▶ 3. АДЕКВАТНОСТЬ (ANOVA)")
|
|
lines.append(interpret_anova(anova, alpha))
|
|
|
|
if n_center >= 2:
|
|
lines.append("\n▶ 4. ВОСПРОИЗВОДИМОСТЬ")
|
|
lines.append(interpret_center_points(center_mean, center_var, n_center, alpha))
|
|
|
|
lines.append("\n▶ 5. ОБЩАЯ РЕКОМЕНДАЦИЯ")
|
|
lines.append(interpret_model_adequacy(anova, alpha))
|
|
|
|
lines.append("\n" + "=" * 60)
|
|
return "\n".join(lines)
|