Compare commits
2 Commits
fa99bba994
...
901a5e5c7b
| Author | SHA1 | Date | |
|---|---|---|---|
| 901a5e5c7b | |||
| 33fd9f1177 |
+196
-14
@@ -1,24 +1,53 @@
|
|||||||
# analysis.py
|
# analysis.py
|
||||||
"""
|
"""
|
||||||
Анализ экспериментальных данных: регрессия, проверка значимости, дисперсия в центре.
|
Модуль анализа экспериментальных данных.
|
||||||
|
|
||||||
|
Выполняет:
|
||||||
|
- множественную линейную регрессию (МНК),
|
||||||
|
- проверку значимости коэффициентов (t-критерий),
|
||||||
|
- дисперсионный анализ (ANOVA) с разложением остатка на чистую ошибку и неадекватность (если есть центр),
|
||||||
|
- оценку воспроизводимости по центральным точкам,
|
||||||
|
- исключение незначимых факторов с пересчётом модели,
|
||||||
|
- интерпретацию результатов на естественном языке.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from core.table_io import handle_table_output
|
from core.table_io import handle_table_output
|
||||||
|
from core.interpreter import full_interpretation
|
||||||
from core.statistics import (
|
from core.statistics import (
|
||||||
compute_regression,
|
compute_regression,
|
||||||
get_center_indices,
|
get_center_indices,
|
||||||
compute_center_stats,
|
compute_center_stats,
|
||||||
chi2_confidence_interval,
|
chi2_confidence_interval,
|
||||||
get_t_critical,
|
get_t_critical,
|
||||||
regression_summary
|
regression_summary,
|
||||||
|
anova_regression
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def analysis_menu(mixture, design, responses):
|
def analysis_menu(mixture, design, responses):
|
||||||
|
"""
|
||||||
|
Главное меню подсистемы анализа.
|
||||||
|
|
||||||
|
Аргументы:
|
||||||
|
mixture (Mixture): текущий состав смеси (нужен для имён факторов).
|
||||||
|
design (ExperimentDesign): объект с шагами и матрицей плана.
|
||||||
|
responses (list): список откликов, каждый как {'name': str, 'values': list}.
|
||||||
|
|
||||||
|
Действия:
|
||||||
|
- проверяет наличие плана и откликов;
|
||||||
|
- определяет активные факторы (шаг > 0);
|
||||||
|
- запрашивает уровень значимости alpha;
|
||||||
|
- строит матрицу X (единичный столбец + активные факторы);
|
||||||
|
- позволяет выбрать один отклик или все для последовательного анализа.
|
||||||
|
"""
|
||||||
|
# Проверка наличия данных
|
||||||
if not design.plan or not responses:
|
if not design.plan or not responses:
|
||||||
print("Нет данных для анализа: план или отклики пусты.")
|
print("Нет данных для анализа: план или отклики пусты.")
|
||||||
input("Нажмите Enter для продолжения...")
|
input("Нажмите Enter для продолжения...")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Активные факторы – те, для которых задан шаг > 0
|
||||||
active_indices = design._active_indices()
|
active_indices = design._active_indices()
|
||||||
k = len(active_indices)
|
k = len(active_indices)
|
||||||
if k < 2:
|
if k < 2:
|
||||||
@@ -26,6 +55,7 @@ def analysis_menu(mixture, design, responses):
|
|||||||
input("Нажмите Enter для продолжения...")
|
input("Нажмите Enter для продолжения...")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Имена активных факторов берём из смеси (индекс = idx + 1, т.к. 0 – растворитель)
|
||||||
factor_names = []
|
factor_names = []
|
||||||
for idx in active_indices:
|
for idx in active_indices:
|
||||||
ing_index = idx + 1
|
ing_index = idx + 1
|
||||||
@@ -34,6 +64,7 @@ def analysis_menu(mixture, design, responses):
|
|||||||
else:
|
else:
|
||||||
factor_names.append(f"x{idx+1}")
|
factor_names.append(f"x{idx+1}")
|
||||||
|
|
||||||
|
# Ввод уровня значимости alpha
|
||||||
print("\nВведите уровень значимости alpha (например, 0.05, 0.01, 0.001):")
|
print("\nВведите уровень значимости alpha (например, 0.05, 0.01, 0.001):")
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
@@ -49,11 +80,13 @@ def analysis_menu(mixture, design, responses):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
print("Введите число.")
|
print("Введите число.")
|
||||||
|
|
||||||
|
# Матрица X: столбец единиц + столбцы активных факторов (в кодировке ±1)
|
||||||
plan_matrix = np.array([[row[j] for j in active_indices] for row in design.plan])
|
plan_matrix = np.array([[row[j] for j in active_indices] for row in design.plan])
|
||||||
N = plan_matrix.shape[0]
|
N = plan_matrix.shape[0]
|
||||||
X = np.hstack([np.ones((N, 1)), plan_matrix])
|
X = np.hstack([np.ones((N, 1)), plan_matrix]) # размер N x (k+1)
|
||||||
df_res = N - (k + 1)
|
df_res = N - (k + 1) # степени свободы остатка для полной модели
|
||||||
|
|
||||||
|
# Цикл выбора отклика
|
||||||
while True:
|
while True:
|
||||||
print("\n" + "=" * 50)
|
print("\n" + "=" * 50)
|
||||||
print("АНАЛИЗ ЗНАЧЕНИЙ")
|
print("АНАЛИЗ ЗНАЧЕНИЙ")
|
||||||
@@ -67,15 +100,19 @@ def analysis_menu(mixture, design, responses):
|
|||||||
choice = input("Выберите отклик для анализа: ").strip()
|
choice = input("Выберите отклик для анализа: ").strip()
|
||||||
if choice == "":
|
if choice == "":
|
||||||
break
|
break
|
||||||
|
|
||||||
try:
|
try:
|
||||||
idx = int(choice) - 1
|
idx = int(choice) - 1
|
||||||
if idx < 0:
|
if idx < 0:
|
||||||
print("Неверный номер.")
|
print("Неверный номер.")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if idx < len(responses):
|
if idx < len(responses):
|
||||||
|
# Анализ одного отклика
|
||||||
analyze_single_response(responses[idx], X, k, df_res, design, alpha, factor_names)
|
analyze_single_response(responses[idx], X, k, df_res, design, alpha, factor_names)
|
||||||
input("Нажмите Enter для продолжения...")
|
input("Нажмите Enter для продолжения...")
|
||||||
elif idx == len(responses):
|
elif idx == len(responses):
|
||||||
|
# Анализ всех откликов подряд
|
||||||
for resp in responses:
|
for resp in responses:
|
||||||
print(f"\n--- Анализ отклика '{resp['name']}' ---")
|
print(f"\n--- Анализ отклика '{resp['name']}' ---")
|
||||||
analyze_single_response(resp, X, k, df_res, design, alpha, factor_names)
|
analyze_single_response(resp, X, k, df_res, design, alpha, factor_names)
|
||||||
@@ -85,26 +122,58 @@ def analysis_menu(mixture, design, responses):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
print("Введите число.")
|
print("Введите число.")
|
||||||
|
|
||||||
|
|
||||||
def analyze_single_response(response, X, k, df_res, design, alpha, factor_names):
|
def analyze_single_response(response, X, k, df_res, design, alpha, factor_names):
|
||||||
|
"""
|
||||||
|
Проводит полный анализ одного отклика.
|
||||||
|
|
||||||
|
Этапы:
|
||||||
|
1. Расчёт регрессии методом наименьших квадратов (МНК).
|
||||||
|
2. Вывод регрессионной статистики: R², скорректированный R², F-критерий,
|
||||||
|
коэффициенты, их стандартные ошибки и t-статистики.
|
||||||
|
3. Дисперсионный анализ (ANOVA) с разложением сумм квадратов.
|
||||||
|
Если есть центральные точки (>=2), остаток разбивается на чистую ошибку
|
||||||
|
и неадекватность (lack-of-fit).
|
||||||
|
4. Анализ центральных точек – оценка воспроизводимости.
|
||||||
|
5. Опция исключения незначимых факторов (p > alpha) и пересчёт упрощённой модели.
|
||||||
|
6. Интерпретация результатов на естественном языке.
|
||||||
|
|
||||||
|
Аргументы:
|
||||||
|
response (dict): {'name': str, 'values': list}
|
||||||
|
X (np.ndarray): матрица регрессоров (с единичным столбцом)
|
||||||
|
k (int): число активных факторов
|
||||||
|
df_res (int): степени свободы остатка (N - k - 1)
|
||||||
|
design (ExperimentDesign): объект с планом
|
||||||
|
alpha (float): уровень значимости
|
||||||
|
factor_names (list): имена факторов (без "Свободный член")
|
||||||
|
"""
|
||||||
|
# Преобразуем значения отклика в массив numpy
|
||||||
y = np.array(response["values"])
|
y = np.array(response["values"])
|
||||||
N = len(y)
|
N = len(y)
|
||||||
if N != X.shape[0]:
|
if N != X.shape[0]:
|
||||||
print(f"Ошибка: число значений отклика ({N}) не соответствует числу опытов ({X.shape[0]})")
|
print(f"Ошибка: число значений отклика ({N}) не соответствует числу опытов ({X.shape[0]})")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# ---------- Шаг 1: Расчёт регрессии ----------
|
||||||
reg = compute_regression(X, y)
|
reg = compute_regression(X, y)
|
||||||
beta = reg["beta"]
|
beta = reg["beta"] # коэффициенты (включая свободный член)
|
||||||
se = reg["se"]
|
se = reg["se"] # стандартные ошибки коэффициентов
|
||||||
t_stats = reg["t_stats"]
|
t_stats = reg["t_stats"] # t-статистики
|
||||||
s2_res = reg["s2_res"]
|
s2_res = reg["s2_res"] # дисперсия остатков
|
||||||
df_res_actual = reg["df_res"]
|
df_res_actual = reg["df_res"] # степени свободы остатка (N - p)
|
||||||
y_pred = reg["y_pred"]
|
y_pred = reg["y_pred"] # предсказанные значения модели
|
||||||
|
|
||||||
|
# Проверка на вырожденность матрицы
|
||||||
if np.isnan(beta).any():
|
if np.isnan(beta).any():
|
||||||
print("Ошибка при расчёте регрессии: матрица вырождена.")
|
print("Ошибка при расчёте регрессии: матрица вырождена.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# ---------- Шаг 2: Вспомогательная функция для вывода регрессионной статистики ----------
|
||||||
def print_and_get_tcrit(beta, se, t_stats, df_res, y_pred, y, k, factor_names, alpha):
|
def print_and_get_tcrit(beta, se, t_stats, df_res, y_pred, y, k, factor_names, alpha):
|
||||||
|
"""
|
||||||
|
Выводит основную регрессионную статистику и возвращает критическое t.
|
||||||
|
"""
|
||||||
|
# Расчёт R², скорр. R² и F-критерия
|
||||||
summary = regression_summary(y, y_pred, k, df_res)
|
summary = regression_summary(y, y_pred, k, df_res)
|
||||||
r2 = summary["r2"]
|
r2 = summary["r2"]
|
||||||
r2_adj = summary["r2_adj"]
|
r2_adj = summary["r2_adj"]
|
||||||
@@ -115,13 +184,16 @@ def analyze_single_response(response, X, k, df_res, design, alpha, factor_names)
|
|||||||
print(f"Число опытов: {len(y)}")
|
print(f"Число опытов: {len(y)}")
|
||||||
print(f"Число факторов: {k}")
|
print(f"Число факторов: {k}")
|
||||||
print(f"Степени свободы остатков: {df_res}")
|
print(f"Степени свободы остатков: {df_res}")
|
||||||
|
# Стандартная ошибка регрессии (корень из дисперсии остатков)
|
||||||
print(f"Стандартная ошибка регрессии: {np.sqrt(s2_res) if not np.isnan(s2_res) else np.nan:.6f}")
|
print(f"Стандартная ошибка регрессии: {np.sqrt(s2_res) if not np.isnan(s2_res) else np.nan:.6f}")
|
||||||
print(f"Множественный R: {np.sqrt(r2) if not np.isnan(r2) else np.nan:.6f}")
|
print(f"Множественный R: {np.sqrt(r2) if not np.isnan(r2) else np.nan:.6f}")
|
||||||
print(f"R-квадрат: {r2:.6f}")
|
print(f"R-квадрат: {r2:.6f}")
|
||||||
print(f"Скорректированный R-квадрат: {r2_adj:.6f}")
|
print(f"Скорректированный R-квадрат: {r2_adj:.6f}")
|
||||||
print(f"F-статистика: {f_stat:.4f}, p-value: {f_pvalue:.6f}")
|
print(f"F-статистика: {f_stat:.4f}, p-value: {f_pvalue:.6f}")
|
||||||
|
|
||||||
|
# Критическое значение t для двустороннего критерия
|
||||||
t_crit = get_t_critical(df_res, alpha=alpha)
|
t_crit = get_t_critical(df_res, alpha=alpha)
|
||||||
|
|
||||||
print("\nКоэффициенты регрессии:")
|
print("\nКоэффициенты регрессии:")
|
||||||
headers = ["Коэффициент", "Значение", "Станд. ошибка", "t-статистика", f"|t| > t_крит({alpha})?"]
|
headers = ["Коэффициент", "Значение", "Станд. ошибка", "t-статистика", f"|t| > t_крит({alpha})?"]
|
||||||
rows = []
|
rows = []
|
||||||
@@ -135,14 +207,89 @@ def analyze_single_response(response, X, k, df_res, design, alpha, factor_names)
|
|||||||
f"{t_stats[i]:.4f}",
|
f"{t_stats[i]:.4f}",
|
||||||
sig
|
sig
|
||||||
])
|
])
|
||||||
|
# Вывод таблицы с возможностью сохранения/копирования
|
||||||
handle_table_output(headers, rows, title="", ask_each_time=True)
|
handle_table_output(headers, rows, title="", ask_each_time=True)
|
||||||
|
|
||||||
if not np.isnan(t_crit):
|
if not np.isnan(t_crit):
|
||||||
print(f"Критическое значение t (alpha={alpha}, df={df_res}): {t_crit:.4f}")
|
print(f"Критическое значение t (alpha={alpha}, df={df_res}): {t_crit:.4f}")
|
||||||
return t_crit
|
|
||||||
|
|
||||||
t_crit = print_and_get_tcrit(beta, se, t_stats, df_res_actual, y_pred, y, k, factor_names, alpha)
|
# Возвращаем summary для дальнейшего использования
|
||||||
|
return t_crit, summary
|
||||||
|
|
||||||
|
# ---------- Шаг 2 (выполнение): вывод статистики для полной модели ----------
|
||||||
|
t_crit, summary_full = print_and_get_tcrit(beta, se, t_stats, df_res_actual, y_pred, y, k, factor_names, alpha)
|
||||||
|
|
||||||
|
# Извлекаем показатели качества
|
||||||
|
r2 = summary_full["r2"]
|
||||||
|
r2_adj = summary_full["r2_adj"]
|
||||||
|
f_stat = summary_full["f_stat"]
|
||||||
|
f_pvalue = summary_full["f_pvalue"]
|
||||||
|
|
||||||
|
# ---------- Шаг 3: Дисперсионный анализ (ANOVA) ----------
|
||||||
|
# Определяем индексы строк плана, соответствующие центральным точкам (все активные факторы = 0)
|
||||||
center_indices = get_center_indices(design.plan, design._active_indices())
|
center_indices = get_center_indices(design.plan, design._active_indices())
|
||||||
|
|
||||||
|
# Выполняем ANOVA (если центр есть, разлагаем остаток)
|
||||||
|
anova = anova_regression(y, y_pred, X, center_indices if len(center_indices) >= 2 else None)
|
||||||
|
|
||||||
|
print("\n=== Дисперсионный анализ (ANOVA) ===")
|
||||||
|
anova_headers = ["Источник", "SS", "df", "MS", "F", "p-value"]
|
||||||
|
anova_rows = []
|
||||||
|
|
||||||
|
# Регрессия
|
||||||
|
anova_rows.append([
|
||||||
|
"Регрессия",
|
||||||
|
f"{anova['SSR']:.6f}",
|
||||||
|
f"{anova['df_reg']}",
|
||||||
|
f"{anova['MSR']:.6f}",
|
||||||
|
f"{anova['F_reg']:.4f}",
|
||||||
|
f"{anova['p_reg']:.6f}"
|
||||||
|
])
|
||||||
|
# Остаток
|
||||||
|
anova_rows.append([
|
||||||
|
"Остаток",
|
||||||
|
f"{anova['SSE']:.6f}",
|
||||||
|
f"{anova['df_res']}",
|
||||||
|
f"{anova['MSE']:.6f}",
|
||||||
|
"-",
|
||||||
|
"-"
|
||||||
|
])
|
||||||
|
|
||||||
|
# Если есть центральные точки (>=2), добавляем разложение остатка
|
||||||
|
if len(center_indices) >= 2:
|
||||||
|
anova_rows.append([
|
||||||
|
" Чистая ошибка",
|
||||||
|
f"{anova['SSPE']:.6f}",
|
||||||
|
f"{anova['df_pe']}",
|
||||||
|
f"{anova['MS_pe']:.6f}",
|
||||||
|
"-",
|
||||||
|
"-"
|
||||||
|
])
|
||||||
|
anova_rows.append([
|
||||||
|
" Неадекватность",
|
||||||
|
f"{anova['SSLOF']:.6f}",
|
||||||
|
f"{anova['df_lof']}",
|
||||||
|
f"{anova['MS_lof']:.6f}",
|
||||||
|
f"{anova['F_lof']:.4f}",
|
||||||
|
f"{anova['p_lof']:.6f}"
|
||||||
|
])
|
||||||
|
# Итого (общая сумма квадратов)
|
||||||
|
anova_rows.append([
|
||||||
|
"Итого",
|
||||||
|
f"{anova['SST']:.6f}",
|
||||||
|
f"{len(y)-1}",
|
||||||
|
"-",
|
||||||
|
"-",
|
||||||
|
"-"
|
||||||
|
])
|
||||||
|
|
||||||
|
handle_table_output(anova_headers, anova_rows, title="", ask_each_time=True)
|
||||||
|
|
||||||
|
# ---------- Шаг 4: Анализ центральных точек (воспроизводимость) ----------
|
||||||
|
# Инициализируем переменные для центра
|
||||||
|
center_mean = None
|
||||||
|
center_var = None
|
||||||
|
|
||||||
if len(center_indices) >= 2:
|
if len(center_indices) >= 2:
|
||||||
y_center = y[center_indices]
|
y_center = y[center_indices]
|
||||||
center_mean, center_var = compute_center_stats(y_center)
|
center_mean, center_var = compute_center_stats(y_center)
|
||||||
@@ -151,32 +298,67 @@ def analyze_single_response(response, X, k, df_res, design, alpha, factor_names)
|
|||||||
print(f"Среднее значение отклика в центре: {center_mean:.6f}")
|
print(f"Среднее значение отклика в центре: {center_mean:.6f}")
|
||||||
print(f"Дисперсия в центре (несмещённая): {center_var:.6f}")
|
print(f"Дисперсия в центре (несмещённая): {center_var:.6f}")
|
||||||
print(f"Стандартное отклонение: {np.sqrt(center_var):.6f}")
|
print(f"Стандартное отклонение: {np.sqrt(center_var):.6f}")
|
||||||
|
|
||||||
|
# Доверительный интервал для дисперсии (хи-квадрат)
|
||||||
lower, upper = chi2_confidence_interval(center_var, len(center_indices), alpha=alpha)
|
lower, upper = chi2_confidence_interval(center_var, len(center_indices), alpha=alpha)
|
||||||
if not np.isnan(lower):
|
if not np.isnan(lower):
|
||||||
print(f"Доверительный интервал для дисперсии (уровень доверия {1-alpha}): [{lower:.6f}, {upper:.6f}]")
|
print(f"Доверительный интервал для дисперсии (уровень доверия {1-alpha}): [{lower:.6f}, {upper:.6f}]")
|
||||||
else:
|
else:
|
||||||
print("\nНедостаточно центральных точек (нужно минимум 2).")
|
print("\nНедостаточно центральных точек (нужно минимум 2).")
|
||||||
|
|
||||||
|
# ---------- Шаг 5: Опция исключения незначимых факторов ----------
|
||||||
if k > 0 and not np.isnan(t_crit):
|
if k > 0 and not np.isnan(t_crit):
|
||||||
print("\nХотите исключить незначимые факторы и пересчитать модель?")
|
print("\nХотите исключить незначимые факторы и пересчитать модель?")
|
||||||
choice = input("(y/n, по умолчанию n): ").strip().lower()
|
choice = input("(y/n, по умолчанию n): ").strip().lower()
|
||||||
if choice == 'y':
|
if choice == 'y':
|
||||||
|
# Маска: первый столбец (свободный член) всегда включаем
|
||||||
significant = [True] + [abs(t_stats[i]) > t_crit for i in range(1, k+1)]
|
significant = [True] + [abs(t_stats[i]) > t_crit for i in range(1, k+1)]
|
||||||
|
|
||||||
|
# Если все факторы значимы, упрощённая модель не отличается
|
||||||
if all(significant):
|
if all(significant):
|
||||||
print("Все факторы значимы. Упрощённая модель не отличается от полной.")
|
print("Все факторы значимы. Упрощённая модель не отличается от полной.")
|
||||||
return
|
else:
|
||||||
|
# Выбираем только значимые столбцы
|
||||||
X_new = X[:, significant]
|
X_new = X[:, significant]
|
||||||
|
# Пересчитываем регрессию
|
||||||
reg_new = compute_regression(X_new, y)
|
reg_new = compute_regression(X_new, y)
|
||||||
beta_new = reg_new["beta"]
|
beta_new = reg_new["beta"]
|
||||||
se_new = reg_new["se"]
|
se_new = reg_new["se"]
|
||||||
t_stats_new = reg_new["t_stats"]
|
t_stats_new = reg_new["t_stats"]
|
||||||
df_res_new = reg_new["df_res"]
|
df_res_new = reg_new["df_res"]
|
||||||
y_pred_new = reg_new["y_pred"]
|
y_pred_new = reg_new["y_pred"]
|
||||||
|
|
||||||
if np.isnan(beta_new).any():
|
if np.isnan(beta_new).any():
|
||||||
print("Ошибка при пересчёте упрощённой модели.")
|
print("Ошибка при пересчёте упрощённой модели.")
|
||||||
return
|
else:
|
||||||
|
# Обновляем список имён факторов
|
||||||
factor_names_new = ["Свободный член"] + [factor_names[i-1] for i in range(1, k+1) if significant[i]]
|
factor_names_new = ["Свободный член"] + [factor_names[i-1] for i in range(1, k+1) if significant[i]]
|
||||||
k_new = len(factor_names_new) - 1
|
k_new = len(factor_names_new) - 1
|
||||||
|
|
||||||
print("\n=== Упрощённая модель (исключены незначимые факторы) ===")
|
print("\n=== Упрощённая модель (исключены незначимые факторы) ===")
|
||||||
|
# Выводим статистику для упрощённой модели
|
||||||
print_and_get_tcrit(beta_new, se_new, t_stats_new, df_res_new, y_pred_new, y, k_new, factor_names_new[1:], alpha)
|
print_and_get_tcrit(beta_new, se_new, t_stats_new, df_res_new, y_pred_new, y, k_new, factor_names_new[1:], alpha)
|
||||||
print("\nСовет: для использования упрощённой модели в крутом восхождении установите шаги для исключённых факторов равными 0 в планировании эксперимента.")
|
print("\nСовет: для использования упрощённой модели в крутом восхождении установите шаги для исключённых факторов равными 0 в планировании эксперимента.")
|
||||||
|
|
||||||
|
# ---------- Шаг 6: Интерпретация результатов ----------
|
||||||
|
# Создаём полный список имён (включая свободный член) для интерпретатора
|
||||||
|
factor_names_full = ["Свободный член"] + factor_names
|
||||||
|
|
||||||
|
# Формируем полное заключение с помощью интерпретатора
|
||||||
|
interpretation = full_interpretation(
|
||||||
|
r2=r2,
|
||||||
|
r2_adj=r2_adj,
|
||||||
|
f_stat=f_stat,
|
||||||
|
f_pvalue=f_pvalue,
|
||||||
|
beta=beta,
|
||||||
|
t_stats=t_stats,
|
||||||
|
p_values=None, # пока не вычисляем p-values для коэффициентов
|
||||||
|
factor_names=factor_names_full, # <-- передаём полный список
|
||||||
|
anova=anova,
|
||||||
|
center_mean=center_mean,
|
||||||
|
center_var=center_var,
|
||||||
|
n_center=len(center_indices) if len(center_indices) >= 2 else 0,
|
||||||
|
alpha=alpha
|
||||||
|
)
|
||||||
|
|
||||||
|
print("\n" + interpretation)
|
||||||
|
|||||||
@@ -0,0 +1,268 @@
|
|||||||
|
# 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)
|
||||||
@@ -128,3 +128,83 @@ def compute_gradient_points(beta, base_levels, step_factor, num_steps, factor_na
|
|||||||
point["Предсказанный отклик"] = predicted
|
point["Предсказанный отклик"] = predicted
|
||||||
points.append(point)
|
points.append(point)
|
||||||
return points
|
return points
|
||||||
|
|
||||||
|
|
||||||
|
def anova_regression(y, y_pred, X, center_indices=None):
|
||||||
|
"""
|
||||||
|
Выполняет полный дисперсионный анализ (ANOVA) для регрессионной модели.
|
||||||
|
|
||||||
|
Аргументы:
|
||||||
|
y – фактические значения отклика
|
||||||
|
y_pred – предсказанные моделью значения
|
||||||
|
X – матрица регрессоров (с единичным столбцом)
|
||||||
|
center_indices – индексы центральных точек (опционально)
|
||||||
|
|
||||||
|
Возвращает словарь с компонентами ANOVA:
|
||||||
|
SSR, SSE, SST, df_reg, df_res, df_pe, df_lof,
|
||||||
|
MSR, MSE, MS_pe, MS_lof,
|
||||||
|
F_regression, p_regression, F_lof, p_lof
|
||||||
|
"""
|
||||||
|
n = len(y)
|
||||||
|
p = X.shape[1] # число параметров (включая свободный член)
|
||||||
|
df_reg = p - 1 # степени свободы регрессии (без свободного члена)
|
||||||
|
df_res = n - p # степени свободы остатка
|
||||||
|
|
||||||
|
# Общая сумма квадратов (SST)
|
||||||
|
y_mean = np.mean(y)
|
||||||
|
SST = np.sum((y - y_mean)**2)
|
||||||
|
|
||||||
|
# Регрессионная сумма квадратов (SSR)
|
||||||
|
SSR = np.sum((y_pred - y_mean)**2)
|
||||||
|
|
||||||
|
# Остаточная сумма квадратов (SSE)
|
||||||
|
SSE = np.sum((y - y_pred)**2)
|
||||||
|
|
||||||
|
# Средние квадраты
|
||||||
|
MSR = SSR / df_reg if df_reg > 0 else np.nan
|
||||||
|
MSE = SSE / df_res if df_res > 0 else np.nan
|
||||||
|
|
||||||
|
# F-статистика для регрессии (значимость всей модели)
|
||||||
|
F_reg = MSR / MSE if MSE > 0 else np.nan
|
||||||
|
p_reg = 1 - stats.f.cdf(F_reg, df_reg, df_res) if not np.isnan(F_reg) and SCIPY_AVAILABLE else np.nan
|
||||||
|
|
||||||
|
# ----- Разложение остатка на чистую ошибку и неадекватность (если есть центр) -----
|
||||||
|
if center_indices is not None and len(center_indices) >= 2:
|
||||||
|
y_center = y[center_indices]
|
||||||
|
y_pred_center = y_pred[center_indices]
|
||||||
|
n_center = len(center_indices)
|
||||||
|
# Среднее в центре
|
||||||
|
y_center_mean = np.mean(y_center)
|
||||||
|
# Сумма квадратов чистой ошибки (SSPE) = сумма квадратов отклонений от среднего в центре
|
||||||
|
SSPE = np.sum((y_center - y_center_mean)**2)
|
||||||
|
df_pe = n_center - 1
|
||||||
|
MS_pe = SSPE / df_pe if df_pe > 0 else np.nan
|
||||||
|
|
||||||
|
# Сумма квадратов неадекватности (SSLOF) = SSE - SSPE
|
||||||
|
SSLOF = SSE - SSPE
|
||||||
|
df_lof = df_res - df_pe
|
||||||
|
MS_lof = SSLOF / df_lof if df_lof > 0 else np.nan
|
||||||
|
|
||||||
|
# F-статистика для проверки адекватности (lack-of-fit)
|
||||||
|
F_lof = MS_lof / MS_pe if MS_pe > 0 and not np.isnan(MS_lof) else np.nan
|
||||||
|
p_lof = 1 - stats.f.cdf(F_lof, df_lof, df_pe) if not np.isnan(F_lof) and SCIPY_AVAILABLE else np.nan
|
||||||
|
else:
|
||||||
|
SSPE = np.nan
|
||||||
|
df_pe = 0
|
||||||
|
MS_pe = np.nan
|
||||||
|
SSLOF = np.nan
|
||||||
|
df_lof = 0
|
||||||
|
MS_lof = np.nan
|
||||||
|
F_lof = np.nan
|
||||||
|
p_lof = np.nan
|
||||||
|
|
||||||
|
return {
|
||||||
|
"SSR": SSR, "SSE": SSE, "SST": SST,
|
||||||
|
"df_reg": df_reg, "df_res": df_res,
|
||||||
|
"MSR": MSR, "MSE": MSE,
|
||||||
|
"F_reg": F_reg, "p_reg": p_reg,
|
||||||
|
"SSPE": SSPE, "SSLOF": SSLOF,
|
||||||
|
"df_pe": df_pe, "df_lof": df_lof,
|
||||||
|
"MS_pe": MS_pe, "MS_lof": MS_lof,
|
||||||
|
"F_lof": F_lof, "p_lof": p_lof
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user