Стандартная ошибка регрессии python

АКТУАЛЬНОСТЬ ТЕМЫ

Общие положения

Про регрессионный анализ вообще, и его применение в DataScience написано очень много. Есть множество учебников, монографий, справочников и статей по прикладной статистике, огромное количество информации в интернете, примеров расчетов. Можно найти множество кейсов, реализованных с использованием средств Python. Казалось бы — что тут еще можно добавить?

Однако, как всегда, есть нюансы:

1. Регрессионный анализ — это прежде всего процесс, набор действий исследователя по определенному алгоритму: «подготовка исходных данных — построение модели — анализ модели — прогнозирование с помощью модели». Это ключевая особенность. Не представляет особой сложности сформировать DataFrame исходных данных и построить модель, запустить процедуру из библиотеки statsmodels. Однако подготовка исходных данных и последующий анализ модели требуют гораздо больших затрат человеко-часов специалиста и строк программного кода, чем, собственно, построение модели. На этих этапах часто приходится возвращаться назад, корректировать модель или исходные данные. Этому, к сожалению, во многих источниках, не удаляется достойного внимания, а иногда — и совсем не уделяется внимания, что приводит к превратному представлению о регрессионном анализе.

2. Далеко не во всех источниках уделяется должное внимание интерпретации промежуточных и финальных результатов. Специалист должен уметь интерпретировать каждую цифру, полученную в ходе работы над моделью.

3. Далеко не все процедуры на этапах подготовки исходных данных или анализа модели в источниках разобраны подробно. Например, про проверку значимости коэффициента детерминации найти информацию не представляет труда, а вот про проверку адекватности модели, построение доверительных интервалов регрессии или про специфические процедуры (например, тест Уайта на гетероскедастичность) информации гораздо меньше.

4. Своеобразная сложность может возникнуть с проверкой статистических гипотез: для отечественной литературы по прикладной статистике больше характерно проверять гипотезы путем сравнения расчетного значения критерия с табличным, а в иностранных источниках чаще определяется расчетный уровень значимости и сравнивается с заданным (чаще всего 0.05 = 1-0.95). В разных источниках информации реализованы разные подходы. Инструменты python (прежде всего библиотеки scipy и statsmodels) также в основном оперируют с расчетным уровнем значимости.

5. Ну и, наконец, нельзя не отметить, что техническая документация библиотеки statsmodels составлена, на мой взгляд, далеко не идеально: информация излагается путано, изобилует повторами и пропусками, описание классов, функций и свойств выполнено фрагментарно и количество примеров расчетов — явно недостаточно.

Поэтому я решил написать ряд обзоров по регрессионному анализу средствами Python, в которых акцент будет сделан на практических примерах, алгоритме действий исследователя, интерпретации всех полученных результатов, конкретных методических рекомендациях. Буду стараться по возможности избегать теории (хотя совсем без нее получится) — все-таки предполагается, что специалист DataScience должен знать теорию вероятностей и математическую статистику, хотя бы в рамках курса высшей математики для технического или экономического вуза.

В данном статье остановимся на самои простом, классическом, стереотипном случае — простой линейной регрессии (simple linear regression), или как ее еще принято называть — парной линейной регрессионной модели (ПЛРМ) — в ситуации, когда исследователя не подстерегают никакие подводные камни и каверзы — исходные данные подчиняются нормальному закону, в выборке отсутствуют аномальные значения, отсутствует ложная корреляция. Более сложные случаи рассмотрим в дальнейшем.

Для построение регрессионной модели будем пользоваться библиотекой statsmodels.

В данной статье мы рассмотрим по возможности полный набор статистических процедур. Некоторые из них (например, дескриптивная статистика или дисперсионный анализ регрессионной модели) могут показаться избыточными. Все так, но эти процедуры улучшают наше представление о процессе и об исходных данных, поэтому в разбор я их включил, а каждый исследователь сам вправе для себя определить, потребуются ему эти процедуры или нет.

Краткий обзор источников

Источников информации по корреляционному и регрессионному анализу огромное количество, в них можно просто утонуть. Поэтому позволю себе просто порекомендовать ряд источников, на мой взгляд, наиболее полезных:

  1. Кобзарь А.И. Прикладная математическая статистика. Для инженеров и научных работников. — М.: ФИЗМАТЛИТ, 2006. — 816 с.

  2. Львовский Е.Н. Статистические методы построения эмпирических формул. — М.: Высшая школа, 1988. — 239 с.

  3. Фёрстер Э., Рёнц Б. Методы корреляционного и регрессионного анализа / пер с нем. — М.: Финансы и статистика, 1983. — 302 с.

  4. Афифи А., Эйзен С. Статистический анализ. Подход с использованием ЭВМ / пер с англ. — М.: Мир, 1982. — 488 с.

  5. Дрейпер Н., Смит Г. Прикладной регрессионный анализ. Книга 1 / пер.с англ. — М.: Финансы и статистика, 1986. — 366 с.

  6. Айвазян С.А. и др. Прикладная статистика: Исследование зависимостей. — М.: Финансы и статистика, 1985. — 487 с.

  7. Прикладная статистика. Основы эконометрики: В 2 т. 2-е изд., испр. — Т.2: Айвазян С.А. Основы эконометрики. — М.: ЮНИТИ-ДАНА, 2001. — 432 с.

  8. Магнус Я.Р. и др. Эконометрика. Начальный курс — М.: Дело, 2004. — 576 с.

  9. Носко В.П. Эконометрика. Книга 1. — М.: Издательский дом «Дело» РАНХиГС, 2011. — 672 с.

  10. Брюс П. Практическая статистика для специалистов Data Science / пер. с англ. — СПб.: БХВ-Петербург, 2018. — 304 с.

  11. Уатт Дж. и др. Машинное обучение: основы, алгоритмы и практика применения / пер. с англ. — СПб.: БХВ-Петербург, 2022. — 640 с.

Прежде всего следует упомянуть справочник Кобзаря А.И. [1] — это безусловно выдающийся труд. Ничего подобного даже близко не издавалось. Всем рекомендую иметь под рукой.

Есть очень хорошее практическое пособие [2] — для начинающих и практиков.>

Добротная работа немецких авторов [3]. Все разобрано подробно, обстоятельно, с примерами — очень хорошая книга. Примеры приведены из области экономики.

Еще одна добротная работа — [4], с примерами медико-биологического характера.

Работа [5] считается одним из наиболее полных изложений прикладного регрессионного анализа.

Более сложные работы — [6] (классика жанра), [7], [8], [9] — выдержаны на достаточно высоком математическом уровне, примеры из экономической области.

Свежие работы [10] (с примерами на языке R) и [11] (с примерами на python).

Cтатьи

Статей про регрессионный анализ в DataScience очень много, обращаю внимание на некоторые весьма полезные из них.

Серия статей «Python, корреляция и регрессия», охватывающая весь процесс регрессионного анализа:

  • первичная обработка данных, визуализация и корреляционный анализ;

  • регрессия;

  • теория матриц в регрессионном анализе, проверка  адекватности, мультиколлинеарность;

  • прогнозирование с помощью регрессионных моделей.

Очень хороший обзор «Интерпретация summary из statsmodels для линейной регрессии». В этой статье даны очень полезные ссылки:

  • Statistical Models

  • Interpreting Linear Regression Through statsmodels .summary()

Статья «Регрессионные модели в Python».

Основные предпосылки (гипотезы) регрессионного анализа

Очень кратко — об этом написано тысячи страниц в учебниках — но все же вспомним некоторые основы теории.

Проверка исходных предпосылок является очень важным моментом при статистическом анализе регрессионной модели. Если мы рассматриваем классическую линейную регрессионную модель вида:

то основными предпосылками при использовании обычного метода наименьших квадратов (МНК) для оценки ее параметров являются:

  1. Среднее значение (математическое ожидание) случайной составляющей равно нулю:

  1. Дисперсия случайной составляющей является постоянной:

В случае нарушения данного условия мы сталкиваемся с явлением гетероскедастичности.

  1. Значения случайной составляющей статистически независимы (некоррелированы) между собой:

В случае нарушения данного условия мы сталкиваемся с явлением автокорреляции.

  1. Условие существования обратной матрицы

что эквивалентно одному из двух следующих условий:

то есть число наблюдений должно превышать число параметров.

  1. Значения случайной составляющей некоррелированы со значениями независимых переменных:

  1. Случайная составляющая имеет нормальный закон распределения (с математическим ожиданием равным нулю — следует из условия 1):

Более подробно — см.: [3, с.90], [4, с.147], [5, с.122], [6, с.208], [7, с.49], [8, с.68], [9, с.88].

Кроме гетероскедастичности и автокорреляции возможно возникновение и других статистических аномалий — мультиколлинеарности, ложной корреляции и т.д.

Доказано, что оценки параметров, полученные с помощью МНК, обладают наилучшими свойствами (несмещенность, состоятельность, эффективность) при соблюдении ряда условий:

  • выполнение приведенных выше исходных предпосылок регрессионного анализа;

  • число наблюдений на одну независимую переменную должно быть не менее 5-6;

  • должны отсутствовать аномальные значения (выбросы).

Кроме обычного МНК существуют и другие его разновидности (взвешенный МНК, обобщенный МНК), которые применяются при наличии статистических аномалий. Кроме МНК применяются и другие методы оценки параметров моделей. В этом обзоре мы эти вопросы рассматривать не будем.

Алгоритм проведения регрессионного анализа

Алгоритм действий исследователя при построении регрессионной модели (полевые работы мы, по понятным причинам, не рассматриваем — считаем, что исходные данные уже получены):

  1. Подготовительный этап — постановка целей и задач исследования.

  2. Первичная обработка исходных данных — об этом много написано в учебниках и пособиях по DataScience, сюда могут относится:

  • выявление нерелевантных признаков (признаков, которые не несут полезной информации), нетипичных данных (выбросов), неинформативных признаков (имеющих большое количество одинаковых значений) и работа с ними (удаление/преобразование);

  • выделение категориальных признаков;

  • работа с пропущенными значениями;

  • преобразование признаков-дат в формат datetime и т.д.

  1. Визуализация исходных данных — предварительный графический анализ.

  2. Дескриптивная (описательная) статистика — расчет выборочных характеристик и предварительные выводы о свойствах исходных данных.

  3. Исследование закона распределения исходных данных и, при необходимости, преобразование исходных данных к нормальному закону распределения.

  4. Выявление статистически аномальных значений (выбросов), принятие решения об их исключении.

    Этапы 4, 5 и 6 могут быть при необходимости объединены.

  5. Корреляционный анализ — исследование корреляционных связей между исходными данными; это разведка перед проведением регрессионного анализа.

  6. Построение регрессионной модели:

  • выбор моделей;

  • выбор методов;

  • оценка параметров модели.

  1. Статистический анализ регрессионной модели:  

  • оценка ошибок аппроксимации (error metrics);

  • анализ остатков (проверка нормальности распределения остатков и гипотезы о равенстве нулю среднего значения остатков);

  • проверка адекватности модели;

  • проверка значимости коэффициента детерминации;

  • проверка значимости коэффициентов регрессии;

  • проверка мультиколлинеарности (для множественных регрессионных моделей; вообще мультиколлинеарные переменные выявляются еще на стадии корреляционного анализа);

  • проверка автокорреляции;

  • проверка гетероскедастичности.

   Этапы 8 и 9 могут быть при необходимости повторяться несколько раз.

  1. Сравнительный анализ нескольких регрессионных моделей, выбор наилучшей (при необходимости).

  2. Прогнозирование с помощью регрессионной модели и оценка качества прогноза.

  3. Выводы и рекомендации.

Само собой, этот алгоритм не есть истина в последней инстанции — в зависимости от особенностей исходных данных и вида модели могут возникать дополнительные задачи.

Применение пользовательских функций

Далее в обзоре мной будут использованы несколько пользовательских функций для решения разнообразных задач. Все эти функции созданы для облегчения работы и уменьшения размера программного кода. Данные функции загружается из пользовательского модуля my_module__stat.py, который доступен в моем репозитории на GitHub. Лично мне так удобнее работать, хотя каждый исследователь сам формирует себе инструменты по душе — особенно в части визуализации. Желающие могут пользоваться этими функциями, либо создать свои.

Итак, вот перечень данных функций:

  • graph_scatterplot_sns — функция позволяет построить точечную диаграмму средствами seaborn и сохранить график в виде png-файла;

  • graph_hist_boxplot_probplot_XY_sns  — функция позволяет визуализировать исходные данные для простой линейной регрессии путем одновременного построения гистограммы, коробчатой диаграммы и вероятностного графика (для переменных X и Y) средствами seaborn и сохранить график в виде png-файла; имеется возможность выбирать, какие графики строить (h — hist, b — boxplot, p — probplot);

  • descriptive_characteristics — функция возвращает в виде DataFrame набор статистических характеристики выборки, их ошибок и доверительных интервалов;

  • detecting_outliers_mad_test — функция выполняет проверку наличия аномальных значений (выбросов) по критерию наибольшего абсолютного отклонения (более подробно — см.[1, с.547]);

  • norm_distr_check — проверка нормальности распределения исходных данных с использованием набора из нескольких статистических тестов;

  • corr_coef_check — функция выполняет расчет коэффициента линейной корреляции Пирсона, проверку его значимости и расчет доверительных интервалов; об этой функции я писал в своей статье.

  • graph_regression_plot_sns —  — функция позволяет построить график регрессионной модели.

Ряд пользовательских функций мы создаем в процессе данного обзора (они тоже включены в пользовательский модуль my_module__stat.py):

  • regression_error_metrics — расчет ошибок аппроксимации регрессионной модели;

  • ANOVA_table_regression_model — вывод таблицы дисперсионного анализа регрессионной модели;

  • regression_model_adequacy_check — проверка адекватности регрессионной модели по критерию Фишера;

  • determination_coef_check — проверка значимости коэффициента детерминации по критерию Фишера;

  • regression_coef_check — проверка значимости коэффициентов регрессии по критеирю Стьюдента;

  • Goldfeld_Quandt_test, Breush_Pagan_test, White_test — проверка гетероскедастичности с использование тестов Голдфелда-Квандта, Бриша-Пэгана и Уайта соответственно;

  • regression_pair_predict — функция для прогнозирования с помощью парной регрессионной модели: рассчитывает прогнозируемое значение переменной Y по заданной модели, а также доверительные интервалы среднего и индивидуального значения для полученного прогнозируемого значения Y;

  • graph_regression_pair_predict_plot_sns — прогнозирование: построение графика регрессионной модели (с доверительными интервалами) и вывод расчетной таблицы с данными для заданной области значений X.

ПОСТАНОВКА ЗАДАЧИ

В качестве примера рассмотрим практическую задачу из области экспертизы промышленной безопасности — калибровку ультразвукового прибора для определения прочности бетона.

Итак, суть задачи: при обследовании несущих конструкций зданий и сооружений эксперт определяет прочность бетона с использованием ультразвукового прибора «ПУЛЬСАР-2.1», для которого необходимо предварительно построить градуировочную зависимость. Заключается это в следующем — производятся замеры с фиксацией следующих показателей:

  • X — показания ультразвукового прибора «ПУЛЬСАР-2.1» (м/с)

  • Y — результаты замера прочности бетона (методом отрыва со скалыванием) склерометром ИПС-МГ4.03.

Предполагается, что между показателями X и Y имеется линейная регрессионная зависимость, которая позволит прогнозировать прочность бетона на основании измерений, проведенных прибором «ПУЛЬСАР-2.1».

Были выполнены замеры фактической прочности бетона конструкций для бетонов одного вида с одним типом крупного заполнителя, с единой технологией производства. Для построения были выбраны 14 участков (не менее 12), включая участки, в которых значение косвенного показателя максимальное, минимальное и имеет промежуточные значения.

Настройка заголовков отчета:

# Общий заголовок проекта
Task_Project = 'Калибровка ультразвукового прибора "ПУЛЬСАР-2.1" \nдля определения прочности бетона'

# Заголовок, фиксирующий момент времени
AsOfTheDate = ""

  # Заголовок раздела проекта
Task_Theme = ""

# Общий заголовок проекта для графиков
Title_String = f"{Task_Project}\n{AsOfTheDate}"

# Наименования переменных
Variable_Name_X = "Скорость УЗК (м/с)"
Variable_Name_Y = "Прочность бетона (МПа)"

# Константы
INCH = 25.4    # мм/дюйм
  DecPlace = 5    # number of decimal places - число знаков после запятой

# Доверительная вероятность и уровень значимости:
p_level = 0.95
a_level = 1 - p_level   

Подключение модулей и библиотек:

# Стандартные модули и библиотеки

import os    # загрузка модуля для работы с операционной системой
import sys
import platform
print('{:<35}{:^0}'.format("Текущая версия Python: ", platform.python_version()), '\n')

import math
from math import *    # подключаем все содержимое модуля math, используем без псевдонимов

import numpy as np
#print ("Текущая версия модуля numpy: ", np.__version__)
print('{:<35}{:^0}'.format("Текущая версия модуля numpy: ", np.__version__))
from numpy import nan

import scipy as sci
print('{:<35}{:^0}'.format("Текущая версия модуля scipy: ", sci.__version__))
import scipy.stats as sps

import pandas as pd
print('{:<35}{:^0}'.format("Текущая версия модуля pandas: ", pd.__version__))

import matplotlib as mpl
print('{:<35}{:^0}'.format("Текущая версия модуля matplotlib: ", mpl.__version__))
import matplotlib.pyplot as plt

import seaborn as sns
print('{:<35}{:^0}'.format("Текущая версия модуля seaborn: ", sns.__version__))

import statsmodels.api as sm
import statsmodels.formula.api as smf
import statsmodels.graphics.api as smg
import statsmodels.stats.api as sms
from statsmodels.compat import lzip
print('{:<35}{:^0}'.format("Текущая версия модуля statsmodels: ", sm.__version__))

import statistics as stat    # module 'statistics' has no attribute '__version__'

import sympy as sym
print('{:<35}{:^0}'.format("Текущая версия модуля sympy: ", sym.__version__))

# Настройки numpy
np.set_printoptions(precision = 4, floatmode='fixed')

# Настройки Pandas
pd.set_option('display.max_colwidth', None)    # текст в ячейке отражался полностью вне зависимости от длины
pd.set_option('display.float_format', lambda x: '%.4f' % x)

# Настройки seaborn
sns.set_style("darkgrid")
sns.set_context(context='paper', font_scale=1, rc=None)    # 'paper', 'notebook', 'talk', 'poster', None

# Настройки Mathplotlib
f_size = 8    # пользовательская переменная для задания базового размера шрифта
plt.rcParams['figure.titlesize'] = f_size + 12    # шрифт заголовка
plt.rcParams['axes.titlesize'] = f_size + 10      # шрифт заголовка
plt.rcParams['axes.labelsize'] = f_size + 6       # шрифт подписей осей
plt.rcParams['xtick.labelsize'] = f_size + 4      # шрифт подписей меток
plt.rcParams['ytick.labelsize'] = f_size + 4
plt.rcParams['legend.fontsize'] = f_size + 6      # шрифт легенды

# Пользовательские модули и библиотеки

Text1 = os.getcwd()    # вывод пути к текущему каталогу
#print(f"Текущий каталог: {Text1}")

sys.path.insert(1, "D:\REPOSITORY\MyModulePython")

from my_module__stat import *

ФОРМИРОВАНИЕ ИСХОДНЫХ ДАННЫХ

Показания ультразвукового прибора «ПУЛЬСАР-2.1» (м/с):

X = np.array([
    4416, 4211, 4113, 4110, 4122,
    4427, 4535, 4311, 4511, 4475,
    3980, 4490, 4007, 4426
    ])

Результаты замера прочности бетона (методом отрыва со скалыванием) прибором ИПС-МГ4.03:

Y = np.array([
    34.2, 35.1, 31.5, 30.8, 30.0,
    34.0, 35.4, 35.8, 38.0, 37.7,
    30.0, 37.8, 31.0, 35.2
    ])

Запишем данные в DataFrame:

calibrarion_df = pd.DataFrame({
    'X': X,
    'Y': Y})
display(calibrarion_df)
calibrarion_df.info()

Сохраняем данные в csv-файл:

calibrarion_df.to_csv(
    path_or_buf='data/calibrarion_df.csv',
    mode='w+',
    sep=';')

Cоздаем копию исходной таблицы для работы:

dataset_df = calibrarion_df.copy()

ВИЗУАЛИЗАЦИЯ ДАННЫХ

Границы значений переменных (при построении графиков):

(Xmin_graph, Xmax_graph) = (3800, 4800)
(Ymin_graph, Ymax_graph) = (25, 45)
# Пользовательская функция
graph_scatterplot_sns(
    X, Y,
    Xmin=Xmin_graph, Xmax=Xmax_graph,
    Ymin=Ymin_graph, Ymax=Ymax_graph,
    color='orange',
    title_figure=Task_Project,
    x_label=Variable_Name_X,
    y_label=Variable_Name_Y,
    s=100,
    file_name='graph/scatterplot_XY_sns.png')

Существует универсальный набор графиков — гистограмма, коробчатая диаграмма, вероятностный график — которые позволяют исследователю сделать предварительные выводы о свойствах исходных данных.

Так как объем выборки невелик (n=14), строить гистограммы распределения переменных X и Y не имеет смысла, поэтому ограничимся построением коробчатых диаграмм и вероятностных графиков:

# Пользовательская функция
graph_hist_boxplot_probplot_XY_sns(
    data_X=X, data_Y=Y,
    data_X_min=Xmin_graph, data_X_max=Xmax_graph,
    data_Y_min=Ymin_graph, data_Y_max=Ymax_graph,  
    graph_inclusion='bp',    # выбираем для построения виды графиков: b - boxplot, p - probplot)
    data_X_label=Variable_Name_X,
    data_Y_label=Variable_Name_Y,
    title_figure=Task_Project,
    file_name='graph/hist_boxplot_probplot_XY_sns.png')    

Для сравнения характера распределений переменных X и Y возможно также построить совмещенную коробчатую диаграмму по стандартизованным данным:

# стандартизуем исходные данные
standardize_df = lambda X: ((X - np.mean(X))/np.std(X))

dataset_df_standardize = dataset_df.copy()
dataset_df_standardize = dataset_df_standardize.apply(standardize_df)
display(dataset_df_standardize)

# построим график
fig, axes = plt.subplots(figsize=(210/INCH, 297/INCH/2))
axes.set_title("Распределение стандартизованных переменных X и Y", fontsize = 16)
sns.boxplot(
    data=dataset_df_standardize,    
    orient='h',
    width=0.5,
    ax=axes)
plt.show()

Графический анализ позволяет сделать следующие выводы:

  1. Отсутствие выбросов на коробчатых диаграммах свидетельствует об однородности распределения переменных.

  2. Смещение медианы вправо на коробчатых диаграммах свидетельствует о левосторонней асимметрии распределения.

ДЕСКРИПТИВНАЯ (ОПИСАТЕЛЬНАЯ СТАТИСТИКА)

Собственно говоря, данный этап требуется проводить далеко не всегда, однако с помощью статистических характеристик выборки мы тоже можем сделать полезные выводы.

Описательная статистика исходных данных средствами библиотеки Pandas — самый простой вариант:

dataset_df.describe()

Описательная статистика исходных данных средствами библиотеки statsmodels — более развернутый вариант, с большим количеством показателей:

from statsmodels.stats.descriptivestats import Description
result = Description(
    dataset_df,
    stats=["nobs", "missing", "mean", "std_err", "ci", "ci", "std", "iqr", "mad", "coef_var", "range", "max", "min", "skew", "kurtosis", "mode",
           "median", "percentiles", "distinct", "top", "freq"],
    alpha=a_level,
    use_t=True)
display(result.summary())

Описательная статистика исходных данных с помощью пользовательской функции descriptive_characteristics:

# Пользовательская функция
descriptive_characteristics(X)

Выводы:

  1. Сравнение показателей среднего арифметического (mean) и медианы (median) свидетельствует о левосторонней асимметрии (т.к.mean < median).

  2. Значение коэффициента вариации CV = 0.0445 и доверительный интервал для него 0.0336 ≤ CV ≤ 0.0657 свидетельствует об однородности исходных данных (т.к. CV ≤ 0.33).

  3. Значение показателя асимметрии skew (As) = -0.3101 свидетельствует об умеренной левосторонней асимметрии распределении (т.к. |As| ≤ 0.5, As < 0).

  4. Значение показателя эксцесса kurtosis (Es) = -1.4551 свидетельствует о плосковершинном распределении (platykurtic distribution) (т.к. Es < 0).

# Пользовательская функция
descriptive_characteristics(Y)

Выводы:

  1. Сравнение показателей среднего арифметического (mean) и медианы (median) свидетельствует о левосторонней асимметрии (т.к.mean < median).

  2. Значение коэффициента вариации CV = 0.0822 и доверительный интервал для него 0.06202 ≤ CV ≤ 0.1217 свидетельствует об однородности исходных данных (т.к. CV ≤ 0.33).

  3. Значение показателя асимметрии skew (As) = -0.1109 свидетельствует о приблизительно симметричном распределении (т.к. |As| ≤ 0.25).

  4. Значение показателя эксцесса kurtosis (Es) = -1.3526 свидетельствует о плосковершинном распределении (platykurtic distribution) (т.к. Es < 0).

ПРОВЕРКА НОРМАЛЬНОСТИ РАСПРЕДЕЛЕНИЯ

Для проверки нормальности распределения использована пользовательская функция norm_distr_check, которая объединяет в себе набор стандартных статистических тестов проверки нормальности. Все тесты относятся к стандартному инструментарию Pyton (библиотека scipy, модуль stats), за исключением теста Эппса-Палли (Epps-Pulley test); о том, как реализовать этот тест средствами Pyton я писал в своей статье https://habr.com/ru/post/685582/.

Примечание: для использования функции norm_distr_check в каталог с ipynb-файлом необходимо поместить папку table c файлом Tep_table.csv, который содержит табличные значения статистики критерия Эппса-Палли.

# пользовательская функция
norm_distr_check(X)

# Пользовательская функция
norm_distr_check (Y)

Вывод: большинство статистических тестов позволяют принять гипотезу о нормальности распределения переменных X и Y.

ПРОВЕРКА АНОМАЛЬНЫХ ЗНАЧЕНИЙ (ВЫБРОСОВ)

Статистическую проверку аномальных значений (выбросов) не стоит путать с проверкой выбросов, которая проводится на этапе первичной обработки результатов наблюдений. Последняя проводится с целью отсеять явные ошибочные данные (например, в результате неправильно поставленной запятой величина показателя может увеличиться/уменьшиться на порядок); здесь же мы говорим о статистической проверке данных, которые уже прошли этап первичной обработки.

Имеется довольно много критериев для проверки аномальных значений (подробнее см.[1]); вообще данная процедура довольно неоднозначная:

  • критерии зависят от вида распределения;

  • мало данных о сравнительной мощности этих критериев;

  • даже в случае принятии гипотезы о нормальном распределении в выборке могут быть обнаружены аномальные значения и пр.

Кроме существует дилемма: если какие-то значения в выборке признаны выбросами — стоит или не стоит исследователю исключать их? Ведь каждое значение несет в себе информацию, причем иногда весьма ценную, а сильно отклоняющиеся от основного массива данные (которые не являются выбросами в смысле первичной обработки, но являются статистическим значимыми аномальными значениями) могут кардинально изменить статистический вывод.

В общем, о задаче выявления аномальных значений (выбросов) можно написать отдельно, а пока, в данном разборе, ограничимся проверкой аномальных значений по критерию наибольшего максимального отклонения (см.[1, с.547]) с помощью пользовательской функции detecting_outliers_mad_test. Данные функция возвращает DataFrame, которые включает список аномальных значений со следующими признаками:

  • value — проверяемое значение из выборки;

  • mad_calc и mad_table — расчетное и табличное значение статистики критерия;

  • outlier_conclusion — вывод (выброс или нет).

Обращаю внимание, что критерий наибольшего максимального отклонения можно использовать только для нормально распределенных данных.

# пользовательская функция
print("Проверка наличия выбросов переменной X:\n")
result = detecting_outliers_mad_test(X)
mask = (result['outlier_conclusion'] == 'outlier')
display(result[mask])

# пользовательская функция
print("Проверка наличия выбросов переменной Y:\n")
result = detecting_outliers_mad_test(Y)
mask = (result['outlier_conclusion'] == 'outlier')
display(result[mask])

Вывод: в случае обеих переменных X и Y список пуст, следовательно, аномальных значений (выбросов) не выявлено.

КОРРЕЛЯЦИОННЫЙ АНАЛИЗ

Корреляционный анализ — это разведка перед построением регрессионной модели.

Выполним расчет коэффициента линейной корреляции Пирсона, проверку его значимости и построение доверительных интервалов с помощью пользовательской функции corr_coef_check (про эту функцию более подробно написано в моей статье https://habr.com/ru/post/683442/):

# пользовательская функция
display(corr_coef_check(X, Y, scale='Evans'))

Выводы:

  1. Значение коэффициента корреляции coef_value = 0.8900 свидетельствует о весьма сильной корреляционной связи (по шкале Эванса).

  2. Коэффициент корреляции значим по критерию Стьюдента: t_calc ≥ t_table, a_calc ≤ a_level.

  3. Доверительный интервал для коэффициента корреляции: 0.6621 ≤ coef_value ≤ 0.9625.

РЕГРЕССИОННЫЙ АНАЛИЗ

Предварительная визуализация

python позволяет выполнить предварительную визуализацию, например, с помощью функции jointplot библиотеки seaborn:

fig = plt.figure(figsize=(297/INCH, 210/INCH))
axes = sns.jointplot(
    x=X, y=Y,
    kind='reg',
    ci=95)
plt.show()

Построение модели

Выполним оценку параметров и анализ простой линейной регрессии (simple linear regression), используя библиотеку statsmodels (https://www.statsmodels.org/) и входящий в нее модуль линейной регрессии Linear Regression (https://www.statsmodels.org/stable/regression.html).

Данный модуль включает в себя классы, реализующие различные методы оценки параметров моделей линейной регрессии, в том числе:

  • класс OLS (https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.OLS.html#statsmodels.regression.linear_model.OLS) — Ordinary Least Squares (обычный метод наименьших квадратов).

  • класс WLS (https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.WLS.html#statsmodels.regression.linear_model.WLS) — Weighted Least Squares (метод взвешенных наименьших квадратов) (https://en.wikipedia.org/wiki/Weighted_least_squares), применяется, если имеет место гетероскедастичность данных (https://ru.wikipedia.org/wiki/Гетероскедастичность).

  • класс GLS (https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.GLS.html#statsmodels.regression.linear_model.GLS) — Generalized Least Squares (обобщенный метод наименьших квадратов) (https://en.wikipedia.org/wiki/Generalized_least_squares), применяется, если существует определенная степень корреляции между остатками в модели регрессии.

  • класс GLSAR (https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.GLSAR.html#statsmodels.regression.linear_model.GLSAR) — Generalized Least Squares with AR covariance structure (обобщенный метод наименьших квадратов, ковариационная структура с автокорреляцией — экспериментальный метод)

  • класс RecurciveLS (https://www.statsmodels.org/stable/examples/notebooks/generated/recursive_ls.html) — Recursive least squares (рекурсивный метод наименьших квадратов) (https://en.wikipedia.org/wiki/Recursive_least_squares_filter)

  • классы RollingOLS (https://www.statsmodels.org/stable/generated/statsmodels.regression.rolling.RollingOLS.html#statsmodels.regression.rolling.RollingOLS) и RollingWLS (https://www.statsmodels.org/stable/generated/statsmodels.regression.rolling.RollingWLS.html#statsmodels.regression.rolling.RollingWLS) — скользящая регрессия (https://www.statsmodels.org/stable/examples/notebooks/generated/rolling_ls.html, https://help.fsight.ru/ru/mergedProjects/lib/01_regression_models/rolling_regression.htm)

    и т.д.

Так как исходные данные подчиняются нормальному закону распределения и аномальные значения (выбросы) отсутствуют, воспользуемся для оценки параметров обычным методом наименьших квадратов (класс OLS):

model_linear_ols = smf.ols(formula='Y ~ X', data=dataset_df)
result_linear_ols = model_linear_ols.fit()
print(result_linear_ols.summary())

Альтернативная форма выдачи результатов:

print(result_linear_ols.summary2())

Результаты построения модели мы получаем как класс statsmodels.regression.linear_model.RegressionResults (https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.RegressionResults.html#statsmodels.regression.linear_model.RegressionResults).

Экспресс-выводы, которые мы можем сразу сделать из результатов построения модели:

  1. Коэффициенты регрессии модели Y = b0 + b1∙X:

    • Intercept = b0 = -21.3741

    • b1 = 0.0129

  2. Коэффициент детерминации R-squared = 0.776, его скорректированная оценка Adj. R-squared = 0.757 — это означает, что регрессионная модуль объясняет 75.75% вариации переменной Y.

  3. Проверка значимости коэффициента детерминации:

    • расчетное значение статистики критерия Фишера: F-statistic = 41.61

    • расчетный уровень значимости Prob (F-statistic) = 3.16e-05

    • так как значение Prob (F-statistic) < 0.05, то нулевая гипотеза R-squared = 0 НЕ ПРИНИМАЕТСЯ, т.е. коэффициент детерминации ЗНАЧИМ

  4. Проверка значимости коэффициентов регрессии:

    • расчетный уровень значимости P>|t| не превышает 0.05 — это означает, что оба коэффициента регрессии значимы

    • об этом же свидетельствует то, что доверительный интервал для обоих коэффициентов регрессии ([0.025; 0.975]) не включает в себя точку 0

    Также в таблице результатов содержится прочая информация по коэффициентам регрессии: стандартная ошибка Std.Err. расчетное значение статистики критерия Стьюдента t для проверки гипотезы о значимости.

  5. Анализ остатков модели:

    • Тест Omnibus — про этот тест подробно написано в https://en.wikipedia.org/wiki/Omnibus_test, https://medium.com/swlh/interpreting-linear-regression-through-statsmodels-summary-4796d359035a, http://work.thaslwanter.at/Stats/html/statsModels.html.

      Расчетное значение статистики критерия Omnibus = 3.466 — по сути расчетное значение F-критерия (см. https://en.wikipedia.org/wiki/Omnibus_test).

      Prob(Omnibus) = 0.177 — показывает вероятность нормального распределения остатков (значение 1 указывает на совершенно нормальное распределение).

      Учитывая, что в дальнейшем мы проверим нормальность распределения остатков по совокупности различных тестов, в том числе с достаточно высокой мощностью, и все тесты позволят принять гипотезу о нормальном распределении — в данном случае к тесту Omnibus возникают вопросы. С этим тестом нужно разбираться отдельно.

    • Skew = 0.014 и Kurtosis = 1.587 — показатели асимметрии и эксцесса остатков свидетельствуют, что распределение остатков практически симметричное, островершинное.

    • проверка нормальности распределения остатков по критерию Харке-Бера: расчетное значение статистики критерия Jarque-Bera (JB) = 1.164 и расчетный уровень значимости Prob(JB) = 0.559. К данным результатам также возникают вопросы, особенно, если учесть, что критерий Харке-Бера является асимптотическим, расчетное значение имеет распределение хи-квадрат, поэтому данный критерий рекомендуют применять только для больших выборок (см. https://en.wikipedia.org/wiki/Jarque–Bera_test). Проверку нормальности распределения остатков модели лучше проводить с использованием набора стандартных статистических тестов python (см. далее).

  6. Проверка автокорреляции по критерию Дарбина-Уотсона: Durbin-Watson = 1.443.

    Мы не будем здесь разбирать данный критерий, так как явление автокорреляции больше характерно для данных, выражаемых в виде временных рядов. Однако, для грубой оценки считается, что при расчетном значении статистики криетрия Дарбина=Уотсона а интервале [1; 2] автокорреляция отсутствует (см.https://en.wikipedia.org/wiki/Durbin–Watson_statistic).

    Более подробно про критерий Дарбина-Уотсона — см. [1, с.659].

Прочая информация, которую можно извлечь из результатов построения модели:

  1. Covariance Type — тип ковариации, подробнее см. https://habr.com/ru/post/681218/, https://towardsdatascience.com/simple-explanation-of-statsmodel-linear-regression-model-summary-35961919868b#:~:text=Covariance type is typically nonrobust,with respect to each other.

  2. Scale — масштабный коэффициент для ковариационной матрицы (https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.RegressionResults.scale.html#statsmodels.regression.linear_model.RegressionResults.scale), равен величине Mean squared error (MSE) (cреднеквадратической ошибке), об подробнее см. далее, в разделе про ошибки аппроксимации моделей.

  3. Показатели сравнения качества различных моделей:

    • Log-Likelihood — логарифмическая функция правдоподобия, подробнее см. https://en.wikipedia.org/wiki/Likelihood_function#Log-likelihood, https://habr.com/ru/post/433804/

    • AIC — информационный критерий Акаике (Akaike information criterion), подробнее см. https://en.wikipedia.org/wiki/Akaike_information_criterion

    • BIC — информационный критерий Байеса (Bayesian information criterion), подробнее см. https://en.wikipedia.org/wiki/Bayesian_information_criterion

    В данной статье мы эти показатели рассматривать не будем, так как задача выбора одной модели из нескольких перед нами не стоит.

  4. Число обусловленности Cond. No = 96792 используется для проверки мультиколлинеарности (считается, что мультиколлинеарность есть, если значение Cond. No > 30) (см. http://work.thaslwanter.at/Stats/html/statsModels.html). В нашем случае парной регрессионной модели о мультиколлинеарности речь не идет.

Далее будем извлекать данные из стандартного набора выдачи результатов и анализировать их более подробно. Последующие этапы вовсе не обязательно проводить в полном объеме при решении задач, но здесь мы рассмотрим их подробно.

Параметры и уравнение регрессионной модели

Извлечем параметры полученной модели — как свойство params модели:

print('Параметры модели: \n', result_linear_ols.params, type(result_linear_ols.params))

Имея параметры модели, можем формализовать уравнение модели Y = b0 + b1*X:

b0 = result_linear_ols.params['Intercept']
b1 = result_linear_ols.params['X']
Y_calc = lambda x: b0 + b1*x

График регрессионной модели

Для построения графиков регрессионных моделей можно воспользоваться стандартными возможностями библиотек statsmodels, seaborn, либо создать пользовательскую функцию — на усмотрение исследователя:

1. Построение графиков регрессионных моделей с использованием библиотеки statsmodels

С помощью функции statsmodels.graphics.plot_fit (https://www.statsmodels.org/stable/generated/statsmodels.graphics.regressionplots.plot_fit.html#statsmodels.graphics.regressionplots.plot_fit) — отображается график Y and Fitted vs.X (фактические и расчетные значения Y с доверительным интервалом для каждого значения Y):

fig, ax = plt.subplots(figsize=(297/INCH, 210/INCH))
fig = sm.graphics.plot_fit(
    result_linear_ols, 'X',
    vlines=True,    # это параметр отвечает за отображение доверительных интервалов для Y
    ax=ax)
ax.set_ylabel(Variable_Name_Y)
ax.set_xlabel(Variable_Name_X)
ax.set_title(Task_Project)
plt.show()

С помощью функции statsmodels.graphics.plot_regress_exog (https://www.statsmodels.org/stable/generated/statsmodels.graphics.regressionplots.plot_regress_exog.html#statsmodels.graphics.regressionplots.plot_regress_exog) — отображается область 2х2, которая содержит:

  • предыдущий график Y and Fitted vs.X;

  • график остатков Residuals versus X;

  • график Partial regression plot — график частичной регрессии, пытается показать эффект добавления другой переменной в модель, которая уже имеет одну или несколько независимых переменных (более подробно см. https://en.wikipedia.org/wiki/Partial_regression_plot);

  • график CCPR Plot (Component-Component plus Residual Plot) — еще один способ оценить влияние одной независимой переменной на переменную отклика, принимая во внимание влияние других независимых переменных (более подробно — см. https://towardsdatascience.com/calculating-price-elasticity-of-demand-statistical-modeling-with-python-6adb2fa7824d, https://www.kirenz.com/post/2021-11-14-linear-regression-diagnostics-in-python/linear-regression-diagnostics-in-python/).

fig = plt.figure(figsize=(297/INCH, 210/INCH))
sm.graphics.plot_regress_exog(result_linear_ols, 'X', fig=fig)
plt.show()

2. Построение графиков регрессионных моделей с использованием библиотеки seaborn

Воспользуемся модулем regplot библиотеки seaborn (https://seaborn.pydata.org/generated/seaborn.regplot.html). Данный модуль позволяет визуализировать различные виды регрессии:

  • линейную

  • полиномиальную

  • логистическую

  • взвешенную локальную регрессию (LOWESS — Locally Weighted Scatterplot Smoothing) (см. http://www.machinelearning.ru/wiki/index.php?title=Алгоритм_LOWESS, https://www.statsmodels.org/stable/generated/statsmodels.nonparametric.smoothers_lowess.lowess.html)

Более подробно про модуль regplot можно прочитать в статье: https://pyprog.pro/sns/sns_8_regression_models.html.

Есть более совершенный модуль lmplot (https://seaborn.pydata.org/generated/seaborn.lmplot.html), который объединяет в себе regplot и FacetGrid, но мы его здесь рассматривать не будем.

# создание рисунка (Figure) и области рисования (Axes)
fig = plt.figure(figsize=(297/INCH, 420/INCH/1.5))
ax1 = plt.subplot(2,1,1)
ax2 = plt.subplot(2,1,2)
# заголовок рисунка (Figure)
title_figure = Task_Project
fig.suptitle(title_figure, fontsize = 18)
# заголовок области рисования (Axes)
title_axes_1 = 'Линейная регрессионная модель'
ax1.set_title(title_axes_1, fontsize = 16)
# график регрессионной модели
order_mod = 1    # порядок модели
#label_legend_regr_model = 'фактические данные'
sns.regplot(
    #data=dataset_df,
    x=X, y=Y,
    #x_estimator=np.mean,
    order=order_mod,
    logistic=False,
    lowess=False,
    robust=False,
    logx=False,
    ci=95,
    scatter_kws={'s': 30, 'color': 'red'},
    line_kws={'color': 'blue'},
    #label=label_legend_regr_model,
    ax=ax1)
ax1.set_ylabel(Variable_Name_Y)
ax1.legend()
# график остатков
title_axes_2 = 'График остатков'
ax2.set_title(title_axes_2, fontsize = 16)
sns.residplot(
    #data=dataset_df,
    x=X, y=Y,
    order=order_mod,
    lowess=False,
    robust=False,
    scatter_kws={'s': 30, 'color': 'darkorange'},
    ax=ax2)
ax2.set_xlabel(Variable_Name_X)

plt.show()

3. Построение графиков регрессионных моделей с помощью пользовательской функции

# Пользовательская функция
graph_regression_plot_sns(
    X, Y,
    regression_model=Y_calc,
    Xmin=Xmin_graph, Xmax=Xmax_graph,
    Ymin=Ymin_graph, Ymax=Ymax_graph,
    title_figure=Task_Project,
    x_label=Variable_Name_X,
    y_label=Variable_Name_Y,
    label_legend_regr_model=f'линейная регрессия Y = {b0:.3f} + {b1:.4f}*X',
    s=80,
    file_name='graph/regression_plot_lin.png')

Статистический анализ регрессионной модели

1. Расчет ошибки аппроксимации (Error Metrics)

Ошибки аппроксимации (Error Metrics) позволяют получить общее представление о качестве модели, а также позволяют сравнивать между собой различные модели.

Создадим пользовательскую функцию, которая рассчитывает основные ошибки аппроксимации для заданной модели:

  • Mean squared error (MSE) или Mean squared deviation (MSD) — среднеквадратическая ошибка (https://en.wikipedia.org/wiki/Mean_squared_error):

  • Root mean square error (RMSE) или Root mean square deviation (RMSD) — квадратный корень из MSE (https://en.wikipedia.org/wiki/Root-mean-square_deviation):

  • Mean absolute error (MAE) — средняя абсолютная ошибка (https://en.wikipedia.org/wiki/Mean_absolute_error):

  • Mean squared prediction error (MSPE) — среднеквадратическая ошибка прогноза (среднеквадратическая ошибка в процентах) (https://en.wikipedia.org/wiki/Mean_squared_prediction_error):

  • Mean absolute percentage error (MAPE) — средняя абсолютная ошибка в процентах (https://en.wikipedia.org/wiki/Mean_absolute_percentage_error):

Про выбор метрики см. также https://machinelearningmastery.ru/how-to-select-the-right-evaluation-metric-for-machine-learning-models-part-2-regression-metrics-d4a1a9ba3d74/.

# Пользовательская функция
def regression_error_metrics(model, model_name=''):
    model_fit = model.fit()
    Ycalc = model_fit.predict()
    n_fit = model_fit.nobs
    Y = model.endog
    
    MSE = (1/n_fit) * np.sum((Y-Ycalc)**2)
    RMSE = sqrt(MSE)
    MAE = (1/n_fit) * np.sum(abs(Y-Ycalc))
    MSPE = (1/n_fit) *  np.sum(((Y-Ycalc)/Y)**2)
    MAPE = (1/n_fit) *  np.sum(abs((Y-Ycalc)/Y))
        
    model_error_metrics = {
        'MSE': MSE,
        'RMSE': RMSE,
        'MAE': MAE,
        'MSPE': MSPE,
        'MAPE': MAPE}
    
    result = pd.DataFrame({
        'MSE': MSE,
        'RMSE': RMSE,
        'MAE': MAE,
        'MSPE': "{:.3%}".format(MSPE),
        'MAPE': "{:.3%}".format(MAPE)},
        index=[model_name])        
        
    return model_error_metrics, result

(model_error_metrics, result) = regression_error_metrics(model_linear_ols, model_name='linear_ols')
display(result)

В литературе по прикладной статистике нет единого мнения о допустимом размере относительных ошибок аппроксимации: в одних источниках допустимой считается ошибка 5-7%, в других она может быть увеличена до 8-10%, и даже до 15%.

Вывод: модель хорошо аппроксимирует фактические данные (относительная ошибка аппроксимации MAPE = 3.405% < 10%).

2. Дисперсионный анализ регрессионной модели (ДАРМ)

ДАРМ не входит в стандартную форму выдачи результатов Regression Results, однако я решил написать здесь о нем по двум причинам:

  1. Именно анализ дисперсии регрессионной модели, разложение этой дисперсии на составляющие дает фундаментальное представление о сути регрессии, а термины, используемые при ДАРМ, применяются на последующих этапах анализа.

  2. С терминами ДАРМ в литературе по прикладной статистике имеется некоторая путаница, в разных источниках они могут именоваться по-разному (см., например, [8, с.52]), поэтому, чтобы двигаться дальше, необходимо определиться с понятиями.

При ДАРМ общую вариацию результативного признака (Y) принято разделять на две составляющие — вариация, обусловленная регрессией и вариация, обусловленная отклонениями от регрессии (остаток), при этом в разных источниках эти термины могут именоваться и обозначаться по-разному, например:

  1. Вариация, обусловленная регрессией — может называться Explained sum of squares (ESS), Sum of Squared Regression (SSR) (https://en.wikipedia.org/wiki/Explained_sum_of_squares, https://towardsdatascience.com/anova-for-regression-fdb49cf5d684), Sum of squared deviations (SSD).

  2. Вариация, обусловленная отклонениями от регрессии (остаток) — может называться Residual sum of squares (RSS), Sum of squared residuals (SSR), Squared estimate of errors, Sum of Squared Error (SSE) (https://en.wikipedia.org/wiki/Residual_sum_of_squares, https://towardsdatascience.com/anova-for-regression-fdb49cf5d684); в отчественной практике также применяется термин остаточная дисперсия.

  3. Общая (полная) вариация — может называться Total sum of squares (TSS), Sum of Squared Total (SST) (https://en.wikipedia.org/wiki/Partition_of_sums_of_squares, https://towardsdatascience.com/anova-for-regression-fdb49cf5d684).

Как видим, путаница знатная:

  • в разных источниках под SSR могут подразумеваться различные показатели;

  • легко перепутать показатели ESS и SSE;

  • в библиотеке statsmodel также есть смешение терминов: для показателя Explained sum of squares используется свойство ess, а для показателя Sum of squared (whitened) residuals — свойство ssr.

Мы будем пользоваться системой обозначений, принятой в большинстве источников — SSR (Sum of Squared Regression), SSE (Sum of Squared Error), SST (Sum of Squared Total). Стандартная таблица ДАРМ в этом случае имеет вид:

Примечания:

  1. Здесь приведена таблица ДАРМ для множественной линейной регрессионной модели (МЛРМ), в нашем случае при ПЛРМ мы имеем частный случай p=1.

  2. Показатели Fcalc-ad и Fcalc-det — расчетные значения статистики критерия Фишера при проверке адекватности модели и значимости коэффициента детерминации (об этом — см.далее).

Более подробно про дисперсионный анализ регрессионной модели — см.[4, глава 3].

Класс statsmodels.regression.linear_model.RegressionResults позволяет нам получить данные для ANOVA (см. https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.RegressionResults.html#statsmodels.regression.linear_model.RegressionResults) как свойства класса:

  1. Сумма квадратов, обусловленная регрессией / SSR (Sum of Squared Regression) — свойство ess.

  2. Сумма квадратов, обусловленная отклонением от регрессии / SSE (Sum of Squared Error) — свойство ssr.

  3. Общая (полная) сумма квадратов / SST (Sum of Squared Total) — свойство centered_tss.

  4. Кол-во наблюдений / Number of observations — свойство nobs.

  5. Число степеней свободы модели / Model degrees of freedom — равно числу переменных модели (за исключением константы, если она присутствует — свойство df_model.

  6. Среднеквадратичная ошибка модели / Mean squared error the model — сумма квадратов, объясненная регрессией, деленная на число степеней свободы регрессии — свойство mse_model.

  7. Среднеквадратичная ошибка остатков / Mean squared error of the residuals — сумма квадратов остатков, деленная на остаточные степени свободы — свойство mse_resid.

  8. Общая среднеквадратичная ошибка / Total mean squared error — общая сумма квадратов, деленная на количество наблюдений — свойство mse_total.

Также имеется модуль statsmodels.stats.anova.anova_lm, который позволяет получить результаты ДАРМ (нескольких типов — 1, 2, 3):

# тип 1
print('The type of Anova test: 1')
display(sm.stats.anova_lm(result_linear_ols, typ=1))

# тип 2
print('The type of Anova test: 2')
display(sm.stats.anova_lm(result_linear_ols, typ=2))

# тип 3
print('The type of Anova test: 3')
display(sm.stats.anova_lm(result_linear_ols, typ=3))

На мой взгляд, форма таблица результатов statsmodels.stats.anova.anova_lm не вполне удобна, поэтому сформируем ее самостоятельно, для чего создадим пользовательскую функцию ANOVA_table_regression_model:

# Пользовательская функция
def ANOVA_table_regression_model(model_fit):
    n = int(model_fit.nobs)
    p = int(model_fit.df_model)
    SSR = model_fit.ess
    SSE = model_fit.ssr
    SST = model_fit.centered_tss

    result = pd.DataFrame({
        'sources_of_variation': ('regression (SSR)', 'deviation from regression (SSE)', 'total (SST)'),
        'sum_of_squares': (SSR, SSE, SST),
        'degrees_of_freedom': (p, n-p-1, n-1)})
    result['squared_error'] = result['sum_of_squares'] / result['degrees_of_freedom']
    R2 = 1 - result.loc[1, 'sum_of_squares'] / result.loc[2, 'sum_of_squares']
    F_calc_adequacy = result.loc[2, 'squared_error'] / result.loc[1, 'squared_error']
    F_calc_determ_check = result.loc[0, 'squared_error'] / result.loc[1, 'squared_error']
    result['F-ratio'] = (F_calc_determ_check, F_calc_adequacy, '')
    
    return result

result = ANOVA_table_regression_model(result_linear_ols)
display(result)
print(f"R2 = 1 - SSE/SST = {1 - result.loc[1, 'sum_of_squares'] / result.loc[2, 'sum_of_squares']}")
print(f"F_calc_adequacy = MST / MSE = {result.loc[2, 'squared_error'] / result.loc[1, 'squared_error']}")
print(f"F_calc_determ_check = MSR / MSE = {result.loc[0, 'squared_error'] / result.loc[1, 'squared_error']}")

ДАРМ позволяет визуализировать вариацию:

fig, axes = plt.subplots(figsize=(210/INCH, 297/INCH/1.5))
axes.pie(
    (result.loc[0, 'sum_of_squares'], result.loc[1, 'sum_of_squares']), 
    labels=(result.loc[0, 'sources_of_variation'], result.loc[1, 'sources_of_variation']),
    autopct='%.1f%%',
    startangle=60)
plt.show()

На основании данных ДАРМ мы рассчитали ряд показателей (R2, Fcalc-ad и Fcalc-det), которые будут использоваться в дальнейшем.

3. Анализ остатков (проверка нормальности распределения остатков и гипотезы о равенстве нулю среднего значения остатков)

Проверка нормальности распределения остатков — один их важнейших этапов анализа регрессионной модели. Требование нормальности распределения остатков не требуется для отыскания параметров модели, но необходимо в дальнейшем для проверки статистических гипотез с использованием критериев Фишера и Стьюдента (проверка адекватности модели, значимости коэффициента детерминации, значимости коэффициентов регрессии) и построения доверительных интервалов [5, с.122].

Остатки регрессионной модели:

print('Остатки регрессионной модели:\n', result_linear_ols.resid, type(result_linear_ols.resid))
res_Y = np.array(result_linear_ols.resid)

statsmodels может выдавать различные преобразованные виды остатков (см. https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.RegressionResults.resid_pearson.html, https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.RegressionResults.wresid.html).

График остатков:

# Пользовательская функция
graph_scatterplot_sns(
    X, res_Y,
    Xmin=Xmin_graph, Xmax=Xmax_graph,
    Ymin=-3.0, Ymax=3.0,
    color='red',
    #title_figure=Task_Project,
    title_axes='Остатки линейной регрессионной модели', title_axes_fontsize=18,
    x_label=Variable_Name_X,
    y_label='ΔY = Y - Ycalc',
    s=75,
    file_name='graph/residuals_plot_sns.png')

Проверка нормальности распределения остатков:

# Пользовательская функция
graph_hist_boxplot_probplot_sns(
    data=res_Y,
    data_min=-2.5, data_max=2.5,
    graph_inclusion='bp',
    data_label='ΔY = Y - Ycalc',
    #title_figure=Task_Project,
    title_axes='Остатки линейной регрессионной модели', title_axes_fontsize=16,
    file_name='graph/residuals_hist_boxplot_probplot_sns.png')    

norm_distr_check(res_Y)

Вывод: большинство статистических тестов позволяют принять гипотезу о нормальности распределения остатков.

Проверка гипотезы о равенстве нулю среднего значения остатков — так как остатки имеют нормальное распределение, воспользуемся критерием Стьюдента (функция scipy.stats.ttest_1samp, https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.ttest_ind.html):

sps.ttest_1samp(res_Y, popmean=0)

Вывод: так как расчетный уровень значимости превышает заданный (0.05), то нулевая гипотеза о равенстве нулю остатков ПРИНИМАЕТСЯ.

4. Проверка адекватности модели

Суть проверки адекватности регрессионной модели заключается в сравнении полной дисперсии MST и остаточной дисперсии MSE — проверяется гипотеза о равенстве этих дисперсий по критерию Фишера. Если дисперсии различаются значимо, то модель считается адекватной. Более подробно про проверку адекватности регрессионной — см.[1, с.658], [2, с.49], [4, с.154].

Для проверки адекватности регрессионной модели создадим пользовательскую функцию regression_model_adequacy_check:

def regression_model_adequacy_check(
    model_fit,
    p_level: float=0.95,
    model_name=''):
    
    n = int(model_fit.nobs)
    p = int(model_fit.df_model)    # Число степеней свободы регрессии, равно числу переменных модели (за исключением константы, если она присутствует)
    
    SST = model_fit.centered_tss    # SST (Sum of Squared Total)
    dfT = n-1
    MST = SST / dfT

    SSE = model_fit.ssr    # SSE (Sum of Squared Error)
    dfE = n - p - 1
    MSE = SSE / dfE
    
    F_calc = MST / MSE
    F_table = sci.stats.f.ppf(p_level, dfT, dfE, loc=0, scale=1)
    a_calc = 1 - sci.stats.f.cdf(F_calc, dfT, dfE, loc=0, scale=1)
    conclusion_model_adequacy_check = 'adequacy' if F_calc >= F_table else 'adequacy'
    
    # формируем результат            
    result = pd.DataFrame({
        'SST': (SST),
        'SSE': (SSE),
        'dfT': (dfT),
        'dfE': (dfE),
        'MST': (MST),
        'MSE': (MSE),
        'p_level': (p_level),
        'a_level': (a_level),
        'F_calc': (F_calc),
        'F_table': (F_table),
        'F_calc >= F_table': (F_calc >= F_table),
        'a_calc': (a_calc),
        'a_calc <= a_level': (a_calc <= a_level),
        'adequacy_check': (conclusion_model_adequacy_check),
        },
        index=[model_name]
        )
    
    return result

regression_model_adequacy_check(result_linear_ols, p_level=0.95, model_name='linear_ols')

Вывод: модель является АДЕКВАТНОЙ.

5. Коэффициент детерминации и проверка его значимости

Различают несколько видов коэффициента детерминации:

  1. Собственно обычный коэффициент детерминации:

Его значение может быть получено как свойство rsquared модели.

  1. Скорректированный (adjusted) коэффициент детерминации — используется для того, чтобы была возможность сравнивать модели с разным числом признаков так, чтобы число регрессоров (признаков) не влияло на статистику R2, при его расчете используются несмещённые оценки дисперсий:

Его значение может быть получено как свойство rsquared_adj модели.

  1. Обобщённый (extended) коэффициент детерминации — используется для сравнения моделей регрессии со свободным членом и без него, а также для сравнения между собой регрессий, построенных с помощью различных методов: МНК, обобщённого метода наименьших квадратов (ОМНК), условного метода наименьших квадратов (УМНК), обобщённо-условного метода наименьших квадратов (ОУМНК). В данном разборе ПЛРМ рассматривать этот коэффициент мы не будем.

Более подробно с теорией вопроса можно ознакомиться, например: http://www.machinelearning.ru/wiki/index.php?title=Коэффициент_детерминации), а также в [7].

Значения коэффициента детерминации и скорректированного коэффициента детерминации, извлеченные с помощью свойств rsquared и rsquared_adj модели.

print('R2 =', result_linear_ols.rsquared)
print('R2_adj =', result_linear_ols.rsquared_adj)

Значимость коэффициента детерминации можно проверить по критерию Фишера [3, с.201-203; 8, с.83].

Расчетное значение статистики критерия Фишера может быть получено с помощью свойства fvalue модели:

print(f"result_linear_ols.fvalue = {result_linear_ols.fvalue}")

Расчетный уровень значимости при проверке гипотезы по критерию Фишера может быть получено с помощью свойства f_pvalue модели:

print(f"result_linear_ols.f_pvalue = {result_linear_ols.f_pvalue}")

Можно рассчитать уровень значимости самостоятельно (так сказать, для лучшего понимания и общей демонстрации возможностей) — для этого воспользуемся библиотекой scipy, модулем распределения Фишера scipy.stats.f, свойством cdf (функция распределения):

df1 = int(result_linear_ols.df_model)
df2 = int(result_linear_ols.nobs - result_linear_ols.df_model - 1)
F_calc = result_linear_ols.fvalue
a_calc = 1 - sci.stats.f.cdf(F_calc, df1, df2, loc=0, scale=1)
print(a_calc)

Как видим, результаты совпадают.

Табличное значение статистики критерия Фишера получить с помощью Regression Results нельзя. Рассчитаем его самостоятельно — для этого воспользуемся библиотекой scipy, модулем распределения Стьюдента scipy.stats.f, свойством ppf (процентные точки):

F_table = sci.stats.f.ppf(p_level, df1, df2, loc=0, scale=1)
print(F_table)

Для удобства создадим пользовательскую функцию determination_coef_check для проверки значимости коэффициента детерминации, которая объединяет все вышеперечисленные расчеты:

# Пользовательская функция
def determination_coef_check(
    model_fit,
    p_level: float=0.95):
    
    a_level = 1 - p_level
    
    R2 = model_fit.rsquared
    R2_adj = model_fit.rsquared_adj
    n = model_fit.nobs    # объем выборки
    p = model_fit.df_model    # Model degrees of freedom. The number of regressors p. Does not include the constant if one is present.
    
    F_calc = R2 / (1 - R2) * (n-p-1)/p
    df1 = int(p)
    df2 = int(n-p-1)
    F_table = sci.stats.f.ppf(p_level, df1, df2, loc=0, scale=1)
    a_calc = 1 - sci.stats.f.cdf(F_calc, df1, df2, loc=0, scale=1)
    conclusion_determ_coef_sign = 'significance' if F_calc >= F_table else 'not significance'
        
    # формируем результат            
    result = pd.DataFrame({
        'notation': ('R2'),
        'coef_value (R)': (sqrt(R2)),
        'coef_value_squared (R2)': (R2),
        'p_level': (p_level),
        'a_level': (a_level),
        'F_calc': (F_calc),
        'df1': (df1),
        'df2': (df2),
        'F_table': (F_table),
        'F_calc >= F_table': (F_calc >= F_table),
        'a_calc': (a_calc),
        'a_calc <= a_level': (a_calc <= a_level),
        'significance_check': (conclusion_determ_coef_sign),
        'conf_int_low': (''),
        'conf_int_high': ('')
        },
        index=['Coef. of determination'])
    return result

determination_coef_check(
    result_linear_ols,
    p_level=0.95)

Вывод: коэффициент детерминации ЗНАЧИМ.

6. Коэффициенты регрессии и проверка их значимости

Ранее мы уже извлекли коэффициенты регрессии как параметры модели b0 и b1 (как свойство params модели). Также можно получить их значения, как свойство bse модели:

print(b0, b1)
print(result_linear_ols.bse, type(result_linear_ols.bse))

Значимость коэффициентов регрессии можно проверить по критерию Стьюдента [3, с.203-212; 8, с.78].

Расчетные значения статистики критерия Стьюдента могут быть получены с помощью свойства tvalues модели:

print(f"result_linear_ols.tvalues = \n{result_linear_ols.tvalues}")

Расчетные значения уровня значимости при проверке гипотезы по критерию Стьюдента могут быть получены с помощью свойства pvalues модели:

print(f"result_linear_ols.pvalues = \n{result_linear_ols.pvalues}")

Доверительные интервалы для коэффициентов регрессии могут быть получены с помощью свойства conf_int модели:

print(result_linear_ols.conf_int(), '\n')

Можно рассчитать уровень значимости самостоятельно (как ранее для критерия Фишера — для лучшего понимания и общей демонстрации возможностей) — для этого воспользуемся библиотекой scipy, модулем распределения Стьюдента scipy.stats.t, свойством cdf (функция распределения):

t_calc = result_linear_ols.tvalues
df = int(result_linear_ols.nobs - result_linear_ols.df_model - 1)
a_calc = 2*(1-sci.stats.t.cdf(abs(t_calc), df, loc=0, scale=1))
print(a_calc)

Как видим, результаты совпадают.

Табличные значения статистики критерия Стьюдента получить с помощью Regression Results нельзя. Рассчитаем их самостоятельно — для этого воспользуемся библиотекой scipy, модулем распределения Стьюдента scipy.stats.t, свойством ppf (процентные точки):

t_table = sci.stats.t.ppf((1 + p_level)/2 , df)
print(t_table)

Для удобства создадим пользовательскую функцию regression_coef_check для проверки значимости коэффициентов регрессии, которая объединяет все вышеперечисленные расчеты:

def regression_coef_check(
    model_fit,
    notation_coef: list='',
    p_level: float=0.95):
    
    a_level = 1 - p_level
    
    # параметры модели (коэффициенты регрессии)
    model_params = model_fit.params
    # стандартные ошибки коэффициентов регрессии
    model_bse = model_fit.bse
    # проверка гипотезы о значимости регрессии
    t_calc = abs(model_params) / model_bse
    n = model_fit.nobs    # объем выборки
    p = model_fit.df_model    # Model degrees of freedom. The number of regressors p. Does not include the constant if one is present.
    df = int(n - p - 1)
    t_table = sci.stats.t.ppf((1 + p_level)/2 , df)
    a_calc = 2*(1-sci.stats.t.cdf(t_calc, df, loc=0, scale=1))
    conclusion_ = ['significance' if elem else 'not significance' for elem in (t_calc >= t_table).values]
        
    # доверительный интервал коэффициента регрессии
    conf_int_low = model_params - t_table*model_bse
    conf_int_high = model_params + t_table*model_bse
    
    # формируем результат            
    result = pd.DataFrame({
        'notation': (notation_coef),
        'coef_value': (model_params),
        'std_err': (model_bse),
        'p_level': (p_level),
        'a_level': (a_level),
        't_calc': (t_calc),
        'df': (df),
        't_table': (t_table),
        't_calc >= t_table': (t_calc >= t_table),
        'a_calc': (a_calc),
        'a_calc <= a_level': (a_calc <= a_level),
        'significance_check': (conclusion_),
        'conf_int_low': (conf_int_low),
        'conf_int_high': (conf_int_high),
        })
    
    return result

regression_coef_check(
    result_linear_ols,
    notation_coef=['b0', 'b1'],
    p_level=0.95)

Вывод: коэффициенты регрессии b0 и b1 ЗНАЧИМЫ.

7. Проверка гетероскедастичности

Для проверка гетероскедастичности statsmodels предлагает нам следующие инструменты:

  • тест Голдфелда-Квандта (https://www.statsmodels.org/stable/generated/statsmodels.stats.diagnostic.het_goldfeldquandt.html#statsmodels.stats.diagnostic.het_goldfeldquandt) — теорию см. [8, с.178], также https://ru.wikipedia.org/wiki/Тест_Голдфелда_—_Куандта.

  • тест Бриша-Пэгана (Breush-Pagan test) (https://www.statsmodels.org/stable/generated/statsmodels.stats.diagnostic.het_breuschpagan.html#statsmodels.stats.diagnostic.het_breuschpagan) — теорию см.[8, с.179], также https://en.wikipedia.org/wiki/Breusch–Pagan_test.

  • тест Уайта (White test) (https://www.statsmodels.org/stable/generated/statsmodels.stats.diagnostic.het_white.html#statsmodels.stats.diagnostic.het_white) — теорию см.[8, с.177], а также https://ru.wikipedia.org/wiki/Тест_Уайта.

    Тест Голдфелда-Квандта (Goldfeld–Quandt test)

# тест Голдфелда-Квандта (Goldfeld–Quandt test)
test = sms.het_goldfeldquandt(result_linear_ols.resid, result_linear_ols.model.exog)
test_result = lzip(['F_calc', 'p_calc'], test)    # распаковка результатов теста
# расчетное значение статистики F-критерия
F_calc_tuple = test_result[0]
F_calc = F_calc_tuple[1]
print(f"Расчетное значение статистики F-критерия: F_calc = {round(F_calc, DecPlace)}")
# расчетный уровень значимости
p_calc_tuple = test_result[1]
p_calc = p_calc_tuple[1]
print(f"Расчетное значение доверительной вероятности: p_calc = {round(p_calc, DecPlace)}")
#a_calc = 1 - p_value
#print(f"Расчетное значение уровня значимости: a_calc = 1 - p_value = {round(a_calc, DecPlace)}")
# вывод
if p_calc < a_level:
    conclusion_GQ_test = f"Так как p_calc = {round(p_calc, DecPlace)} < a_level = {round(a_level, DecPlace)}" + \
        ", то дисперсии в подвыборках отличаются значимо, т.е. гипотеза о наличии гетероскедастичности ПРИНИМАЕТСЯ"
else:
    conclusion_GQ_test = f"Так как p_calc = {round(p_calc, DecPlace)} >= a_level = {round(a_level, DecPlace)}" + \
        ", то дисперсии в подвыборках отличаются незначимо, т.е. гипотеза о наличии гетероскедастичности ОТВЕРГАЕТСЯ"
print(conclusion_GQ_test)

Для удобства создадим пользовательскую функцию Goldfeld_Quandt_test:

def Goldfeld_Quandt_test(
    model_fit,
    p_level: float=0.95,
    model_name=''):
    
    a_level = 1 - p_level
    
    # реализация теста
    test = sms.het_goldfeldquandt(model_fit.resid, model_fit.model.exog)
    test_result = lzip(['F_statistic', 'p_calc'], test)    # распаковка результатов теста
    # расчетное значение статистики F-критерия
    F_calc_tuple = test_result[0]
    F_statistic = F_calc_tuple[1]
    # расчетный уровень значимости
    p_calc_tuple = test_result[1]
    p_calc = p_calc_tuple[1]
    # вывод
    conclusion_test = 'heteroscedasticity' if p_calc < a_level else 'not heteroscedasticity'
    
    result = pd.DataFrame({
        'test': ('Goldfeld–Quandt test'),
        'p_level': (p_level),
        'a_level': (a_level),
        'F_statistic': (F_statistic),
        'p_calc': (p_calc),
        'p_calc < a_level': (p_calc < a_level),
        'heteroscedasticity_check': (conclusion_test)
        },
        index=[model_name])
    
    return result

Goldfeld_Quandt_test(result_linear_ols, p_level=0.95, model_name='linear_ols')

Тест Бриша-Пэгана (Breush-Pagan test)

# тест Бриша-Пэгана (Breush-Pagan test)
name = ["Lagrange multiplier statistic", "p-value", "f-value", "f p-value"]
test = sms.het_breuschpagan(result_linear_ols.resid, result_linear_ols.model.exog)
lzip(name, test)

Для удобства создадим пользовательскую функцию Breush_Pagan_test:

def Breush_Pagan_test(
    model_fit,
    p_level: float=0.95,
    model_name=''):
    
    a_level = 1 - p_level
    
    # реализация теста
    test = sms.het_breuschpagan(model_fit.resid, model_fit.model.exog)
    name = ['Lagrange_multiplier_statistic', 'p_calc_LM', 'F_statistic', 'p_calc']
    test_result = lzip(name, test)    # распаковка результатов теста
    # расчетное значение статистики теста множителей Лагранжа
    LM_calc_tuple = test_result[0]
    Lagrange_multiplier_statistic = LM_calc_tuple[1]
    # расчетный уровень значимости статистики теста множителей Лагранжа
    p_calc_LM_tuple = test_result[1]
    p_calc_LM = p_calc_LM_tuple[1]
    # расчетное значение F-статистики гипотезы о том, что дисперсия ошибки не зависит от x
    F_calc_tuple = test_result[2]
    F_statistic = F_calc_tuple[1]
    # расчетный уровень значимости F-статистики
    p_calc_tuple = test_result[3]
    p_calc = p_calc_tuple[1]
    # вывод
    conclusion_test = 'heteroscedasticity' if p_calc < a_level else 'not heteroscedasticity'

    # вывод
    conclusion_test = 'heteroscedasticity' if p_calc < a_level else 'not heteroscedasticity'
    
    result = pd.DataFrame({
        'test': ('Breush-Pagan test'),
        'p_level': (p_level),
        'a_level': (a_level),
        'Lagrange_multiplier_statistic': (Lagrange_multiplier_statistic),
        'p_calc_LM': (p_calc_LM),
        'p_calc_LM < a_level': (p_calc_LM < a_level),
        'F_statistic': (F_statistic),
        'p_calc': (p_calc),
        'p_calc < a_level': (p_calc < a_level),
        'heteroscedasticity_check': (conclusion_test)
        },
        index=[model_name])
    
    return result

Breush_Pagan_test(result_linear_ols, p_level=0.95, model_name='linear_ols')

Тест Уайта (White test)

# тест Уайта (White test)
name = ["Lagrange multiplier statistic", "p-value", "f-value", "f p-value"]
test = sms.het_white(result_linear_ols.resid, result_linear_ols.model.exog)
lzip(name, test)

Для удобства создадим пользовательскую функцию White_test:

def White_test(
    model_fit,
    p_level: float=0.95,
    model_name=''):
    
    a_level = 1 - p_level
    
    # реализация теста
    test = sms.het_white(model_fit.resid, model_fit.model.exog)
    name = ['Lagrange_multiplier_statistic', 'p_calc_LM', 'F_statistic', 'p_calc']
    test_result = lzip(name, test)    # распаковка результатов теста
    # расчетное значение статистики теста множителей Лагранжа
    LM_calc_tuple = test_result[0]
    Lagrange_multiplier_statistic = LM_calc_tuple[1]
    # расчетный уровень значимости статистики теста множителей Лагранжа
    p_calc_LM_tuple = test_result[1]
    p_calc_LM = p_calc_LM_tuple[1]
    # расчетное значение F-статистики гипотезы о том, что дисперсия ошибки не зависит от x
    F_calc_tuple = test_result[2]
    F_statistic = F_calc_tuple[1]
    # расчетный уровень значимости F-статистики
    p_calc_tuple = test_result[3]
    p_calc = p_calc_tuple[1]
    # вывод
    conclusion_test = 'heteroscedasticity' if p_calc < a_level else 'not heteroscedasticity'

    # вывод
    conclusion_test = 'heteroscedasticity' if p_calc < a_level else 'not heteroscedasticity'
    
    result = pd.DataFrame({
        'test': ('White test'),
        'p_level': (p_level),
        'a_level': (a_level),
        'Lagrange_multiplier_statistic': (Lagrange_multiplier_statistic),
        'p_calc_LM': (p_calc_LM),
        'p_calc_LM < a_level': (p_calc_LM < a_level),
        'F_statistic': (F_statistic),
        'p_calc': (p_calc),
        'p_calc < a_level': (p_calc < a_level),
        'heteroscedasticity_check': (conclusion_test)
        },
        index=[model_name])
    
    return result

White_test(result_linear_ols, p_level=0.95, model_name='linear_ols')

Объединим результаты всех тестов гетероскедастичность в один DataFrame:

Goldfeld_Quandt_test_df = Goldfeld_Quandt_test(result_linear_ols, p_level=0.95, model_name='linear_ols')
Breush_Pagan_test_df = Breush_Pagan_test(result_linear_ols, p_level=0.95, model_name='linear_ols')
White_test_df = White_test(result_linear_ols, p_level=0.95, model_name='linear_ols')

heteroscedasticity_tests_df = pd.concat([Breush_Pagan_test_df, White_test_df, Goldfeld_Quandt_test_df])
display(heteroscedasticity_tests_df)

Выводы

Итак, мы провели статистический анализ регрессионной модели и установили:

  • исходные данные имеют нормальное распределение;

  • между переменными имеется весьма сильная корреляционная связь;

  • регрессионная модель хорошо аппроксимирует фактические данные;

  • остатки модели имеют нормальное распределение;

  • регрессионная модель адекватна по критерию Фишера;

  • коэффициент детерминации значим по критеию Фишера;

  • коэффициенты регрессии значимы по критерию Стьюдента;

  • гетероскедастичность отсутствует.

Применительно к рассматриваемой задаче выполнять проверку автокорреляции не имеет особого смысла из-за особенностей исходных данных (результаты замеров прочности бетона на разных участках здания).

Про статистический анализ регрессионных моделей с помощью statsmodels— см. еще https://www.statsmodels.org/stable/examples/notebooks/generated/regression_diagnostics.html.

Доверительные интервалы регрессионной модели

Для регрессионных моделей определяют доверительные интервалы двух видов [3, с.184-192; 4, с.172; 8, с.205-209]:

  1. Доверительный интервал средних значений переменной Y.

  2. Доверительный интервал индивидуальных значений переменной Y.

При этом размер доверительного интервала для индивидуальных значений больше, чем для средних значений.

Доверительные интервалы регрессионных моделей (ДИРМ) могут быть найдены разными способами:

  • непосредственно путем расчетов по формулам (см., например, https://habr.com/ru/post/558158/);

  • с использованием инструментария библиотеки statsmodels (см., например, https://www.stackfinder.ru/questions/17559408/confidence-and-prediction-intervals-with-statsmodels).

Разбререм более подробно способ с использованием библиотеки statsmodels. Прежде всего, с помощью свойства summary_table класса statsmodels.stats.outliers_influence.OLSInfluence (https://www.statsmodels.org/stable/generated/statsmodels.stats.outliers_influence.OLSInfluence.html?highlight=olsinfluence) мы можем получить таблицу данных, содержащую необходимую нам информацию:

  • Dep Var Population — фактические значения переменной Y;

  • Predicted Value — предсказанные значения переменной Y по по регрессионной модели;

  • Std Error Mean Predict — среднеквадратическая ошибка предсказанного среднего;

  • Mean ci 95% low и Mean ci 95% upp — границы доверительного интервала средних значений переменной Y;

  • Predict ci 95% low и Predict ci 95% upp — границы доверительного интервала индивидуальных значений переменной Y;

  • Residual — остатки регрессионной модели;

  • Std Error Residual — среднеквадратическая ошибка остатков;

  • Student Residual — стьюдентизированные остатки (подробнее см. http://statistica.ru/glossary/general/studentizirovannie-ostatki/);

  • Cook’s D — Расстояние Кука (Cook’s distance) — оценивает эффект от удаления одного (рассматриваемого) наблюдения; наблюдение считается выбросом, если Di > 4/n (более подробно — см.https://translated.turbopages.org/proxy_u/en-ru.ru.f584ceb5-63296427-aded8f31-74722d776562/https/en.wikipedia.org/wiki/Cook’s_distance, http://www.machinelearning.ru/wiki/index.php?title=Расстояние_Кука).

from statsmodels.stats.outliers_influence import summary_table
st, data, ss2 = summary_table(result_linear_ols, alpha=0.05)
print(st, '\n', type(st))

В нашем случае критическое значение расстояния Кука равно:

print(f'D_crit = 4/n = {4/result_linear_ols.nobs}')

то есть выбросов, смещающих оценки коэффициентов регрессии, не наблюдается.

Мы получили данные как класс statsmodels.iolib.table.SimpleTable. Свойство data преобразует данные в список. Далее для удобства работы преобразуем данные в DataFrame:

  st_data_df = pd.DataFrame(st.data)

Будем использовать данный DataFrame в дальнейшем, несколько преобразуем его:

  • изменим наименование столбцов (с цифр на названия показателей из таблицы summary_table)

  • удалим строки с текстовыми значениями

  • изменим индекс

  • добавим новый столбец — значения переменной X

  • отсортируем по возрастанию значений переменной X (это необходимо, чтобы графики границ доверительных интервалов выглядели как линии)

st_df = st_data_df.copy()
# изменим наименования столбцов
str = st_df.iloc[0,0:] + ' ' + st_df.iloc[1,0:]
st_df = st_df.rename(str, axis='columns')
# удалим строки 0, 1
st_df = st_df.drop([0,1])
# изменим индекс
st_df = st_df.set_index(np.arange(0, result_linear_ols.nobs))
# добавим новый столбец - значения переменной X
st_df.insert(1, 'X', X)
# отсортируем по возрастанию значений переменной X
st_df = st_df.sort_values(by='X')

display(st_df)

С помощью полученных данных мы можем построить график регрессионной модели с доверительными интервалами:

# создание рисунка (Figure) и области рисования (Axes)
fig, axes = plt.subplots(figsize=(297/INCH, 210/INCH))
# заголовок рисунка (Figure)
title_figure = Task_Project
fig.suptitle(title_figure, fontsize = 16)
# заголовок области рисования (Axes)
title_axes = 'Линейная регрессионная модель'
axes.set_title(title_axes, fontsize = 14)
# фактические данные
sns.scatterplot(
    x=st_df['X'], y=st_df['Dep Var Population'],
    label='фактические данные',
    s=50,
    color='red',
    ax=axes)
# график регрессионной модели
label_legend_regr_model=f'линейная регрессия Y = {b0:.3f} + {b1:.4f}*X'
sns.lineplot(
    x=st_df['X'], y=st_df['Predicted Value'],
    label=label_legend_regr_model,
    color='blue',
    ax=axes)
# доверительный интервал средних значений переменной Y
Mean_ci_low = st_df['Mean ci 95% low']
plt.plot(
    st_df['X'], Mean_ci_low,
    color='magenta', linestyle='--', linewidth=1,
    label='доверительный интервал средних значений Y')
Mean_ci_upp = st_df['Mean ci 95% upp']
plt.plot(
    st_df['X'], Mean_ci_upp,
    color='magenta', linestyle='--', linewidth=1)
# доверительный интервал индивидуальных значений переменной Y
Predict_ci_low = st_df['Predict ci 95% low']
plt.plot(
    st_df['X'], Predict_ci_low,
    color='orange', linestyle='-.', linewidth=2,
    label='доверительный интервал индивидуальных значений Y')
Predict_ci_upp = st_df['Predict ci 95% upp']
plt.plot(
    st_df['X'], Predict_ci_upp,
    color='orange', linestyle='-.', linewidth=2)

axes.set_xlabel(Variable_Name_X)
axes.set_ylabel(Variable_Name_Y)
axes.legend(prop={'size': 12})
plt.show()

Однако, мы получили данные о границах доверительных интервалов регрессионной модели только в пределах области фактических значений переменной X. Как быть, если мы хотим распространить прогноз за пределы этой области?

Прогнозирование

Под прогнозированием мы в данном случае будем понимать определение значений переменной Y и доверительных интервалов для ее средних и индивидуальных значений при заданном X. По сути, нам предстоит построить аналог рассмотренной выше таблицы summary_table, только с другими значениями X, причем эти значения могут выходить за пределы тех значений, которые использовались нами для построения регрессии.

Методика расчета доверительных интервалов регрессионных моделей разобрана в статье «Python, корреляция и регрессия: часть 4» (https://habr.com/ru/post/558158/), всем рекомендую ознакомиться.

Найти прогнозные значения Y не представляет труда, так как ранее мы уже формализовали модель в виде лямбда-функции, а вот для построения доверительных интервалов придется выполнить расчеты по формулам. Для этого создадим пользовательскую функцию regression_pair_predict, которая в случае парной регрессии (pair regression) для заданного значения X возвращает:

  • прогнозируемое по регрессионной модели значение y_calc

  • доверительный интервал [y_calc_mean_ci_low, y_calc_mean_ci_upp] средних значений переменной Y

  • доверительный интервал [y_calc_predict_ci_low, y_calc_predict_ci_upp] индивидуальных значений переменной Y

Алгоритм расчета доверительных интервалов для множественной регрессии (multiple regression) отличается и в данном обзоре не рассматривается (рассмотрим в дальнейшем).

Про прогнозирование с помощью регрессионных моделей — см.также:

  • https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.RegressionResults.predict.html?highlight=predict#statsmodels.regression.linear_model.RegressionResults.predict

  • How to Make Predictions Using Regression Model in Statsmodels

  • https://www.statsmodels.org/stable/examples/notebooks/generated/predict.html

def regression_pair_predict(
    x_in,
    model_fit,
    regression_model,
    p_level: float=0.95):
    
    a_level = 1 - p_level
    
    X = pd.DataFrame(model_fit.model.exog)[1].values    # найти лучшее решение
    Y = model_fit.model.endog
    
    # вспомогательные величины
    n = int(result_linear_ols.nobs)
    SSE = model_fit.ssr    # SSE (Sum of Squared Error)
    dfE = n - p - 1
    MSE = SSE / dfE    # остаточная дисперсия
    
    Xmean = np.mean(X)
    SST_X = np.sum([(X[i] - Xmean)**2 for i in range(0, n)])
    
    t_table = sci.stats.t.ppf((1 + p_level)/2 , dfE)
    S2_y_calc_mean = MSE * (1/n + (x_in - Xmean)**2 / SST_X)
    S2_y_calc_predict = MSE * (1 + 1/n + (x_in - Xmean)**2 / SST_X)
        
    # прогнозируемое значение переменной Y
    y_calc=regression_model(x_in)
    # доверительный интервал средних значений переменной Y
    y_calc_mean_ci_low = y_calc - t_table*sqrt(S2_y_calc_mean)
    y_calc_mean_ci_upp = y_calc + t_table*sqrt(S2_y_calc_mean)
    # доверительный интервал индивидуальных значений переменной Y
    y_calc_predict_ci_low = y_calc - t_table*sqrt(S2_y_calc_predict)
    y_calc_predict_ci_upp = y_calc + t_table*sqrt(S2_y_calc_predict)
    
    result = y_calc, y_calc_mean_ci_low, y_calc_mean_ci_upp, y_calc_predict_ci_low, y_calc_predict_ci_upp
    
    return result

Сравним результаты расчета доверительных интервалов разными способами — с использованием функции regression_pair_predict и средствами statsmodels, для этого сформируем DaraFrame с новыми данными:

regression_pair_predict_df = pd.DataFrame(
    [regression_pair_predict(elem, result_linear_ols, regression_model=Y_calc) for elem in st_df['X'].values],
    columns=['y_calc', 'y_calc_mean_ci_low', 'y_calc_mean_ci_upp', 'y_calc_predict_ci_low', 'y_calc_predict_ci_upp'])
regression_pair_predict_df.insert(0, 'X', st_df['X'].values)
display(regression_pair_predict_df)

Видим, что результаты расчетов идентичны, следовательно мы можем использовать функцию regression_pair_predict для прогнозирования.

def graph_regression_pair_predict_plot_sns(
    model_fit,
    regression_model_in,
    Xmin=None, Xmax=None, Nx=10,
    Ymin_graph=None, Ymax_graph=None,
    title_figure=None, title_figure_fontsize=18,
    title_axes=None, title_axes_fontsize=16,
    x_label=None,
    y_label=None,
    label_fontsize=14, tick_fontsize=12, 
    label_legend_regr_model='', label_legend_fontsize=12,
    s=50, linewidth_regr_model=2,
    graph_size=(297/INCH, 210/INCH),
    result_output=True,
    file_name=None):
    
    # фактические данные
    X = pd.DataFrame(model_fit.model.exog)[1].values    # найти лучшее решение
    Y = model_fit.model.endog
    X = np.array(X)
    Y = np.array(Y)
    
    # границы
    if not(Xmin) and not(Xmax):
        Xmin=min(X)
        Xmax=max(X)
        Xmin_graph=min(X)*0.99
        Xmax_graph=max(X)*1.01
    else:
        Xmin_graph=Xmin
        Xmax_graph=Xmax
    
    if not(Ymin_graph) and not(Ymax_graph):
        Ymin_graph=min(Y)*0.99
        Ymax_graph=max(Y)*1.01       
    
    # формируем DataFrame данных
    Xcalc = np.linspace(Xmin, Xmax, num=Nx)
    Ycalc = regression_model_in(Xcalc)
    
    result_df = pd.DataFrame(
        [regression_pair_predict(elem, model_fit, regression_model=regression_model_in) for elem in Xcalc],
        columns=['y_calc', 'y_calc_mean_ci_low', 'y_calc_mean_ci_upp', 'y_calc_predict_ci_low', 'y_calc_predict_ci_upp'])
    result_df.insert(0, 'x_calc', Xcalc)
            
    # заголовки графика
    fig, axes = plt.subplots(figsize=graph_size)
    fig.suptitle(title_figure, fontsize = title_figure_fontsize)
    axes.set_title(title_axes, fontsize = title_axes_fontsize)
    
    # фактические данные
    sns.scatterplot(
        x=X, y=Y,
        label='фактические данные',
        s=s,
        color='red',
        ax=axes)
    
    # график регрессионной модели
    sns.lineplot(
        x=Xcalc, y=Ycalc,
        color='blue',
        linewidth=linewidth_regr_model,
        legend=True,
        label=label_legend_regr_model,
        ax=axes)
    
    # доверительный интервал средних значений переменной Y
    Mean_ci_low = result_df['y_calc_mean_ci_low']
    plt.plot(
        result_df['x_calc'], Mean_ci_low,
        color='magenta', linestyle='--', linewidth=1,
        label='доверительный интервал средних значений Y')
    
    Mean_ci_upp = result_df['y_calc_mean_ci_upp']
    plt.plot(
        result_df['x_calc'], Mean_ci_upp,
        color='magenta', linestyle='--', linewidth=1)
    
    # доверительный интервал индивидуальных значений переменной Y
    Predict_ci_low = result_df['y_calc_predict_ci_low']
    plt.plot(
        result_df['x_calc'], Predict_ci_low,
        color='orange', linestyle='-.', linewidth=2,
        label='доверительный интервал индивидуальных значений Y')
    
    Predict_ci_upp = result_df['y_calc_predict_ci_upp']
    plt.plot(
        result_df['x_calc'], Predict_ci_upp,
        color='orange', linestyle='-.', linewidth=2)
    
        
    axes.set_xlim(Xmin_graph, Xmax_graph)
    axes.set_ylim(Ymin_graph, Ymax_graph)        
    axes.set_xlabel(x_label, fontsize = label_fontsize)
    axes.set_ylabel(y_label, fontsize = label_fontsize)
    axes.tick_params(labelsize = tick_fontsize)
    #axes.tick_params(labelsize = tick_fontsize)
    axes.legend(prop={'size': label_legend_fontsize})
        
    plt.show()
    if file_name:
        fig.savefig(file_name, orientation = "portrait", dpi = 300)
        
    if result_output:
        return result_df
    else:
        return

graph_regression_pair_predict_plot_sns(
    model_fit=result_linear_ols,
    regression_model_in=Y_calc,
    Xmin=Xmin_graph-300, Xmax=Xmax_graph+200, Nx=25,
    Ymin_graph=Ymin_graph-5, Ymax_graph=Ymax_graph+5,
    title_figure=Task_Project, title_figure_fontsize=16,
    title_axes='Линейная регрессионная модель', title_axes_fontsize=14,
    x_label=Variable_Name_X,
    y_label=Variable_Name_Y,
    label_legend_regr_model=f'линейная регрессия Y = {b0:.3f} + {b1:.4f}*X',
    s=50,
    result_output=True,
    file_name='graph/regression_plot_lin.png')

Выводы и рекомендации

Исследована зависимость показаний ультразвукового прибора «ПУЛЬСАР-2.1» (X) и результатов замера прочности бетона (методом отрыва со скалыванием) склерометром ИПС-МГ4.03 (Y).

Между переменными имеется весьма сильная линейная корреляционная связь. Получена регрессионная модель:

Y = b0 + b1∙X = -21.3741 + 0.0129∙X

Модель хорошо аппроксимирует фактические данные, является адекватной, значимой и может использоваться для предсказания прочности бетона.

Также построен график прогноза с доверительными интервалами.

ИТОГИ

Итак, мы рассмотрели все этапы регрессионного анализа в случае простой линейной регрессии (simple linear regression) с использованием библиотеки statsmodels на конкретном практическом примере; подробно остановились на статистическом анализа модели с проверкой гипотез; также предложен ряд пользовательских функций, облегчающих работу исследователя и уменьшающих размер программного кода.

Конечно, мы разобрали далеко не все вопросы анализа регрессионных моделей и возможности библиотеки statsmodels применительно к simple linear regression, в частности статистики влияния (Influence Statistics), инструмент Leverage, анализ стьюдентизированных остатков и пр. — это темы для отдельных обзоров.

Исходный код находится в моем репозитории на GitHub.

Надеюсь, данный обзор поможет специалистам DataScience в работе.

Introduction

In the previous chapter, we studied machine learning and some key terms associated with it.

From this chapter onwards, we will start with learning and implementing machine learning algorithms. So in that series, in this chapter, we will start with Linear Regression.

Note:

if you can correlate anything with yourself or your life, there are greater chances of understanding the concept. So try to understand everything by relating it to humans. 

What is Regression and Why is it called so? 

Regression is an ML algorithm that can be trained to predict real numbered outputs; like temperature, stock price, etc. Regression is based on a hypothesis that can be linear, quadratic, polynomial, non-linear, etc. The hypothesis is a function based on some hidden parameters and input values. In the training phase, the hidden parameters are optimized w.r.t. the input values presented in the training. The process that does the optimization is the gradient descent algorithm. You also need a Back-propagation algorithm that can be used to compute the gradient at each layer, If you are using neural networks. Once the hypothesis parameters got trained (when they gave the least error during the training), then the same hypothesis with the trained parameters is used with new input values to predict outcomes that will be again real values. 

Types of Regression 

1. Linear regression is used for predictive analysis. Linear regression is a linear approach for modeling the relationship between the criterion or the scalar response and the multiple predictors or explanatory variables. Linear regression focuses on the conditional probability distribution of the response given the values of the predictors. For linear regression, there is a danger of overfitting. The formula for linear regression is Y’ = bX + A.

2. Logistic regression is used when the dependent variable is dichotomous. Logistic regression estimates the parameters of a logistic model and is a form of binomial regression. Logistic regression is used to deal with data that has two possible criteria and the relationship between the criteria and the predictors. The equation for logistic regression is l = \beta_{0}+\beta_{1}x_{1}+\beta_{2}x_{2}.

3. Polynomial regression is used for curvilinear data. Polynomial regression is fit with the method of least squares. The goal of regression analysis to model the expected value of a dependent variable y in regards to the independent variable x. The equation for polynomial regression is l = \beta_{0}+\beta_{0}x_{1}+\epsilon

4. Stepwise regression is used for fitting regression models with predictive models. It is carried out automatically. With each step, the variable is added or subtracted from the set of explanatory variables. The approaches for stepwise regression are forward selection, backward elimination, and bidirectional elimination. The formula for stepwise regression is b_{j.std} = b_{j}(s_{x} * s_{y}^{-1})

5. Ridge regression is a technique for analyzing multiple regression data. When multicollinearity occurs, least squares estimates are unbiased. A degree of bias is added to the regression estimates, and a result, ridge regression reduces the standard errors. The formula for ridge regression is \beta = (X^{T}X + \lambda * I)^{-1}X^{T}y

6. Lasso regression is a regression analysis method that performs both variable selection and regularization. Lasso regression uses soft thresholding. Lasso regression selects only a subset of the provided covariates for use in the final model. Lasso regression is: N^{-1}\sum^{N}_{i=1}f(x_{i}, y_{I}, \alpha, \beta).

7. ElasticNet regression is a regularized regression method that linearly combines the penalties of the lasso and ridge methods. ElasticNet regression is used to support vector machines, metric learning, and portfolio optimization. The penalty function is given by: || \beta ||_1=\sum^{p}_{j=1}|\beta_j|

8. Support Vector regression is a regression method where we identify a hyperplane with maximum margin such that the maximum number of data points are within that margin. SVRs are almost similar to the SVM classification algorithm. Instead of minimizing the error rate as in simple linear regression, we try to fit the error within a certain threshold. Our objective in SVR is to basically consider the points that are within the margin. Our best fit line is the hyperplane that has the maximum number of points.

9. Decision Tree regression is a regression where the ID3 algorithm can be used to identify the splitting node by reducing the standard deviation (in classification information gain is used).
A decision tree is built by partitioning the data into subsets containing instances with similar values (homogenous). Standard deviation is used to calculate the homogeneity of a numerical sample. If the numerical sample is completely homogeneous, its standard deviation is zero.

10. Random Forest regression is an ensemble approach where we take into account the predictions of several decision regression trees.

  1. Select K random points
  2. Identify n where n is the number of decision tree regressors to be created. Repeat steps 1 and 2 to create several regression trees.
  3. The average of each branch is assigned to the leaf node in each decision tree.
  4. To predict output for a variable, the average of all the predictions of all decision trees are taken into consideration.

Random Forest prevents overfitting (which is common in decision trees) by creating random subsets of the features and building smaller trees using these subsets.

 

Key Terms 

1. Estimator

 A formula or algorithm for generating estimates of parameters, given relevant data.

2. Bias

An estimate is unbiased if its expectation equals the value of the parameter being estimated; otherwise, it is biased.

3.Efficiency

An estimator A is more efficient than an estimator B if A has a smaller sampling variance — that is, if the particular values generated by A are more tightly clustered around their expectation.

4. Consistency

An estimator is consistent if the estimates it produces converge on the true parameter value as the sample size increases without limit. Consider an estimator that produces estimates θ^ of some parameter θ, and let ^ denote a small number. If the estimator is consistent, we can make the probability as close to 1.0 as we like or as small as we like by drawing a sufficiently large sample. Note that a biased estimator may nonetheless be consistent if the bias tends to zero in the limit. Conversely, an unbiased estimator may be inconsistent if its sampling variance fails to shrink appropriately as the sample size increases.

5. Standard error of the Regression (SER)

An estimate of the standard deviation of the error term in a regression model.

6. R-squared

A standardized measure of the goodness of fit for a regression model.

7. Standard error of regression coefficient

An estimate of the standard deviation of the sampling distribution for the coefficient in question.

8. P-value

The probability, supposing the null hypothesis to be true, of drawing sample data that are as adverse to the null as the data are actually drawn, or more so. When a small p-value is found, the two possibilities are that we happened to draw a low-probability unrepresentative sample or that the null hypothesis is in fact false.

9. Significance level

For a hypothesis test, this is the smallest p-value for which we will not reject the null hypothesis. If we choose a significance level of 1%, we’re saying that we’ll reject the null if and only if the p-value for the test is less than 0.01. The significance level is also the probability of making a type 1 error (that is, rejecting a true null hypothesis).

10. T-test

The t-test (or z-test, which is the same thing asymptotically) is a common test for the null hypothesis that a particular regression parameter, βi, has some specific value (commonly zero, but generically βH0).

11. F-test

A common procedure for jointly testing a set of linear restrictions on a regression model.

12. Multicollinearity

A situation where there is a high degree of correlation among the independent variables in a regression model — or, more generally, where some of the Xs are close to being linear combinations of other Xs. Symptoms include large standard errors and the inability to produce precise parameter estimates. This is not a serious problem if one is primarily interested in forecasting; it is a problem is one is trying to estimate causal influences.

13. Omitted variable bias

Bias in the estimation of regression parameters that arises when a relevant independent variable is omitted from a model and the omitted variable is correlated with one or more of the included variables.

14. Log variables

A common transformation permits the estimation of a nonlinear model using OLS to substitute the natural log of a variable for the level of that variable. This can be done for the dependent variable and/or one or more independent variables. A key point to remember about logs is that for small changes, the change in the log of a variable is a good approximation to the proportional change in the variable itself. For example, if log(y) changes by 0.04, y changes by about 4%.

15. Quadratic terms

Another common transformation. When both xi and x^2_i are included as regressors, it is important to remember that the estimated effect of xi on y is given by the derivative of the regression equation with respect to xi. If the coefficient on xi is β and the coefficient on x 2 i is γ, the derivative is β + 2γ xi.

16. Interaction terms

Pairwise products of the «original» independent variables. The inclusion of interaction terms in a regression allows for the possibility that the degree to which xi affects y depends on the value of some other variable x j. In other words, x j modulates the effect of xi on y. For example, the effect of experience on wages (xi) might depend on the gender (x j) of the worker.

17. Independent Variable

An independent variable is a variable that is changed or controlled in a scientific experiment to test the effects on the dependent variable.

18. Dependent Variable 

A dependent variable is a variable being tested and measured in a scientific experiment. The dependent variable is ‘dependent’ on the independent variable. As the experimenter changes the independent variable, the effect on the dependent variable is observed and recorded.

19. Regularization

There are extensions of the training of the linear model called regularization methods. These seek to both minimize the sum of the squared error of the model on the training data (using ordinary least squares) but also to reduce the complexity of the model (like the number or absolute size of the sum of all coefficients in the model).

Gradient Descent

Gradient descent is an optimization algorithm used to minimize some function by iteratively moving in the direction of steepest descent as defined by the negative of the gradient. In machine learning, we use gradient descent to update the parameters of our model. Parameters refer to coefficients in Linear Regression and weights in neural networks.

1. Learning Rate

The size of these steps is called the learning rate. With a high learning rate, we can cover more ground each step, but we risk overshooting the lowest point since the slope of the hill is constantly changing. With a very low learning rate, we can confidently move in the direction of the negative gradient since we are recalculating it so frequently. A low learning rate is more precise, but calculating the gradient is time-consuming, so it will take us a very long time to get to the bottom. 

2. Cost Function

A Loss Function or Cost Function tells us “how good” our model is at making predictions for a given set of parameters. The cost function has its own curve and its own gradients. The slope of this curve tells us how to update our parameters to make the model more accurate. 

gradient_cost

Python Implementation of Gradient Descent

  1. def update_weights(m, b, X, Y, learning_rate):      
  2.     m_deriv = 0      
  3.     b_deriv = 0      
  4.     N = len(X)      
  5.     for i in range(N):      
  6.           
  7.           
  8.         m_deriv += —2*X[i] * (Y[i] — (m*X[i] + b))      
  9.       
  10.           
  11.         b_deriv += —2*(Y[i] — (m*X[i] + b))      
  12.       
  13.       
  14.     m -= (m_deriv / float(N)) * learning_rate      
  15.     b -= (b_deriv / float(N)) * learning_rate      
  16.       
  17.     return m, b    

Remembering Variables With DRYMIX

When results are plotted in graphs, the convention is to use the independent variable as the x-axis and the dependent variable as the y-axis. The DRY MIX acronym can help keep the variables straight:

  • D is the dependent variable
  • R is the responding variable
  • Y is the axis on which the dependent or responding variable is graphed (the vertical axis)
  • M is the manipulated variable or the one that is changed in an experiment
  • I is the independent variable
  • X is the axis on which the independent or manipulated variable is graphed (the horizontal axis)

Simple Linear Regression Model

The simple linear regression model is represented like this: y = (β0 +β1 + Ε)

By mathematical convention, the two factors that are involved in simple linear regression analysis are designated x and y. The equation that describes how y is related to x is known as the regression model. The linear regression model also contains an error term that is represented by Ε, or the Greek letter epsilon. The error term is used to account for the variability in y that cannot be explained by the linear relationship between x and y. There also parameters that represent the population being studied. These parameters of the model are represented by (β0+β1x).

The simple linear regression equation is graphed as a straight line. 

The simple linear regression equation is represented like this: Ε(y) = (β0 +β1*x)

  1. β0 is the y-intercept of the regression line.
  2. β1 is the slope.
  3. Ε(y) is the mean or expected value of y for a given value of x.

A regression line can show a positive linear relationship, a negative linear relationship, or no relationship.

  • If the graphed line in a simple linear regression is flat (not sloped), there is no relationship between the two variables.
  • If the regression line slopes upward with the lower end of the line at the y-intercept (axis) of the graph, and the upper end of the line extending upward into the graph field, away from the x-intercept (axis) a positive linear relationship exists.
  • If the regression line slopes downward with the upper end of the line at the y-intercept (axis) of the graph, and the lower end of the line extending downward into the graph field, toward the x-intercept (axis) a negative linear relationship exists. 

y = β0 +β1*x

lr 

Important Note

1. Regression analysis is not used to interpret cause-and-effect relationships( a mechanism where one event makes another event happen, i.e each event is dependent on one-another) between variables. Regression analysis can, however, indicate how variables are related or to what extent variables are associated with each other.

2. It is also known as bivariate regression or regression analysis

Sum of Square Errors 

This is the sum of differences between the points and the regression line.​ It can serve as a measure of how well the line fits the data.

 

 

Standard Estimate of Errors 

The mean error is equal to zero.​ If se(sigma_epsilon) is small the errors tend to be close to zero (close to the mean error). Then, the model fits the data well.​ Therefore, we can use se as a measure of the suitability of using a linear model.​ An estimator of se is given by se(sigma_epsilon)

ClipCapIt-140603-233234.PNG

Coefficient of Determination 

To measure the strength of the linear relationship we use the coefficient of determination. R^2 takes on any value between zero and one.​

  • R^2 = 1: Perfect match between the line and the data points.​
  • R^2 = 0: There is no linear relationship between x and y.

Image result for coefficient of determination formula 

Simple Linear Regression Example

Let’s take the example of Housing Price Prediction, the data that I am using is KC House Prices. Feel free to use any dataset, there some very good datasets available on kaggle and with Google Colab. 

  1. import pandas as pd    
  2.     
  3. df = pd.read_csv(«kc_house_data.csv»)    
  4. df.describe()    

Output

dataset 

From the above data, we can see that we have a lot of tables, but simple linear regression can only process two columns, so we select the «price» and «sqrt_living». Here we will take the first 100 rows for our demonstration.

Before fitting the data let’s analyze the data:

  1. import seaborn as sns    
  2.     
  3. df = pd.read_csv(«kc_house_data.csv»)    
  4.     
  5. sns.distplot(df[‘price’][1:100])   

Output

price_distplot 

  1. import seaborn as sns      
  2.       
  3. df = pd.read_csv(«kc_house_data.csv»)      
  4.       
  5. sns.distplot(df[‘sqrt_living’][1:100])     

Output

sqrt_living_distplot 

1. Demo using Numpy 

In the below code, we will just use numpy to perform linear regression. 

  1. import pandas as pd    
  2. import numpy as np    
  3. import matplotlib.pyplot as plt    
  4.     
  5. %matplotlib inline    
  6.     
  7. def estimate_coef(x, y):     
  8.       
  9.     n = np.size(x)     
  10.     
  11.       
  12.     m_x, m_y = np.mean(x), np.mean(y)     
  13.     
  14.       
  15.     SS_xy = np.sum(y*x) — n*m_y*m_x     
  16.     SS_xx = np.sum(x*x) — n*m_x*m_x     
  17.     
  18.       
  19.     b_1 = SS_xy / SS_xx     
  20.     b_0 = m_y — b_1*m_x     
  21.     
  22.     return(b_0, b_1)     
  23.     
  24. def plot_regression_line(x, y, b):     
  25.       
  26.     plt.scatter(x, y, color = «m»,     
  27.                     marker = «o», s = 30)     
  28.     
  29.       
  30.     y_pred = b[0] + b[1]*x     
  31.     
  32.       
  33.     plt.plot(x, y_pred, color = «g»)     
  34.     
  35.       
  36.     plt.xlabel(‘x’)     
  37.     plt.ylabel(‘y’)     
  38.     
  39.       
  40.     plt.show()     
  41.     
  42. def main():     
  43.       
  44.   df = pd.read_csv(«kc_house_data.csv»)    
  45.   y= df[‘price’][1:100]    
  46.   x= df[‘sqft_living’][1:100]    
  47.     
  48.   plt.scatter(x, y)     
  49.     
  50.       
  51.   plt.xlabel(‘x’)     
  52.   plt.ylabel(‘y’)     
  53.     
  54.       
  55.   plt.show()     
  56.     
  57.       
  58.   b = estimate_coef(x, y)     
  59.   print(«Estimated coefficients:\nb_0 = {}  \nb_1 = {}».format(b[0], b[1]))     
  60.     
  61.       
  62.   plot_regression_line(x, y, b)     
  63.     
  64. if __name__ == «__main__»:     
  65.     main()      

Output 

The input data can be visualized as:

lr_preprocessedData 

After, executing the code we get the following output

Estimated coefficients: b_0 = 41517.979725295736 b_1 = 229.10249314945074

So the Linear Regression equation becomes :

[price] = 41517.979725795736+ 229.10249374945074*[sqrt_living]

i.e y = b[0] + b[1]*x

Let’s see the final plot with the Regression Line

lr_processedData 

To predict the values we run the following command:

  1. print(«For x =100»«the predicted value would be»,(b[1]*100+b[0]))     

So the predicted value that we get is 64428.22904024081 

Since the machine has to learn, hence would be doing some errors in predicting, so the let’s see what percentage of error it is performing at each given point

  1. y_diff = y -(b[1]+b[0]*x)    
  2. sns.distplot(y_diff)    

difference_dsitplot 

2. Demo using Sklearn 

In the below code, we will learn how to code linear regression using SkLearn

  1. import pandas as pd    
  2. import numpy as np    
  3. import matplotlib.pyplot as plt    
  4. import seaborn as sns    
  5.     
  6. df = pd.read_csv(«kc_house_data.csv»)    
  7. y= df[‘price’][1:100]    
  8. x= df[‘sqft_living’][1:100]    
  9.     
  10. from sklearn.model_selection import train_test_split        
  11.   
  12.     
  13. x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=101)    
  14.     
  15. from sklearn.linear_model import LinearRegression    
  16.     
  17. lr = LinearRegression()     
  18.     
  19. x_train = x_train.values.reshape(-1,1  
  20.     
  21. lr.fit(x_train, y_train)    
  22.   
  23.     
  24. print(«b[0] = «,lr.intercept_)    
  25.     
  26. print(«b[1] = «,lr.coef_)         
  27.     
  28. x_test = x_test.values.reshape(-1,1  
  29.     
  30. pred = lr.predict(x_test)     
  31.     
  32. plt.scatter(x_train,y_train)    
  33.   
  34. y_pred = lr.intercept_ + lr.coef_*x_train    
  35.     
  36.   
  37. plt.plot(x_train, y_pred, color = «g»)     
  38.     
  39.   
  40. plt.xlabel(‘x’)     
  41. plt.ylabel(‘y’)     
  42.     
  43.   
  44. plt.show()      

Output

lr_train 

After, executing the code we get the following output 

b[0] = 21803.55365770642 b[1] = [232.07541739]

So the Linear Regression equation becomes :

[price] = 21803.55365770642+ 232.07541739*[sqrt_living]

i.e y = b[0] + b[1]*x

Let’s now see a graphical representation between predicted value wrt test set 

lr_test 

Since the machine has to learn, hence would be doing some errors in predicting, so the let’s see what percentage of error it is performing at each given point

  1. sns.distplot((y_test-pred))     

lr_error 

3. Demo using TensorFlow

In this, I will not be using «kc_house_data.csv». To make it as simple as possible, I used some dummy data and then I will explain each and everything

  1. np.random.seed(101)     
  2. tf.set_random_seed(101)     

We first set the seed value for both NumPy and TensorFlow. The seed value is used for random number generation. 

  1. x = np.linspace(05050)     
  2. y = np.linspace(05050)     

We now generate dummy data using the NumPy’s linspace function, which generated 50 equally distributed points between 0 and 50

  1. x += np.random.uniform(-4450)     
  2. y += np.random.uniform(-4450)    

Since the data so generated is too perfect, so we add a liitle bit of uniformaly distributed white noise.

  1. n = len(x)   
  2.   
  3. plt.scatter(x, y)     
  4. plt.xlabel(‘x’)     
  5. plt.xlabel(‘y’)     
  6. plt.title(«Training Data»)     
  7. plt.show()      

Here, we thus plot the graphical representation of the dummy data. 

  1. X = tf.placeholder(«float»)     
  2. Y = tf.placeholder(«float»)     
  3. W = tf.Variable(np.random.randn(), name = «W»)     
  4. b = tf.Variable(np.random.randn(), name = «b»)    

Here we are setting X and Y as the actual training data and the W and b as the trainable data, where:

  • W means Weight
  • b means bais 
  • X means the dependent variable
  • Y means the independent variable 

We have to give initial weights and bias to the model. So, we initialize them with some random values.

  1. learning_rate = 0.01    
  2. training_epochs = 1000     

Here, we assume the learning rate to be 0.01 i.e. the gradient descent value would increase/decrease by 0.01 and we will train the model 1000 times or for 1000 epochs 

  1.   
  2. y_pred = tf.add(tf.multiply(X, W), b)     
  3.     
  4.   
  5. cost = tf.reduce_sum(tf.pow(y_pred-Y, 2)) / (2 * n)     
  6.     
  7.   
  8. optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)     
  9.     
  10.   
  11. init = tf.global_variables_initializer()    

Next thing, we have to set the hypothesis, cost function, the optimizer (which will make sure that model is learning on every epoch), and the global variable initializer.

  • Since we are using simple linear regression hence the hypothesis is set as: 

Depedent_Variable*Weight+Bias or Depedent_Variable*b[1]+b[0]

  • We choose the cost function to be mean squared error cost function 
  • We choose the optimizer as Gradient Descent with the motive of minimizing the cost with given learning rate
  • We use TensorFlow’s global variable initialize the global variable so that we can re-use the value of variables
  1.   
  2. with tf.Session() as sess:     
  3.         
  4.       
  5.     sess.run(init)     
  6.         
  7.       
  8.     for epoch in range(training_epochs):     
  9.             
  10.           
  11.         for (_x, _y) in zip(x, y):     
  12.             sess.run(optimizer, feed_dict = {X : _x, Y : _y})     
  13.             
  14.           
  15.         if (epoch + 1) % 50 == 0:     
  16.               
  17.             c = sess.run(cost, feed_dict = {X : x, Y : y})     
  18.             print(«Epoch», (epoch + 1), «: cost =», c, «W =», sess.run(W), «b =», sess.run(b))     
  • tf.Session()
    we start the TensorFlow session and name the Session as sess. We then initialize all the required variable using the
  • sess.run(init)
    initialize all the required variable using the
  • for epoch in range(training_epochs)
    create a for loop that runs till epoch becomes equal to training_epocs 
  • for (_x, _y) in zip (x,y)
    this is used to form a set of values from the given data and then parse through the so formed sets
  • sess.run(optimizer, feed_dict = {X: _x, Y: _y})
    used to run the first optimizer iteration on each the data points
  • if(epoch+1)%50==0
    to break the given iterations into a batch of 50 each
  • c= sess.run(cost, feed_dict= {X: x, Y: y})
    performs the training on the given data points
  • print(«Epoch»,(epoch+1),»: cost», c, «W =», sess.run(W), «b =», sess.run(b)
    print the values after each 50 epochs  
  1.   
  2. training_cost = sess.run(cost, feed_dict ={X: x, Y: y})     
  3. weight = sess.run(W)     
  4. bias = sess.run(b)     

the above code is used to store the values for the next session

  1.   
  2. predictions = weight * x + bias     
  3. print(«Training cost =», training_cost, «Weight =», weight, «bias =», bias, ‘\n’)     
  4.   
  5. plt.plot(x, y, ‘ro’, label =‘Original data’)     
  6. plt.plot(x, predictions, label =‘Fitted line’)     
  7. plt.title(‘Linear Regression Result’)     
  8. plt.legend()     
  9. plt.show()     

The above code demonstrates how we can use the trained model to predict values

LR_TensorFlow.py

  1. import numpy as np     
  2. import tensorflow as tf     
  3. import matplotlib.pyplot as plt     
  4. np.random.seed(101)     
  5. tf.set_random_seed(101)     
  6.   
  7.   
  8. x = np.linspace(05050)     
  9. y = np.linspace(05050)     
  10.     
  11.   
  12. x += np.random.uniform(-4450)     
  13. y += np.random.uniform(-4450)     
  14.     
  15. n = len(x)   
  16.   
  17. plt.scatter(x, y)     
  18. plt.xlabel(‘x’)     
  19. plt.xlabel(‘y’)     
  20. plt.title(«Training Data»)     
  21. plt.show()     
  22.     
  23. X = tf.placeholder(«float»)     
  24. Y = tf.placeholder(«float»)     
  25. W = tf.Variable(np.random.randn(), name = «W»)     
  26. b = tf.Variable(np.random.randn(), name = «b»)     
  27. learning_rate = 0.01    
  28. training_epochs = 1000    
  29.   
  30. y_pred = tf.add(tf.multiply(X, W), b)     
  31.     
  32.   
  33. cost = tf.reduce_sum(tf.pow(y_pred-Y, 2)) / (2 * n)     
  34.     
  35.   
  36. optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)     
  37.     
  38.   
  39. init = tf.global_variables_initializer()     
  40.   
  41. with tf.Session() as sess:     
  42.         
  43.       
  44.     sess.run(init)     
  45.         
  46.       
  47.     for epoch in range(training_epochs):     
  48.             
  49.           
  50.         for (_x, _y) in zip(x, y):     
  51.             sess.run(optimizer, feed_dict = {X : _x, Y : _y})     
  52.             
  53.           
  54.         if (epoch + 1) % 50 == 0:     
  55.               
  56.             c = sess.run(cost, feed_dict = {X : x, Y : y})     
  57.             print(«Epoch», (epoch + 1), «: cost =», c, «W =», sess.run(W), «b =», sess.run(b))     
  58.         
  59.       
  60.     training_cost = sess.run(cost, feed_dict ={X: x, Y: y})     
  61.     weight = sess.run(W)     
  62.     bias = sess.run(b)     
  63.     
  64.   
  65. predictions = weight * x + bias     
  66. print(«Training cost =», training_cost, «Weight =», weight, «bias =», bias, ‘\n’)     
  67.   
  68. plt.plot(x, y, ‘ro’, label =‘Original data’)     
  69. plt.plot(x, predictions, label =‘Fitted line’)     
  70. plt.title(‘Linear Regression Result’)     
  71. plt.legend()     
  72. plt.show()      

Output 

input_tf 

tf_train 

output_tf 

After, executing the code we get the following output

b[0] = 1.0199214 b[1] = 0.02561676

So the Linear Regression equation becomes :

[y] = 1.0199214+ 1.0199214*[x]

i.e y = b[0] + b[1]*x 

Conclusion

In this chapter, we studied regression and simple linear regression.

In the next chapter, we will study the simple logistic regression, which is another type of regression.

Как подогнать модель линейной регрессии к данным, вывод коэффициентов, стандартной ошибки, t-статистики и доверительного интервала из модели, а также методы визуализации для построения доверительных интервалов, дисперсии и эффектов отдельных ковариат.

Линейная регрессия — это наиболее часто используемая регрессионная модель. Причина в том, что он прост в использовании, он может давать полезную информацию и его легко понять. В этой статье мы обсудим подгонку модели линейной регрессии к данным, вывод из нее и некоторую полезную визуализацию.

Используемые инструменты:

  1. Язык программирования Python и несколько его популярных библиотек. Если вы не знаете всех этих библиотек, вы все равно сможете прочитать эту статью и понять концепцию.
  2. Среда Jupyter Notebook.

Выбор функции

По моему опыту, лучший способ научиться — это использовать пример. Я буду использовать набор данных и продолжу объяснять процесс подгонки модели к данным и выводить информацию. Я использую набор данных обследования под названием NHANES dataset. Это очень хороший набор данных для практики. Пожалуйста, не стесняйтесь скачать набор данных по этой ссылке:

Давайте импортируем пакеты и набор данных:

%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import statsmodels.api as sm
import numpy as npdf = pd.read_csv('nhanes_2015_2016.csv')

Этот набор данных довольно большой, и нам не нужны все функции для этой статьи. Мы сосредоточимся на регрессионной модели, в которой переменной результата будет размер талии. Если вы хотите работать с любой другой переменной, убедитесь, что вы выбрали количественную, а не категориальную переменную. Поскольку линейная регрессия предназначена для прогнозирования только количественной переменной, а не категориальной переменной. Но вы можете использовать категориальные переменные в качестве независимых переменных. Проверьте названия всех столбцов, чтобы иметь представление о наборе данных.

df.columns#Output:
Index(['SEQN', 'ALQ101', 'ALQ110', 'ALQ130', 'SMQ020', 'RIAGENDR', 'RIDAGEYR', 'RIDRETH1', 'DMDCITZN', 'DMDEDUC2', 'DMDMARTL', 'DMDHHSIZ', 'WTINT2YR', 'SDMVPSU', 'SDMVSTRA', 'INDFMPIR', 'BPXSY1', 'BPXDI1', 'BPXSY2', 'BPXDI2', 'BMXWT', 'BMXHT', 'BMXBMI', 'BMXLEG', 'BMXARML', 'BMXARMC', 'BMXWAIST', 'HIQ210'], dtype='object')

Итак, просто сохраните размер талии и несколько других переменных, которые, похоже, связаны с размером талии, и создайте новый DataFrame меньшего размера. Инстинктивно любой догадается, что вес может иметь сильную корреляцию с размером талии. Пол, ИМТ, рост тоже могут сыграть важную роль. Итак, создайте новый DataFrame меньшего размера только с этими столбцами и для удобства удалите все нулевые значения.

keep = ['BMXWT', 'BMXHT', 'BMXBMI', 'BMXWAIST', 'RIAGENDR']
db = df[keep].dropna()
db.head()

Линейная регрессия и интерпретация

У нас есть набор данных, содержащий пять столбцов: вес, рост, индекс массы тела (ИМТ), размер талии и пол. Как упоминалось ранее, размер талии — это выходная переменная, которую мы попытаемся предсказать, используя другие переменные. Первоначально используйте только одну переменную или одну ковариату, чтобы предсказать размер талии. Вес (BMXWT) может быть хорошей ковариатой для начала, потому что при более высоком весе ожидается, что объем талии будет выше. Хотя есть и другие факторы, такие как рост или пол. Но о них мы подумаем позже. Подойдет модель, в которой размер талии будет выражаться как функция веса.

model = sm.OLS.from_formula("BMXWAIST ~ BMXWT", data=db)
result = model.fit()
result.summary()

Эта таблица выше может показаться вам пугающей. Но большая часть информации для нас не так важна. Нам понадобится только эта часть таблицы:

В первом столбце у нас есть коэффициенты. Помните формулу линейной регрессии:

Y = AX + B

В таблице выше 42,7189 — это B, а 0,6991 — это A. И мы знаем, что A — это наклон. Итак, наш наклон равен 0,6991. Это означает, что если человек имеет одну единицу лишнего веса, его талия будет на 0,6991 единицы больше, и это основано на p-значении, упомянутом в P ›| t | столбец. Затем стандартная ошибка составляет 0,005, что указывает на расстояние этого оцененного наклона от истинного наклона. t-статистика говорит, что предполагаемый наклон 0,6991 составляет стандартную ошибку 144,292 выше нуля. Последние два столбца — это уровни достоверности. По умолчанию это уровень достоверности 95%. Доверительный интервал составляет 0,69 и 0,709, что является очень узким диапазоном. Позже мы нарисуем полосу доверительного интервала.

db.BMXWAIST.std()

Стандартное отклонение составляет 16,85, что кажется намного выше, чем наклон регрессии 0,6991. Но наклон регрессии — это среднее изменение размера талии за сдвиг веса на одну единицу. Это означает, что если у человека на 10 единиц больше веса, чем у другого человека, его талия будет на 0,6991 * 10 или на 6,99 единиц больше.

Корреляция

Помимо этих значений в небольшой подтаблице, важен еще один параметр из сводки результатов. Это значение R-квадрата в верхней строке сводки результатов. Здесь значение R-квадрата составляет 0,795, что означает, что 79,5% объема талии можно объяснить весом. Теперь проверьте этот коэффициент регрессии с квадратом коэффициента Пирсона.

cc = db[["BMXWAIST", "BMXWT"]].corr()
print(cc)BMXWAIST     BMXWT
BMXWAIST  1.000000  0.891828
BMXWT     0.891828  1.000000

Чтобы найти значение R-квадрата:

cc.BMXWAIST.BMXWT**2

Это снова возвращает значение R в квадрате 0,795. Самая важная часть, а именно прогнозируемые значения размера талии в зависимости от веса, может быть найдена следующим образом:

result.fittedvalues

Это лишь часть результата. Исходный результат намного больше.

Давайте добавим вторую ковариату и посмотрим, как она влияет на эффективность регрессии. Я выбираю пол в качестве второй ковариаты. Я хочу использовать столбец с пометкой «пол»:

db["RIAGENDRx"] = db.RIAGENDR.replace({1: "Male", 2: "Female"})

Вот модель и краткое описание модели:

model = sm.OLS.from_formula("BMXWAIST ~ BMXWT + RIAGENDRx", data=db)
result = model.fit()
result.summary()

В приведенном выше коде BMXWT + RIAGENDRx не означает, что эти два столбца объединены или математически сложены. Это просто указывает на то, что они оба включены в модель. В этой новой модели размер талии выражается как функция веса и пола. Из приведенного выше результата мы можем найти, что коэффициент веса (BMXWT) составляет 0,7272, что немного выше, чем раньше. На этот раз коэффициент подразумевает, что если вес двух людей одного пола отличается на одну единицу, их талия будет отличаться на 0,7272 единицы. С другой стороны, коэффициент пола (RIAGENDRx) -5,0832 означает, что если мы сравним мужчину и женщину с одинаковым весом, у мужчины будет размер талии на 5,0832 единицы меньше, чем у женщины.

Все коэффициенты выражены как средние. Если мы сравним мужчину веса 70 и женщину веса 50, талия мужчины будет примерно в -5,0832 + (70–50) * 0,7272 раза больше, чем талия женщины.

Коэффициент регрессии веса (BMXWT) изменился немного, не слишком сильно после добавления пола в модель. Добавление второй ковариаты изменяет первую ковариату, если они в некоторой степени коррелированы. Давайте проверим корреляцию между двумя ковариатами:

db[['BMXWT', 'RIAGENDR']].corr()

Как видите, корреляция составляет -0,2375. Так что корреляция есть, но не слишком сильная. Вы можете найти прогнозируемые значения размера талии, используя метод подобранной стоимости, как я показал ранее. Я не буду этого снова демонстрировать.

Добавим третью ковариату. В качестве третьей ковариаты я выбрал BMXBMI. Вы можете попробовать и другие переменные.

model = sm.OLS.from_formula("BMXWAIST ~ BMXWT + RIAGENDRx + BMXBMI", data=db)
result = model.fit()
result.summary()

Обратите внимание, что после добавления BMXBMI коэффициент для гендерной переменной значительно изменился. Можно сказать, что ИМТ работает как маскирующая часть связи между размером талии и гендерной переменной. С другой стороны, коэффициент веса также существенно изменился. ИМТ также работал как маскирующая часть во взаимосвязи между талией и весом.

Вы можете добавить в модель дополнительные ковариаты и увидеть эффект каждой ковариаты.

Визуализация модели

В этом разделе мы визуализируем результат регрессионной модели. Мы построим линию регрессии, которая представляет собой подогнанные значения или предсказанные значения с доверительным интервалом. Если вам нужно освежить в памяти концепции доверительного интервала, ознакомьтесь с этой статьей:

Для этого графика мы зафиксируем пол как женский, а ИМТ как 25. Кроме того, нам нужно сохранить одну независимую переменную в качестве переменной фокуса. Мы сохраним его как вес (BMXWT). Итак, график покажет прогнозируемый размер талии женщины с ИМТ 25 всех возрастов.

from statsmodels.sandbox.predict_functional import predict_functional
values = {"RIAGENDRx": "Female", "RIAGENDR": 1, "BMXBMI": 25}pr, cb, fv = predict_functional(result, "BMXWT",
                values=values, ci_method="simultaneous")#Here, pr is the predicted values(pr), cb is the confidence band and #the fv is the function valuesax = sns.lineplot(fv, pr, lw=4)
ax.fill_between(fv, cb[:, 0], cb[:, 1], color='grey', alpha=0.4)
ax.set_xlabel("BMXWT")
_ = ax.set_ylabel("BMXWAIST")

Серая область на картинке — это полосы уверенности. Это означает, что истинный размер талии будет в этой области. Ширина серой области варьируется вдоль линии регрессии. Итак, доверительный интервал с возрастом меняется.

Вы можете зафиксировать вес и увидеть результат для определенного веса. Давайте зафиксируем вес на 65 и построим график зависимости ИМТ от талии для женского населения. Для этого нам нужно изменить параметр «значения». Потому что мы установили здесь значение ИМТ на 25. Теперь мы хотим исправить вес. Итак, нам нужно удалить значение BMI из параметра values ​​и добавить в него вес.

del values["BMXBMI"] # Delete this as it is now the focus variable
#del values['BMXWT']
values["BMXWT"] = 65
pr, cb, fv = predict_functional(result, "BMXBMI",
                values=values, ci_method="simultaneous")ax = sns.lineplot(fv, pr, lw=4)
ax.fill_between(fv, cb[:, 0], cb[:, 1], color='grey', alpha=0.4)
ax.set_xlabel("BMI")
_ = ax.set_ylabel("BMXWAIST")

На графиках выше мы построили только средние значения. Модель среднего размера талии для данного веса, пола или ИМТ. Используя ту же модель регрессии, можно также оценить структуру дисперсии, которая покажет, насколько наблюдаемые значения отклоняются от среднего. Для этого мы можем построить график остатков против подобранного значения. Помните, что подобранные значения — это предсказанные значения или наблюдаемые средние значения, а остатки — это разница между наблюдаемыми средними и истинными значениями.

pp = sns.scatterplot(result.fittedvalues, result.resid)
pp.set_xlabel("Fitted values")
_ = pp.set_ylabel("Residuals")

Похоже, что когда наблюдаемые значения ниже, дисперсия немного выше.

Также возможно визуализировать эффект только одной ковариаты. Мы можем увидеть график компонент плюс остаток или график частичного остатка, используя только одну ковариату, чтобы увидеть, сохраняем ли мы другие коварианты фиксированными, как изменяются зависимые переменные:

from statsmodels.graphics.regressionplots import plot_ccprax = plt.axes()
plot_ccpr(result, "BMXWT", ax)
ax.lines[0].set_alpha(0.2) # Reduce overplotting with transparency
_ = ax.lines[1].set_color('orange')

Теперь мы можем увидеть влияние ИМТ таким же образом, когда весовая переменная фиксирована.

ax = plt.axes()
plot_ccpr(result, "BMXBMI", ax)
ax.lines[0].set_alpha(0.2)
ax.lines[1].set_color("orange")

Влияние ИМТ намного сильнее, чем вес.

В этой статье вы узнали, как подобрать модель линейной регрессии, различные статистические параметры, связанные с линейной регрессией, и некоторые хорошие методы визуализации. Методы визуализации включали построение доверительной полосы линии регрессии, построение остатков и построение графика эффекта одной ковариаты.

Дополнительные рекомендации для чтения:

Проверка гипотез, характеристики и расчет

Доверительный интервал, расчет и характеристики

Как рассчитать доверительный интервал среднего и разницу среднего

Как выполнить проверку гипотез на Python: одно среднее и разница двумя способами

Как представить отношения между несколькими переменными в Python

Группа мастеров Панд за эффективное обобщение и анализ данных

Создайте полную нейронную сеть с нуля на Python

АКТУАЛЬНОСТЬ ТЕМЫ

Общие положения

Про регрессионный анализ вообще, и его применение в DataScience написано очень много. Есть множество учебников, монографий, справочников и статей по прикладной статистике, огромное количество информации в интернете, примеров расчетов. Можно найти множество кейсов, реализованных с использованием средств Python. Казалось бы — что тут еще можно добавить?

Однако, как всегда, есть нюансы:

1. Регрессионный анализ — это прежде всего процесс, набор действий исследователя по определенному алгоритму: «подготовка исходных данных — построение модели — анализ модели — прогнозирование с помощью модели». Это ключевая особенность. Не представляет особой сложности сформировать DataFrame исходных данных и построить модель, запустить процедуру из библиотеки statsmodels. Однако подготовка исходных данных и последующий анализ модели требуют гораздо больших затрат человеко-часов специалиста и строк программного кода, чем, собственно, построение модели. На этих этапах часто приходится возвращаться назад, корректировать модель или исходные данные. Этому, к сожалению, во многих источниках, не удаляется достойного внимания, а иногда — и совсем не уделяется внимания, что приводит к превратному представлению о регрессионном анализе.

2. Далеко не во всех источниках уделяется должное внимание интерпретации промежуточных и финальных результатов. Специалист должен уметь интерпретировать каждую цифру, полученную в ходе работы над моделью.

3. Далеко не все процедуры на этапах подготовки исходных данных или анализа модели в источниках разобраны подробно. Например, про проверку значимости коэффициента детерминации найти информацию не представляет труда, а вот про проверку адекватности модели, построение доверительных интервалов регрессии или про специфические процедуры (например, тест Уайта на гетероскедастичность) информации гораздо меньше.

4. Своеобразная сложность может возникнуть с проверкой статистических гипотез: для отечественной литературы по прикладной статистике больше характерно проверять гипотезы путем сравнения расчетного значения критерия с табличным, а в иностранных источниках чаще определяется расчетный уровень значимости и сравнивается с заданным (чаще всего 0.05 = 1-0.95). В разных источниках информации реализованы разные подходы. Инструменты python (прежде всего библиотеки scipy и statsmodels) также в основном оперируют с расчетным уровнем значимости.

5. Ну и, наконец, нельзя не отметить, что техническая документация библиотеки statsmodels составлена, на мой взгляд, далеко не идеально: информация излагается путано, изобилует повторами и пропусками, описание классов, функций и свойств выполнено фрагментарно и количество примеров расчетов — явно недостаточно.

Поэтому я решил написать ряд обзоров по регрессионному анализу средствами Python, в которых акцент будет сделан на практических примерах, алгоритме действий исследователя, интерпретации всех полученных результатов, конкретных методических рекомендациях. Буду стараться по возможности избегать теории (хотя совсем без нее получится) — все-таки предполагается, что специалист DataScience должен знать теорию вероятностей и математическую статистику, хотя бы в рамках курса высшей математики для технического или экономического вуза.

В данном статье остановимся на самои простом, классическом, стереотипном случае — простой линейной регрессии (simple linear regression), или как ее еще принято называть — парной линейной регрессионной модели (ПЛРМ) — в ситуации, когда исследователя не подстерегают никакие подводные камни и каверзы — исходные данные подчиняются нормальному закону, в выборке отсутствуют аномальные значения, отсутствует ложная корреляция. Более сложные случаи рассмотрим в дальнейшем.

Для построение регрессионной модели будем пользоваться библиотекой statsmodels.

В данной статье мы рассмотрим по возможности полный набор статистических процедур. Некоторые из них (например, дескриптивная статистика или дисперсионный анализ регрессионной модели) могут показаться избыточными. Все так, но эти процедуры улучшают наше представление о процессе и об исходных данных, поэтому в разбор я их включил, а каждый исследователь сам вправе для себя определить, потребуются ему эти процедуры или нет.

Краткий обзор источников

Источников информации по корреляционному и регрессионному анализу огромное количество, в них можно просто утонуть. Поэтому позволю себе просто порекомендовать ряд источников, на мой взгляд, наиболее полезных:

  1. Кобзарь А.И. Прикладная математическая статистика. Для инженеров и научных работников. — М.: ФИЗМАТЛИТ, 2006. — 816 с.

  2. Львовский Е.Н. Статистические методы построения эмпирических формул. — М.: Высшая школа, 1988. — 239 с.

  3. Фёрстер Э., Рёнц Б. Методы корреляционного и регрессионного анализа / пер с нем. — М.: Финансы и статистика, 1983. — 302 с.

  4. Афифи А., Эйзен С. Статистический анализ. Подход с использованием ЭВМ / пер с англ. — М.: Мир, 1982. — 488 с.

  5. Дрейпер Н., Смит Г. Прикладной регрессионный анализ. Книга 1 / пер.с англ. — М.: Финансы и статистика, 1986. — 366 с.

  6. Айвазян С.А. и др. Прикладная статистика: Исследование зависимостей. — М.: Финансы и статистика, 1985. — 487 с.

  7. Прикладная статистика. Основы эконометрики: В 2 т. 2-е изд., испр. — Т.2: Айвазян С.А. Основы эконометрики. — М.: ЮНИТИ-ДАНА, 2001. — 432 с.

  8. Магнус Я.Р. и др. Эконометрика. Начальный курс — М.: Дело, 2004. — 576 с.

  9. Носко В.П. Эконометрика. Книга 1. — М.: Издательский дом «Дело» РАНХиГС, 2011. — 672 с.

  10. Брюс П. Практическая статистика для специалистов Data Science / пер. с англ. — СПб.: БХВ-Петербург, 2018. — 304 с.

  11. Уатт Дж. и др. Машинное обучение: основы, алгоритмы и практика применения / пер. с англ. — СПб.: БХВ-Петербург, 2022. — 640 с.

Прежде всего следует упомянуть справочник Кобзаря А.И. [1] — это безусловно выдающийся труд. Ничего подобного даже близко не издавалось. Всем рекомендую иметь под рукой.

Есть очень хорошее практическое пособие [2] — для начинающих и практиков.>

Добротная работа немецких авторов [3]. Все разобрано подробно, обстоятельно, с примерами — очень хорошая книга. Примеры приведены из области экономики.

Еще одна добротная работа — [4], с примерами медико-биологического характера.

Работа [5] считается одним из наиболее полных изложений прикладного регрессионного анализа.

Более сложные работы — [6] (классика жанра), [7], [8], [9] — выдержаны на достаточно высоком математическом уровне, примеры из экономической области.

Свежие работы [10] (с примерами на языке R) и [11] (с примерами на python).

Cтатьи

Статей про регрессионный анализ в DataScience очень много, обращаю внимание на некоторые весьма полезные из них.

Серия статей «Python, корреляция и регрессия», охватывающая весь процесс регрессионного анализа:

  • первичная обработка данных, визуализация и корреляционный анализ;

  • регрессия;

  • теория матриц в регрессионном анализе, проверка  адекватности, мультиколлинеарность;

  • прогнозирование с помощью регрессионных моделей.

Очень хороший обзор «Интерпретация summary из statsmodels для линейной регрессии». В этой статье даны очень полезные ссылки:

  • Statistical Models

  • Interpreting Linear Regression Through statsmodels .summary()

Статья «Регрессионные модели в Python».

Основные предпосылки (гипотезы) регрессионного анализа

Очень кратко — об этом написано тысячи страниц в учебниках — но все же вспомним некоторые основы теории.

Проверка исходных предпосылок является очень важным моментом при статистическом анализе регрессионной модели. Если мы рассматриваем классическую линейную регрессионную модель вида:

то основными предпосылками при использовании обычного метода наименьших квадратов (МНК) для оценки ее параметров являются:

  1. Среднее значение (математическое ожидание) случайной составляющей равно нулю:

  1. Дисперсия случайной составляющей является постоянной:

В случае нарушения данного условия мы сталкиваемся с явлением гетероскедастичности.

  1. Значения случайной составляющей статистически независимы (некоррелированы) между собой:

В случае нарушения данного условия мы сталкиваемся с явлением автокорреляции.

  1. Условие существования обратной матрицы

что эквивалентно одному из двух следующих условий:

то есть число наблюдений должно превышать число параметров.

  1. Значения случайной составляющей некоррелированы со значениями независимых переменных:

  1. Случайная составляющая имеет нормальный закон распределения (с математическим ожиданием равным нулю — следует из условия 1):

Более подробно — см.: [3, с.90], [4, с.147], [5, с.122], [6, с.208], [7, с.49], [8, с.68], [9, с.88].

Кроме гетероскедастичности и автокорреляции возможно возникновение и других статистических аномалий — мультиколлинеарности, ложной корреляции и т.д.

Доказано, что оценки параметров, полученные с помощью МНК, обладают наилучшими свойствами (несмещенность, состоятельность, эффективность) при соблюдении ряда условий:

  • выполнение приведенных выше исходных предпосылок регрессионного анализа;

  • число наблюдений на одну независимую переменную должно быть не менее 5-6;

  • должны отсутствовать аномальные значения (выбросы).

Кроме обычного МНК существуют и другие его разновидности (взвешенный МНК, обобщенный МНК), которые применяются при наличии статистических аномалий. Кроме МНК применяются и другие методы оценки параметров моделей. В этом обзоре мы эти вопросы рассматривать не будем.

Алгоритм проведения регрессионного анализа

Алгоритм действий исследователя при построении регрессионной модели (полевые работы мы, по понятным причинам, не рассматриваем — считаем, что исходные данные уже получены):

  1. Подготовительный этап — постановка целей и задач исследования.

  2. Первичная обработка исходных данных — об этом много написано в учебниках и пособиях по DataScience, сюда могут относится:

  • выявление нерелевантных признаков (признаков, которые не несут полезной информации), нетипичных данных (выбросов), неинформативных признаков (имеющих большое количество одинаковых значений) и работа с ними (удаление/преобразование);

  • выделение категориальных признаков;

  • работа с пропущенными значениями;

  • преобразование признаков-дат в формат datetime и т.д.

  1. Визуализация исходных данных — предварительный графический анализ.

  2. Дескриптивная (описательная) статистика — расчет выборочных характеристик и предварительные выводы о свойствах исходных данных.

  3. Исследование закона распределения исходных данных и, при необходимости, преобразование исходных данных к нормальному закону распределения.

  4. Выявление статистически аномальных значений (выбросов), принятие решения об их исключении.

    Этапы 4, 5 и 6 могут быть при необходимости объединены.

  5. Корреляционный анализ — исследование корреляционных связей между исходными данными; это разведка перед проведением регрессионного анализа.

  6. Построение регрессионной модели:

  • выбор моделей;

  • выбор методов;

  • оценка параметров модели.

  1. Статистический анализ регрессионной модели:  

  • оценка ошибок аппроксимации (error metrics);

  • анализ остатков (проверка нормальности распределения остатков и гипотезы о равенстве нулю среднего значения остатков);

  • проверка адекватности модели;

  • проверка значимости коэффициента детерминации;

  • проверка значимости коэффициентов регрессии;

  • проверка мультиколлинеарности (для множественных регрессионных моделей; вообще мультиколлинеарные переменные выявляются еще на стадии корреляционного анализа);

  • проверка автокорреляции;

  • проверка гетероскедастичности.

   Этапы 8 и 9 могут быть при необходимости повторяться несколько раз.

  1. Сравнительный анализ нескольких регрессионных моделей, выбор наилучшей (при необходимости).

  2. Прогнозирование с помощью регрессионной модели и оценка качества прогноза.

  3. Выводы и рекомендации.

Само собой, этот алгоритм не есть истина в последней инстанции — в зависимости от особенностей исходных данных и вида модели могут возникать дополнительные задачи.

Применение пользовательских функций

Далее в обзоре мной будут использованы несколько пользовательских функций для решения разнообразных задач. Все эти функции созданы для облегчения работы и уменьшения размера программного кода. Данные функции загружается из пользовательского модуля my_module__stat.py, который доступен в моем репозитории на GitHub. Лично мне так удобнее работать, хотя каждый исследователь сам формирует себе инструменты по душе — особенно в части визуализации. Желающие могут пользоваться этими функциями, либо создать свои.

Итак, вот перечень данных функций:

  • graph_scatterplot_sns — функция позволяет построить точечную диаграмму средствами seaborn и сохранить график в виде png-файла;

  • graph_hist_boxplot_probplot_XY_sns  — функция позволяет визуализировать исходные данные для простой линейной регрессии путем одновременного построения гистограммы, коробчатой диаграммы и вероятностного графика (для переменных X и Y) средствами seaborn и сохранить график в виде png-файла; имеется возможность выбирать, какие графики строить (h — hist, b — boxplot, p — probplot);

  • descriptive_characteristics — функция возвращает в виде DataFrame набор статистических характеристики выборки, их ошибок и доверительных интервалов;

  • detecting_outliers_mad_test — функция выполняет проверку наличия аномальных значений (выбросов) по критерию наибольшего абсолютного отклонения (более подробно — см.[1, с.547]);

  • norm_distr_check — проверка нормальности распределения исходных данных с использованием набора из нескольких статистических тестов;

  • corr_coef_check — функция выполняет расчет коэффициента линейной корреляции Пирсона, проверку его значимости и расчет доверительных интервалов; об этой функции я писал в своей статье.

  • graph_regression_plot_sns —  — функция позволяет построить график регрессионной модели.

Ряд пользовательских функций мы создаем в процессе данного обзора (они тоже включены в пользовательский модуль my_module__stat.py):

  • regression_error_metrics — расчет ошибок аппроксимации регрессионной модели;

  • ANOVA_table_regression_model — вывод таблицы дисперсионного анализа регрессионной модели;

  • regression_model_adequacy_check — проверка адекватности регрессионной модели по критерию Фишера;

  • determination_coef_check — проверка значимости коэффициента детерминации по критерию Фишера;

  • regression_coef_check — проверка значимости коэффициентов регрессии по критеирю Стьюдента;

  • Goldfeld_Quandt_test, Breush_Pagan_test, White_test — проверка гетероскедастичности с использование тестов Голдфелда-Квандта, Бриша-Пэгана и Уайта соответственно;

  • regression_pair_predict — функция для прогнозирования с помощью парной регрессионной модели: рассчитывает прогнозируемое значение переменной Y по заданной модели, а также доверительные интервалы среднего и индивидуального значения для полученного прогнозируемого значения Y;

  • graph_regression_pair_predict_plot_sns — прогнозирование: построение графика регрессионной модели (с доверительными интервалами) и вывод расчетной таблицы с данными для заданной области значений X.

ПОСТАНОВКА ЗАДАЧИ

В качестве примера рассмотрим практическую задачу из области экспертизы промышленной безопасности — калибровку ультразвукового прибора для определения прочности бетона.

Итак, суть задачи: при обследовании несущих конструкций зданий и сооружений эксперт определяет прочность бетона с использованием ультразвукового прибора «ПУЛЬСАР-2.1», для которого необходимо предварительно построить градуировочную зависимость. Заключается это в следующем — производятся замеры с фиксацией следующих показателей:

  • X — показания ультразвукового прибора «ПУЛЬСАР-2.1» (м/с)

  • Y — результаты замера прочности бетона (методом отрыва со скалыванием) склерометром ИПС-МГ4.03.

Предполагается, что между показателями X и Y имеется линейная регрессионная зависимость, которая позволит прогнозировать прочность бетона на основании измерений, проведенных прибором «ПУЛЬСАР-2.1».

Были выполнены замеры фактической прочности бетона конструкций для бетонов одного вида с одним типом крупного заполнителя, с единой технологией производства. Для построения были выбраны 14 участков (не менее 12), включая участки, в которых значение косвенного показателя максимальное, минимальное и имеет промежуточные значения.

Настройка заголовков отчета:

# Общий заголовок проекта
Task_Project = 'Калибровка ультразвукового прибора "ПУЛЬСАР-2.1" nдля определения прочности бетона'

# Заголовок, фиксирующий момент времени
AsOfTheDate = ""

  # Заголовок раздела проекта
Task_Theme = ""

# Общий заголовок проекта для графиков
Title_String = f"{Task_Project}n{AsOfTheDate}"

# Наименования переменных
Variable_Name_X = "Скорость УЗК (м/с)"
Variable_Name_Y = "Прочность бетона (МПа)"

# Константы
INCH = 25.4    # мм/дюйм
  DecPlace = 5    # number of decimal places - число знаков после запятой

# Доверительная вероятность и уровень значимости:
p_level = 0.95
a_level = 1 - p_level   

Подключение модулей и библиотек:

# Стандартные модули и библиотеки

import os    # загрузка модуля для работы с операционной системой
import sys
import platform
print('{:<35}{:^0}'.format("Текущая версия Python: ", platform.python_version()), 'n')

import math
from math import *    # подключаем все содержимое модуля math, используем без псевдонимов

import numpy as np
#print ("Текущая версия модуля numpy: ", np.__version__)
print('{:<35}{:^0}'.format("Текущая версия модуля numpy: ", np.__version__))
from numpy import nan

import scipy as sci
print('{:<35}{:^0}'.format("Текущая версия модуля scipy: ", sci.__version__))
import scipy.stats as sps

import pandas as pd
print('{:<35}{:^0}'.format("Текущая версия модуля pandas: ", pd.__version__))

import matplotlib as mpl
print('{:<35}{:^0}'.format("Текущая версия модуля matplotlib: ", mpl.__version__))
import matplotlib.pyplot as plt

import seaborn as sns
print('{:<35}{:^0}'.format("Текущая версия модуля seaborn: ", sns.__version__))

import statsmodels.api as sm
import statsmodels.formula.api as smf
import statsmodels.graphics.api as smg
import statsmodels.stats.api as sms
from statsmodels.compat import lzip
print('{:<35}{:^0}'.format("Текущая версия модуля statsmodels: ", sm.__version__))

import statistics as stat    # module 'statistics' has no attribute '__version__'

import sympy as sym
print('{:<35}{:^0}'.format("Текущая версия модуля sympy: ", sym.__version__))

# Настройки numpy
np.set_printoptions(precision = 4, floatmode='fixed')

# Настройки Pandas
pd.set_option('display.max_colwidth', None)    # текст в ячейке отражался полностью вне зависимости от длины
pd.set_option('display.float_format', lambda x: '%.4f' % x)

# Настройки seaborn
sns.set_style("darkgrid")
sns.set_context(context='paper', font_scale=1, rc=None)    # 'paper', 'notebook', 'talk', 'poster', None

# Настройки Mathplotlib
f_size = 8    # пользовательская переменная для задания базового размера шрифта
plt.rcParams['figure.titlesize'] = f_size + 12    # шрифт заголовка
plt.rcParams['axes.titlesize'] = f_size + 10      # шрифт заголовка
plt.rcParams['axes.labelsize'] = f_size + 6       # шрифт подписей осей
plt.rcParams['xtick.labelsize'] = f_size + 4      # шрифт подписей меток
plt.rcParams['ytick.labelsize'] = f_size + 4
plt.rcParams['legend.fontsize'] = f_size + 6      # шрифт легенды

# Пользовательские модули и библиотеки

Text1 = os.getcwd()    # вывод пути к текущему каталогу
#print(f"Текущий каталог: {Text1}")

sys.path.insert(1, "D:REPOSITORYMyModulePython")

from my_module__stat import *

ФОРМИРОВАНИЕ ИСХОДНЫХ ДАННЫХ

Показания ультразвукового прибора «ПУЛЬСАР-2.1» (м/с):

X = np.array([
    4416, 4211, 4113, 4110, 4122,
    4427, 4535, 4311, 4511, 4475,
    3980, 4490, 4007, 4426
    ])

Результаты замера прочности бетона (методом отрыва со скалыванием) прибором ИПС-МГ4.03:

Y = np.array([
    34.2, 35.1, 31.5, 30.8, 30.0,
    34.0, 35.4, 35.8, 38.0, 37.7,
    30.0, 37.8, 31.0, 35.2
    ])

Запишем данные в DataFrame:

calibrarion_df = pd.DataFrame({
    'X': X,
    'Y': Y})
display(calibrarion_df)
calibrarion_df.info()

Сохраняем данные в csv-файл:

calibrarion_df.to_csv(
    path_or_buf='data/calibrarion_df.csv',
    mode='w+',
    sep=';')

Cоздаем копию исходной таблицы для работы:

dataset_df = calibrarion_df.copy()

ВИЗУАЛИЗАЦИЯ ДАННЫХ

Границы значений переменных (при построении графиков):

(Xmin_graph, Xmax_graph) = (3800, 4800)
(Ymin_graph, Ymax_graph) = (25, 45)
# Пользовательская функция
graph_scatterplot_sns(
    X, Y,
    Xmin=Xmin_graph, Xmax=Xmax_graph,
    Ymin=Ymin_graph, Ymax=Ymax_graph,
    color='orange',
    title_figure=Task_Project,
    x_label=Variable_Name_X,
    y_label=Variable_Name_Y,
    s=100,
    file_name='graph/scatterplot_XY_sns.png')

Существует универсальный набор графиков — гистограмма, коробчатая диаграмма, вероятностный график — которые позволяют исследователю сделать предварительные выводы о свойствах исходных данных.

Так как объем выборки невелик (n=14), строить гистограммы распределения переменных X и Y не имеет смысла, поэтому ограничимся построением коробчатых диаграмм и вероятностных графиков:

# Пользовательская функция
graph_hist_boxplot_probplot_XY_sns(
    data_X=X, data_Y=Y,
    data_X_min=Xmin_graph, data_X_max=Xmax_graph,
    data_Y_min=Ymin_graph, data_Y_max=Ymax_graph,  
    graph_inclusion='bp',    # выбираем для построения виды графиков: b - boxplot, p - probplot)
    data_X_label=Variable_Name_X,
    data_Y_label=Variable_Name_Y,
    title_figure=Task_Project,
    file_name='graph/hist_boxplot_probplot_XY_sns.png')    

Для сравнения характера распределений переменных X и Y возможно также построить совмещенную коробчатую диаграмму по стандартизованным данным:

# стандартизуем исходные данные
standardize_df = lambda X: ((X - np.mean(X))/np.std(X))

dataset_df_standardize = dataset_df.copy()
dataset_df_standardize = dataset_df_standardize.apply(standardize_df)
display(dataset_df_standardize)

# построим график
fig, axes = plt.subplots(figsize=(210/INCH, 297/INCH/2))
axes.set_title("Распределение стандартизованных переменных X и Y", fontsize = 16)
sns.boxplot(
    data=dataset_df_standardize,    
    orient='h',
    width=0.5,
    ax=axes)
plt.show()

Графический анализ позволяет сделать следующие выводы:

  1. Отсутствие выбросов на коробчатых диаграммах свидетельствует об однородности распределения переменных.

  2. Смещение медианы вправо на коробчатых диаграммах свидетельствует о левосторонней асимметрии распределения.

ДЕСКРИПТИВНАЯ (ОПИСАТЕЛЬНАЯ СТАТИСТИКА)

Собственно говоря, данный этап требуется проводить далеко не всегда, однако с помощью статистических характеристик выборки мы тоже можем сделать полезные выводы.

Описательная статистика исходных данных средствами библиотеки Pandas — самый простой вариант:

dataset_df.describe()

Описательная статистика исходных данных средствами библиотеки statsmodels — более развернутый вариант, с большим количеством показателей:

from statsmodels.stats.descriptivestats import Description
result = Description(
    dataset_df,
    stats=["nobs", "missing", "mean", "std_err", "ci", "ci", "std", "iqr", "mad", "coef_var", "range", "max", "min", "skew", "kurtosis", "mode",
           "median", "percentiles", "distinct", "top", "freq"],
    alpha=a_level,
    use_t=True)
display(result.summary())

Описательная статистика исходных данных с помощью пользовательской функции descriptive_characteristics:

# Пользовательская функция
descriptive_characteristics(X)

Выводы:

  1. Сравнение показателей среднего арифметического (mean) и медианы (median) свидетельствует о левосторонней асимметрии (т.к.mean < median).

  2. Значение коэффициента вариации CV = 0.0445 и доверительный интервал для него 0.0336 ≤ CV ≤ 0.0657 свидетельствует об однородности исходных данных (т.к. CV ≤ 0.33).

  3. Значение показателя асимметрии skew (As) = -0.3101 свидетельствует об умеренной левосторонней асимметрии распределении (т.к. |As| ≤ 0.5, As < 0).

  4. Значение показателя эксцесса kurtosis (Es) = -1.4551 свидетельствует о плосковершинном распределении (platykurtic distribution) (т.к. Es < 0).

# Пользовательская функция
descriptive_characteristics(Y)

Выводы:

  1. Сравнение показателей среднего арифметического (mean) и медианы (median) свидетельствует о левосторонней асимметрии (т.к.mean < median).

  2. Значение коэффициента вариации CV = 0.0822 и доверительный интервал для него 0.06202 ≤ CV ≤ 0.1217 свидетельствует об однородности исходных данных (т.к. CV ≤ 0.33).

  3. Значение показателя асимметрии skew (As) = -0.1109 свидетельствует о приблизительно симметричном распределении (т.к. |As| ≤ 0.25).

  4. Значение показателя эксцесса kurtosis (Es) = -1.3526 свидетельствует о плосковершинном распределении (platykurtic distribution) (т.к. Es < 0).

ПРОВЕРКА НОРМАЛЬНОСТИ РАСПРЕДЕЛЕНИЯ

Для проверки нормальности распределения использована пользовательская функция norm_distr_check, которая объединяет в себе набор стандартных статистических тестов проверки нормальности. Все тесты относятся к стандартному инструментарию Pyton (библиотека scipy, модуль stats), за исключением теста Эппса-Палли (Epps-Pulley test); о том, как реализовать этот тест средствами Pyton я писал в своей статье https://habr.com/ru/post/685582/.

Примечание: для использования функции norm_distr_check в каталог с ipynb-файлом необходимо поместить папку table c файлом Tep_table.csv, который содержит табличные значения статистики критерия Эппса-Палли.

# пользовательская функция
norm_distr_check(X)

# Пользовательская функция
norm_distr_check (Y)

Вывод: большинство статистических тестов позволяют принять гипотезу о нормальности распределения переменных X и Y.

ПРОВЕРКА АНОМАЛЬНЫХ ЗНАЧЕНИЙ (ВЫБРОСОВ)

Статистическую проверку аномальных значений (выбросов) не стоит путать с проверкой выбросов, которая проводится на этапе первичной обработки результатов наблюдений. Последняя проводится с целью отсеять явные ошибочные данные (например, в результате неправильно поставленной запятой величина показателя может увеличиться/уменьшиться на порядок); здесь же мы говорим о статистической проверке данных, которые уже прошли этап первичной обработки.

Имеется довольно много критериев для проверки аномальных значений (подробнее см.[1]); вообще данная процедура довольно неоднозначная:

  • критерии зависят от вида распределения;

  • мало данных о сравнительной мощности этих критериев;

  • даже в случае принятии гипотезы о нормальном распределении в выборке могут быть обнаружены аномальные значения и пр.

Кроме существует дилемма: если какие-то значения в выборке признаны выбросами — стоит или не стоит исследователю исключать их? Ведь каждое значение несет в себе информацию, причем иногда весьма ценную, а сильно отклоняющиеся от основного массива данные (которые не являются выбросами в смысле первичной обработки, но являются статистическим значимыми аномальными значениями) могут кардинально изменить статистический вывод.

В общем, о задаче выявления аномальных значений (выбросов) можно написать отдельно, а пока, в данном разборе, ограничимся проверкой аномальных значений по критерию наибольшего максимального отклонения (см.[1, с.547]) с помощью пользовательской функции detecting_outliers_mad_test. Данные функция возвращает DataFrame, которые включает список аномальных значений со следующими признаками:

  • value — проверяемое значение из выборки;

  • mad_calc и mad_table — расчетное и табличное значение статистики критерия;

  • outlier_conclusion — вывод (выброс или нет).

Обращаю внимание, что критерий наибольшего максимального отклонения можно использовать только для нормально распределенных данных.

# пользовательская функция
print("Проверка наличия выбросов переменной X:n")
result = detecting_outliers_mad_test(X)
mask = (result['outlier_conclusion'] == 'outlier')
display(result[mask])

# пользовательская функция
print("Проверка наличия выбросов переменной Y:n")
result = detecting_outliers_mad_test(Y)
mask = (result['outlier_conclusion'] == 'outlier')
display(result[mask])

Вывод: в случае обеих переменных X и Y список пуст, следовательно, аномальных значений (выбросов) не выявлено.

КОРРЕЛЯЦИОННЫЙ АНАЛИЗ

Корреляционный анализ — это разведка перед построением регрессионной модели.

Выполним расчет коэффициента линейной корреляции Пирсона, проверку его значимости и построение доверительных интервалов с помощью пользовательской функции corr_coef_check (про эту функцию более подробно написано в моей статье https://habr.com/ru/post/683442/):

# пользовательская функция
display(corr_coef_check(X, Y, scale='Evans'))

Выводы:

  1. Значение коэффициента корреляции coef_value = 0.8900 свидетельствует о весьма сильной корреляционной связи (по шкале Эванса).

  2. Коэффициент корреляции значим по критерию Стьюдента: t_calc ≥ t_table, a_calc ≤ a_level.

  3. Доверительный интервал для коэффициента корреляции: 0.6621 ≤ coef_value ≤ 0.9625.

РЕГРЕССИОННЫЙ АНАЛИЗ

Предварительная визуализация

python позволяет выполнить предварительную визуализацию, например, с помощью функции jointplot библиотеки seaborn:

fig = plt.figure(figsize=(297/INCH, 210/INCH))
axes = sns.jointplot(
    x=X, y=Y,
    kind='reg',
    ci=95)
plt.show()

Построение модели

Выполним оценку параметров и анализ простой линейной регрессии (simple linear regression), используя библиотеку statsmodels (https://www.statsmodels.org/) и входящий в нее модуль линейной регрессии Linear Regression (https://www.statsmodels.org/stable/regression.html).

Данный модуль включает в себя классы, реализующие различные методы оценки параметров моделей линейной регрессии, в том числе:

  • класс OLS (https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.OLS.html#statsmodels.regression.linear_model.OLS) — Ordinary Least Squares (обычный метод наименьших квадратов).

  • класс WLS (https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.WLS.html#statsmodels.regression.linear_model.WLS) — Weighted Least Squares (метод взвешенных наименьших квадратов) (https://en.wikipedia.org/wiki/Weighted_least_squares), применяется, если имеет место гетероскедастичность данных (https://ru.wikipedia.org/wiki/Гетероскедастичность).

  • класс GLS (https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.GLS.html#statsmodels.regression.linear_model.GLS) — Generalized Least Squares (обобщенный метод наименьших квадратов) (https://en.wikipedia.org/wiki/Generalized_least_squares), применяется, если существует определенная степень корреляции между остатками в модели регрессии.

  • класс GLSAR (https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.GLSAR.html#statsmodels.regression.linear_model.GLSAR) — Generalized Least Squares with AR covariance structure (обобщенный метод наименьших квадратов, ковариационная структура с автокорреляцией — экспериментальный метод)

  • класс RecurciveLS (https://www.statsmodels.org/stable/examples/notebooks/generated/recursive_ls.html) — Recursive least squares (рекурсивный метод наименьших квадратов) (https://en.wikipedia.org/wiki/Recursive_least_squares_filter)

  • классы RollingOLS (https://www.statsmodels.org/stable/generated/statsmodels.regression.rolling.RollingOLS.html#statsmodels.regression.rolling.RollingOLS) и RollingWLS (https://www.statsmodels.org/stable/generated/statsmodels.regression.rolling.RollingWLS.html#statsmodels.regression.rolling.RollingWLS) — скользящая регрессия (https://www.statsmodels.org/stable/examples/notebooks/generated/rolling_ls.html, https://help.fsight.ru/ru/mergedProjects/lib/01_regression_models/rolling_regression.htm)

    и т.д.

Так как исходные данные подчиняются нормальному закону распределения и аномальные значения (выбросы) отсутствуют, воспользуемся для оценки параметров обычным методом наименьших квадратов (класс OLS):

model_linear_ols = smf.ols(formula='Y ~ X', data=dataset_df)
result_linear_ols = model_linear_ols.fit()
print(result_linear_ols.summary())

Альтернативная форма выдачи результатов:

print(result_linear_ols.summary2())

Результаты построения модели мы получаем как класс statsmodels.regression.linear_model.RegressionResults (https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.RegressionResults.html#statsmodels.regression.linear_model.RegressionResults).

Экспресс-выводы, которые мы можем сразу сделать из результатов построения модели:

  1. Коэффициенты регрессии модели Y = b0 + b1∙X:

    • Intercept = b0 = -21.3741

    • b1 = 0.0129

  2. Коэффициент детерминации R-squared = 0.776, его скорректированная оценка Adj. R-squared = 0.757 — это означает, что регрессионная модуль объясняет 75.75% вариации переменной Y.

  3. Проверка значимости коэффициента детерминации:

    • расчетное значение статистики критерия Фишера: F-statistic = 41.61

    • расчетный уровень значимости Prob (F-statistic) = 3.16e-05

    • так как значение Prob (F-statistic) < 0.05, то нулевая гипотеза R-squared = 0 НЕ ПРИНИМАЕТСЯ, т.е. коэффициент детерминации ЗНАЧИМ

  4. Проверка значимости коэффициентов регрессии:

    • расчетный уровень значимости P>|t| не превышает 0.05 — это означает, что оба коэффициента регрессии значимы

    • об этом же свидетельствует то, что доверительный интервал для обоих коэффициентов регрессии ([0.025; 0.975]) не включает в себя точку 0

    Также в таблице результатов содержится прочая информация по коэффициентам регрессии: стандартная ошибка Std.Err. расчетное значение статистики критерия Стьюдента t для проверки гипотезы о значимости.

  5. Анализ остатков модели:

    • Тест Omnibus — про этот тест подробно написано в https://en.wikipedia.org/wiki/Omnibus_test, https://medium.com/swlh/interpreting-linear-regression-through-statsmodels-summary-4796d359035a, http://work.thaslwanter.at/Stats/html/statsModels.html.

      Расчетное значение статистики критерия Omnibus = 3.466 — по сути расчетное значение F-критерия (см. https://en.wikipedia.org/wiki/Omnibus_test).

      Prob(Omnibus) = 0.177 — показывает вероятность нормального распределения остатков (значение 1 указывает на совершенно нормальное распределение).

      Учитывая, что в дальнейшем мы проверим нормальность распределения остатков по совокупности различных тестов, в том числе с достаточно высокой мощностью, и все тесты позволят принять гипотезу о нормальном распределении — в данном случае к тесту Omnibus возникают вопросы. С этим тестом нужно разбираться отдельно.

    • Skew = 0.014 и Kurtosis = 1.587 — показатели асимметрии и эксцесса остатков свидетельствуют, что распределение остатков практически симметричное, островершинное.

    • проверка нормальности распределения остатков по критерию Харке-Бера: расчетное значение статистики критерия Jarque-Bera (JB) = 1.164 и расчетный уровень значимости Prob(JB) = 0.559. К данным результатам также возникают вопросы, особенно, если учесть, что критерий Харке-Бера является асимптотическим, расчетное значение имеет распределение хи-квадрат, поэтому данный критерий рекомендуют применять только для больших выборок (см. https://en.wikipedia.org/wiki/Jarque–Bera_test). Проверку нормальности распределения остатков модели лучше проводить с использованием набора стандартных статистических тестов python (см. далее).

  6. Проверка автокорреляции по критерию Дарбина-Уотсона: Durbin-Watson = 1.443.

    Мы не будем здесь разбирать данный критерий, так как явление автокорреляции больше характерно для данных, выражаемых в виде временных рядов. Однако, для грубой оценки считается, что при расчетном значении статистики криетрия Дарбина=Уотсона а интервале [1; 2] автокорреляция отсутствует (см.https://en.wikipedia.org/wiki/Durbin–Watson_statistic).

    Более подробно про критерий Дарбина-Уотсона — см. [1, с.659].

Прочая информация, которую можно извлечь из результатов построения модели:

  1. Covariance Type — тип ковариации, подробнее см. https://habr.com/ru/post/681218/, https://towardsdatascience.com/simple-explanation-of-statsmodel-linear-regression-model-summary-35961919868b#:~:text=Covariance type is typically nonrobust,with respect to each other.

  2. Scale — масштабный коэффициент для ковариационной матрицы (https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.RegressionResults.scale.html#statsmodels.regression.linear_model.RegressionResults.scale), равен величине Mean squared error (MSE) (cреднеквадратической ошибке), об подробнее см. далее, в разделе про ошибки аппроксимации моделей.

  3. Показатели сравнения качества различных моделей:

    • Log-Likelihood — логарифмическая функция правдоподобия, подробнее см. https://en.wikipedia.org/wiki/Likelihood_function#Log-likelihood, https://habr.com/ru/post/433804/

    • AIC — информационный критерий Акаике (Akaike information criterion), подробнее см. https://en.wikipedia.org/wiki/Akaike_information_criterion

    • BIC — информационный критерий Байеса (Bayesian information criterion), подробнее см. https://en.wikipedia.org/wiki/Bayesian_information_criterion

    В данной статье мы эти показатели рассматривать не будем, так как задача выбора одной модели из нескольких перед нами не стоит.

  4. Число обусловленности Cond. No = 96792 используется для проверки мультиколлинеарности (считается, что мультиколлинеарность есть, если значение Cond. No > 30) (см. http://work.thaslwanter.at/Stats/html/statsModels.html). В нашем случае парной регрессионной модели о мультиколлинеарности речь не идет.

Далее будем извлекать данные из стандартного набора выдачи результатов и анализировать их более подробно. Последующие этапы вовсе не обязательно проводить в полном объеме при решении задач, но здесь мы рассмотрим их подробно.

Параметры и уравнение регрессионной модели

Извлечем параметры полученной модели — как свойство params модели:

print('Параметры модели: n', result_linear_ols.params, type(result_linear_ols.params))

Имея параметры модели, можем формализовать уравнение модели Y = b0 + b1*X:

b0 = result_linear_ols.params['Intercept']
b1 = result_linear_ols.params['X']
Y_calc = lambda x: b0 + b1*x

График регрессионной модели

Для построения графиков регрессионных моделей можно воспользоваться стандартными возможностями библиотек statsmodels, seaborn, либо создать пользовательскую функцию — на усмотрение исследователя:

1. Построение графиков регрессионных моделей с использованием библиотеки statsmodels

С помощью функции statsmodels.graphics.plot_fit (https://www.statsmodels.org/stable/generated/statsmodels.graphics.regressionplots.plot_fit.html#statsmodels.graphics.regressionplots.plot_fit) — отображается график Y and Fitted vs.X (фактические и расчетные значения Y с доверительным интервалом для каждого значения Y):

fig, ax = plt.subplots(figsize=(297/INCH, 210/INCH))
fig = sm.graphics.plot_fit(
    result_linear_ols, 'X',
    vlines=True,    # это параметр отвечает за отображение доверительных интервалов для Y
    ax=ax)
ax.set_ylabel(Variable_Name_Y)
ax.set_xlabel(Variable_Name_X)
ax.set_title(Task_Project)
plt.show()

С помощью функции statsmodels.graphics.plot_regress_exog (https://www.statsmodels.org/stable/generated/statsmodels.graphics.regressionplots.plot_regress_exog.html#statsmodels.graphics.regressionplots.plot_regress_exog) — отображается область 2х2, которая содержит:

  • предыдущий график Y and Fitted vs.X;

  • график остатков Residuals versus X;

  • график Partial regression plot — график частичной регрессии, пытается показать эффект добавления другой переменной в модель, которая уже имеет одну или несколько независимых переменных (более подробно см. https://en.wikipedia.org/wiki/Partial_regression_plot);

  • график CCPR Plot (Component-Component plus Residual Plot) — еще один способ оценить влияние одной независимой переменной на переменную отклика, принимая во внимание влияние других независимых переменных (более подробно — см. https://towardsdatascience.com/calculating-price-elasticity-of-demand-statistical-modeling-with-python-6adb2fa7824d, https://www.kirenz.com/post/2021-11-14-linear-regression-diagnostics-in-python/linear-regression-diagnostics-in-python/).

fig = plt.figure(figsize=(297/INCH, 210/INCH))
sm.graphics.plot_regress_exog(result_linear_ols, 'X', fig=fig)
plt.show()

2. Построение графиков регрессионных моделей с использованием библиотеки seaborn

Воспользуемся модулем regplot библиотеки seaborn (https://seaborn.pydata.org/generated/seaborn.regplot.html). Данный модуль позволяет визуализировать различные виды регрессии:

  • линейную

  • полиномиальную

  • логистическую

  • взвешенную локальную регрессию (LOWESS — Locally Weighted Scatterplot Smoothing) (см. http://www.machinelearning.ru/wiki/index.php?title=Алгоритм_LOWESS, https://www.statsmodels.org/stable/generated/statsmodels.nonparametric.smoothers_lowess.lowess.html)

Более подробно про модуль regplot можно прочитать в статье: https://pyprog.pro/sns/sns_8_regression_models.html.

Есть более совершенный модуль lmplot (https://seaborn.pydata.org/generated/seaborn.lmplot.html), который объединяет в себе regplot и FacetGrid, но мы его здесь рассматривать не будем.

# создание рисунка (Figure) и области рисования (Axes)
fig = plt.figure(figsize=(297/INCH, 420/INCH/1.5))
ax1 = plt.subplot(2,1,1)
ax2 = plt.subplot(2,1,2)
# заголовок рисунка (Figure)
title_figure = Task_Project
fig.suptitle(title_figure, fontsize = 18)
# заголовок области рисования (Axes)
title_axes_1 = 'Линейная регрессионная модель'
ax1.set_title(title_axes_1, fontsize = 16)
# график регрессионной модели
order_mod = 1    # порядок модели
#label_legend_regr_model = 'фактические данные'
sns.regplot(
    #data=dataset_df,
    x=X, y=Y,
    #x_estimator=np.mean,
    order=order_mod,
    logistic=False,
    lowess=False,
    robust=False,
    logx=False,
    ci=95,
    scatter_kws={'s': 30, 'color': 'red'},
    line_kws={'color': 'blue'},
    #label=label_legend_regr_model,
    ax=ax1)
ax1.set_ylabel(Variable_Name_Y)
ax1.legend()
# график остатков
title_axes_2 = 'График остатков'
ax2.set_title(title_axes_2, fontsize = 16)
sns.residplot(
    #data=dataset_df,
    x=X, y=Y,
    order=order_mod,
    lowess=False,
    robust=False,
    scatter_kws={'s': 30, 'color': 'darkorange'},
    ax=ax2)
ax2.set_xlabel(Variable_Name_X)

plt.show()

3. Построение графиков регрессионных моделей с помощью пользовательской функции

# Пользовательская функция
graph_regression_plot_sns(
    X, Y,
    regression_model=Y_calc,
    Xmin=Xmin_graph, Xmax=Xmax_graph,
    Ymin=Ymin_graph, Ymax=Ymax_graph,
    title_figure=Task_Project,
    x_label=Variable_Name_X,
    y_label=Variable_Name_Y,
    label_legend_regr_model=f'линейная регрессия Y = {b0:.3f} + {b1:.4f}*X',
    s=80,
    file_name='graph/regression_plot_lin.png')

Статистический анализ регрессионной модели

1. Расчет ошибки аппроксимации (Error Metrics)

Ошибки аппроксимации (Error Metrics) позволяют получить общее представление о качестве модели, а также позволяют сравнивать между собой различные модели.

Создадим пользовательскую функцию, которая рассчитывает основные ошибки аппроксимации для заданной модели:

  • Mean squared error (MSE) или Mean squared deviation (MSD) — среднеквадратическая ошибка (https://en.wikipedia.org/wiki/Mean_squared_error):

  • Root mean square error (RMSE) или Root mean square deviation (RMSD) — квадратный корень из MSE (https://en.wikipedia.org/wiki/Root-mean-square_deviation):

  • Mean absolute error (MAE) — средняя абсолютная ошибка (https://en.wikipedia.org/wiki/Mean_absolute_error):

  • Mean squared prediction error (MSPE) — среднеквадратическая ошибка прогноза (среднеквадратическая ошибка в процентах) (https://en.wikipedia.org/wiki/Mean_squared_prediction_error):

  • Mean absolute percentage error (MAPE) — средняя абсолютная ошибка в процентах (https://en.wikipedia.org/wiki/Mean_absolute_percentage_error):

Про выбор метрики см. также https://machinelearningmastery.ru/how-to-select-the-right-evaluation-metric-for-machine-learning-models-part-2-regression-metrics-d4a1a9ba3d74/.

# Пользовательская функция
def regression_error_metrics(model, model_name=''):
    model_fit = model.fit()
    Ycalc = model_fit.predict()
    n_fit = model_fit.nobs
    Y = model.endog
    
    MSE = (1/n_fit) * np.sum((Y-Ycalc)**2)
    RMSE = sqrt(MSE)
    MAE = (1/n_fit) * np.sum(abs(Y-Ycalc))
    MSPE = (1/n_fit) *  np.sum(((Y-Ycalc)/Y)**2)
    MAPE = (1/n_fit) *  np.sum(abs((Y-Ycalc)/Y))
        
    model_error_metrics = {
        'MSE': MSE,
        'RMSE': RMSE,
        'MAE': MAE,
        'MSPE': MSPE,
        'MAPE': MAPE}
    
    result = pd.DataFrame({
        'MSE': MSE,
        'RMSE': RMSE,
        'MAE': MAE,
        'MSPE': "{:.3%}".format(MSPE),
        'MAPE': "{:.3%}".format(MAPE)},
        index=[model_name])        
        
    return model_error_metrics, result

(model_error_metrics, result) = regression_error_metrics(model_linear_ols, model_name='linear_ols')
display(result)

В литературе по прикладной статистике нет единого мнения о допустимом размере относительных ошибок аппроксимации: в одних источниках допустимой считается ошибка 5-7%, в других она может быть увеличена до 8-10%, и даже до 15%.

Вывод: модель хорошо аппроксимирует фактические данные (относительная ошибка аппроксимации MAPE = 3.405% < 10%).

2. Дисперсионный анализ регрессионной модели (ДАРМ)

ДАРМ не входит в стандартную форму выдачи результатов Regression Results, однако я решил написать здесь о нем по двум причинам:

  1. Именно анализ дисперсии регрессионной модели, разложение этой дисперсии на составляющие дает фундаментальное представление о сути регрессии, а термины, используемые при ДАРМ, применяются на последующих этапах анализа.

  2. С терминами ДАРМ в литературе по прикладной статистике имеется некоторая путаница, в разных источниках они могут именоваться по-разному (см., например, [8, с.52]), поэтому, чтобы двигаться дальше, необходимо определиться с понятиями.

При ДАРМ общую вариацию результативного признака (Y) принято разделять на две составляющие — вариация, обусловленная регрессией и вариация, обусловленная отклонениями от регрессии (остаток), при этом в разных источниках эти термины могут именоваться и обозначаться по-разному, например:

  1. Вариация, обусловленная регрессией — может называться Explained sum of squares (ESS), Sum of Squared Regression (SSR) (https://en.wikipedia.org/wiki/Explained_sum_of_squares, https://towardsdatascience.com/anova-for-regression-fdb49cf5d684), Sum of squared deviations (SSD).

  2. Вариация, обусловленная отклонениями от регрессии (остаток) — может называться Residual sum of squares (RSS), Sum of squared residuals (SSR), Squared estimate of errors, Sum of Squared Error (SSE) (https://en.wikipedia.org/wiki/Residual_sum_of_squares, https://towardsdatascience.com/anova-for-regression-fdb49cf5d684); в отчественной практике также применяется термин остаточная дисперсия.

  3. Общая (полная) вариация — может называться Total sum of squares (TSS), Sum of Squared Total (SST) (https://en.wikipedia.org/wiki/Partition_of_sums_of_squares, https://towardsdatascience.com/anova-for-regression-fdb49cf5d684).

Как видим, путаница знатная:

  • в разных источниках под SSR могут подразумеваться различные показатели;

  • легко перепутать показатели ESS и SSE;

  • в библиотеке statsmodel также есть смешение терминов: для показателя Explained sum of squares используется свойство ess, а для показателя Sum of squared (whitened) residuals — свойство ssr.

Мы будем пользоваться системой обозначений, принятой в большинстве источников — SSR (Sum of Squared Regression), SSE (Sum of Squared Error), SST (Sum of Squared Total). Стандартная таблица ДАРМ в этом случае имеет вид:

Примечания:

  1. Здесь приведена таблица ДАРМ для множественной линейной регрессионной модели (МЛРМ), в нашем случае при ПЛРМ мы имеем частный случай p=1.

  2. Показатели Fcalc-ad и Fcalc-det — расчетные значения статистики критерия Фишера при проверке адекватности модели и значимости коэффициента детерминации (об этом — см.далее).

Более подробно про дисперсионный анализ регрессионной модели — см.[4, глава 3].

Класс statsmodels.regression.linear_model.RegressionResults позволяет нам получить данные для ANOVA (см. https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.RegressionResults.html#statsmodels.regression.linear_model.RegressionResults) как свойства класса:

  1. Сумма квадратов, обусловленная регрессией / SSR (Sum of Squared Regression) — свойство ess.

  2. Сумма квадратов, обусловленная отклонением от регрессии / SSE (Sum of Squared Error) — свойство ssr.

  3. Общая (полная) сумма квадратов / SST (Sum of Squared Total) — свойство centered_tss.

  4. Кол-во наблюдений / Number of observations — свойство nobs.

  5. Число степеней свободы модели / Model degrees of freedom — равно числу переменных модели (за исключением константы, если она присутствует — свойство df_model.

  6. Среднеквадратичная ошибка модели / Mean squared error the model — сумма квадратов, объясненная регрессией, деленная на число степеней свободы регрессии — свойство mse_model.

  7. Среднеквадратичная ошибка остатков / Mean squared error of the residuals — сумма квадратов остатков, деленная на остаточные степени свободы — свойство mse_resid.

  8. Общая среднеквадратичная ошибка / Total mean squared error — общая сумма квадратов, деленная на количество наблюдений — свойство mse_total.

Также имеется модуль statsmodels.stats.anova.anova_lm, который позволяет получить результаты ДАРМ (нескольких типов — 1, 2, 3):

# тип 1
print('The type of Anova test: 1')
display(sm.stats.anova_lm(result_linear_ols, typ=1))

# тип 2
print('The type of Anova test: 2')
display(sm.stats.anova_lm(result_linear_ols, typ=2))

# тип 3
print('The type of Anova test: 3')
display(sm.stats.anova_lm(result_linear_ols, typ=3))

На мой взгляд, форма таблица результатов statsmodels.stats.anova.anova_lm не вполне удобна, поэтому сформируем ее самостоятельно, для чего создадим пользовательскую функцию ANOVA_table_regression_model:

# Пользовательская функция
def ANOVA_table_regression_model(model_fit):
    n = int(model_fit.nobs)
    p = int(model_fit.df_model)
    SSR = model_fit.ess
    SSE = model_fit.ssr
    SST = model_fit.centered_tss

    result = pd.DataFrame({
        'sources_of_variation': ('regression (SSR)', 'deviation from regression (SSE)', 'total (SST)'),
        'sum_of_squares': (SSR, SSE, SST),
        'degrees_of_freedom': (p, n-p-1, n-1)})
    result['squared_error'] = result['sum_of_squares'] / result['degrees_of_freedom']
    R2 = 1 - result.loc[1, 'sum_of_squares'] / result.loc[2, 'sum_of_squares']
    F_calc_adequacy = result.loc[2, 'squared_error'] / result.loc[1, 'squared_error']
    F_calc_determ_check = result.loc[0, 'squared_error'] / result.loc[1, 'squared_error']
    result['F-ratio'] = (F_calc_determ_check, F_calc_adequacy, '')
    
    return result

result = ANOVA_table_regression_model(result_linear_ols)
display(result)
print(f"R2 = 1 - SSE/SST = {1 - result.loc[1, 'sum_of_squares'] / result.loc[2, 'sum_of_squares']}")
print(f"F_calc_adequacy = MST / MSE = {result.loc[2, 'squared_error'] / result.loc[1, 'squared_error']}")
print(f"F_calc_determ_check = MSR / MSE = {result.loc[0, 'squared_error'] / result.loc[1, 'squared_error']}")

ДАРМ позволяет визуализировать вариацию:

fig, axes = plt.subplots(figsize=(210/INCH, 297/INCH/1.5))
axes.pie(
    (result.loc[0, 'sum_of_squares'], result.loc[1, 'sum_of_squares']), 
    labels=(result.loc[0, 'sources_of_variation'], result.loc[1, 'sources_of_variation']),
    autopct='%.1f%%',
    startangle=60)
plt.show()

На основании данных ДАРМ мы рассчитали ряд показателей (R2, Fcalc-ad и Fcalc-det), которые будут использоваться в дальнейшем.

3. Анализ остатков (проверка нормальности распределения остатков и гипотезы о равенстве нулю среднего значения остатков)

Проверка нормальности распределения остатков — один их важнейших этапов анализа регрессионной модели. Требование нормальности распределения остатков не требуется для отыскания параметров модели, но необходимо в дальнейшем для проверки статистических гипотез с использованием критериев Фишера и Стьюдента (проверка адекватности модели, значимости коэффициента детерминации, значимости коэффициентов регрессии) и построения доверительных интервалов [5, с.122].

Остатки регрессионной модели:

print('Остатки регрессионной модели:n', result_linear_ols.resid, type(result_linear_ols.resid))
res_Y = np.array(result_linear_ols.resid)

statsmodels может выдавать различные преобразованные виды остатков (см. https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.RegressionResults.resid_pearson.html, https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.RegressionResults.wresid.html).

График остатков:

# Пользовательская функция
graph_scatterplot_sns(
    X, res_Y,
    Xmin=Xmin_graph, Xmax=Xmax_graph,
    Ymin=-3.0, Ymax=3.0,
    color='red',
    #title_figure=Task_Project,
    title_axes='Остатки линейной регрессионной модели', title_axes_fontsize=18,
    x_label=Variable_Name_X,
    y_label='ΔY = Y - Ycalc',
    s=75,
    file_name='graph/residuals_plot_sns.png')

Проверка нормальности распределения остатков:

# Пользовательская функция
graph_hist_boxplot_probplot_sns(
    data=res_Y,
    data_min=-2.5, data_max=2.5,
    graph_inclusion='bp',
    data_label='ΔY = Y - Ycalc',
    #title_figure=Task_Project,
    title_axes='Остатки линейной регрессионной модели', title_axes_fontsize=16,
    file_name='graph/residuals_hist_boxplot_probplot_sns.png')    

norm_distr_check(res_Y)

Вывод: большинство статистических тестов позволяют принять гипотезу о нормальности распределения остатков.

Проверка гипотезы о равенстве нулю среднего значения остатков — так как остатки имеют нормальное распределение, воспользуемся критерием Стьюдента (функция scipy.stats.ttest_1samp, https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.ttest_ind.html):

sps.ttest_1samp(res_Y, popmean=0)

Вывод: так как расчетный уровень значимости превышает заданный (0.05), то нулевая гипотеза о равенстве нулю остатков ПРИНИМАЕТСЯ.

4. Проверка адекватности модели

Суть проверки адекватности регрессионной модели заключается в сравнении полной дисперсии MST и остаточной дисперсии MSE — проверяется гипотеза о равенстве этих дисперсий по критерию Фишера. Если дисперсии различаются значимо, то модель считается адекватной. Более подробно про проверку адекватности регрессионной — см.[1, с.658], [2, с.49], [4, с.154].

Для проверки адекватности регрессионной модели создадим пользовательскую функцию regression_model_adequacy_check:

def regression_model_adequacy_check(
    model_fit,
    p_level: float=0.95,
    model_name=''):
    
    n = int(model_fit.nobs)
    p = int(model_fit.df_model)    # Число степеней свободы регрессии, равно числу переменных модели (за исключением константы, если она присутствует)
    
    SST = model_fit.centered_tss    # SST (Sum of Squared Total)
    dfT = n-1
    MST = SST / dfT

    SSE = model_fit.ssr    # SSE (Sum of Squared Error)
    dfE = n - p - 1
    MSE = SSE / dfE
    
    F_calc = MST / MSE
    F_table = sci.stats.f.ppf(p_level, dfT, dfE, loc=0, scale=1)
    a_calc = 1 - sci.stats.f.cdf(F_calc, dfT, dfE, loc=0, scale=1)
    conclusion_model_adequacy_check = 'adequacy' if F_calc >= F_table else 'adequacy'
    
    # формируем результат            
    result = pd.DataFrame({
        'SST': (SST),
        'SSE': (SSE),
        'dfT': (dfT),
        'dfE': (dfE),
        'MST': (MST),
        'MSE': (MSE),
        'p_level': (p_level),
        'a_level': (a_level),
        'F_calc': (F_calc),
        'F_table': (F_table),
        'F_calc >= F_table': (F_calc >= F_table),
        'a_calc': (a_calc),
        'a_calc <= a_level': (a_calc <= a_level),
        'adequacy_check': (conclusion_model_adequacy_check),
        },
        index=[model_name]
        )
    
    return result

regression_model_adequacy_check(result_linear_ols, p_level=0.95, model_name='linear_ols')

Вывод: модель является АДЕКВАТНОЙ.

5. Коэффициент детерминации и проверка его значимости

Различают несколько видов коэффициента детерминации:

  1. Собственно обычный коэффициент детерминации:

Его значение может быть получено как свойство rsquared модели.

  1. Скорректированный (adjusted) коэффициент детерминации — используется для того, чтобы была возможность сравнивать модели с разным числом признаков так, чтобы число регрессоров (признаков) не влияло на статистику R2, при его расчете используются несмещённые оценки дисперсий:

Его значение может быть получено как свойство rsquared_adj модели.

  1. Обобщённый (extended) коэффициент детерминации — используется для сравнения моделей регрессии со свободным членом и без него, а также для сравнения между собой регрессий, построенных с помощью различных методов: МНК, обобщённого метода наименьших квадратов (ОМНК), условного метода наименьших квадратов (УМНК), обобщённо-условного метода наименьших квадратов (ОУМНК). В данном разборе ПЛРМ рассматривать этот коэффициент мы не будем.

Более подробно с теорией вопроса можно ознакомиться, например: http://www.machinelearning.ru/wiki/index.php?title=Коэффициент_детерминации), а также в [7].

Значения коэффициента детерминации и скорректированного коэффициента детерминации, извлеченные с помощью свойств rsquared и rsquared_adj модели.

print('R2 =', result_linear_ols.rsquared)
print('R2_adj =', result_linear_ols.rsquared_adj)

Значимость коэффициента детерминации можно проверить по критерию Фишера [3, с.201-203; 8, с.83].

Расчетное значение статистики критерия Фишера может быть получено с помощью свойства fvalue модели:

print(f"result_linear_ols.fvalue = {result_linear_ols.fvalue}")

Расчетный уровень значимости при проверке гипотезы по критерию Фишера может быть получено с помощью свойства f_pvalue модели:

print(f"result_linear_ols.f_pvalue = {result_linear_ols.f_pvalue}")

Можно рассчитать уровень значимости самостоятельно (так сказать, для лучшего понимания и общей демонстрации возможностей) — для этого воспользуемся библиотекой scipy, модулем распределения Фишера scipy.stats.f, свойством cdf (функция распределения):

df1 = int(result_linear_ols.df_model)
df2 = int(result_linear_ols.nobs - result_linear_ols.df_model - 1)
F_calc = result_linear_ols.fvalue
a_calc = 1 - sci.stats.f.cdf(F_calc, df1, df2, loc=0, scale=1)
print(a_calc)

Как видим, результаты совпадают.

Табличное значение статистики критерия Фишера получить с помощью Regression Results нельзя. Рассчитаем его самостоятельно — для этого воспользуемся библиотекой scipy, модулем распределения Стьюдента scipy.stats.f, свойством ppf (процентные точки):

F_table = sci.stats.f.ppf(p_level, df1, df2, loc=0, scale=1)
print(F_table)

Для удобства создадим пользовательскую функцию determination_coef_check для проверки значимости коэффициента детерминации, которая объединяет все вышеперечисленные расчеты:

# Пользовательская функция
def determination_coef_check(
    model_fit,
    p_level: float=0.95):
    
    a_level = 1 - p_level
    
    R2 = model_fit.rsquared
    R2_adj = model_fit.rsquared_adj
    n = model_fit.nobs    # объем выборки
    p = model_fit.df_model    # Model degrees of freedom. The number of regressors p. Does not include the constant if one is present.
    
    F_calc = R2 / (1 - R2) * (n-p-1)/p
    df1 = int(p)
    df2 = int(n-p-1)
    F_table = sci.stats.f.ppf(p_level, df1, df2, loc=0, scale=1)
    a_calc = 1 - sci.stats.f.cdf(F_calc, df1, df2, loc=0, scale=1)
    conclusion_determ_coef_sign = 'significance' if F_calc >= F_table else 'not significance'
        
    # формируем результат            
    result = pd.DataFrame({
        'notation': ('R2'),
        'coef_value (R)': (sqrt(R2)),
        'coef_value_squared (R2)': (R2),
        'p_level': (p_level),
        'a_level': (a_level),
        'F_calc': (F_calc),
        'df1': (df1),
        'df2': (df2),
        'F_table': (F_table),
        'F_calc >= F_table': (F_calc >= F_table),
        'a_calc': (a_calc),
        'a_calc <= a_level': (a_calc <= a_level),
        'significance_check': (conclusion_determ_coef_sign),
        'conf_int_low': (''),
        'conf_int_high': ('')
        },
        index=['Coef. of determination'])
    return result

determination_coef_check(
    result_linear_ols,
    p_level=0.95)

Вывод: коэффициент детерминации ЗНАЧИМ.

6. Коэффициенты регрессии и проверка их значимости

Ранее мы уже извлекли коэффициенты регрессии как параметры модели b0 и b1 (как свойство params модели). Также можно получить их значения, как свойство bse модели:

print(b0, b1)
print(result_linear_ols.bse, type(result_linear_ols.bse))

Значимость коэффициентов регрессии можно проверить по критерию Стьюдента [3, с.203-212; 8, с.78].

Расчетные значения статистики критерия Стьюдента могут быть получены с помощью свойства tvalues модели:

print(f"result_linear_ols.tvalues = n{result_linear_ols.tvalues}")

Расчетные значения уровня значимости при проверке гипотезы по критерию Стьюдента могут быть получены с помощью свойства pvalues модели:

print(f"result_linear_ols.pvalues = n{result_linear_ols.pvalues}")

Доверительные интервалы для коэффициентов регрессии могут быть получены с помощью свойства conf_int модели:

print(result_linear_ols.conf_int(), 'n')

Можно рассчитать уровень значимости самостоятельно (как ранее для критерия Фишера — для лучшего понимания и общей демонстрации возможностей) — для этого воспользуемся библиотекой scipy, модулем распределения Стьюдента scipy.stats.t, свойством cdf (функция распределения):

t_calc = result_linear_ols.tvalues
df = int(result_linear_ols.nobs - result_linear_ols.df_model - 1)
a_calc = 2*(1-sci.stats.t.cdf(abs(t_calc), df, loc=0, scale=1))
print(a_calc)

Как видим, результаты совпадают.

Табличные значения статистики критерия Стьюдента получить с помощью Regression Results нельзя. Рассчитаем их самостоятельно — для этого воспользуемся библиотекой scipy, модулем распределения Стьюдента scipy.stats.t, свойством ppf (процентные точки):

t_table = sci.stats.t.ppf((1 + p_level)/2 , df)
print(t_table)

Для удобства создадим пользовательскую функцию regression_coef_check для проверки значимости коэффициентов регрессии, которая объединяет все вышеперечисленные расчеты:

def regression_coef_check(
    model_fit,
    notation_coef: list='',
    p_level: float=0.95):
    
    a_level = 1 - p_level
    
    # параметры модели (коэффициенты регрессии)
    model_params = model_fit.params
    # стандартные ошибки коэффициентов регрессии
    model_bse = model_fit.bse
    # проверка гипотезы о значимости регрессии
    t_calc = abs(model_params) / model_bse
    n = model_fit.nobs    # объем выборки
    p = model_fit.df_model    # Model degrees of freedom. The number of regressors p. Does not include the constant if one is present.
    df = int(n - p - 1)
    t_table = sci.stats.t.ppf((1 + p_level)/2 , df)
    a_calc = 2*(1-sci.stats.t.cdf(t_calc, df, loc=0, scale=1))
    conclusion_ = ['significance' if elem else 'not significance' for elem in (t_calc >= t_table).values]
        
    # доверительный интервал коэффициента регрессии
    conf_int_low = model_params - t_table*model_bse
    conf_int_high = model_params + t_table*model_bse
    
    # формируем результат            
    result = pd.DataFrame({
        'notation': (notation_coef),
        'coef_value': (model_params),
        'std_err': (model_bse),
        'p_level': (p_level),
        'a_level': (a_level),
        't_calc': (t_calc),
        'df': (df),
        't_table': (t_table),
        't_calc >= t_table': (t_calc >= t_table),
        'a_calc': (a_calc),
        'a_calc <= a_level': (a_calc <= a_level),
        'significance_check': (conclusion_),
        'conf_int_low': (conf_int_low),
        'conf_int_high': (conf_int_high),
        })
    
    return result

regression_coef_check(
    result_linear_ols,
    notation_coef=['b0', 'b1'],
    p_level=0.95)

Вывод: коэффициенты регрессии b0 и b1 ЗНАЧИМЫ.

7. Проверка гетероскедастичности

Для проверка гетероскедастичности statsmodels предлагает нам следующие инструменты:

  • тест Голдфелда-Квандта (https://www.statsmodels.org/stable/generated/statsmodels.stats.diagnostic.het_goldfeldquandt.html#statsmodels.stats.diagnostic.het_goldfeldquandt) — теорию см. [8, с.178], также https://ru.wikipedia.org/wiki/Тест_Голдфелда_—_Куандта.

  • тест Бриша-Пэгана (Breush-Pagan test) (https://www.statsmodels.org/stable/generated/statsmodels.stats.diagnostic.het_breuschpagan.html#statsmodels.stats.diagnostic.het_breuschpagan) — теорию см.[8, с.179], также https://en.wikipedia.org/wiki/Breusch–Pagan_test.

  • тест Уайта (White test) (https://www.statsmodels.org/stable/generated/statsmodels.stats.diagnostic.het_white.html#statsmodels.stats.diagnostic.het_white) — теорию см.[8, с.177], а также https://ru.wikipedia.org/wiki/Тест_Уайта.

    Тест Голдфелда-Квандта (Goldfeld–Quandt test)

# тест Голдфелда-Квандта (Goldfeld–Quandt test)
test = sms.het_goldfeldquandt(result_linear_ols.resid, result_linear_ols.model.exog)
test_result = lzip(['F_calc', 'p_calc'], test)    # распаковка результатов теста
# расчетное значение статистики F-критерия
F_calc_tuple = test_result[0]
F_calc = F_calc_tuple[1]
print(f"Расчетное значение статистики F-критерия: F_calc = {round(F_calc, DecPlace)}")
# расчетный уровень значимости
p_calc_tuple = test_result[1]
p_calc = p_calc_tuple[1]
print(f"Расчетное значение доверительной вероятности: p_calc = {round(p_calc, DecPlace)}")
#a_calc = 1 - p_value
#print(f"Расчетное значение уровня значимости: a_calc = 1 - p_value = {round(a_calc, DecPlace)}")
# вывод
if p_calc < a_level:
    conclusion_GQ_test = f"Так как p_calc = {round(p_calc, DecPlace)} < a_level = {round(a_level, DecPlace)}" + 
        ", то дисперсии в подвыборках отличаются значимо, т.е. гипотеза о наличии гетероскедастичности ПРИНИМАЕТСЯ"
else:
    conclusion_GQ_test = f"Так как p_calc = {round(p_calc, DecPlace)} >= a_level = {round(a_level, DecPlace)}" + 
        ", то дисперсии в подвыборках отличаются незначимо, т.е. гипотеза о наличии гетероскедастичности ОТВЕРГАЕТСЯ"
print(conclusion_GQ_test)

Для удобства создадим пользовательскую функцию Goldfeld_Quandt_test:

def Goldfeld_Quandt_test(
    model_fit,
    p_level: float=0.95,
    model_name=''):
    
    a_level = 1 - p_level
    
    # реализация теста
    test = sms.het_goldfeldquandt(model_fit.resid, model_fit.model.exog)
    test_result = lzip(['F_statistic', 'p_calc'], test)    # распаковка результатов теста
    # расчетное значение статистики F-критерия
    F_calc_tuple = test_result[0]
    F_statistic = F_calc_tuple[1]
    # расчетный уровень значимости
    p_calc_tuple = test_result[1]
    p_calc = p_calc_tuple[1]
    # вывод
    conclusion_test = 'heteroscedasticity' if p_calc < a_level else 'not heteroscedasticity'
    
    result = pd.DataFrame({
        'test': ('Goldfeld–Quandt test'),
        'p_level': (p_level),
        'a_level': (a_level),
        'F_statistic': (F_statistic),
        'p_calc': (p_calc),
        'p_calc < a_level': (p_calc < a_level),
        'heteroscedasticity_check': (conclusion_test)
        },
        index=[model_name])
    
    return result

Goldfeld_Quandt_test(result_linear_ols, p_level=0.95, model_name='linear_ols')

Тест Бриша-Пэгана (Breush-Pagan test)

# тест Бриша-Пэгана (Breush-Pagan test)
name = ["Lagrange multiplier statistic", "p-value", "f-value", "f p-value"]
test = sms.het_breuschpagan(result_linear_ols.resid, result_linear_ols.model.exog)
lzip(name, test)

Для удобства создадим пользовательскую функцию Breush_Pagan_test:

def Breush_Pagan_test(
    model_fit,
    p_level: float=0.95,
    model_name=''):
    
    a_level = 1 - p_level
    
    # реализация теста
    test = sms.het_breuschpagan(model_fit.resid, model_fit.model.exog)
    name = ['Lagrange_multiplier_statistic', 'p_calc_LM', 'F_statistic', 'p_calc']
    test_result = lzip(name, test)    # распаковка результатов теста
    # расчетное значение статистики теста множителей Лагранжа
    LM_calc_tuple = test_result[0]
    Lagrange_multiplier_statistic = LM_calc_tuple[1]
    # расчетный уровень значимости статистики теста множителей Лагранжа
    p_calc_LM_tuple = test_result[1]
    p_calc_LM = p_calc_LM_tuple[1]
    # расчетное значение F-статистики гипотезы о том, что дисперсия ошибки не зависит от x
    F_calc_tuple = test_result[2]
    F_statistic = F_calc_tuple[1]
    # расчетный уровень значимости F-статистики
    p_calc_tuple = test_result[3]
    p_calc = p_calc_tuple[1]
    # вывод
    conclusion_test = 'heteroscedasticity' if p_calc < a_level else 'not heteroscedasticity'

    # вывод
    conclusion_test = 'heteroscedasticity' if p_calc < a_level else 'not heteroscedasticity'
    
    result = pd.DataFrame({
        'test': ('Breush-Pagan test'),
        'p_level': (p_level),
        'a_level': (a_level),
        'Lagrange_multiplier_statistic': (Lagrange_multiplier_statistic),
        'p_calc_LM': (p_calc_LM),
        'p_calc_LM < a_level': (p_calc_LM < a_level),
        'F_statistic': (F_statistic),
        'p_calc': (p_calc),
        'p_calc < a_level': (p_calc < a_level),
        'heteroscedasticity_check': (conclusion_test)
        },
        index=[model_name])
    
    return result

Breush_Pagan_test(result_linear_ols, p_level=0.95, model_name='linear_ols')

Тест Уайта (White test)

# тест Уайта (White test)
name = ["Lagrange multiplier statistic", "p-value", "f-value", "f p-value"]
test = sms.het_white(result_linear_ols.resid, result_linear_ols.model.exog)
lzip(name, test)

Для удобства создадим пользовательскую функцию White_test:

def White_test(
    model_fit,
    p_level: float=0.95,
    model_name=''):
    
    a_level = 1 - p_level
    
    # реализация теста
    test = sms.het_white(model_fit.resid, model_fit.model.exog)
    name = ['Lagrange_multiplier_statistic', 'p_calc_LM', 'F_statistic', 'p_calc']
    test_result = lzip(name, test)    # распаковка результатов теста
    # расчетное значение статистики теста множителей Лагранжа
    LM_calc_tuple = test_result[0]
    Lagrange_multiplier_statistic = LM_calc_tuple[1]
    # расчетный уровень значимости статистики теста множителей Лагранжа
    p_calc_LM_tuple = test_result[1]
    p_calc_LM = p_calc_LM_tuple[1]
    # расчетное значение F-статистики гипотезы о том, что дисперсия ошибки не зависит от x
    F_calc_tuple = test_result[2]
    F_statistic = F_calc_tuple[1]
    # расчетный уровень значимости F-статистики
    p_calc_tuple = test_result[3]
    p_calc = p_calc_tuple[1]
    # вывод
    conclusion_test = 'heteroscedasticity' if p_calc < a_level else 'not heteroscedasticity'

    # вывод
    conclusion_test = 'heteroscedasticity' if p_calc < a_level else 'not heteroscedasticity'
    
    result = pd.DataFrame({
        'test': ('White test'),
        'p_level': (p_level),
        'a_level': (a_level),
        'Lagrange_multiplier_statistic': (Lagrange_multiplier_statistic),
        'p_calc_LM': (p_calc_LM),
        'p_calc_LM < a_level': (p_calc_LM < a_level),
        'F_statistic': (F_statistic),
        'p_calc': (p_calc),
        'p_calc < a_level': (p_calc < a_level),
        'heteroscedasticity_check': (conclusion_test)
        },
        index=[model_name])
    
    return result

White_test(result_linear_ols, p_level=0.95, model_name='linear_ols')

Объединим результаты всех тестов гетероскедастичность в один DataFrame:

Goldfeld_Quandt_test_df = Goldfeld_Quandt_test(result_linear_ols, p_level=0.95, model_name='linear_ols')
Breush_Pagan_test_df = Breush_Pagan_test(result_linear_ols, p_level=0.95, model_name='linear_ols')
White_test_df = White_test(result_linear_ols, p_level=0.95, model_name='linear_ols')

heteroscedasticity_tests_df = pd.concat([Breush_Pagan_test_df, White_test_df, Goldfeld_Quandt_test_df])
display(heteroscedasticity_tests_df)

Выводы

Итак, мы провели статистический анализ регрессионной модели и установили:

  • исходные данные имеют нормальное распределение;

  • между переменными имеется весьма сильная корреляционная связь;

  • регрессионная модель хорошо аппроксимирует фактические данные;

  • остатки модели имеют нормальное распределение;

  • регрессионная модель адекватна по критерию Фишера;

  • коэффициент детерминации значим по критеию Фишера;

  • коэффициенты регрессии значимы по критерию Стьюдента;

  • гетероскедастичность отсутствует.

Применительно к рассматриваемой задаче выполнять проверку автокорреляции не имеет особого смысла из-за особенностей исходных данных (результаты замеров прочности бетона на разных участках здания).

Про статистический анализ регрессионных моделей с помощью statsmodels— см. еще https://www.statsmodels.org/stable/examples/notebooks/generated/regression_diagnostics.html.

Доверительные интервалы регрессионной модели

Для регрессионных моделей определяют доверительные интервалы двух видов [3, с.184-192; 4, с.172; 8, с.205-209]:

  1. Доверительный интервал средних значений переменной Y.

  2. Доверительный интервал индивидуальных значений переменной Y.

При этом размер доверительного интервала для индивидуальных значений больше, чем для средних значений.

Доверительные интервалы регрессионных моделей (ДИРМ) могут быть найдены разными способами:

  • непосредственно путем расчетов по формулам (см., например, https://habr.com/ru/post/558158/);

  • с использованием инструментария библиотеки statsmodels (см., например, https://www.stackfinder.ru/questions/17559408/confidence-and-prediction-intervals-with-statsmodels).

Разбререм более подробно способ с использованием библиотеки statsmodels. Прежде всего, с помощью свойства summary_table класса statsmodels.stats.outliers_influence.OLSInfluence (https://www.statsmodels.org/stable/generated/statsmodels.stats.outliers_influence.OLSInfluence.html?highlight=olsinfluence) мы можем получить таблицу данных, содержащую необходимую нам информацию:

  • Dep Var Population — фактические значения переменной Y;

  • Predicted Value — предсказанные значения переменной Y по по регрессионной модели;

  • Std Error Mean Predict — среднеквадратическая ошибка предсказанного среднего;

  • Mean ci 95% low и Mean ci 95% upp — границы доверительного интервала средних значений переменной Y;

  • Predict ci 95% low и Predict ci 95% upp — границы доверительного интервала индивидуальных значений переменной Y;

  • Residual — остатки регрессионной модели;

  • Std Error Residual — среднеквадратическая ошибка остатков;

  • Student Residual — стьюдентизированные остатки (подробнее см. http://statistica.ru/glossary/general/studentizirovannie-ostatki/);

  • Cook’s D — Расстояние Кука (Cook’s distance) — оценивает эффект от удаления одного (рассматриваемого) наблюдения; наблюдение считается выбросом, если Di > 4/n (более подробно — см.https://translated.turbopages.org/proxy_u/en-ru.ru.f584ceb5-63296427-aded8f31-74722d776562/https/en.wikipedia.org/wiki/Cook’s_distance, http://www.machinelearning.ru/wiki/index.php?title=Расстояние_Кука).

from statsmodels.stats.outliers_influence import summary_table
st, data, ss2 = summary_table(result_linear_ols, alpha=0.05)
print(st, 'n', type(st))

В нашем случае критическое значение расстояния Кука равно:

print(f'D_crit = 4/n = {4/result_linear_ols.nobs}')

то есть выбросов, смещающих оценки коэффициентов регрессии, не наблюдается.

Мы получили данные как класс statsmodels.iolib.table.SimpleTable. Свойство data преобразует данные в список. Далее для удобства работы преобразуем данные в DataFrame:

  st_data_df = pd.DataFrame(st.data)

Будем использовать данный DataFrame в дальнейшем, несколько преобразуем его:

  • изменим наименование столбцов (с цифр на названия показателей из таблицы summary_table)

  • удалим строки с текстовыми значениями

  • изменим индекс

  • добавим новый столбец — значения переменной X

  • отсортируем по возрастанию значений переменной X (это необходимо, чтобы графики границ доверительных интервалов выглядели как линии)

st_df = st_data_df.copy()
# изменим наименования столбцов
str = st_df.iloc[0,0:] + ' ' + st_df.iloc[1,0:]
st_df = st_df.rename(str, axis='columns')
# удалим строки 0, 1
st_df = st_df.drop([0,1])
# изменим индекс
st_df = st_df.set_index(np.arange(0, result_linear_ols.nobs))
# добавим новый столбец - значения переменной X
st_df.insert(1, 'X', X)
# отсортируем по возрастанию значений переменной X
st_df = st_df.sort_values(by='X')

display(st_df)

С помощью полученных данных мы можем построить график регрессионной модели с доверительными интервалами:

# создание рисунка (Figure) и области рисования (Axes)
fig, axes = plt.subplots(figsize=(297/INCH, 210/INCH))
# заголовок рисунка (Figure)
title_figure = Task_Project
fig.suptitle(title_figure, fontsize = 16)
# заголовок области рисования (Axes)
title_axes = 'Линейная регрессионная модель'
axes.set_title(title_axes, fontsize = 14)
# фактические данные
sns.scatterplot(
    x=st_df['X'], y=st_df['Dep Var Population'],
    label='фактические данные',
    s=50,
    color='red',
    ax=axes)
# график регрессионной модели
label_legend_regr_model=f'линейная регрессия Y = {b0:.3f} + {b1:.4f}*X'
sns.lineplot(
    x=st_df['X'], y=st_df['Predicted Value'],
    label=label_legend_regr_model,
    color='blue',
    ax=axes)
# доверительный интервал средних значений переменной Y
Mean_ci_low = st_df['Mean ci 95% low']
plt.plot(
    st_df['X'], Mean_ci_low,
    color='magenta', linestyle='--', linewidth=1,
    label='доверительный интервал средних значений Y')
Mean_ci_upp = st_df['Mean ci 95% upp']
plt.plot(
    st_df['X'], Mean_ci_upp,
    color='magenta', linestyle='--', linewidth=1)
# доверительный интервал индивидуальных значений переменной Y
Predict_ci_low = st_df['Predict ci 95% low']
plt.plot(
    st_df['X'], Predict_ci_low,
    color='orange', linestyle='-.', linewidth=2,
    label='доверительный интервал индивидуальных значений Y')
Predict_ci_upp = st_df['Predict ci 95% upp']
plt.plot(
    st_df['X'], Predict_ci_upp,
    color='orange', linestyle='-.', linewidth=2)

axes.set_xlabel(Variable_Name_X)
axes.set_ylabel(Variable_Name_Y)
axes.legend(prop={'size': 12})
plt.show()

Однако, мы получили данные о границах доверительных интервалов регрессионной модели только в пределах области фактических значений переменной X. Как быть, если мы хотим распространить прогноз за пределы этой области?

Прогнозирование

Под прогнозированием мы в данном случае будем понимать определение значений переменной Y и доверительных интервалов для ее средних и индивидуальных значений при заданном X. По сути, нам предстоит построить аналог рассмотренной выше таблицы summary_table, только с другими значениями X, причем эти значения могут выходить за пределы тех значений, которые использовались нами для построения регрессии.

Методика расчета доверительных интервалов регрессионных моделей разобрана в статье «Python, корреляция и регрессия: часть 4» (https://habr.com/ru/post/558158/), всем рекомендую ознакомиться.

Найти прогнозные значения Y не представляет труда, так как ранее мы уже формализовали модель в виде лямбда-функции, а вот для построения доверительных интервалов придется выполнить расчеты по формулам. Для этого создадим пользовательскую функцию regression_pair_predict, которая в случае парной регрессии (pair regression) для заданного значения X возвращает:

  • прогнозируемое по регрессионной модели значение y_calc

  • доверительный интервал [y_calc_mean_ci_low, y_calc_mean_ci_upp] средних значений переменной Y

  • доверительный интервал [y_calc_predict_ci_low, y_calc_predict_ci_upp] индивидуальных значений переменной Y

Алгоритм расчета доверительных интервалов для множественной регрессии (multiple regression) отличается и в данном обзоре не рассматривается (рассмотрим в дальнейшем).

Про прогнозирование с помощью регрессионных моделей — см.также:

  • https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.RegressionResults.predict.html?highlight=predict#statsmodels.regression.linear_model.RegressionResults.predict

  • How to Make Predictions Using Regression Model in Statsmodels

  • https://www.statsmodels.org/stable/examples/notebooks/generated/predict.html

def regression_pair_predict(
    x_in,
    model_fit,
    regression_model,
    p_level: float=0.95):
    
    a_level = 1 - p_level
    
    X = pd.DataFrame(model_fit.model.exog)[1].values    # найти лучшее решение
    Y = model_fit.model.endog
    
    # вспомогательные величины
    n = int(result_linear_ols.nobs)
    SSE = model_fit.ssr    # SSE (Sum of Squared Error)
    dfE = n - p - 1
    MSE = SSE / dfE    # остаточная дисперсия
    
    Xmean = np.mean(X)
    SST_X = np.sum([(X[i] - Xmean)**2 for i in range(0, n)])
    
    t_table = sci.stats.t.ppf((1 + p_level)/2 , dfE)
    S2_y_calc_mean = MSE * (1/n + (x_in - Xmean)**2 / SST_X)
    S2_y_calc_predict = MSE * (1 + 1/n + (x_in - Xmean)**2 / SST_X)
        
    # прогнозируемое значение переменной Y
    y_calc=regression_model(x_in)
    # доверительный интервал средних значений переменной Y
    y_calc_mean_ci_low = y_calc - t_table*sqrt(S2_y_calc_mean)
    y_calc_mean_ci_upp = y_calc + t_table*sqrt(S2_y_calc_mean)
    # доверительный интервал индивидуальных значений переменной Y
    y_calc_predict_ci_low = y_calc - t_table*sqrt(S2_y_calc_predict)
    y_calc_predict_ci_upp = y_calc + t_table*sqrt(S2_y_calc_predict)
    
    result = y_calc, y_calc_mean_ci_low, y_calc_mean_ci_upp, y_calc_predict_ci_low, y_calc_predict_ci_upp
    
    return result

Сравним результаты расчета доверительных интервалов разными способами — с использованием функции regression_pair_predict и средствами statsmodels, для этого сформируем DaraFrame с новыми данными:

regression_pair_predict_df = pd.DataFrame(
    [regression_pair_predict(elem, result_linear_ols, regression_model=Y_calc) for elem in st_df['X'].values],
    columns=['y_calc', 'y_calc_mean_ci_low', 'y_calc_mean_ci_upp', 'y_calc_predict_ci_low', 'y_calc_predict_ci_upp'])
regression_pair_predict_df.insert(0, 'X', st_df['X'].values)
display(regression_pair_predict_df)

Видим, что результаты расчетов идентичны, следовательно мы можем использовать функцию regression_pair_predict для прогнозирования.

def graph_regression_pair_predict_plot_sns(
    model_fit,
    regression_model_in,
    Xmin=None, Xmax=None, Nx=10,
    Ymin_graph=None, Ymax_graph=None,
    title_figure=None, title_figure_fontsize=18,
    title_axes=None, title_axes_fontsize=16,
    x_label=None,
    y_label=None,
    label_fontsize=14, tick_fontsize=12, 
    label_legend_regr_model='', label_legend_fontsize=12,
    s=50, linewidth_regr_model=2,
    graph_size=(297/INCH, 210/INCH),
    result_output=True,
    file_name=None):
    
    # фактические данные
    X = pd.DataFrame(model_fit.model.exog)[1].values    # найти лучшее решение
    Y = model_fit.model.endog
    X = np.array(X)
    Y = np.array(Y)
    
    # границы
    if not(Xmin) and not(Xmax):
        Xmin=min(X)
        Xmax=max(X)
        Xmin_graph=min(X)*0.99
        Xmax_graph=max(X)*1.01
    else:
        Xmin_graph=Xmin
        Xmax_graph=Xmax
    
    if not(Ymin_graph) and not(Ymax_graph):
        Ymin_graph=min(Y)*0.99
        Ymax_graph=max(Y)*1.01       
    
    # формируем DataFrame данных
    Xcalc = np.linspace(Xmin, Xmax, num=Nx)
    Ycalc = regression_model_in(Xcalc)
    
    result_df = pd.DataFrame(
        [regression_pair_predict(elem, model_fit, regression_model=regression_model_in) for elem in Xcalc],
        columns=['y_calc', 'y_calc_mean_ci_low', 'y_calc_mean_ci_upp', 'y_calc_predict_ci_low', 'y_calc_predict_ci_upp'])
    result_df.insert(0, 'x_calc', Xcalc)
            
    # заголовки графика
    fig, axes = plt.subplots(figsize=graph_size)
    fig.suptitle(title_figure, fontsize = title_figure_fontsize)
    axes.set_title(title_axes, fontsize = title_axes_fontsize)
    
    # фактические данные
    sns.scatterplot(
        x=X, y=Y,
        label='фактические данные',
        s=s,
        color='red',
        ax=axes)
    
    # график регрессионной модели
    sns.lineplot(
        x=Xcalc, y=Ycalc,
        color='blue',
        linewidth=linewidth_regr_model,
        legend=True,
        label=label_legend_regr_model,
        ax=axes)
    
    # доверительный интервал средних значений переменной Y
    Mean_ci_low = result_df['y_calc_mean_ci_low']
    plt.plot(
        result_df['x_calc'], Mean_ci_low,
        color='magenta', linestyle='--', linewidth=1,
        label='доверительный интервал средних значений Y')
    
    Mean_ci_upp = result_df['y_calc_mean_ci_upp']
    plt.plot(
        result_df['x_calc'], Mean_ci_upp,
        color='magenta', linestyle='--', linewidth=1)
    
    # доверительный интервал индивидуальных значений переменной Y
    Predict_ci_low = result_df['y_calc_predict_ci_low']
    plt.plot(
        result_df['x_calc'], Predict_ci_low,
        color='orange', linestyle='-.', linewidth=2,
        label='доверительный интервал индивидуальных значений Y')
    
    Predict_ci_upp = result_df['y_calc_predict_ci_upp']
    plt.plot(
        result_df['x_calc'], Predict_ci_upp,
        color='orange', linestyle='-.', linewidth=2)
    
        
    axes.set_xlim(Xmin_graph, Xmax_graph)
    axes.set_ylim(Ymin_graph, Ymax_graph)        
    axes.set_xlabel(x_label, fontsize = label_fontsize)
    axes.set_ylabel(y_label, fontsize = label_fontsize)
    axes.tick_params(labelsize = tick_fontsize)
    #axes.tick_params(labelsize = tick_fontsize)
    axes.legend(prop={'size': label_legend_fontsize})
        
    plt.show()
    if file_name:
        fig.savefig(file_name, orientation = "portrait", dpi = 300)
        
    if result_output:
        return result_df
    else:
        return

graph_regression_pair_predict_plot_sns(
    model_fit=result_linear_ols,
    regression_model_in=Y_calc,
    Xmin=Xmin_graph-300, Xmax=Xmax_graph+200, Nx=25,
    Ymin_graph=Ymin_graph-5, Ymax_graph=Ymax_graph+5,
    title_figure=Task_Project, title_figure_fontsize=16,
    title_axes='Линейная регрессионная модель', title_axes_fontsize=14,
    x_label=Variable_Name_X,
    y_label=Variable_Name_Y,
    label_legend_regr_model=f'линейная регрессия Y = {b0:.3f} + {b1:.4f}*X',
    s=50,
    result_output=True,
    file_name='graph/regression_plot_lin.png')

Выводы и рекомендации

Исследована зависимость показаний ультразвукового прибора «ПУЛЬСАР-2.1» (X) и результатов замера прочности бетона (методом отрыва со скалыванием) склерометром ИПС-МГ4.03 (Y).

Между переменными имеется весьма сильная линейная корреляционная связь. Получена регрессионная модель:

Y = b0 + b1∙X = -21.3741 + 0.0129∙X

Модель хорошо аппроксимирует фактические данные, является адекватной, значимой и может использоваться для предсказания прочности бетона.

Также построен график прогноза с доверительными интервалами.

ИТОГИ

Итак, мы рассмотрели все этапы регрессионного анализа в случае простой линейной регрессии (simple linear regression) с использованием библиотеки statsmodels на конкретном практическом примере; подробно остановились на статистическом анализа модели с проверкой гипотез; также предложен ряд пользовательских функций, облегчающих работу исследователя и уменьшающих размер программного кода.

Конечно, мы разобрали далеко не все вопросы анализа регрессионных моделей и возможности библиотеки statsmodels применительно к simple linear regression, в частности статистики влияния (Influence Statistics), инструмент Leverage, анализ стьюдентизированных остатков и пр. — это темы для отдельных обзоров.

Исходный код находится в моем репозитории на GitHub.

Надеюсь, данный обзор поможет специалистам DataScience в работе.

Как подогнать модель линейной регрессии к данным, вывод коэффициентов, стандартной ошибки, t-статистики и доверительного интервала из модели, а также методы визуализации для построения доверительных интервалов, дисперсии и эффектов отдельных ковариат.

Линейная регрессия — это наиболее часто используемая регрессионная модель. Причина в том, что он прост в использовании, он может давать полезную информацию и его легко понять. В этой статье мы обсудим подгонку модели линейной регрессии к данным, вывод из нее и некоторую полезную визуализацию.

Используемые инструменты:

  1. Язык программирования Python и несколько его популярных библиотек. Если вы не знаете всех этих библиотек, вы все равно сможете прочитать эту статью и понять концепцию.
  2. Среда Jupyter Notebook.

Выбор функции

По моему опыту, лучший способ научиться — это использовать пример. Я буду использовать набор данных и продолжу объяснять процесс подгонки модели к данным и выводить информацию. Я использую набор данных обследования под названием NHANES dataset. Это очень хороший набор данных для практики. Пожалуйста, не стесняйтесь скачать набор данных по этой ссылке:

Давайте импортируем пакеты и набор данных:

%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import statsmodels.api as sm
import numpy as npdf = pd.read_csv('nhanes_2015_2016.csv')

Этот набор данных довольно большой, и нам не нужны все функции для этой статьи. Мы сосредоточимся на регрессионной модели, в которой переменной результата будет размер талии. Если вы хотите работать с любой другой переменной, убедитесь, что вы выбрали количественную, а не категориальную переменную. Поскольку линейная регрессия предназначена для прогнозирования только количественной переменной, а не категориальной переменной. Но вы можете использовать категориальные переменные в качестве независимых переменных. Проверьте названия всех столбцов, чтобы иметь представление о наборе данных.

df.columns#Output:
Index(['SEQN', 'ALQ101', 'ALQ110', 'ALQ130', 'SMQ020', 'RIAGENDR', 'RIDAGEYR', 'RIDRETH1', 'DMDCITZN', 'DMDEDUC2', 'DMDMARTL', 'DMDHHSIZ', 'WTINT2YR', 'SDMVPSU', 'SDMVSTRA', 'INDFMPIR', 'BPXSY1', 'BPXDI1', 'BPXSY2', 'BPXDI2', 'BMXWT', 'BMXHT', 'BMXBMI', 'BMXLEG', 'BMXARML', 'BMXARMC', 'BMXWAIST', 'HIQ210'], dtype='object')

Итак, просто сохраните размер талии и несколько других переменных, которые, похоже, связаны с размером талии, и создайте новый DataFrame меньшего размера. Инстинктивно любой догадается, что вес может иметь сильную корреляцию с размером талии. Пол, ИМТ, рост тоже могут сыграть важную роль. Итак, создайте новый DataFrame меньшего размера только с этими столбцами и для удобства удалите все нулевые значения.

keep = ['BMXWT', 'BMXHT', 'BMXBMI', 'BMXWAIST', 'RIAGENDR']
db = df[keep].dropna()
db.head()

Линейная регрессия и интерпретация

У нас есть набор данных, содержащий пять столбцов: вес, рост, индекс массы тела (ИМТ), размер талии и пол. Как упоминалось ранее, размер талии — это выходная переменная, которую мы попытаемся предсказать, используя другие переменные. Первоначально используйте только одну переменную или одну ковариату, чтобы предсказать размер талии. Вес (BMXWT) может быть хорошей ковариатой для начала, потому что при более высоком весе ожидается, что объем талии будет выше. Хотя есть и другие факторы, такие как рост или пол. Но о них мы подумаем позже. Подойдет модель, в которой размер талии будет выражаться как функция веса.

model = sm.OLS.from_formula("BMXWAIST ~ BMXWT", data=db)
result = model.fit()
result.summary()

Эта таблица выше может показаться вам пугающей. Но большая часть информации для нас не так важна. Нам понадобится только эта часть таблицы:

В первом столбце у нас есть коэффициенты. Помните формулу линейной регрессии:

Y = AX + B

В таблице выше 42,7189 — это B, а 0,6991 — это A. И мы знаем, что A — это наклон. Итак, наш наклон равен 0,6991. Это означает, что если человек имеет одну единицу лишнего веса, его талия будет на 0,6991 единицы больше, и это основано на p-значении, упомянутом в P ›| t | столбец. Затем стандартная ошибка составляет 0,005, что указывает на расстояние этого оцененного наклона от истинного наклона. t-статистика говорит, что предполагаемый наклон 0,6991 составляет стандартную ошибку 144,292 выше нуля. Последние два столбца — это уровни достоверности. По умолчанию это уровень достоверности 95%. Доверительный интервал составляет 0,69 и 0,709, что является очень узким диапазоном. Позже мы нарисуем полосу доверительного интервала.

db.BMXWAIST.std()

Стандартное отклонение составляет 16,85, что кажется намного выше, чем наклон регрессии 0,6991. Но наклон регрессии — это среднее изменение размера талии за сдвиг веса на одну единицу. Это означает, что если у человека на 10 единиц больше веса, чем у другого человека, его талия будет на 0,6991 * 10 или на 6,99 единиц больше.

Корреляция

Помимо этих значений в небольшой подтаблице, важен еще один параметр из сводки результатов. Это значение R-квадрата в верхней строке сводки результатов. Здесь значение R-квадрата составляет 0,795, что означает, что 79,5% объема талии можно объяснить весом. Теперь проверьте этот коэффициент регрессии с квадратом коэффициента Пирсона.

cc = db[["BMXWAIST", "BMXWT"]].corr()
print(cc)BMXWAIST     BMXWT
BMXWAIST  1.000000  0.891828
BMXWT     0.891828  1.000000

Чтобы найти значение R-квадрата:

cc.BMXWAIST.BMXWT**2

Это снова возвращает значение R в квадрате 0,795. Самая важная часть, а именно прогнозируемые значения размера талии в зависимости от веса, может быть найдена следующим образом:

result.fittedvalues

Это лишь часть результата. Исходный результат намного больше.

Давайте добавим вторую ковариату и посмотрим, как она влияет на эффективность регрессии. Я выбираю пол в качестве второй ковариаты. Я хочу использовать столбец с пометкой «пол»:

db["RIAGENDRx"] = db.RIAGENDR.replace({1: "Male", 2: "Female"})

Вот модель и краткое описание модели:

model = sm.OLS.from_formula("BMXWAIST ~ BMXWT + RIAGENDRx", data=db)
result = model.fit()
result.summary()

В приведенном выше коде BMXWT + RIAGENDRx не означает, что эти два столбца объединены или математически сложены. Это просто указывает на то, что они оба включены в модель. В этой новой модели размер талии выражается как функция веса и пола. Из приведенного выше результата мы можем найти, что коэффициент веса (BMXWT) составляет 0,7272, что немного выше, чем раньше. На этот раз коэффициент подразумевает, что если вес двух людей одного пола отличается на одну единицу, их талия будет отличаться на 0,7272 единицы. С другой стороны, коэффициент пола (RIAGENDRx) -5,0832 означает, что если мы сравним мужчину и женщину с одинаковым весом, у мужчины будет размер талии на 5,0832 единицы меньше, чем у женщины.

Все коэффициенты выражены как средние. Если мы сравним мужчину веса 70 и женщину веса 50, талия мужчины будет примерно в -5,0832 + (70–50) * 0,7272 раза больше, чем талия женщины.

Коэффициент регрессии веса (BMXWT) изменился немного, не слишком сильно после добавления пола в модель. Добавление второй ковариаты изменяет первую ковариату, если они в некоторой степени коррелированы. Давайте проверим корреляцию между двумя ковариатами:

db[['BMXWT', 'RIAGENDR']].corr()

Как видите, корреляция составляет -0,2375. Так что корреляция есть, но не слишком сильная. Вы можете найти прогнозируемые значения размера талии, используя метод подобранной стоимости, как я показал ранее. Я не буду этого снова демонстрировать.

Добавим третью ковариату. В качестве третьей ковариаты я выбрал BMXBMI. Вы можете попробовать и другие переменные.

model = sm.OLS.from_formula("BMXWAIST ~ BMXWT + RIAGENDRx + BMXBMI", data=db)
result = model.fit()
result.summary()

Обратите внимание, что после добавления BMXBMI коэффициент для гендерной переменной значительно изменился. Можно сказать, что ИМТ работает как маскирующая часть связи между размером талии и гендерной переменной. С другой стороны, коэффициент веса также существенно изменился. ИМТ также работал как маскирующая часть во взаимосвязи между талией и весом.

Вы можете добавить в модель дополнительные ковариаты и увидеть эффект каждой ковариаты.

Визуализация модели

В этом разделе мы визуализируем результат регрессионной модели. Мы построим линию регрессии, которая представляет собой подогнанные значения или предсказанные значения с доверительным интервалом. Если вам нужно освежить в памяти концепции доверительного интервала, ознакомьтесь с этой статьей:

Для этого графика мы зафиксируем пол как женский, а ИМТ как 25. Кроме того, нам нужно сохранить одну независимую переменную в качестве переменной фокуса. Мы сохраним его как вес (BMXWT). Итак, график покажет прогнозируемый размер талии женщины с ИМТ 25 всех возрастов.

from statsmodels.sandbox.predict_functional import predict_functional
values = {"RIAGENDRx": "Female", "RIAGENDR": 1, "BMXBMI": 25}pr, cb, fv = predict_functional(result, "BMXWT",
                values=values, ci_method="simultaneous")#Here, pr is the predicted values(pr), cb is the confidence band and #the fv is the function valuesax = sns.lineplot(fv, pr, lw=4)
ax.fill_between(fv, cb[:, 0], cb[:, 1], color='grey', alpha=0.4)
ax.set_xlabel("BMXWT")
_ = ax.set_ylabel("BMXWAIST")

Серая область на картинке — это полосы уверенности. Это означает, что истинный размер талии будет в этой области. Ширина серой области варьируется вдоль линии регрессии. Итак, доверительный интервал с возрастом меняется.

Вы можете зафиксировать вес и увидеть результат для определенного веса. Давайте зафиксируем вес на 65 и построим график зависимости ИМТ от талии для женского населения. Для этого нам нужно изменить параметр «значения». Потому что мы установили здесь значение ИМТ на 25. Теперь мы хотим исправить вес. Итак, нам нужно удалить значение BMI из параметра values ​​и добавить в него вес.

del values["BMXBMI"] # Delete this as it is now the focus variable
#del values['BMXWT']
values["BMXWT"] = 65
pr, cb, fv = predict_functional(result, "BMXBMI",
                values=values, ci_method="simultaneous")ax = sns.lineplot(fv, pr, lw=4)
ax.fill_between(fv, cb[:, 0], cb[:, 1], color='grey', alpha=0.4)
ax.set_xlabel("BMI")
_ = ax.set_ylabel("BMXWAIST")

На графиках выше мы построили только средние значения. Модель среднего размера талии для данного веса, пола или ИМТ. Используя ту же модель регрессии, можно также оценить структуру дисперсии, которая покажет, насколько наблюдаемые значения отклоняются от среднего. Для этого мы можем построить график остатков против подобранного значения. Помните, что подобранные значения — это предсказанные значения или наблюдаемые средние значения, а остатки — это разница между наблюдаемыми средними и истинными значениями.

pp = sns.scatterplot(result.fittedvalues, result.resid)
pp.set_xlabel("Fitted values")
_ = pp.set_ylabel("Residuals")

Похоже, что когда наблюдаемые значения ниже, дисперсия немного выше.

Также возможно визуализировать эффект только одной ковариаты. Мы можем увидеть график компонент плюс остаток или график частичного остатка, используя только одну ковариату, чтобы увидеть, сохраняем ли мы другие коварианты фиксированными, как изменяются зависимые переменные:

from statsmodels.graphics.regressionplots import plot_ccprax = plt.axes()
plot_ccpr(result, "BMXWT", ax)
ax.lines[0].set_alpha(0.2) # Reduce overplotting with transparency
_ = ax.lines[1].set_color('orange')

Теперь мы можем увидеть влияние ИМТ таким же образом, когда весовая переменная фиксирована.

ax = plt.axes()
plot_ccpr(result, "BMXBMI", ax)
ax.lines[0].set_alpha(0.2)
ax.lines[1].set_color("orange")

Влияние ИМТ намного сильнее, чем вес.

В этой статье вы узнали, как подобрать модель линейной регрессии, различные статистические параметры, связанные с линейной регрессией, и некоторые хорошие методы визуализации. Методы визуализации включали построение доверительной полосы линии регрессии, построение остатков и построение графика эффекта одной ковариаты.

Дополнительные рекомендации для чтения:

Проверка гипотез, характеристики и расчет

Доверительный интервал, расчет и характеристики

Как рассчитать доверительный интервал среднего и разницу среднего

Как выполнить проверку гипотез на Python: одно среднее и разница двумя способами

Как представить отношения между несколькими переменными в Python

Группа мастеров Панд за эффективное обобщение и анализ данных

Создайте полную нейронную сеть с нуля на Python

I found that the accepted answer had some mathematical glitches that in total would require edits beyond the recommended etiquette for modifying posts. So here is a solution to compute the standard error estimate for the coefficients obtained through the linear model (using an unbiased estimate as suggested here):

# preparation
X = np.concatenate((np.ones(TST.shape[0], 1)), TST), axis=1)
y_hat = clf.predict(TST).T
m, n = X.shape

# computation
MSE = np.sum((y_hat - y)**2)/(m - n)
coef_var_est = MSE * np.diag(np.linalg.pinv(np.dot(X.T,X)))
coef_SE_est = np.sqrt(var_est)

Note that we have to add a column of ones to TST as the original post used the linear_model.LinearRegression in a way that will fit the intercept term. Furthermore, we need to compute the mean squared error (MSE) as in ANOVA. That is, we need to divide the sum of squared errors (SSE) by the degrees of freedom for the error, i.e., df_error = df_observations — df_features.

The resulting array coef_SE_est contains the standard error estimates of the intercept and all other coefficients in coef_SE_est[0] and coef_SE_est[1:] resp. To print them out you could use

print('intercept: coef={:.4f} / std_err={:.4f}'.format(clf.intercept_[0], coef_SE_est[0]))
for i, coef in enumerate(clf.coef_[0,:]):
    print('x{}: coef={:.4f} / std_err={:.4f}'.format(i+1, coef, coef_SE_est[i+1]))

I found that the accepted answer had some mathematical glitches that in total would require edits beyond the recommended etiquette for modifying posts. So here is a solution to compute the standard error estimate for the coefficients obtained through the linear model (using an unbiased estimate as suggested here):

# preparation
X = np.concatenate((np.ones(TST.shape[0], 1)), TST), axis=1)
y_hat = clf.predict(TST).T
m, n = X.shape

# computation
MSE = np.sum((y_hat - y)**2)/(m - n)
coef_var_est = MSE * np.diag(np.linalg.pinv(np.dot(X.T,X)))
coef_SE_est = np.sqrt(var_est)

Note that we have to add a column of ones to TST as the original post used the linear_model.LinearRegression in a way that will fit the intercept term. Furthermore, we need to compute the mean squared error (MSE) as in ANOVA. That is, we need to divide the sum of squared errors (SSE) by the degrees of freedom for the error, i.e., df_error = df_observations — df_features.

The resulting array coef_SE_est contains the standard error estimates of the intercept and all other coefficients in coef_SE_est[0] and coef_SE_est[1:] resp. To print them out you could use

print('intercept: coef={:.4f} / std_err={:.4f}'.format(clf.intercept_[0], coef_SE_est[0]))
for i, coef in enumerate(clf.coef_[0,:]):
    print('x{}: coef={:.4f} / std_err={:.4f}'.format(i+1, coef, coef_SE_est[i+1]))

АКТУАЛЬНОСТЬ ТЕМЫ

Общие положения

Про регрессионный анализ вообще, и его применение в DataScience написано очень много. Есть множество учебников, монографий, справочников и статей по прикладной статистике, огромное количество информации в интернете, примеров расчетов. Можно найти множество кейсов, реализованных с использованием средств Python. Казалось бы — что тут еще можно добавить?

Однако, как всегда, есть нюансы:

1. Регрессионный анализ — это прежде всего процесс, набор действий исследователя по определенному алгоритму: «подготовка исходных данных — построение модели — анализ модели — прогнозирование с помощью модели». Это ключевая особенность. Не представляет особой сложности сформировать DataFrame исходных данных и построить модель, запустить процедуру из библиотеки statsmodels. Однако подготовка исходных данных и последующий анализ модели требуют гораздо больших затрат человеко-часов специалиста и строк программного кода, чем, собственно, построение модели. На этих этапах часто приходится возвращаться назад, корректировать модель или исходные данные. Этому, к сожалению, во многих источниках, не удаляется достойного внимания, а иногда — и совсем не уделяется внимания, что приводит к превратному представлению о регрессионном анализе.

2. Далеко не во всех источниках уделяется должное внимание интерпретации промежуточных и финальных результатов. Специалист должен уметь интерпретировать каждую цифру, полученную в ходе работы над моделью.

3. Далеко не все процедуры на этапах подготовки исходных данных или анализа модели в источниках разобраны подробно. Например, про проверку значимости коэффициента детерминации найти информацию не представляет труда, а вот про проверку адекватности модели, построение доверительных интервалов регрессии или про специфические процедуры (например, тест Уайта на гетероскедастичность) информации гораздо меньше.

4. Своеобразная сложность может возникнуть с проверкой статистических гипотез: для отечественной литературы по прикладной статистике больше характерно проверять гипотезы путем сравнения расчетного значения критерия с табличным, а в иностранных источниках чаще определяется расчетный уровень значимости и сравнивается с заданным (чаще всего 0.05 = 1-0.95). В разных источниках информации реализованы разные подходы. Инструменты python (прежде всего библиотеки scipy и statsmodels) также в основном оперируют с расчетным уровнем значимости.

5. Ну и, наконец, нельзя не отметить, что техническая документация библиотеки statsmodels составлена, на мой взгляд, далеко не идеально: информация излагается путано, изобилует повторами и пропусками, описание классов, функций и свойств выполнено фрагментарно и количество примеров расчетов — явно недостаточно.

Поэтому я решил написать ряд обзоров по регрессионному анализу средствами Python, в которых акцент будет сделан на практических примерах, алгоритме действий исследователя, интерпретации всех полученных результатов, конкретных методических рекомендациях. Буду стараться по возможности избегать теории (хотя совсем без нее получится) — все-таки предполагается, что специалист DataScience должен знать теорию вероятностей и математическую статистику, хотя бы в рамках курса высшей математики для технического или экономического вуза.

В данном статье остановимся на самои простом, классическом, стереотипном случае — простой линейной регрессии (simple linear regression), или как ее еще принято называть — парной линейной регрессионной модели (ПЛРМ) — в ситуации, когда исследователя не подстерегают никакие подводные камни и каверзы — исходные данные подчиняются нормальному закону, в выборке отсутствуют аномальные значения, отсутствует ложная корреляция. Более сложные случаи рассмотрим в дальнейшем.

Для построение регрессионной модели будем пользоваться библиотекой statsmodels.

В данной статье мы рассмотрим по возможности полный набор статистических процедур. Некоторые из них (например, дескриптивная статистика или дисперсионный анализ регрессионной модели) могут показаться избыточными. Все так, но эти процедуры улучшают наше представление о процессе и об исходных данных, поэтому в разбор я их включил, а каждый исследователь сам вправе для себя определить, потребуются ему эти процедуры или нет.

Краткий обзор источников

Источников информации по корреляционному и регрессионному анализу огромное количество, в них можно просто утонуть. Поэтому позволю себе просто порекомендовать ряд источников, на мой взгляд, наиболее полезных:

  1. Кобзарь А.И. Прикладная математическая статистика. Для инженеров и научных работников. — М.: ФИЗМАТЛИТ, 2006. — 816 с.

  2. Львовский Е.Н. Статистические методы построения эмпирических формул. — М.: Высшая школа, 1988. — 239 с.

  3. Фёрстер Э., Рёнц Б. Методы корреляционного и регрессионного анализа / пер с нем. — М.: Финансы и статистика, 1983. — 302 с.

  4. Афифи А., Эйзен С. Статистический анализ. Подход с использованием ЭВМ / пер с англ. — М.: Мир, 1982. — 488 с.

  5. Дрейпер Н., Смит Г. Прикладной регрессионный анализ. Книга 1 / пер.с англ. — М.: Финансы и статистика, 1986. — 366 с.

  6. Айвазян С.А. и др. Прикладная статистика: Исследование зависимостей. — М.: Финансы и статистика, 1985. — 487 с.

  7. Прикладная статистика. Основы эконометрики: В 2 т. 2-е изд., испр. — Т.2: Айвазян С.А. Основы эконометрики. — М.: ЮНИТИ-ДАНА, 2001. — 432 с.

  8. Магнус Я.Р. и др. Эконометрика. Начальный курс — М.: Дело, 2004. — 576 с.

  9. Носко В.П. Эконометрика. Книга 1. — М.: Издательский дом «Дело» РАНХиГС, 2011. — 672 с.

  10. Брюс П. Практическая статистика для специалистов Data Science / пер. с англ. — СПб.: БХВ-Петербург, 2018. — 304 с.

  11. Уатт Дж. и др. Машинное обучение: основы, алгоритмы и практика применения / пер. с англ. — СПб.: БХВ-Петербург, 2022. — 640 с.

Прежде всего следует упомянуть справочник Кобзаря А.И. [1] — это безусловно выдающийся труд. Ничего подобного даже близко не издавалось. Всем рекомендую иметь под рукой.

Есть очень хорошее практическое пособие [2] — для начинающих и практиков.>

Добротная работа немецких авторов [3]. Все разобрано подробно, обстоятельно, с примерами — очень хорошая книга. Примеры приведены из области экономики.

Еще одна добротная работа — [4], с примерами медико-биологического характера.

Работа [5] считается одним из наиболее полных изложений прикладного регрессионного анализа.

Более сложные работы — [6] (классика жанра), [7], [8], [9] — выдержаны на достаточно высоком математическом уровне, примеры из экономической области.

Свежие работы [10] (с примерами на языке R) и [11] (с примерами на python).

Cтатьи

Статей про регрессионный анализ в DataScience очень много, обращаю внимание на некоторые весьма полезные из них.

Серия статей «Python, корреляция и регрессия», охватывающая весь процесс регрессионного анализа:

  • первичная обработка данных, визуализация и корреляционный анализ;

  • регрессия;

  • теория матриц в регрессионном анализе, проверка  адекватности, мультиколлинеарность;

  • прогнозирование с помощью регрессионных моделей.

Очень хороший обзор «Интерпретация summary из statsmodels для линейной регрессии». В этой статье даны очень полезные ссылки:

  • Statistical Models

  • Interpreting Linear Regression Through statsmodels .summary()

Статья «Регрессионные модели в Python».

Основные предпосылки (гипотезы) регрессионного анализа

Очень кратко — об этом написано тысячи страниц в учебниках — но все же вспомним некоторые основы теории.

Проверка исходных предпосылок является очень важным моментом при статистическом анализе регрессионной модели. Если мы рассматриваем классическую линейную регрессионную модель вида:

то основными предпосылками при использовании обычного метода наименьших квадратов (МНК) для оценки ее параметров являются:

  1. Среднее значение (математическое ожидание) случайной составляющей равно нулю:

  1. Дисперсия случайной составляющей является постоянной:

В случае нарушения данного условия мы сталкиваемся с явлением гетероскедастичности.

  1. Значения случайной составляющей статистически независимы (некоррелированы) между собой:

В случае нарушения данного условия мы сталкиваемся с явлением автокорреляции.

  1. Условие существования обратной матрицы

что эквивалентно одному из двух следующих условий:

то есть число наблюдений должно превышать число параметров.

  1. Значения случайной составляющей некоррелированы со значениями независимых переменных:

  1. Случайная составляющая имеет нормальный закон распределения (с математическим ожиданием равным нулю — следует из условия 1):

Более подробно — см.: [3, с.90], [4, с.147], [5, с.122], [6, с.208], [7, с.49], [8, с.68], [9, с.88].

Кроме гетероскедастичности и автокорреляции возможно возникновение и других статистических аномалий — мультиколлинеарности, ложной корреляции и т.д.

Доказано, что оценки параметров, полученные с помощью МНК, обладают наилучшими свойствами (несмещенность, состоятельность, эффективность) при соблюдении ряда условий:

  • выполнение приведенных выше исходных предпосылок регрессионного анализа;

  • число наблюдений на одну независимую переменную должно быть не менее 5-6;

  • должны отсутствовать аномальные значения (выбросы).

Кроме обычного МНК существуют и другие его разновидности (взвешенный МНК, обобщенный МНК), которые применяются при наличии статистических аномалий. Кроме МНК применяются и другие методы оценки параметров моделей. В этом обзоре мы эти вопросы рассматривать не будем.

Алгоритм проведения регрессионного анализа

Алгоритм действий исследователя при построении регрессионной модели (полевые работы мы, по понятным причинам, не рассматриваем — считаем, что исходные данные уже получены):

  1. Подготовительный этап — постановка целей и задач исследования.

  2. Первичная обработка исходных данных — об этом много написано в учебниках и пособиях по DataScience, сюда могут относится:

  • выявление нерелевантных признаков (признаков, которые не несут полезной информации), нетипичных данных (выбросов), неинформативных признаков (имеющих большое количество одинаковых значений) и работа с ними (удаление/преобразование);

  • выделение категориальных признаков;

  • работа с пропущенными значениями;

  • преобразование признаков-дат в формат datetime и т.д.

  1. Визуализация исходных данных — предварительный графический анализ.

  2. Дескриптивная (описательная) статистика — расчет выборочных характеристик и предварительные выводы о свойствах исходных данных.

  3. Исследование закона распределения исходных данных и, при необходимости, преобразование исходных данных к нормальному закону распределения.

  4. Выявление статистически аномальных значений (выбросов), принятие решения об их исключении.

    Этапы 4, 5 и 6 могут быть при необходимости объединены.

  5. Корреляционный анализ — исследование корреляционных связей между исходными данными; это разведка перед проведением регрессионного анализа.

  6. Построение регрессионной модели:

  • выбор моделей;

  • выбор методов;

  • оценка параметров модели.

  1. Статистический анализ регрессионной модели:  

  • оценка ошибок аппроксимации (error metrics);

  • анализ остатков (проверка нормальности распределения остатков и гипотезы о равенстве нулю среднего значения остатков);

  • проверка адекватности модели;

  • проверка значимости коэффициента детерминации;

  • проверка значимости коэффициентов регрессии;

  • проверка мультиколлинеарности (для множественных регрессионных моделей; вообще мультиколлинеарные переменные выявляются еще на стадии корреляционного анализа);

  • проверка автокорреляции;

  • проверка гетероскедастичности.

   Этапы 8 и 9 могут быть при необходимости повторяться несколько раз.

  1. Сравнительный анализ нескольких регрессионных моделей, выбор наилучшей (при необходимости).

  2. Прогнозирование с помощью регрессионной модели и оценка качества прогноза.

  3. Выводы и рекомендации.

Само собой, этот алгоритм не есть истина в последней инстанции — в зависимости от особенностей исходных данных и вида модели могут возникать дополнительные задачи.

Применение пользовательских функций

Далее в обзоре мной будут использованы несколько пользовательских функций для решения разнообразных задач. Все эти функции созданы для облегчения работы и уменьшения размера программного кода. Данные функции загружается из пользовательского модуля my_module__stat.py, который доступен в моем репозитории на GitHub. Лично мне так удобнее работать, хотя каждый исследователь сам формирует себе инструменты по душе — особенно в части визуализации. Желающие могут пользоваться этими функциями, либо создать свои.

Итак, вот перечень данных функций:

  • graph_scatterplot_sns — функция позволяет построить точечную диаграмму средствами seaborn и сохранить график в виде png-файла;

  • graph_hist_boxplot_probplot_XY_sns  — функция позволяет визуализировать исходные данные для простой линейной регрессии путем одновременного построения гистограммы, коробчатой диаграммы и вероятностного графика (для переменных X и Y) средствами seaborn и сохранить график в виде png-файла; имеется возможность выбирать, какие графики строить (h — hist, b — boxplot, p — probplot);

  • descriptive_characteristics — функция возвращает в виде DataFrame набор статистических характеристики выборки, их ошибок и доверительных интервалов;

  • detecting_outliers_mad_test — функция выполняет проверку наличия аномальных значений (выбросов) по критерию наибольшего абсолютного отклонения (более подробно — см.[1, с.547]);

  • norm_distr_check — проверка нормальности распределения исходных данных с использованием набора из нескольких статистических тестов;

  • corr_coef_check — функция выполняет расчет коэффициента линейной корреляции Пирсона, проверку его значимости и расчет доверительных интервалов; об этой функции я писал в своей статье.

  • graph_regression_plot_sns —  — функция позволяет построить график регрессионной модели.

Ряд пользовательских функций мы создаем в процессе данного обзора (они тоже включены в пользовательский модуль my_module__stat.py):

  • regression_error_metrics — расчет ошибок аппроксимации регрессионной модели;

  • ANOVA_table_regression_model — вывод таблицы дисперсионного анализа регрессионной модели;

  • regression_model_adequacy_check — проверка адекватности регрессионной модели по критерию Фишера;

  • determination_coef_check — проверка значимости коэффициента детерминации по критерию Фишера;

  • regression_coef_check — проверка значимости коэффициентов регрессии по критеирю Стьюдента;

  • Goldfeld_Quandt_test, Breush_Pagan_test, White_test — проверка гетероскедастичности с использование тестов Голдфелда-Квандта, Бриша-Пэгана и Уайта соответственно;

  • regression_pair_predict — функция для прогнозирования с помощью парной регрессионной модели: рассчитывает прогнозируемое значение переменной Y по заданной модели, а также доверительные интервалы среднего и индивидуального значения для полученного прогнозируемого значения Y;

  • graph_regression_pair_predict_plot_sns — прогнозирование: построение графика регрессионной модели (с доверительными интервалами) и вывод расчетной таблицы с данными для заданной области значений X.

ПОСТАНОВКА ЗАДАЧИ

В качестве примера рассмотрим практическую задачу из области экспертизы промышленной безопасности — калибровку ультразвукового прибора для определения прочности бетона.

Итак, суть задачи: при обследовании несущих конструкций зданий и сооружений эксперт определяет прочность бетона с использованием ультразвукового прибора «ПУЛЬСАР-2.1», для которого необходимо предварительно построить градуировочную зависимость. Заключается это в следующем — производятся замеры с фиксацией следующих показателей:

  • X — показания ультразвукового прибора «ПУЛЬСАР-2.1» (м/с)

  • Y — результаты замера прочности бетона (методом отрыва со скалыванием) склерометром ИПС-МГ4.03.

Предполагается, что между показателями X и Y имеется линейная регрессионная зависимость, которая позволит прогнозировать прочность бетона на основании измерений, проведенных прибором «ПУЛЬСАР-2.1».

Были выполнены замеры фактической прочности бетона конструкций для бетонов одного вида с одним типом крупного заполнителя, с единой технологией производства. Для построения были выбраны 14 участков (не менее 12), включая участки, в которых значение косвенного показателя максимальное, минимальное и имеет промежуточные значения.

Настройка заголовков отчета:

# Общий заголовок проекта
Task_Project = 'Калибровка ультразвукового прибора "ПУЛЬСАР-2.1" nдля определения прочности бетона'

# Заголовок, фиксирующий момент времени
AsOfTheDate = ""

  # Заголовок раздела проекта
Task_Theme = ""

# Общий заголовок проекта для графиков
Title_String = f"{Task_Project}n{AsOfTheDate}"

# Наименования переменных
Variable_Name_X = "Скорость УЗК (м/с)"
Variable_Name_Y = "Прочность бетона (МПа)"

# Константы
INCH = 25.4    # мм/дюйм
  DecPlace = 5    # number of decimal places - число знаков после запятой

# Доверительная вероятность и уровень значимости:
p_level = 0.95
a_level = 1 - p_level   

Подключение модулей и библиотек:

# Стандартные модули и библиотеки

import os    # загрузка модуля для работы с операционной системой
import sys
import platform
print('{:<35}{:^0}'.format("Текущая версия Python: ", platform.python_version()), 'n')

import math
from math import *    # подключаем все содержимое модуля math, используем без псевдонимов

import numpy as np
#print ("Текущая версия модуля numpy: ", np.__version__)
print('{:<35}{:^0}'.format("Текущая версия модуля numpy: ", np.__version__))
from numpy import nan

import scipy as sci
print('{:<35}{:^0}'.format("Текущая версия модуля scipy: ", sci.__version__))
import scipy.stats as sps

import pandas as pd
print('{:<35}{:^0}'.format("Текущая версия модуля pandas: ", pd.__version__))

import matplotlib as mpl
print('{:<35}{:^0}'.format("Текущая версия модуля matplotlib: ", mpl.__version__))
import matplotlib.pyplot as plt

import seaborn as sns
print('{:<35}{:^0}'.format("Текущая версия модуля seaborn: ", sns.__version__))

import statsmodels.api as sm
import statsmodels.formula.api as smf
import statsmodels.graphics.api as smg
import statsmodels.stats.api as sms
from statsmodels.compat import lzip
print('{:<35}{:^0}'.format("Текущая версия модуля statsmodels: ", sm.__version__))

import statistics as stat    # module 'statistics' has no attribute '__version__'

import sympy as sym
print('{:<35}{:^0}'.format("Текущая версия модуля sympy: ", sym.__version__))

# Настройки numpy
np.set_printoptions(precision = 4, floatmode='fixed')

# Настройки Pandas
pd.set_option('display.max_colwidth', None)    # текст в ячейке отражался полностью вне зависимости от длины
pd.set_option('display.float_format', lambda x: '%.4f' % x)

# Настройки seaborn
sns.set_style("darkgrid")
sns.set_context(context='paper', font_scale=1, rc=None)    # 'paper', 'notebook', 'talk', 'poster', None

# Настройки Mathplotlib
f_size = 8    # пользовательская переменная для задания базового размера шрифта
plt.rcParams['figure.titlesize'] = f_size + 12    # шрифт заголовка
plt.rcParams['axes.titlesize'] = f_size + 10      # шрифт заголовка
plt.rcParams['axes.labelsize'] = f_size + 6       # шрифт подписей осей
plt.rcParams['xtick.labelsize'] = f_size + 4      # шрифт подписей меток
plt.rcParams['ytick.labelsize'] = f_size + 4
plt.rcParams['legend.fontsize'] = f_size + 6      # шрифт легенды

# Пользовательские модули и библиотеки

Text1 = os.getcwd()    # вывод пути к текущему каталогу
#print(f"Текущий каталог: {Text1}")

sys.path.insert(1, "D:REPOSITORYMyModulePython")

from my_module__stat import *

ФОРМИРОВАНИЕ ИСХОДНЫХ ДАННЫХ

Показания ультразвукового прибора «ПУЛЬСАР-2.1» (м/с):

X = np.array([
    4416, 4211, 4113, 4110, 4122,
    4427, 4535, 4311, 4511, 4475,
    3980, 4490, 4007, 4426
    ])

Результаты замера прочности бетона (методом отрыва со скалыванием) прибором ИПС-МГ4.03:

Y = np.array([
    34.2, 35.1, 31.5, 30.8, 30.0,
    34.0, 35.4, 35.8, 38.0, 37.7,
    30.0, 37.8, 31.0, 35.2
    ])

Запишем данные в DataFrame:

calibrarion_df = pd.DataFrame({
    'X': X,
    'Y': Y})
display(calibrarion_df)
calibrarion_df.info()

Сохраняем данные в csv-файл:

calibrarion_df.to_csv(
    path_or_buf='data/calibrarion_df.csv',
    mode='w+',
    sep=';')

Cоздаем копию исходной таблицы для работы:

dataset_df = calibrarion_df.copy()

ВИЗУАЛИЗАЦИЯ ДАННЫХ

Границы значений переменных (при построении графиков):

(Xmin_graph, Xmax_graph) = (3800, 4800)
(Ymin_graph, Ymax_graph) = (25, 45)
# Пользовательская функция
graph_scatterplot_sns(
    X, Y,
    Xmin=Xmin_graph, Xmax=Xmax_graph,
    Ymin=Ymin_graph, Ymax=Ymax_graph,
    color='orange',
    title_figure=Task_Project,
    x_label=Variable_Name_X,
    y_label=Variable_Name_Y,
    s=100,
    file_name='graph/scatterplot_XY_sns.png')

Существует универсальный набор графиков — гистограмма, коробчатая диаграмма, вероятностный график — которые позволяют исследователю сделать предварительные выводы о свойствах исходных данных.

Так как объем выборки невелик (n=14), строить гистограммы распределения переменных X и Y не имеет смысла, поэтому ограничимся построением коробчатых диаграмм и вероятностных графиков:

# Пользовательская функция
graph_hist_boxplot_probplot_XY_sns(
    data_X=X, data_Y=Y,
    data_X_min=Xmin_graph, data_X_max=Xmax_graph,
    data_Y_min=Ymin_graph, data_Y_max=Ymax_graph,  
    graph_inclusion='bp',    # выбираем для построения виды графиков: b - boxplot, p - probplot)
    data_X_label=Variable_Name_X,
    data_Y_label=Variable_Name_Y,
    title_figure=Task_Project,
    file_name='graph/hist_boxplot_probplot_XY_sns.png')    

Для сравнения характера распределений переменных X и Y возможно также построить совмещенную коробчатую диаграмму по стандартизованным данным:

# стандартизуем исходные данные
standardize_df = lambda X: ((X - np.mean(X))/np.std(X))

dataset_df_standardize = dataset_df.copy()
dataset_df_standardize = dataset_df_standardize.apply(standardize_df)
display(dataset_df_standardize)

# построим график
fig, axes = plt.subplots(figsize=(210/INCH, 297/INCH/2))
axes.set_title("Распределение стандартизованных переменных X и Y", fontsize = 16)
sns.boxplot(
    data=dataset_df_standardize,    
    orient='h',
    width=0.5,
    ax=axes)
plt.show()

Графический анализ позволяет сделать следующие выводы:

  1. Отсутствие выбросов на коробчатых диаграммах свидетельствует об однородности распределения переменных.

  2. Смещение медианы вправо на коробчатых диаграммах свидетельствует о левосторонней асимметрии распределения.

ДЕСКРИПТИВНАЯ (ОПИСАТЕЛЬНАЯ СТАТИСТИКА)

Собственно говоря, данный этап требуется проводить далеко не всегда, однако с помощью статистических характеристик выборки мы тоже можем сделать полезные выводы.

Описательная статистика исходных данных средствами библиотеки Pandas — самый простой вариант:

dataset_df.describe()

Описательная статистика исходных данных средствами библиотеки statsmodels — более развернутый вариант, с большим количеством показателей:

from statsmodels.stats.descriptivestats import Description
result = Description(
    dataset_df,
    stats=["nobs", "missing", "mean", "std_err", "ci", "ci", "std", "iqr", "mad", "coef_var", "range", "max", "min", "skew", "kurtosis", "mode",
           "median", "percentiles", "distinct", "top", "freq"],
    alpha=a_level,
    use_t=True)
display(result.summary())

Описательная статистика исходных данных с помощью пользовательской функции descriptive_characteristics:

# Пользовательская функция
descriptive_characteristics(X)

Выводы:

  1. Сравнение показателей среднего арифметического (mean) и медианы (median) свидетельствует о левосторонней асимметрии (т.к.mean < median).

  2. Значение коэффициента вариации CV = 0.0445 и доверительный интервал для него 0.0336 ≤ CV ≤ 0.0657 свидетельствует об однородности исходных данных (т.к. CV ≤ 0.33).

  3. Значение показателя асимметрии skew (As) = -0.3101 свидетельствует об умеренной левосторонней асимметрии распределении (т.к. |As| ≤ 0.5, As < 0).

  4. Значение показателя эксцесса kurtosis (Es) = -1.4551 свидетельствует о плосковершинном распределении (platykurtic distribution) (т.к. Es < 0).

# Пользовательская функция
descriptive_characteristics(Y)

Выводы:

  1. Сравнение показателей среднего арифметического (mean) и медианы (median) свидетельствует о левосторонней асимметрии (т.к.mean < median).

  2. Значение коэффициента вариации CV = 0.0822 и доверительный интервал для него 0.06202 ≤ CV ≤ 0.1217 свидетельствует об однородности исходных данных (т.к. CV ≤ 0.33).

  3. Значение показателя асимметрии skew (As) = -0.1109 свидетельствует о приблизительно симметричном распределении (т.к. |As| ≤ 0.25).

  4. Значение показателя эксцесса kurtosis (Es) = -1.3526 свидетельствует о плосковершинном распределении (platykurtic distribution) (т.к. Es < 0).

ПРОВЕРКА НОРМАЛЬНОСТИ РАСПРЕДЕЛЕНИЯ

Для проверки нормальности распределения использована пользовательская функция norm_distr_check, которая объединяет в себе набор стандартных статистических тестов проверки нормальности. Все тесты относятся к стандартному инструментарию Pyton (библиотека scipy, модуль stats), за исключением теста Эппса-Палли (Epps-Pulley test); о том, как реализовать этот тест средствами Pyton я писал в своей статье https://habr.com/ru/post/685582/.

Примечание: для использования функции norm_distr_check в каталог с ipynb-файлом необходимо поместить папку table c файлом Tep_table.csv, который содержит табличные значения статистики критерия Эппса-Палли.

# пользовательская функция
norm_distr_check(X)

# Пользовательская функция
norm_distr_check (Y)

Вывод: большинство статистических тестов позволяют принять гипотезу о нормальности распределения переменных X и Y.

ПРОВЕРКА АНОМАЛЬНЫХ ЗНАЧЕНИЙ (ВЫБРОСОВ)

Статистическую проверку аномальных значений (выбросов) не стоит путать с проверкой выбросов, которая проводится на этапе первичной обработки результатов наблюдений. Последняя проводится с целью отсеять явные ошибочные данные (например, в результате неправильно поставленной запятой величина показателя может увеличиться/уменьшиться на порядок); здесь же мы говорим о статистической проверке данных, которые уже прошли этап первичной обработки.

Имеется довольно много критериев для проверки аномальных значений (подробнее см.[1]); вообще данная процедура довольно неоднозначная:

  • критерии зависят от вида распределения;

  • мало данных о сравнительной мощности этих критериев;

  • даже в случае принятии гипотезы о нормальном распределении в выборке могут быть обнаружены аномальные значения и пр.

Кроме существует дилемма: если какие-то значения в выборке признаны выбросами — стоит или не стоит исследователю исключать их? Ведь каждое значение несет в себе информацию, причем иногда весьма ценную, а сильно отклоняющиеся от основного массива данные (которые не являются выбросами в смысле первичной обработки, но являются статистическим значимыми аномальными значениями) могут кардинально изменить статистический вывод.

В общем, о задаче выявления аномальных значений (выбросов) можно написать отдельно, а пока, в данном разборе, ограничимся проверкой аномальных значений по критерию наибольшего максимального отклонения (см.[1, с.547]) с помощью пользовательской функции detecting_outliers_mad_test. Данные функция возвращает DataFrame, которые включает список аномальных значений со следующими признаками:

  • value — проверяемое значение из выборки;

  • mad_calc и mad_table — расчетное и табличное значение статистики критерия;

  • outlier_conclusion — вывод (выброс или нет).

Обращаю внимание, что критерий наибольшего максимального отклонения можно использовать только для нормально распределенных данных.

# пользовательская функция
print("Проверка наличия выбросов переменной X:n")
result = detecting_outliers_mad_test(X)
mask = (result['outlier_conclusion'] == 'outlier')
display(result[mask])

# пользовательская функция
print("Проверка наличия выбросов переменной Y:n")
result = detecting_outliers_mad_test(Y)
mask = (result['outlier_conclusion'] == 'outlier')
display(result[mask])

Вывод: в случае обеих переменных X и Y список пуст, следовательно, аномальных значений (выбросов) не выявлено.

КОРРЕЛЯЦИОННЫЙ АНАЛИЗ

Корреляционный анализ — это разведка перед построением регрессионной модели.

Выполним расчет коэффициента линейной корреляции Пирсона, проверку его значимости и построение доверительных интервалов с помощью пользовательской функции corr_coef_check (про эту функцию более подробно написано в моей статье https://habr.com/ru/post/683442/):

# пользовательская функция
display(corr_coef_check(X, Y, scale='Evans'))

Выводы:

  1. Значение коэффициента корреляции coef_value = 0.8900 свидетельствует о весьма сильной корреляционной связи (по шкале Эванса).

  2. Коэффициент корреляции значим по критерию Стьюдента: t_calc ≥ t_table, a_calc ≤ a_level.

  3. Доверительный интервал для коэффициента корреляции: 0.6621 ≤ coef_value ≤ 0.9625.

РЕГРЕССИОННЫЙ АНАЛИЗ

Предварительная визуализация

python позволяет выполнить предварительную визуализацию, например, с помощью функции jointplot библиотеки seaborn:

fig = plt.figure(figsize=(297/INCH, 210/INCH))
axes = sns.jointplot(
    x=X, y=Y,
    kind='reg',
    ci=95)
plt.show()

Построение модели

Выполним оценку параметров и анализ простой линейной регрессии (simple linear regression), используя библиотеку statsmodels (https://www.statsmodels.org/) и входящий в нее модуль линейной регрессии Linear Regression (https://www.statsmodels.org/stable/regression.html).

Данный модуль включает в себя классы, реализующие различные методы оценки параметров моделей линейной регрессии, в том числе:

  • класс OLS (https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.OLS.html#statsmodels.regression.linear_model.OLS) — Ordinary Least Squares (обычный метод наименьших квадратов).

  • класс WLS (https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.WLS.html#statsmodels.regression.linear_model.WLS) — Weighted Least Squares (метод взвешенных наименьших квадратов) (https://en.wikipedia.org/wiki/Weighted_least_squares), применяется, если имеет место гетероскедастичность данных (https://ru.wikipedia.org/wiki/Гетероскедастичность).

  • класс GLS (https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.GLS.html#statsmodels.regression.linear_model.GLS) — Generalized Least Squares (обобщенный метод наименьших квадратов) (https://en.wikipedia.org/wiki/Generalized_least_squares), применяется, если существует определенная степень корреляции между остатками в модели регрессии.

  • класс GLSAR (https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.GLSAR.html#statsmodels.regression.linear_model.GLSAR) — Generalized Least Squares with AR covariance structure (обобщенный метод наименьших квадратов, ковариационная структура с автокорреляцией — экспериментальный метод)

  • класс RecurciveLS (https://www.statsmodels.org/stable/examples/notebooks/generated/recursive_ls.html) — Recursive least squares (рекурсивный метод наименьших квадратов) (https://en.wikipedia.org/wiki/Recursive_least_squares_filter)

  • классы RollingOLS (https://www.statsmodels.org/stable/generated/statsmodels.regression.rolling.RollingOLS.html#statsmodels.regression.rolling.RollingOLS) и RollingWLS (https://www.statsmodels.org/stable/generated/statsmodels.regression.rolling.RollingWLS.html#statsmodels.regression.rolling.RollingWLS) — скользящая регрессия (https://www.statsmodels.org/stable/examples/notebooks/generated/rolling_ls.html, https://help.fsight.ru/ru/mergedProjects/lib/01_regression_models/rolling_regression.htm)

    и т.д.

Так как исходные данные подчиняются нормальному закону распределения и аномальные значения (выбросы) отсутствуют, воспользуемся для оценки параметров обычным методом наименьших квадратов (класс OLS):

model_linear_ols = smf.ols(formula='Y ~ X', data=dataset_df)
result_linear_ols = model_linear_ols.fit()
print(result_linear_ols.summary())

Альтернативная форма выдачи результатов:

print(result_linear_ols.summary2())

Результаты построения модели мы получаем как класс statsmodels.regression.linear_model.RegressionResults (https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.RegressionResults.html#statsmodels.regression.linear_model.RegressionResults).

Экспресс-выводы, которые мы можем сразу сделать из результатов построения модели:

  1. Коэффициенты регрессии модели Y = b0 + b1∙X:

    • Intercept = b0 = -21.3741

    • b1 = 0.0129

  2. Коэффициент детерминации R-squared = 0.776, его скорректированная оценка Adj. R-squared = 0.757 — это означает, что регрессионная модуль объясняет 75.75% вариации переменной Y.

  3. Проверка значимости коэффициента детерминации:

    • расчетное значение статистики критерия Фишера: F-statistic = 41.61

    • расчетный уровень значимости Prob (F-statistic) = 3.16e-05

    • так как значение Prob (F-statistic) < 0.05, то нулевая гипотеза R-squared = 0 НЕ ПРИНИМАЕТСЯ, т.е. коэффициент детерминации ЗНАЧИМ

  4. Проверка значимости коэффициентов регрессии:

    • расчетный уровень значимости P>|t| не превышает 0.05 — это означает, что оба коэффициента регрессии значимы

    • об этом же свидетельствует то, что доверительный интервал для обоих коэффициентов регрессии ([0.025; 0.975]) не включает в себя точку 0

    Также в таблице результатов содержится прочая информация по коэффициентам регрессии: стандартная ошибка Std.Err. расчетное значение статистики критерия Стьюдента t для проверки гипотезы о значимости.

  5. Анализ остатков модели:

    • Тест Omnibus — про этот тест подробно написано в https://en.wikipedia.org/wiki/Omnibus_test, https://medium.com/swlh/interpreting-linear-regression-through-statsmodels-summary-4796d359035a, http://work.thaslwanter.at/Stats/html/statsModels.html.

      Расчетное значение статистики критерия Omnibus = 3.466 — по сути расчетное значение F-критерия (см. https://en.wikipedia.org/wiki/Omnibus_test).

      Prob(Omnibus) = 0.177 — показывает вероятность нормального распределения остатков (значение 1 указывает на совершенно нормальное распределение).

      Учитывая, что в дальнейшем мы проверим нормальность распределения остатков по совокупности различных тестов, в том числе с достаточно высокой мощностью, и все тесты позволят принять гипотезу о нормальном распределении — в данном случае к тесту Omnibus возникают вопросы. С этим тестом нужно разбираться отдельно.

    • Skew = 0.014 и Kurtosis = 1.587 — показатели асимметрии и эксцесса остатков свидетельствуют, что распределение остатков практически симметричное, островершинное.

    • проверка нормальности распределения остатков по критерию Харке-Бера: расчетное значение статистики критерия Jarque-Bera (JB) = 1.164 и расчетный уровень значимости Prob(JB) = 0.559. К данным результатам также возникают вопросы, особенно, если учесть, что критерий Харке-Бера является асимптотическим, расчетное значение имеет распределение хи-квадрат, поэтому данный критерий рекомендуют применять только для больших выборок (см. https://en.wikipedia.org/wiki/Jarque–Bera_test). Проверку нормальности распределения остатков модели лучше проводить с использованием набора стандартных статистических тестов python (см. далее).

  6. Проверка автокорреляции по критерию Дарбина-Уотсона: Durbin-Watson = 1.443.

    Мы не будем здесь разбирать данный критерий, так как явление автокорреляции больше характерно для данных, выражаемых в виде временных рядов. Однако, для грубой оценки считается, что при расчетном значении статистики криетрия Дарбина=Уотсона а интервале [1; 2] автокорреляция отсутствует (см.https://en.wikipedia.org/wiki/Durbin–Watson_statistic).

    Более подробно про критерий Дарбина-Уотсона — см. [1, с.659].

Прочая информация, которую можно извлечь из результатов построения модели:

  1. Covariance Type — тип ковариации, подробнее см. https://habr.com/ru/post/681218/, https://towardsdatascience.com/simple-explanation-of-statsmodel-linear-regression-model-summary-35961919868b#:~:text=Covariance type is typically nonrobust,with respect to each other.

  2. Scale — масштабный коэффициент для ковариационной матрицы (https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.RegressionResults.scale.html#statsmodels.regression.linear_model.RegressionResults.scale), равен величине Mean squared error (MSE) (cреднеквадратической ошибке), об подробнее см. далее, в разделе про ошибки аппроксимации моделей.

  3. Показатели сравнения качества различных моделей:

    • Log-Likelihood — логарифмическая функция правдоподобия, подробнее см. https://en.wikipedia.org/wiki/Likelihood_function#Log-likelihood, https://habr.com/ru/post/433804/

    • AIC — информационный критерий Акаике (Akaike information criterion), подробнее см. https://en.wikipedia.org/wiki/Akaike_information_criterion

    • BIC — информационный критерий Байеса (Bayesian information criterion), подробнее см. https://en.wikipedia.org/wiki/Bayesian_information_criterion

    В данной статье мы эти показатели рассматривать не будем, так как задача выбора одной модели из нескольких перед нами не стоит.

  4. Число обусловленности Cond. No = 96792 используется для проверки мультиколлинеарности (считается, что мультиколлинеарность есть, если значение Cond. No > 30) (см. http://work.thaslwanter.at/Stats/html/statsModels.html). В нашем случае парной регрессионной модели о мультиколлинеарности речь не идет.

Далее будем извлекать данные из стандартного набора выдачи результатов и анализировать их более подробно. Последующие этапы вовсе не обязательно проводить в полном объеме при решении задач, но здесь мы рассмотрим их подробно.

Параметры и уравнение регрессионной модели

Извлечем параметры полученной модели — как свойство params модели:

print('Параметры модели: n', result_linear_ols.params, type(result_linear_ols.params))

Имея параметры модели, можем формализовать уравнение модели Y = b0 + b1*X:

b0 = result_linear_ols.params['Intercept']
b1 = result_linear_ols.params['X']
Y_calc = lambda x: b0 + b1*x

График регрессионной модели

Для построения графиков регрессионных моделей можно воспользоваться стандартными возможностями библиотек statsmodels, seaborn, либо создать пользовательскую функцию — на усмотрение исследователя:

1. Построение графиков регрессионных моделей с использованием библиотеки statsmodels

С помощью функции statsmodels.graphics.plot_fit (https://www.statsmodels.org/stable/generated/statsmodels.graphics.regressionplots.plot_fit.html#statsmodels.graphics.regressionplots.plot_fit) — отображается график Y and Fitted vs.X (фактические и расчетные значения Y с доверительным интервалом для каждого значения Y):

fig, ax = plt.subplots(figsize=(297/INCH, 210/INCH))
fig = sm.graphics.plot_fit(
    result_linear_ols, 'X',
    vlines=True,    # это параметр отвечает за отображение доверительных интервалов для Y
    ax=ax)
ax.set_ylabel(Variable_Name_Y)
ax.set_xlabel(Variable_Name_X)
ax.set_title(Task_Project)
plt.show()

С помощью функции statsmodels.graphics.plot_regress_exog (https://www.statsmodels.org/stable/generated/statsmodels.graphics.regressionplots.plot_regress_exog.html#statsmodels.graphics.regressionplots.plot_regress_exog) — отображается область 2х2, которая содержит:

  • предыдущий график Y and Fitted vs.X;

  • график остатков Residuals versus X;

  • график Partial regression plot — график частичной регрессии, пытается показать эффект добавления другой переменной в модель, которая уже имеет одну или несколько независимых переменных (более подробно см. https://en.wikipedia.org/wiki/Partial_regression_plot);

  • график CCPR Plot (Component-Component plus Residual Plot) — еще один способ оценить влияние одной независимой переменной на переменную отклика, принимая во внимание влияние других независимых переменных (более подробно — см. https://towardsdatascience.com/calculating-price-elasticity-of-demand-statistical-modeling-with-python-6adb2fa7824d, https://www.kirenz.com/post/2021-11-14-linear-regression-diagnostics-in-python/linear-regression-diagnostics-in-python/).

fig = plt.figure(figsize=(297/INCH, 210/INCH))
sm.graphics.plot_regress_exog(result_linear_ols, 'X', fig=fig)
plt.show()

2. Построение графиков регрессионных моделей с использованием библиотеки seaborn

Воспользуемся модулем regplot библиотеки seaborn (https://seaborn.pydata.org/generated/seaborn.regplot.html). Данный модуль позволяет визуализировать различные виды регрессии:

  • линейную

  • полиномиальную

  • логистическую

  • взвешенную локальную регрессию (LOWESS — Locally Weighted Scatterplot Smoothing) (см. http://www.machinelearning.ru/wiki/index.php?title=Алгоритм_LOWESS, https://www.statsmodels.org/stable/generated/statsmodels.nonparametric.smoothers_lowess.lowess.html)

Более подробно про модуль regplot можно прочитать в статье: https://pyprog.pro/sns/sns_8_regression_models.html.

Есть более совершенный модуль lmplot (https://seaborn.pydata.org/generated/seaborn.lmplot.html), который объединяет в себе regplot и FacetGrid, но мы его здесь рассматривать не будем.

# создание рисунка (Figure) и области рисования (Axes)
fig = plt.figure(figsize=(297/INCH, 420/INCH/1.5))
ax1 = plt.subplot(2,1,1)
ax2 = plt.subplot(2,1,2)
# заголовок рисунка (Figure)
title_figure = Task_Project
fig.suptitle(title_figure, fontsize = 18)
# заголовок области рисования (Axes)
title_axes_1 = 'Линейная регрессионная модель'
ax1.set_title(title_axes_1, fontsize = 16)
# график регрессионной модели
order_mod = 1    # порядок модели
#label_legend_regr_model = 'фактические данные'
sns.regplot(
    #data=dataset_df,
    x=X, y=Y,
    #x_estimator=np.mean,
    order=order_mod,
    logistic=False,
    lowess=False,
    robust=False,
    logx=False,
    ci=95,
    scatter_kws={'s': 30, 'color': 'red'},
    line_kws={'color': 'blue'},
    #label=label_legend_regr_model,
    ax=ax1)
ax1.set_ylabel(Variable_Name_Y)
ax1.legend()
# график остатков
title_axes_2 = 'График остатков'
ax2.set_title(title_axes_2, fontsize = 16)
sns.residplot(
    #data=dataset_df,
    x=X, y=Y,
    order=order_mod,
    lowess=False,
    robust=False,
    scatter_kws={'s': 30, 'color': 'darkorange'},
    ax=ax2)
ax2.set_xlabel(Variable_Name_X)

plt.show()

3. Построение графиков регрессионных моделей с помощью пользовательской функции

# Пользовательская функция
graph_regression_plot_sns(
    X, Y,
    regression_model=Y_calc,
    Xmin=Xmin_graph, Xmax=Xmax_graph,
    Ymin=Ymin_graph, Ymax=Ymax_graph,
    title_figure=Task_Project,
    x_label=Variable_Name_X,
    y_label=Variable_Name_Y,
    label_legend_regr_model=f'линейная регрессия Y = {b0:.3f} + {b1:.4f}*X',
    s=80,
    file_name='graph/regression_plot_lin.png')

Статистический анализ регрессионной модели

1. Расчет ошибки аппроксимации (Error Metrics)

Ошибки аппроксимации (Error Metrics) позволяют получить общее представление о качестве модели, а также позволяют сравнивать между собой различные модели.

Создадим пользовательскую функцию, которая рассчитывает основные ошибки аппроксимации для заданной модели:

  • Mean squared error (MSE) или Mean squared deviation (MSD) — среднеквадратическая ошибка (https://en.wikipedia.org/wiki/Mean_squared_error):

  • Root mean square error (RMSE) или Root mean square deviation (RMSD) — квадратный корень из MSE (https://en.wikipedia.org/wiki/Root-mean-square_deviation):

  • Mean absolute error (MAE) — средняя абсолютная ошибка (https://en.wikipedia.org/wiki/Mean_absolute_error):

  • Mean squared prediction error (MSPE) — среднеквадратическая ошибка прогноза (среднеквадратическая ошибка в процентах) (https://en.wikipedia.org/wiki/Mean_squared_prediction_error):

  • Mean absolute percentage error (MAPE) — средняя абсолютная ошибка в процентах (https://en.wikipedia.org/wiki/Mean_absolute_percentage_error):

Про выбор метрики см. также https://machinelearningmastery.ru/how-to-select-the-right-evaluation-metric-for-machine-learning-models-part-2-regression-metrics-d4a1a9ba3d74/.

# Пользовательская функция
def regression_error_metrics(model, model_name=''):
    model_fit = model.fit()
    Ycalc = model_fit.predict()
    n_fit = model_fit.nobs
    Y = model.endog
    
    MSE = (1/n_fit) * np.sum((Y-Ycalc)**2)
    RMSE = sqrt(MSE)
    MAE = (1/n_fit) * np.sum(abs(Y-Ycalc))
    MSPE = (1/n_fit) *  np.sum(((Y-Ycalc)/Y)**2)
    MAPE = (1/n_fit) *  np.sum(abs((Y-Ycalc)/Y))
        
    model_error_metrics = {
        'MSE': MSE,
        'RMSE': RMSE,
        'MAE': MAE,
        'MSPE': MSPE,
        'MAPE': MAPE}
    
    result = pd.DataFrame({
        'MSE': MSE,
        'RMSE': RMSE,
        'MAE': MAE,
        'MSPE': "{:.3%}".format(MSPE),
        'MAPE': "{:.3%}".format(MAPE)},
        index=[model_name])        
        
    return model_error_metrics, result

(model_error_metrics, result) = regression_error_metrics(model_linear_ols, model_name='linear_ols')
display(result)

В литературе по прикладной статистике нет единого мнения о допустимом размере относительных ошибок аппроксимации: в одних источниках допустимой считается ошибка 5-7%, в других она может быть увеличена до 8-10%, и даже до 15%.

Вывод: модель хорошо аппроксимирует фактические данные (относительная ошибка аппроксимации MAPE = 3.405% < 10%).

2. Дисперсионный анализ регрессионной модели (ДАРМ)

ДАРМ не входит в стандартную форму выдачи результатов Regression Results, однако я решил написать здесь о нем по двум причинам:

  1. Именно анализ дисперсии регрессионной модели, разложение этой дисперсии на составляющие дает фундаментальное представление о сути регрессии, а термины, используемые при ДАРМ, применяются на последующих этапах анализа.

  2. С терминами ДАРМ в литературе по прикладной статистике имеется некоторая путаница, в разных источниках они могут именоваться по-разному (см., например, [8, с.52]), поэтому, чтобы двигаться дальше, необходимо определиться с понятиями.

При ДАРМ общую вариацию результативного признака (Y) принято разделять на две составляющие — вариация, обусловленная регрессией и вариация, обусловленная отклонениями от регрессии (остаток), при этом в разных источниках эти термины могут именоваться и обозначаться по-разному, например:

  1. Вариация, обусловленная регрессией — может называться Explained sum of squares (ESS), Sum of Squared Regression (SSR) (https://en.wikipedia.org/wiki/Explained_sum_of_squares, https://towardsdatascience.com/anova-for-regression-fdb49cf5d684), Sum of squared deviations (SSD).

  2. Вариация, обусловленная отклонениями от регрессии (остаток) — может называться Residual sum of squares (RSS), Sum of squared residuals (SSR), Squared estimate of errors, Sum of Squared Error (SSE) (https://en.wikipedia.org/wiki/Residual_sum_of_squares, https://towardsdatascience.com/anova-for-regression-fdb49cf5d684); в отчественной практике также применяется термин остаточная дисперсия.

  3. Общая (полная) вариация — может называться Total sum of squares (TSS), Sum of Squared Total (SST) (https://en.wikipedia.org/wiki/Partition_of_sums_of_squares, https://towardsdatascience.com/anova-for-regression-fdb49cf5d684).

Как видим, путаница знатная:

  • в разных источниках под SSR могут подразумеваться различные показатели;

  • легко перепутать показатели ESS и SSE;

  • в библиотеке statsmodel также есть смешение терминов: для показателя Explained sum of squares используется свойство ess, а для показателя Sum of squared (whitened) residuals — свойство ssr.

Мы будем пользоваться системой обозначений, принятой в большинстве источников — SSR (Sum of Squared Regression), SSE (Sum of Squared Error), SST (Sum of Squared Total). Стандартная таблица ДАРМ в этом случае имеет вид:

Примечания:

  1. Здесь приведена таблица ДАРМ для множественной линейной регрессионной модели (МЛРМ), в нашем случае при ПЛРМ мы имеем частный случай p=1.

  2. Показатели Fcalc-ad и Fcalc-det — расчетные значения статистики критерия Фишера при проверке адекватности модели и значимости коэффициента детерминации (об этом — см.далее).

Более подробно про дисперсионный анализ регрессионной модели — см.[4, глава 3].

Класс statsmodels.regression.linear_model.RegressionResults позволяет нам получить данные для ANOVA (см. https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.RegressionResults.html#statsmodels.regression.linear_model.RegressionResults) как свойства класса:

  1. Сумма квадратов, обусловленная регрессией / SSR (Sum of Squared Regression) — свойство ess.

  2. Сумма квадратов, обусловленная отклонением от регрессии / SSE (Sum of Squared Error) — свойство ssr.

  3. Общая (полная) сумма квадратов / SST (Sum of Squared Total) — свойство centered_tss.

  4. Кол-во наблюдений / Number of observations — свойство nobs.

  5. Число степеней свободы модели / Model degrees of freedom — равно числу переменных модели (за исключением константы, если она присутствует — свойство df_model.

  6. Среднеквадратичная ошибка модели / Mean squared error the model — сумма квадратов, объясненная регрессией, деленная на число степеней свободы регрессии — свойство mse_model.

  7. Среднеквадратичная ошибка остатков / Mean squared error of the residuals — сумма квадратов остатков, деленная на остаточные степени свободы — свойство mse_resid.

  8. Общая среднеквадратичная ошибка / Total mean squared error — общая сумма квадратов, деленная на количество наблюдений — свойство mse_total.

Также имеется модуль statsmodels.stats.anova.anova_lm, который позволяет получить результаты ДАРМ (нескольких типов — 1, 2, 3):

# тип 1
print('The type of Anova test: 1')
display(sm.stats.anova_lm(result_linear_ols, typ=1))

# тип 2
print('The type of Anova test: 2')
display(sm.stats.anova_lm(result_linear_ols, typ=2))

# тип 3
print('The type of Anova test: 3')
display(sm.stats.anova_lm(result_linear_ols, typ=3))

На мой взгляд, форма таблица результатов statsmodels.stats.anova.anova_lm не вполне удобна, поэтому сформируем ее самостоятельно, для чего создадим пользовательскую функцию ANOVA_table_regression_model:

# Пользовательская функция
def ANOVA_table_regression_model(model_fit):
    n = int(model_fit.nobs)
    p = int(model_fit.df_model)
    SSR = model_fit.ess
    SSE = model_fit.ssr
    SST = model_fit.centered_tss

    result = pd.DataFrame({
        'sources_of_variation': ('regression (SSR)', 'deviation from regression (SSE)', 'total (SST)'),
        'sum_of_squares': (SSR, SSE, SST),
        'degrees_of_freedom': (p, n-p-1, n-1)})
    result['squared_error'] = result['sum_of_squares'] / result['degrees_of_freedom']
    R2 = 1 - result.loc[1, 'sum_of_squares'] / result.loc[2, 'sum_of_squares']
    F_calc_adequacy = result.loc[2, 'squared_error'] / result.loc[1, 'squared_error']
    F_calc_determ_check = result.loc[0, 'squared_error'] / result.loc[1, 'squared_error']
    result['F-ratio'] = (F_calc_determ_check, F_calc_adequacy, '')
    
    return result

result = ANOVA_table_regression_model(result_linear_ols)
display(result)
print(f"R2 = 1 - SSE/SST = {1 - result.loc[1, 'sum_of_squares'] / result.loc[2, 'sum_of_squares']}")
print(f"F_calc_adequacy = MST / MSE = {result.loc[2, 'squared_error'] / result.loc[1, 'squared_error']}")
print(f"F_calc_determ_check = MSR / MSE = {result.loc[0, 'squared_error'] / result.loc[1, 'squared_error']}")

ДАРМ позволяет визуализировать вариацию:

fig, axes = plt.subplots(figsize=(210/INCH, 297/INCH/1.5))
axes.pie(
    (result.loc[0, 'sum_of_squares'], result.loc[1, 'sum_of_squares']), 
    labels=(result.loc[0, 'sources_of_variation'], result.loc[1, 'sources_of_variation']),
    autopct='%.1f%%',
    startangle=60)
plt.show()

На основании данных ДАРМ мы рассчитали ряд показателей (R2, Fcalc-ad и Fcalc-det), которые будут использоваться в дальнейшем.

3. Анализ остатков (проверка нормальности распределения остатков и гипотезы о равенстве нулю среднего значения остатков)

Проверка нормальности распределения остатков — один их важнейших этапов анализа регрессионной модели. Требование нормальности распределения остатков не требуется для отыскания параметров модели, но необходимо в дальнейшем для проверки статистических гипотез с использованием критериев Фишера и Стьюдента (проверка адекватности модели, значимости коэффициента детерминации, значимости коэффициентов регрессии) и построения доверительных интервалов [5, с.122].

Остатки регрессионной модели:

print('Остатки регрессионной модели:n', result_linear_ols.resid, type(result_linear_ols.resid))
res_Y = np.array(result_linear_ols.resid)

statsmodels может выдавать различные преобразованные виды остатков (см. https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.RegressionResults.resid_pearson.html, https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.RegressionResults.wresid.html).

График остатков:

# Пользовательская функция
graph_scatterplot_sns(
    X, res_Y,
    Xmin=Xmin_graph, Xmax=Xmax_graph,
    Ymin=-3.0, Ymax=3.0,
    color='red',
    #title_figure=Task_Project,
    title_axes='Остатки линейной регрессионной модели', title_axes_fontsize=18,
    x_label=Variable_Name_X,
    y_label='ΔY = Y - Ycalc',
    s=75,
    file_name='graph/residuals_plot_sns.png')

Проверка нормальности распределения остатков:

# Пользовательская функция
graph_hist_boxplot_probplot_sns(
    data=res_Y,
    data_min=-2.5, data_max=2.5,
    graph_inclusion='bp',
    data_label='ΔY = Y - Ycalc',
    #title_figure=Task_Project,
    title_axes='Остатки линейной регрессионной модели', title_axes_fontsize=16,
    file_name='graph/residuals_hist_boxplot_probplot_sns.png')    

norm_distr_check(res_Y)

Вывод: большинство статистических тестов позволяют принять гипотезу о нормальности распределения остатков.

Проверка гипотезы о равенстве нулю среднего значения остатков — так как остатки имеют нормальное распределение, воспользуемся критерием Стьюдента (функция scipy.stats.ttest_1samp, https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.ttest_ind.html):

sps.ttest_1samp(res_Y, popmean=0)

Вывод: так как расчетный уровень значимости превышает заданный (0.05), то нулевая гипотеза о равенстве нулю остатков ПРИНИМАЕТСЯ.

4. Проверка адекватности модели

Суть проверки адекватности регрессионной модели заключается в сравнении полной дисперсии MST и остаточной дисперсии MSE — проверяется гипотеза о равенстве этих дисперсий по критерию Фишера. Если дисперсии различаются значимо, то модель считается адекватной. Более подробно про проверку адекватности регрессионной — см.[1, с.658], [2, с.49], [4, с.154].

Для проверки адекватности регрессионной модели создадим пользовательскую функцию regression_model_adequacy_check:

def regression_model_adequacy_check(
    model_fit,
    p_level: float=0.95,
    model_name=''):
    
    n = int(model_fit.nobs)
    p = int(model_fit.df_model)    # Число степеней свободы регрессии, равно числу переменных модели (за исключением константы, если она присутствует)
    
    SST = model_fit.centered_tss    # SST (Sum of Squared Total)
    dfT = n-1
    MST = SST / dfT

    SSE = model_fit.ssr    # SSE (Sum of Squared Error)
    dfE = n - p - 1
    MSE = SSE / dfE
    
    F_calc = MST / MSE
    F_table = sci.stats.f.ppf(p_level, dfT, dfE, loc=0, scale=1)
    a_calc = 1 - sci.stats.f.cdf(F_calc, dfT, dfE, loc=0, scale=1)
    conclusion_model_adequacy_check = 'adequacy' if F_calc >= F_table else 'adequacy'
    
    # формируем результат            
    result = pd.DataFrame({
        'SST': (SST),
        'SSE': (SSE),
        'dfT': (dfT),
        'dfE': (dfE),
        'MST': (MST),
        'MSE': (MSE),
        'p_level': (p_level),
        'a_level': (a_level),
        'F_calc': (F_calc),
        'F_table': (F_table),
        'F_calc >= F_table': (F_calc >= F_table),
        'a_calc': (a_calc),
        'a_calc <= a_level': (a_calc <= a_level),
        'adequacy_check': (conclusion_model_adequacy_check),
        },
        index=[model_name]
        )
    
    return result

regression_model_adequacy_check(result_linear_ols, p_level=0.95, model_name='linear_ols')

Вывод: модель является АДЕКВАТНОЙ.

5. Коэффициент детерминации и проверка его значимости

Различают несколько видов коэффициента детерминации:

  1. Собственно обычный коэффициент детерминации:

Его значение может быть получено как свойство rsquared модели.

  1. Скорректированный (adjusted) коэффициент детерминации — используется для того, чтобы была возможность сравнивать модели с разным числом признаков так, чтобы число регрессоров (признаков) не влияло на статистику R2, при его расчете используются несмещённые оценки дисперсий:

Его значение может быть получено как свойство rsquared_adj модели.

  1. Обобщённый (extended) коэффициент детерминации — используется для сравнения моделей регрессии со свободным членом и без него, а также для сравнения между собой регрессий, построенных с помощью различных методов: МНК, обобщённого метода наименьших квадратов (ОМНК), условного метода наименьших квадратов (УМНК), обобщённо-условного метода наименьших квадратов (ОУМНК). В данном разборе ПЛРМ рассматривать этот коэффициент мы не будем.

Более подробно с теорией вопроса можно ознакомиться, например: http://www.machinelearning.ru/wiki/index.php?title=Коэффициент_детерминации), а также в [7].

Значения коэффициента детерминации и скорректированного коэффициента детерминации, извлеченные с помощью свойств rsquared и rsquared_adj модели.

print('R2 =', result_linear_ols.rsquared)
print('R2_adj =', result_linear_ols.rsquared_adj)

Значимость коэффициента детерминации можно проверить по критерию Фишера [3, с.201-203; 8, с.83].

Расчетное значение статистики критерия Фишера может быть получено с помощью свойства fvalue модели:

print(f"result_linear_ols.fvalue = {result_linear_ols.fvalue}")

Расчетный уровень значимости при проверке гипотезы по критерию Фишера может быть получено с помощью свойства f_pvalue модели:

print(f"result_linear_ols.f_pvalue = {result_linear_ols.f_pvalue}")

Можно рассчитать уровень значимости самостоятельно (так сказать, для лучшего понимания и общей демонстрации возможностей) — для этого воспользуемся библиотекой scipy, модулем распределения Фишера scipy.stats.f, свойством cdf (функция распределения):

df1 = int(result_linear_ols.df_model)
df2 = int(result_linear_ols.nobs - result_linear_ols.df_model - 1)
F_calc = result_linear_ols.fvalue
a_calc = 1 - sci.stats.f.cdf(F_calc, df1, df2, loc=0, scale=1)
print(a_calc)

Как видим, результаты совпадают.

Табличное значение статистики критерия Фишера получить с помощью Regression Results нельзя. Рассчитаем его самостоятельно — для этого воспользуемся библиотекой scipy, модулем распределения Стьюдента scipy.stats.f, свойством ppf (процентные точки):

F_table = sci.stats.f.ppf(p_level, df1, df2, loc=0, scale=1)
print(F_table)

Для удобства создадим пользовательскую функцию determination_coef_check для проверки значимости коэффициента детерминации, которая объединяет все вышеперечисленные расчеты:

# Пользовательская функция
def determination_coef_check(
    model_fit,
    p_level: float=0.95):
    
    a_level = 1 - p_level
    
    R2 = model_fit.rsquared
    R2_adj = model_fit.rsquared_adj
    n = model_fit.nobs    # объем выборки
    p = model_fit.df_model    # Model degrees of freedom. The number of regressors p. Does not include the constant if one is present.
    
    F_calc = R2 / (1 - R2) * (n-p-1)/p
    df1 = int(p)
    df2 = int(n-p-1)
    F_table = sci.stats.f.ppf(p_level, df1, df2, loc=0, scale=1)
    a_calc = 1 - sci.stats.f.cdf(F_calc, df1, df2, loc=0, scale=1)
    conclusion_determ_coef_sign = 'significance' if F_calc >= F_table else 'not significance'
        
    # формируем результат            
    result = pd.DataFrame({
        'notation': ('R2'),
        'coef_value (R)': (sqrt(R2)),
        'coef_value_squared (R2)': (R2),
        'p_level': (p_level),
        'a_level': (a_level),
        'F_calc': (F_calc),
        'df1': (df1),
        'df2': (df2),
        'F_table': (F_table),
        'F_calc >= F_table': (F_calc >= F_table),
        'a_calc': (a_calc),
        'a_calc <= a_level': (a_calc <= a_level),
        'significance_check': (conclusion_determ_coef_sign),
        'conf_int_low': (''),
        'conf_int_high': ('')
        },
        index=['Coef. of determination'])
    return result

determination_coef_check(
    result_linear_ols,
    p_level=0.95)

Вывод: коэффициент детерминации ЗНАЧИМ.

6. Коэффициенты регрессии и проверка их значимости

Ранее мы уже извлекли коэффициенты регрессии как параметры модели b0 и b1 (как свойство params модели). Также можно получить их значения, как свойство bse модели:

print(b0, b1)
print(result_linear_ols.bse, type(result_linear_ols.bse))

Значимость коэффициентов регрессии можно проверить по критерию Стьюдента [3, с.203-212; 8, с.78].

Расчетные значения статистики критерия Стьюдента могут быть получены с помощью свойства tvalues модели:

print(f"result_linear_ols.tvalues = n{result_linear_ols.tvalues}")

Расчетные значения уровня значимости при проверке гипотезы по критерию Стьюдента могут быть получены с помощью свойства pvalues модели:

print(f"result_linear_ols.pvalues = n{result_linear_ols.pvalues}")

Доверительные интервалы для коэффициентов регрессии могут быть получены с помощью свойства conf_int модели:

print(result_linear_ols.conf_int(), 'n')

Можно рассчитать уровень значимости самостоятельно (как ранее для критерия Фишера — для лучшего понимания и общей демонстрации возможностей) — для этого воспользуемся библиотекой scipy, модулем распределения Стьюдента scipy.stats.t, свойством cdf (функция распределения):

t_calc = result_linear_ols.tvalues
df = int(result_linear_ols.nobs - result_linear_ols.df_model - 1)
a_calc = 2*(1-sci.stats.t.cdf(abs(t_calc), df, loc=0, scale=1))
print(a_calc)

Как видим, результаты совпадают.

Табличные значения статистики критерия Стьюдента получить с помощью Regression Results нельзя. Рассчитаем их самостоятельно — для этого воспользуемся библиотекой scipy, модулем распределения Стьюдента scipy.stats.t, свойством ppf (процентные точки):

t_table = sci.stats.t.ppf((1 + p_level)/2 , df)
print(t_table)

Для удобства создадим пользовательскую функцию regression_coef_check для проверки значимости коэффициентов регрессии, которая объединяет все вышеперечисленные расчеты:

def regression_coef_check(
    model_fit,
    notation_coef: list='',
    p_level: float=0.95):
    
    a_level = 1 - p_level
    
    # параметры модели (коэффициенты регрессии)
    model_params = model_fit.params
    # стандартные ошибки коэффициентов регрессии
    model_bse = model_fit.bse
    # проверка гипотезы о значимости регрессии
    t_calc = abs(model_params) / model_bse
    n = model_fit.nobs    # объем выборки
    p = model_fit.df_model    # Model degrees of freedom. The number of regressors p. Does not include the constant if one is present.
    df = int(n - p - 1)
    t_table = sci.stats.t.ppf((1 + p_level)/2 , df)
    a_calc = 2*(1-sci.stats.t.cdf(t_calc, df, loc=0, scale=1))
    conclusion_ = ['significance' if elem else 'not significance' for elem in (t_calc >= t_table).values]
        
    # доверительный интервал коэффициента регрессии
    conf_int_low = model_params - t_table*model_bse
    conf_int_high = model_params + t_table*model_bse
    
    # формируем результат            
    result = pd.DataFrame({
        'notation': (notation_coef),
        'coef_value': (model_params),
        'std_err': (model_bse),
        'p_level': (p_level),
        'a_level': (a_level),
        't_calc': (t_calc),
        'df': (df),
        't_table': (t_table),
        't_calc >= t_table': (t_calc >= t_table),
        'a_calc': (a_calc),
        'a_calc <= a_level': (a_calc <= a_level),
        'significance_check': (conclusion_),
        'conf_int_low': (conf_int_low),
        'conf_int_high': (conf_int_high),
        })
    
    return result

regression_coef_check(
    result_linear_ols,
    notation_coef=['b0', 'b1'],
    p_level=0.95)

Вывод: коэффициенты регрессии b0 и b1 ЗНАЧИМЫ.

7. Проверка гетероскедастичности

Для проверка гетероскедастичности statsmodels предлагает нам следующие инструменты:

  • тест Голдфелда-Квандта (https://www.statsmodels.org/stable/generated/statsmodels.stats.diagnostic.het_goldfeldquandt.html#statsmodels.stats.diagnostic.het_goldfeldquandt) — теорию см. [8, с.178], также https://ru.wikipedia.org/wiki/Тест_Голдфелда_—_Куандта.

  • тест Бриша-Пэгана (Breush-Pagan test) (https://www.statsmodels.org/stable/generated/statsmodels.stats.diagnostic.het_breuschpagan.html#statsmodels.stats.diagnostic.het_breuschpagan) — теорию см.[8, с.179], также https://en.wikipedia.org/wiki/Breusch–Pagan_test.

  • тест Уайта (White test) (https://www.statsmodels.org/stable/generated/statsmodels.stats.diagnostic.het_white.html#statsmodels.stats.diagnostic.het_white) — теорию см.[8, с.177], а также https://ru.wikipedia.org/wiki/Тест_Уайта.

    Тест Голдфелда-Квандта (Goldfeld–Quandt test)

# тест Голдфелда-Квандта (Goldfeld–Quandt test)
test = sms.het_goldfeldquandt(result_linear_ols.resid, result_linear_ols.model.exog)
test_result = lzip(['F_calc', 'p_calc'], test)    # распаковка результатов теста
# расчетное значение статистики F-критерия
F_calc_tuple = test_result[0]
F_calc = F_calc_tuple[1]
print(f"Расчетное значение статистики F-критерия: F_calc = {round(F_calc, DecPlace)}")
# расчетный уровень значимости
p_calc_tuple = test_result[1]
p_calc = p_calc_tuple[1]
print(f"Расчетное значение доверительной вероятности: p_calc = {round(p_calc, DecPlace)}")
#a_calc = 1 - p_value
#print(f"Расчетное значение уровня значимости: a_calc = 1 - p_value = {round(a_calc, DecPlace)}")
# вывод
if p_calc < a_level:
    conclusion_GQ_test = f"Так как p_calc = {round(p_calc, DecPlace)} < a_level = {round(a_level, DecPlace)}" + 
        ", то дисперсии в подвыборках отличаются значимо, т.е. гипотеза о наличии гетероскедастичности ПРИНИМАЕТСЯ"
else:
    conclusion_GQ_test = f"Так как p_calc = {round(p_calc, DecPlace)} >= a_level = {round(a_level, DecPlace)}" + 
        ", то дисперсии в подвыборках отличаются незначимо, т.е. гипотеза о наличии гетероскедастичности ОТВЕРГАЕТСЯ"
print(conclusion_GQ_test)

Для удобства создадим пользовательскую функцию Goldfeld_Quandt_test:

def Goldfeld_Quandt_test(
    model_fit,
    p_level: float=0.95,
    model_name=''):
    
    a_level = 1 - p_level
    
    # реализация теста
    test = sms.het_goldfeldquandt(model_fit.resid, model_fit.model.exog)
    test_result = lzip(['F_statistic', 'p_calc'], test)    # распаковка результатов теста
    # расчетное значение статистики F-критерия
    F_calc_tuple = test_result[0]
    F_statistic = F_calc_tuple[1]
    # расчетный уровень значимости
    p_calc_tuple = test_result[1]
    p_calc = p_calc_tuple[1]
    # вывод
    conclusion_test = 'heteroscedasticity' if p_calc < a_level else 'not heteroscedasticity'
    
    result = pd.DataFrame({
        'test': ('Goldfeld–Quandt test'),
        'p_level': (p_level),
        'a_level': (a_level),
        'F_statistic': (F_statistic),
        'p_calc': (p_calc),
        'p_calc < a_level': (p_calc < a_level),
        'heteroscedasticity_check': (conclusion_test)
        },
        index=[model_name])
    
    return result

Goldfeld_Quandt_test(result_linear_ols, p_level=0.95, model_name='linear_ols')

Тест Бриша-Пэгана (Breush-Pagan test)

# тест Бриша-Пэгана (Breush-Pagan test)
name = ["Lagrange multiplier statistic", "p-value", "f-value", "f p-value"]
test = sms.het_breuschpagan(result_linear_ols.resid, result_linear_ols.model.exog)
lzip(name, test)

Для удобства создадим пользовательскую функцию Breush_Pagan_test:

def Breush_Pagan_test(
    model_fit,
    p_level: float=0.95,
    model_name=''):
    
    a_level = 1 - p_level
    
    # реализация теста
    test = sms.het_breuschpagan(model_fit.resid, model_fit.model.exog)
    name = ['Lagrange_multiplier_statistic', 'p_calc_LM', 'F_statistic', 'p_calc']
    test_result = lzip(name, test)    # распаковка результатов теста
    # расчетное значение статистики теста множителей Лагранжа
    LM_calc_tuple = test_result[0]
    Lagrange_multiplier_statistic = LM_calc_tuple[1]
    # расчетный уровень значимости статистики теста множителей Лагранжа
    p_calc_LM_tuple = test_result[1]
    p_calc_LM = p_calc_LM_tuple[1]
    # расчетное значение F-статистики гипотезы о том, что дисперсия ошибки не зависит от x
    F_calc_tuple = test_result[2]
    F_statistic = F_calc_tuple[1]
    # расчетный уровень значимости F-статистики
    p_calc_tuple = test_result[3]
    p_calc = p_calc_tuple[1]
    # вывод
    conclusion_test = 'heteroscedasticity' if p_calc < a_level else 'not heteroscedasticity'

    # вывод
    conclusion_test = 'heteroscedasticity' if p_calc < a_level else 'not heteroscedasticity'
    
    result = pd.DataFrame({
        'test': ('Breush-Pagan test'),
        'p_level': (p_level),
        'a_level': (a_level),
        'Lagrange_multiplier_statistic': (Lagrange_multiplier_statistic),
        'p_calc_LM': (p_calc_LM),
        'p_calc_LM < a_level': (p_calc_LM < a_level),
        'F_statistic': (F_statistic),
        'p_calc': (p_calc),
        'p_calc < a_level': (p_calc < a_level),
        'heteroscedasticity_check': (conclusion_test)
        },
        index=[model_name])
    
    return result

Breush_Pagan_test(result_linear_ols, p_level=0.95, model_name='linear_ols')

Тест Уайта (White test)

# тест Уайта (White test)
name = ["Lagrange multiplier statistic", "p-value", "f-value", "f p-value"]
test = sms.het_white(result_linear_ols.resid, result_linear_ols.model.exog)
lzip(name, test)

Для удобства создадим пользовательскую функцию White_test:

def White_test(
    model_fit,
    p_level: float=0.95,
    model_name=''):
    
    a_level = 1 - p_level
    
    # реализация теста
    test = sms.het_white(model_fit.resid, model_fit.model.exog)
    name = ['Lagrange_multiplier_statistic', 'p_calc_LM', 'F_statistic', 'p_calc']
    test_result = lzip(name, test)    # распаковка результатов теста
    # расчетное значение статистики теста множителей Лагранжа
    LM_calc_tuple = test_result[0]
    Lagrange_multiplier_statistic = LM_calc_tuple[1]
    # расчетный уровень значимости статистики теста множителей Лагранжа
    p_calc_LM_tuple = test_result[1]
    p_calc_LM = p_calc_LM_tuple[1]
    # расчетное значение F-статистики гипотезы о том, что дисперсия ошибки не зависит от x
    F_calc_tuple = test_result[2]
    F_statistic = F_calc_tuple[1]
    # расчетный уровень значимости F-статистики
    p_calc_tuple = test_result[3]
    p_calc = p_calc_tuple[1]
    # вывод
    conclusion_test = 'heteroscedasticity' if p_calc < a_level else 'not heteroscedasticity'

    # вывод
    conclusion_test = 'heteroscedasticity' if p_calc < a_level else 'not heteroscedasticity'
    
    result = pd.DataFrame({
        'test': ('White test'),
        'p_level': (p_level),
        'a_level': (a_level),
        'Lagrange_multiplier_statistic': (Lagrange_multiplier_statistic),
        'p_calc_LM': (p_calc_LM),
        'p_calc_LM < a_level': (p_calc_LM < a_level),
        'F_statistic': (F_statistic),
        'p_calc': (p_calc),
        'p_calc < a_level': (p_calc < a_level),
        'heteroscedasticity_check': (conclusion_test)
        },
        index=[model_name])
    
    return result

White_test(result_linear_ols, p_level=0.95, model_name='linear_ols')

Объединим результаты всех тестов гетероскедастичность в один DataFrame:

Goldfeld_Quandt_test_df = Goldfeld_Quandt_test(result_linear_ols, p_level=0.95, model_name='linear_ols')
Breush_Pagan_test_df = Breush_Pagan_test(result_linear_ols, p_level=0.95, model_name='linear_ols')
White_test_df = White_test(result_linear_ols, p_level=0.95, model_name='linear_ols')

heteroscedasticity_tests_df = pd.concat([Breush_Pagan_test_df, White_test_df, Goldfeld_Quandt_test_df])
display(heteroscedasticity_tests_df)

Выводы

Итак, мы провели статистический анализ регрессионной модели и установили:

  • исходные данные имеют нормальное распределение;

  • между переменными имеется весьма сильная корреляционная связь;

  • регрессионная модель хорошо аппроксимирует фактические данные;

  • остатки модели имеют нормальное распределение;

  • регрессионная модель адекватна по критерию Фишера;

  • коэффициент детерминации значим по критеию Фишера;

  • коэффициенты регрессии значимы по критерию Стьюдента;

  • гетероскедастичность отсутствует.

Применительно к рассматриваемой задаче выполнять проверку автокорреляции не имеет особого смысла из-за особенностей исходных данных (результаты замеров прочности бетона на разных участках здания).

Про статистический анализ регрессионных моделей с помощью statsmodels— см. еще https://www.statsmodels.org/stable/examples/notebooks/generated/regression_diagnostics.html.

Доверительные интервалы регрессионной модели

Для регрессионных моделей определяют доверительные интервалы двух видов [3, с.184-192; 4, с.172; 8, с.205-209]:

  1. Доверительный интервал средних значений переменной Y.

  2. Доверительный интервал индивидуальных значений переменной Y.

При этом размер доверительного интервала для индивидуальных значений больше, чем для средних значений.

Доверительные интервалы регрессионных моделей (ДИРМ) могут быть найдены разными способами:

  • непосредственно путем расчетов по формулам (см., например, https://habr.com/ru/post/558158/);

  • с использованием инструментария библиотеки statsmodels (см., например, https://www.stackfinder.ru/questions/17559408/confidence-and-prediction-intervals-with-statsmodels).

Разбререм более подробно способ с использованием библиотеки statsmodels. Прежде всего, с помощью свойства summary_table класса statsmodels.stats.outliers_influence.OLSInfluence (https://www.statsmodels.org/stable/generated/statsmodels.stats.outliers_influence.OLSInfluence.html?highlight=olsinfluence) мы можем получить таблицу данных, содержащую необходимую нам информацию:

  • Dep Var Population — фактические значения переменной Y;

  • Predicted Value — предсказанные значения переменной Y по по регрессионной модели;

  • Std Error Mean Predict — среднеквадратическая ошибка предсказанного среднего;

  • Mean ci 95% low и Mean ci 95% upp — границы доверительного интервала средних значений переменной Y;

  • Predict ci 95% low и Predict ci 95% upp — границы доверительного интервала индивидуальных значений переменной Y;

  • Residual — остатки регрессионной модели;

  • Std Error Residual — среднеквадратическая ошибка остатков;

  • Student Residual — стьюдентизированные остатки (подробнее см. http://statistica.ru/glossary/general/studentizirovannie-ostatki/);

  • Cook’s D — Расстояние Кука (Cook’s distance) — оценивает эффект от удаления одного (рассматриваемого) наблюдения; наблюдение считается выбросом, если Di > 4/n (более подробно — см.https://translated.turbopages.org/proxy_u/en-ru.ru.f584ceb5-63296427-aded8f31-74722d776562/https/en.wikipedia.org/wiki/Cook’s_distance, http://www.machinelearning.ru/wiki/index.php?title=Расстояние_Кука).

from statsmodels.stats.outliers_influence import summary_table
st, data, ss2 = summary_table(result_linear_ols, alpha=0.05)
print(st, 'n', type(st))

В нашем случае критическое значение расстояния Кука равно:

print(f'D_crit = 4/n = {4/result_linear_ols.nobs}')

то есть выбросов, смещающих оценки коэффициентов регрессии, не наблюдается.

Мы получили данные как класс statsmodels.iolib.table.SimpleTable. Свойство data преобразует данные в список. Далее для удобства работы преобразуем данные в DataFrame:

  st_data_df = pd.DataFrame(st.data)

Будем использовать данный DataFrame в дальнейшем, несколько преобразуем его:

  • изменим наименование столбцов (с цифр на названия показателей из таблицы summary_table)

  • удалим строки с текстовыми значениями

  • изменим индекс

  • добавим новый столбец — значения переменной X

  • отсортируем по возрастанию значений переменной X (это необходимо, чтобы графики границ доверительных интервалов выглядели как линии)

st_df = st_data_df.copy()
# изменим наименования столбцов
str = st_df.iloc[0,0:] + ' ' + st_df.iloc[1,0:]
st_df = st_df.rename(str, axis='columns')
# удалим строки 0, 1
st_df = st_df.drop([0,1])
# изменим индекс
st_df = st_df.set_index(np.arange(0, result_linear_ols.nobs))
# добавим новый столбец - значения переменной X
st_df.insert(1, 'X', X)
# отсортируем по возрастанию значений переменной X
st_df = st_df.sort_values(by='X')

display(st_df)

С помощью полученных данных мы можем построить график регрессионной модели с доверительными интервалами:

# создание рисунка (Figure) и области рисования (Axes)
fig, axes = plt.subplots(figsize=(297/INCH, 210/INCH))
# заголовок рисунка (Figure)
title_figure = Task_Project
fig.suptitle(title_figure, fontsize = 16)
# заголовок области рисования (Axes)
title_axes = 'Линейная регрессионная модель'
axes.set_title(title_axes, fontsize = 14)
# фактические данные
sns.scatterplot(
    x=st_df['X'], y=st_df['Dep Var Population'],
    label='фактические данные',
    s=50,
    color='red',
    ax=axes)
# график регрессионной модели
label_legend_regr_model=f'линейная регрессия Y = {b0:.3f} + {b1:.4f}*X'
sns.lineplot(
    x=st_df['X'], y=st_df['Predicted Value'],
    label=label_legend_regr_model,
    color='blue',
    ax=axes)
# доверительный интервал средних значений переменной Y
Mean_ci_low = st_df['Mean ci 95% low']
plt.plot(
    st_df['X'], Mean_ci_low,
    color='magenta', linestyle='--', linewidth=1,
    label='доверительный интервал средних значений Y')
Mean_ci_upp = st_df['Mean ci 95% upp']
plt.plot(
    st_df['X'], Mean_ci_upp,
    color='magenta', linestyle='--', linewidth=1)
# доверительный интервал индивидуальных значений переменной Y
Predict_ci_low = st_df['Predict ci 95% low']
plt.plot(
    st_df['X'], Predict_ci_low,
    color='orange', linestyle='-.', linewidth=2,
    label='доверительный интервал индивидуальных значений Y')
Predict_ci_upp = st_df['Predict ci 95% upp']
plt.plot(
    st_df['X'], Predict_ci_upp,
    color='orange', linestyle='-.', linewidth=2)

axes.set_xlabel(Variable_Name_X)
axes.set_ylabel(Variable_Name_Y)
axes.legend(prop={'size': 12})
plt.show()

Однако, мы получили данные о границах доверительных интервалов регрессионной модели только в пределах области фактических значений переменной X. Как быть, если мы хотим распространить прогноз за пределы этой области?

Прогнозирование

Под прогнозированием мы в данном случае будем понимать определение значений переменной Y и доверительных интервалов для ее средних и индивидуальных значений при заданном X. По сути, нам предстоит построить аналог рассмотренной выше таблицы summary_table, только с другими значениями X, причем эти значения могут выходить за пределы тех значений, которые использовались нами для построения регрессии.

Методика расчета доверительных интервалов регрессионных моделей разобрана в статье «Python, корреляция и регрессия: часть 4» (https://habr.com/ru/post/558158/), всем рекомендую ознакомиться.

Найти прогнозные значения Y не представляет труда, так как ранее мы уже формализовали модель в виде лямбда-функции, а вот для построения доверительных интервалов придется выполнить расчеты по формулам. Для этого создадим пользовательскую функцию regression_pair_predict, которая в случае парной регрессии (pair regression) для заданного значения X возвращает:

  • прогнозируемое по регрессионной модели значение y_calc

  • доверительный интервал [y_calc_mean_ci_low, y_calc_mean_ci_upp] средних значений переменной Y

  • доверительный интервал [y_calc_predict_ci_low, y_calc_predict_ci_upp] индивидуальных значений переменной Y

Алгоритм расчета доверительных интервалов для множественной регрессии (multiple regression) отличается и в данном обзоре не рассматривается (рассмотрим в дальнейшем).

Про прогнозирование с помощью регрессионных моделей — см.также:

  • https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.RegressionResults.predict.html?highlight=predict#statsmodels.regression.linear_model.RegressionResults.predict

  • How to Make Predictions Using Regression Model in Statsmodels

  • https://www.statsmodels.org/stable/examples/notebooks/generated/predict.html

def regression_pair_predict(
    x_in,
    model_fit,
    regression_model,
    p_level: float=0.95):
    
    a_level = 1 - p_level
    
    X = pd.DataFrame(model_fit.model.exog)[1].values    # найти лучшее решение
    Y = model_fit.model.endog
    
    # вспомогательные величины
    n = int(result_linear_ols.nobs)
    SSE = model_fit.ssr    # SSE (Sum of Squared Error)
    dfE = n - p - 1
    MSE = SSE / dfE    # остаточная дисперсия
    
    Xmean = np.mean(X)
    SST_X = np.sum([(X[i] - Xmean)**2 for i in range(0, n)])
    
    t_table = sci.stats.t.ppf((1 + p_level)/2 , dfE)
    S2_y_calc_mean = MSE * (1/n + (x_in - Xmean)**2 / SST_X)
    S2_y_calc_predict = MSE * (1 + 1/n + (x_in - Xmean)**2 / SST_X)
        
    # прогнозируемое значение переменной Y
    y_calc=regression_model(x_in)
    # доверительный интервал средних значений переменной Y
    y_calc_mean_ci_low = y_calc - t_table*sqrt(S2_y_calc_mean)
    y_calc_mean_ci_upp = y_calc + t_table*sqrt(S2_y_calc_mean)
    # доверительный интервал индивидуальных значений переменной Y
    y_calc_predict_ci_low = y_calc - t_table*sqrt(S2_y_calc_predict)
    y_calc_predict_ci_upp = y_calc + t_table*sqrt(S2_y_calc_predict)
    
    result = y_calc, y_calc_mean_ci_low, y_calc_mean_ci_upp, y_calc_predict_ci_low, y_calc_predict_ci_upp
    
    return result

Сравним результаты расчета доверительных интервалов разными способами — с использованием функции regression_pair_predict и средствами statsmodels, для этого сформируем DaraFrame с новыми данными:

regression_pair_predict_df = pd.DataFrame(
    [regression_pair_predict(elem, result_linear_ols, regression_model=Y_calc) for elem in st_df['X'].values],
    columns=['y_calc', 'y_calc_mean_ci_low', 'y_calc_mean_ci_upp', 'y_calc_predict_ci_low', 'y_calc_predict_ci_upp'])
regression_pair_predict_df.insert(0, 'X', st_df['X'].values)
display(regression_pair_predict_df)

Видим, что результаты расчетов идентичны, следовательно мы можем использовать функцию regression_pair_predict для прогнозирования.

def graph_regression_pair_predict_plot_sns(
    model_fit,
    regression_model_in,
    Xmin=None, Xmax=None, Nx=10,
    Ymin_graph=None, Ymax_graph=None,
    title_figure=None, title_figure_fontsize=18,
    title_axes=None, title_axes_fontsize=16,
    x_label=None,
    y_label=None,
    label_fontsize=14, tick_fontsize=12, 
    label_legend_regr_model='', label_legend_fontsize=12,
    s=50, linewidth_regr_model=2,
    graph_size=(297/INCH, 210/INCH),
    result_output=True,
    file_name=None):
    
    # фактические данные
    X = pd.DataFrame(model_fit.model.exog)[1].values    # найти лучшее решение
    Y = model_fit.model.endog
    X = np.array(X)
    Y = np.array(Y)
    
    # границы
    if not(Xmin) and not(Xmax):
        Xmin=min(X)
        Xmax=max(X)
        Xmin_graph=min(X)*0.99
        Xmax_graph=max(X)*1.01
    else:
        Xmin_graph=Xmin
        Xmax_graph=Xmax
    
    if not(Ymin_graph) and not(Ymax_graph):
        Ymin_graph=min(Y)*0.99
        Ymax_graph=max(Y)*1.01       
    
    # формируем DataFrame данных
    Xcalc = np.linspace(Xmin, Xmax, num=Nx)
    Ycalc = regression_model_in(Xcalc)
    
    result_df = pd.DataFrame(
        [regression_pair_predict(elem, model_fit, regression_model=regression_model_in) for elem in Xcalc],
        columns=['y_calc', 'y_calc_mean_ci_low', 'y_calc_mean_ci_upp', 'y_calc_predict_ci_low', 'y_calc_predict_ci_upp'])
    result_df.insert(0, 'x_calc', Xcalc)
            
    # заголовки графика
    fig, axes = plt.subplots(figsize=graph_size)
    fig.suptitle(title_figure, fontsize = title_figure_fontsize)
    axes.set_title(title_axes, fontsize = title_axes_fontsize)
    
    # фактические данные
    sns.scatterplot(
        x=X, y=Y,
        label='фактические данные',
        s=s,
        color='red',
        ax=axes)
    
    # график регрессионной модели
    sns.lineplot(
        x=Xcalc, y=Ycalc,
        color='blue',
        linewidth=linewidth_regr_model,
        legend=True,
        label=label_legend_regr_model,
        ax=axes)
    
    # доверительный интервал средних значений переменной Y
    Mean_ci_low = result_df['y_calc_mean_ci_low']
    plt.plot(
        result_df['x_calc'], Mean_ci_low,
        color='magenta', linestyle='--', linewidth=1,
        label='доверительный интервал средних значений Y')
    
    Mean_ci_upp = result_df['y_calc_mean_ci_upp']
    plt.plot(
        result_df['x_calc'], Mean_ci_upp,
        color='magenta', linestyle='--', linewidth=1)
    
    # доверительный интервал индивидуальных значений переменной Y
    Predict_ci_low = result_df['y_calc_predict_ci_low']
    plt.plot(
        result_df['x_calc'], Predict_ci_low,
        color='orange', linestyle='-.', linewidth=2,
        label='доверительный интервал индивидуальных значений Y')
    
    Predict_ci_upp = result_df['y_calc_predict_ci_upp']
    plt.plot(
        result_df['x_calc'], Predict_ci_upp,
        color='orange', linestyle='-.', linewidth=2)
    
        
    axes.set_xlim(Xmin_graph, Xmax_graph)
    axes.set_ylim(Ymin_graph, Ymax_graph)        
    axes.set_xlabel(x_label, fontsize = label_fontsize)
    axes.set_ylabel(y_label, fontsize = label_fontsize)
    axes.tick_params(labelsize = tick_fontsize)
    #axes.tick_params(labelsize = tick_fontsize)
    axes.legend(prop={'size': label_legend_fontsize})
        
    plt.show()
    if file_name:
        fig.savefig(file_name, orientation = "portrait", dpi = 300)
        
    if result_output:
        return result_df
    else:
        return

graph_regression_pair_predict_plot_sns(
    model_fit=result_linear_ols,
    regression_model_in=Y_calc,
    Xmin=Xmin_graph-300, Xmax=Xmax_graph+200, Nx=25,
    Ymin_graph=Ymin_graph-5, Ymax_graph=Ymax_graph+5,
    title_figure=Task_Project, title_figure_fontsize=16,
    title_axes='Линейная регрессионная модель', title_axes_fontsize=14,
    x_label=Variable_Name_X,
    y_label=Variable_Name_Y,
    label_legend_regr_model=f'линейная регрессия Y = {b0:.3f} + {b1:.4f}*X',
    s=50,
    result_output=True,
    file_name='graph/regression_plot_lin.png')

Выводы и рекомендации

Исследована зависимость показаний ультразвукового прибора «ПУЛЬСАР-2.1» (X) и результатов замера прочности бетона (методом отрыва со скалыванием) склерометром ИПС-МГ4.03 (Y).

Между переменными имеется весьма сильная линейная корреляционная связь. Получена регрессионная модель:

Y = b0 + b1∙X = -21.3741 + 0.0129∙X

Модель хорошо аппроксимирует фактические данные, является адекватной, значимой и может использоваться для предсказания прочности бетона.

Также построен график прогноза с доверительными интервалами.

ИТОГИ

Итак, мы рассмотрели все этапы регрессионного анализа в случае простой линейной регрессии (simple linear regression) с использованием библиотеки statsmodels на конкретном практическом примере; подробно остановились на статистическом анализа модели с проверкой гипотез; также предложен ряд пользовательских функций, облегчающих работу исследователя и уменьшающих размер программного кода.

Конечно, мы разобрали далеко не все вопросы анализа регрессионных моделей и возможности библиотеки statsmodels применительно к simple linear regression, в частности статистики влияния (Influence Statistics), инструмент Leverage, анализ стьюдентизированных остатков и пр. — это темы для отдельных обзоров.

Исходный код находится в моем репозитории на GitHub.

Надеюсь, данный обзор поможет специалистам DataScience в работе.


  Перевод


  Ссылка на автора

Введение

Эта статья пытается быть справочной информацией, которая вам нужна, когда дело доходит до понимания и выполнения линейной регрессии. Хотя алгоритм прост, лишь немногие действительно понимают основные принципы.

Во-первых, мы углубимся в теорию линейной регрессии, чтобы понять ее внутреннюю работу. Затем мы реализуем алгоритм на Python для моделирования бизнес-задачи.

Я надеюсь, что эта статья найдет свой путь в ваши закладки! А пока давайте к этому!

Теория

Как вы будете себя чувствовать, изучая линейную регрессию

Линейная регрессия, вероятно, является самым простым подходом для статистического обучения. Это хорошая отправная точка для более продвинутых подходов, и фактически многие причудливые статистические методы обучения можно рассматривать как расширение линейной регрессии. Следовательно, понимание этой простой модели создаст хорошую базу, прежде чем перейти к более сложным подходам.

Линейная регрессия очень хороша, чтобы ответить на следующие вопросы:

  • Есть ли связь между 2 переменными?
  • Насколько прочны отношения?
  • Какая переменная вносит наибольший вклад?
  • Насколько точно мы можем оценить влияние каждой переменной?
  • Насколько точно мы можем предсказать цель?
  • Являются ли отношения линейными? (Дух)
  • Есть ли эффект взаимодействия?

Оценка коэффициентов

Предположим, у нас есть только одна переменная и одна цель. Тогда линейная регрессия выражается как:

Уравнение для линейной модели с 1 переменной и 1 целью

В приведенном выше уравнениибетыявляются коэффициентами. Эти коэффициенты — то, что нам нужно, чтобы делать прогнозы с нашей моделью.

Итак, как мы можем найти эти параметры?

Чтобы найти параметры, нам нужно минимизироватьнаименьших квадратовилисумма квадратов ошибок, Конечно, линейная модель не идеальна, и она не будет точно предсказывать все данные, а это означает, что существует разница между фактическим значением и прогнозом. Ошибка легко вычисляется с помощью:

Вычтите прогноз из истинного значения

Но почему ошибки возводятся в квадрат?

Мы возводим в квадрат ошибку, потому что прогноз может быть выше или ниже истинного значения, что приводит к отрицательной или положительной разнице соответственно. Если бы мы не возводили в квадрат ошибки, сумма ошибок могла бы уменьшиться из-за отрицательных различий, а не потому, что модель хорошо подходит.

Кроме того, возведение в квадрат ошибок учитывает большие различия, поэтому минимизация квадратов ошибок «гарантирует» лучшую модель.

Давайте посмотрим на график, чтобы лучше понять.

Линейная подгонка к набору данных

На приведенном выше графике красные точки — это истинные данные, а синяя линия — линейная модель. Серые линии иллюстрируют ошибки между предсказанными и истинными значениями. Таким образом, синяя линия — это та, которая минимизирует сумму квадратов длины серых линий.

После некоторой математики, которая является слишком тяжелой для этой статьи, вы можете наконец оценить коэффициенты с помощью следующих уравнений:

гдех бара такжеу барпредставляют собой среднее.

Оцените актуальность коэффициентов

Теперь, когда у вас есть коэффициенты, как вы можете определить, актуальны ли они для прогнозирования вашей цели?

Лучший способ — найтир-значение.р-значениеиспользуется для количественной оценки статистической значимости; это позволяет определить, следует ли отклонить нулевую гипотезу или нет.

Нулевая гипотеза?

Для любой задачи моделирования гипотеза состоит в том, чтоесть некоторая корреляциямежду функциями и целью. Таким образом, нулевая гипотеза противоположна:нет корреляциимежду функциями и целью.

Итак, нахождениер-значениедля каждого коэффициента будет указано, является ли переменная статистически значимой для прогнозирования цели. Как правило, еслир-значениеявляетсяменее чем 0,05: между переменной и целью существует тесная связь.

Оцените точность модели

Вы обнаружили, что ваша переменная была статистически значимой, найдя еер-значение, Большой!

Теперь, как узнать, хороша ли ваша линейная модель?

Чтобы оценить это, мы обычно используем RSE (остаточная стандартная ошибка) и статистику R².

Формула RSE

Формула R²

Первая метрика ошибки проста для понимания: чем меньше остаточных ошибок, тем лучше модель соответствует данным (в этом случае, чем ближе данные к линейной зависимости).

Что касается метрики R², он измеряетдоля изменчивости в цели, которая может быть объяснена с помощью функции X, Следовательно, при условии линейного отношения, если признак X может объяснить (предсказать) цель, тогда пропорция высока, и значение R² будет близко к 1. Если противоположное верно, значение R² будет тогда ближе к 0.

Теория множественной линейной регрессии

В реальных ситуациях никогда не будет единственной функции, чтобы предсказать цель Итак, выполняем ли мы линейную регрессию по одному объекту за раз? Конечно нет. Мы просто выполняем множественную линейную регрессию.

Уравнение очень похоже на простую линейную регрессию; просто добавьте количество предикторов и соответствующие им коэффициенты:

Уравнение множественной линейной регрессии.пэто количество предикторов

Оцените актуальность предиктора

Ранее в простой линейной регрессии мы оценивали релевантность функции, находя еер-значение,

В случае множественной линейной регрессии мы используем другую метрику: F-статистику.

Формула F-статистики.Nколичество точек данных ипэто количество предикторов

Здесь F-статистика рассчитывается для всей модели, тогда какр-значениеспецифичен для каждого предиктора. Если существует сильная связь, то F будет намного больше 1. В противном случае она будет приблизительно равна 1.

Какбольшечем 1достаточно большой?

Это сложно ответить. Обычно, если имеется большое количество точек данных, F может быть немного больше 1 и предполагать тесную связь. Для небольших наборов данных значение F должно быть намного больше 1, чтобы предполагать тесную связь.

Почему мы не можем использоватьр-значениев этом случае?

Поскольку мы подгоняем много предикторов, нам нужно рассмотреть случай, когда есть много функций (пбольшой). При очень большом количестве предикторов всегда будет около 5%, у которых, случайнор-значениечетноехотя они не являются статистически значимыми.Поэтому мы используем F-статистику, чтобы не рассматривать неважные предикторы как значимые предикторы.

Оцените точность модели

Как и в простой линейной регрессии, R² может использоваться для множественной линейной регрессии. Однако знайте, что добавление большего количества предикторов всегда будет увеличивать значение R², потому что модель обязательно будет лучше соответствовать обучающим данным.

Тем не менее, это не означает, что он будет хорошо работать с тестовыми данными (делая прогнозы для неизвестных точек данных).

Добавление взаимодействия

Наличие нескольких предикторов в линейной модели означает, что некоторые предикторы могут влиять на другие предикторы.

Например, вы хотите предсказать зарплату человека, зная его возраст и количество лет, проведенных в школе. Конечно, чем старше человек, тем больше времени он мог бы проводить в школе. Итак, как мы моделируем этот эффект взаимодействия?

Рассмотрим этот очень простой пример с двумя предикторами:

Эффект взаимодействия при множественной линейной регрессии

Как видите, мы просто умножаем оба предиктора вместе и связываем новый коэффициент. Упрощая формулу, мы теперь видим, что на коэффициент влияет значение другого признака.

Как правило, если мы включаем модель взаимодействия, мы должны включать индивидуальный эффект объекта, даже если егор-значениенезначительный. Это известно какиерархический принцип, Это объясняется тем, что если два предиктора взаимодействуют, то включение их индивидуального вклада окажет небольшое влияние на модель.


Хорошо! Теперь, когда мы знаем, как это работает, давайте заставим это работать! Мы будем работать как с простой, так и с множественной линейной регрессией в Python, и я покажу, как оценивать качество параметров и общую модель в обеих ситуациях.

Вы можете получить код и данные Вот,

Я настоятельно рекомендую вам следовать и воссоздать шаги в своем собственном блокноте Jupyter, чтобы в полной мере воспользоваться этим руководством.

Давайте доберемся до этого!

Мы все так кодируем, верно?

Введение

Набор данных содержит информацию о деньгах, потраченных на рекламу, и их продажах. Деньги были потрачены на рекламу на телевидении, радио и в газетах.

Цель состоит в том, чтобы использовать линейную регрессию, чтобы понять, как расходы на рекламу влияют на продажи.

Импорт библиотек

Преимущество работы с Python заключается в том, что у нас есть доступ ко многим библиотекам, которые позволяют нам быстро читать данные, наносить на график данные и выполнять линейную регрессию.

Мне нравится импортировать все необходимые библиотеки поверх ноутбука, чтобы все было организовано. Импортируйте следующее:

import pandas as pd
import numpy as npimport matplotlib.pyplot as pltfrom sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_scoreimport statsmodels.api as sm

Читать данные

Предполагая, что вы загрузили набор данных, поместите его вdataкаталог в папке вашего проекта. Затем прочитайте данные так:

data = pd.read_csv("data/Advertising.csv")

Чтобы увидеть, как выглядят данные, мы делаем следующее:

data.head()

И вы должны увидеть это:

Как видите, столбецUnnamed: 0избыточно Следовательно, мы удалим это.

data.drop(['Unnamed: 0'], axis=1)

Хорошо, наши данные чисты и готовы к линейной регрессии!

Простая линейная регрессия

моделирование

Для простой линейной регрессии, давайте рассмотрим только влияние телевизионной рекламы на продажи. Прежде чем перейти непосредственно к моделированию, давайте посмотрим, как выглядят данные.

Мы используемmatplotlib, популярная библиотека для построения графиков на Python

plt.figure(figsize=(16, 8))
plt.scatter(
data['TV'],
data['sales'],
c='black'
)
plt.xlabel("Money spent on TV ads ($)")
plt.ylabel("Sales ($)")
plt.show()

Запустите эту ячейку кода, и вы должны увидеть этот график:

Разброс сюжета денег, потраченных на телевизионную рекламу и продажи

Как видите, существует четкая взаимосвязь между суммой, потраченной на телевизионную рекламу, и продажами.

Давайте посмотрим, как мы можем сгенерировать линейное приближение этих данных.

X = data['TV'].values.reshape(-1,1)
y = data['sales'].values.reshape(-1,1)reg = LinearRegression()
reg.fit(X, y)print("The linear model is: Y = {:.5} + {:.5}X".format(reg.intercept_[0], reg.coef_[0][0]))

Это оно?

Да! Это так просто, чтобы подогнать прямую линию к набору данных и увидеть параметры уравнения. В этом случае мы имеем

Простое уравнение линейной регрессии

Давайте представим, как линия соответствует данным.

predictions = reg.predict(X)plt.figure(figsize=(16, 8))
plt.scatter(
data['TV'],
data['sales'],
c='black'
)
plt.plot(
data['TV'],
predictions,
c='blue',
linewidth=2
)
plt.xlabel("Money spent on TV ads ($)")
plt.ylabel("Sales ($)")
plt.show()

И теперь вы видите:

Линейная посадка

Из приведенного выше графика видно, что простая линейная регрессия может объяснить общее влияние суммы, потраченной на телевизионную рекламу и продажи.

Оценка актуальности модели

Теперь, если вы помните из этого после, чтобы увидеть, является ли модель хорошей, нам нужно посмотреть на значение R² ир-значениеот каждого коэффициента.

Вот как мы это делаем:

X = data['TV']
y = data['sales']X2 = sm.add_constant(X)
est = sm.OLS(y, X2)
est2 = est.fit()
print(est2.summary())

Что дает вам этот прекрасный вывод:

R² и р-значение

Глядя на оба коэффициента, мы имеемр-значениеэто очень мало (хотя, вероятно, это не совсем 0). Это означает, что существует сильная корреляция между этими коэффициентами и целью (продажи).

Затем, глядя на значение R², мы получаем 0,612. Следовательно,около 60% изменчивости продаж объясняется суммой, потраченной на телевизионную рекламу, Это нормально, но точно не лучшее, что мы можем точно предсказать продажи. Конечно, расходы на рекламу в газетах и ​​на радио должны оказывать определенное влияние на продажи.

Посмотрим, будет ли лучше работать множественная линейная регрессия.

Множественная линейная регрессия

моделирование

Как и для простой линейной регрессии, мы определим наши функции и целевую переменную и используемscikit учитьсябиблиотека для выполнения линейной регрессии.

Xs = data.drop(['sales', 'Unnamed: 0'], axis=1)
y = data['sales'].reshape(-1,1)reg = LinearRegression()
reg.fit(Xs, y)print("The linear model is: Y = {:.5} + {:.5}*TV + {:.5}*radio + {:.5}*newspaper".format(reg.intercept_[0], reg.coef_[0][0], reg.coef_[0][1], reg.coef_[0][2]))

Больше ничего! Из этой ячейки кода мы получаем следующее уравнение:

Уравнение множественной линейной регрессии

Конечно, мы не можем визуализировать влияние всех трех сред на продажи, так как оно имеет четыре измерения.

Обратите внимание, что коэффициент для газеты является отрицательным, но также довольно небольшим. Это относится к нашей модели? Давайте посмотрим, рассчитав F-статистику, значение R² ир-значениеза каждый коэффициент.

Оценка актуальности модели

Как и следовало ожидать, процедура здесь очень похожа на то, что мы делали в простой линейной регрессии.

X = np.column_stack((data['TV'], data['radio'], data['newspaper']))
y = data['sales']X2 = sm.add_constant(X)
est = sm.OLS(y, X2)
est2 = est.fit()
print(est2.summary())

И вы получите следующее:

R², p-значение и F-статистика

Как вы можете видеть, R² намного выше, чем у простой линейной регрессии, со значением0,897!

Кроме того, F-статистика570,3, Это намного больше, чем 1, и поскольку наш набор данных достаточно мал (всего 200 точек),демонстрирует тесную связь между расходами на рекламу и продажами,

Наконец, поскольку у нас есть только три предиктора, мы можем рассмотреть ихр-значениеопределить, имеют ли они отношение к модели или нет. Конечно, вы заметили, что третий коэффициент (для газеты) имеет большойр-значение, Поэтому рекламные расходы на газетуне является статистически значимым, Удаление этого предиктора немного уменьшит значение R², но мы могли бы сделать более точные прогнозы.


Вы качаетесь 🤘. Поздравляю с завершением, теперь вы мастер линейной регрессии!

Как упоминалось выше, это может быть не самый эффективный алгоритм, но он важен для понимания линейной регрессии, поскольку он формирует основу более сложных статистических подходов к обучению.

Я надеюсь, что вы когда-нибудь вернетесь к этой статье.

Ура!

Линейная регрессия применяется для анализа данных и в машинном обучении. Постройте свою модель на Python и получите первые результаты!

Линейная регрессия на Python: объясняем на пальцах

Что такое регрессия?

Регрессия ищет отношения между переменными.

Для примера можно взять сотрудников какой-нибудь компании и понять, как значение зарплаты зависит от других данных, таких как опыт работы, уровень образования, роль, город, в котором они работают, и так далее.

Регрессия решает проблему единого представления данных анализа для каждого работника. Причём опыт, образование, роль и город – это независимые переменные при зависимой от них зарплате.

Таким же способом можно установить математическую зависимость между ценами домов в определённой области, количеством комнат, расстоянием от центра и т. д.

Регрессия рассматривает некоторое явление и ряд наблюдений. Каждое наблюдение имеет две и более переменных. Предполагая, что одна переменная зависит от других, вы пытаетесь построить отношения между ними.

Другими словами, вам нужно найти функцию, которая отображает зависимость одних переменных или данных от других.

Зависимые данные называются зависимыми переменными, выходами или ответами.

Независимые данные называются независимыми переменными, входами или предсказателями.

Обычно в регрессии присутствует одна непрерывная и неограниченная зависимая переменная. Входные переменные могут быть неограниченными, дискретными или категорическими данными, такими как пол, национальность, бренд, etc.

Общей практикой является обозначение данных на выходе – ?, входных данных – ?. В случае с двумя или более независимыми переменными, их можно представить в виде вектора ? = (?₁, …, ?ᵣ), где ? – количество входных переменных.

Когда вам нужна регрессия?

Регрессия полезна для прогнозирования ответа на новые условия. Можно угадать потребление электроэнергии в жилом доме из данных температуры, времени суток и количества жильцов.

Где она вообще нужна?

Регрессия используется во многих отраслях: экономика, компьютерные и социальные науки, прочее. Её важность растёт с доступностью больших данных.

Линейная регрессия

Линейная регрессия – одна из важнейших и широко используемых техник регрессии. Эта самый простой метод регрессии. Одним из его достоинств является лёгкость интерпретации результатов.

Постановка проблемы

Линейная регрессия некоторой зависимой переменной y на набор независимых переменных  x = (x₁, …, xᵣ), где r – это число предсказателей, предполагает, что линейное отношение между y и x: y = 𝛽₀ + 𝛽₁x₁ + ⋯ + 𝛽ᵣxᵣ + 𝜀. Это уравнение регрессии. 𝛽₀, 𝛽₁, …, 𝛽ᵣ – коэффициенты регрессии, и 𝜀 – случайная ошибка.

Линейная регрессия вычисляет оценочные функции коэффициентов регрессии или просто прогнозируемые весы измерения, обозначаемые как b₀, b₁, …, bᵣ. Они определяют оценочную функцию регрессии f(x) = b₀ + b₁x₁ + ⋯ + bᵣxᵣ. Эта функция захватывает зависимости между входами и выходом достаточно хорошо.

Для каждого результата наблюдения i = 1, …, n, оценочный или предсказанный ответ f(xᵢ) должен быть как можно ближе к соответствующему фактическому ответу yᵢ. Разницы yᵢ − f(xᵢ) для всех результатов наблюдений называются остатками. Регрессия определяет лучшие прогнозируемые весы измерения, которые соответствуют наименьшим остаткам.

Для получения лучших весов, вам нужно минимизировать сумму остаточных квадратов (SSR) для всех результатов наблюдений: SSR = Σᵢ(yᵢ − f(xᵢ))². Этот подход называется методом наименьших квадратов.

Простая линейная регрессия

Простая или одномерная линейная регрессия – случай линейной регрессии с единственной независимой переменной x.

А вот и она:

Линейная регрессия на Python: объясняем на пальцах

Реализация простой линейной регрессии начинается с заданным набором пар (зелёные круги) входов-выходов (x-y). Эти пары – результаты наблюдений. Наблюдение, крайнее слева (зелёный круг) имеет на входе x = 5 и соответствующий выход (ответ) y = 5. Следующее наблюдение имеет  x = 15 и y = 20, и так далее.

Оценочная функция регрессии (чёрная линия) выражается уравнением f(x) = b₀ + b₁x. Нужно рассчитать оптимальные значения спрогнозированных весов b₀ и b₁ для минимизации SSR и определить оценочную функцию регрессии. Величина b₀, также называемая отрезком, показывает точку, где расчётная линия регрессии пересекает ось y. Это значение расчётного ответа f(x) для x = 0. Величина b₁ определяет наклон расчетной линии регрессии.

Предсказанные ответы (красные квадраты) – точки линии регрессии, соответствующие входным значениям. Для входа x = 5 предсказанный ответ равен f(5) = 8.33 (представленный крайним левыми квадратом).

Остатки (вертикальные пунктирные серые линии) могут быть вычислены как yᵢ − f(xᵢ) = yᵢ − b₀ − b₁xᵢ для i = 1, …, n. Они представляют собой расстояния между зелёными и красными пунктами. При реализации линейной регрессии вы минимизируете эти расстояния и делаете красные квадраты как можно ближе к предопределённым зелёным кругам.

Пришло время реализовать линейную регрессию в Python. Всё, что вам нужно, – подходящие пакеты, функции и классы.

NumPy – фундаментальный научный пакет для быстрых операций над одномерными и многомерными массивами. Он облегчает математическую рутину и, конечно, находится в open-source.

Незнакомы с NumPy? Начните с официального гайда.

Пакет scikit-learn – это библиотека, широко используемая в машинном обучении. Она предоставляет значения для данных предварительной обработки, уменьшает размерность, реализует регрессию, классификацию, кластеризацию и т. д. Находится в open-source, как и NumPy.

Начните знакомство с линейными моделями и работой пакета на сайте scikit-learn.

Простая линейная регрессия со scikit-learn

Начнём с простейшего случая линейной регрессии.

Следуйте пяти шагам реализации линейной регрессии:

  1. Импортируйте необходимые пакеты и классы.
  2. Предоставьте данные для работы и преобразования.
  3. Создайте модель регрессии и приспособьте к существующим данным.
  4. Проверьте результаты совмещения и удовлетворительность модели.
  5. Примените модель для прогнозов.

Это общие шаги для большинства подходов и реализаций регрессии.

Шаг 1: Импортируйте пакеты и классы

Первым шагом импортируем пакет NumPy и класс LinearRegressionиз sklearn.linear_model:

import numpy as np
from sklearn.linear_model import LinearRegression

Теперь у вас есть весь функционал для реализации линейной регрессии.

Фундаментальный тип данных NumPy – это тип массива numpy.ndarray. Далее под массивом подразумеваются все экземпляры типа numpy.ndarray.

Класс sklearn.linear_model.LinearRegression используем для линейной регрессии и прогнозов.

Шаг 2 : Предоставьте данные

Вторым шагом определите данные, с которыми предстоит работать. Входы (регрессоры, x) и выход (предиктор, y) должны быть массивами (экземпляры класса numpy.ndarray) или похожими объектами. Вот простейший способ предоставления данных регрессии:

x = np.array([5, 15, 25, 35, 45, 55]).reshape((-1, 1))
y = np.array([5, 20, 14, 32, 22, 38])

Теперь у вас два массива: вход x и выход y. Вам нужно вызвать .reshape()на x, потому что этот массив должен быть двумерным или более точным – иметь одну колонку и необходимое количество рядов. Это как раз то, что определяет аргумент (-1, 1).

Вот как x и y выглядят теперь:

>>> print(x)
[[ 5]
 [15]
 [25]
 [35]
 [45]
 [55]]
>>> print(y)
[ 5 20 14 32 22 38]

Шаг 3: Создайте модель

На этом шаге создайте и приспособьте модель линейной регрессии к существующим данным.

Давайте сделаем экземпляр класса LinearRegression, который представит модель регрессии:

model = LinearRegression()

Эта операция создаёт переменную model в качестве экземпляра LinearRegression. Вы можете предоставить несколько опциональных параметров классу LinearRegression:

  • fit_intercept – логический (True по умолчанию) параметр, который решает, вычислять отрезок b₀ (True) или рассматривать его как равный нулю (False).
  • normalize – логический (False по умолчанию) параметр, который решает, нормализовать входные переменные (True) или нет (False).
  • copy_X – логический (True по умолчанию) параметр, который решает, копировать (True) или перезаписывать входные переменные (False).
  • n_jobs – целое или None (по умолчанию), представляющее количество процессов, задействованных в параллельных вычислениях. None означает отсутствие процессов, при -1 используются все доступные процессоры.

Наш пример использует состояния параметров по умолчанию.

Пришло время задействовать model. Сначала вызовите .fit() на model:

model.fit(x, y)

С помощью .fit() вычисляются оптимальные значение весов b₀ и b₁, используя существующие вход и выход (x и y) в качестве аргументов. Другими словами, .fit() совмещает модель. Она возвращает self — переменную model. Поэтому можно заменить две последние операции на:

model = LinearRegression().fit(x, y)

Эта операция короче и делает то же, что и две предыдущие.

Шаг 4: Получите результаты

После совмещения модели нужно убедиться в удовлетворительности результатов для интерпретации.

Вы можете получить определения (R²) с помощью .score(), вызванной на model:

>>> r_sq = model.score(x, y)
>>> print('coefficient of determination:', r_sq)
coefficient of determination: 0.715875613747954

.score() принимает в качестве аргументов предсказатель x и регрессор y, и возвращает значение R².

model содержит атрибуты .intercept_, который представляет собой коэффициент, и b₀ с .coef_, которые представляют b₁:

>>> print('intercept:', model.intercept_)
intercept: 5.633333333333329
>>> print('slope:', model.coef_)
slope: [0.54]

Код выше показывает, как получить b₀ и b₁. Заметьте, что .intercept_ – это скаляр, в то время как .coef_ – массив.

Примерное значение b₀ = 5.63 показывает, что ваша модель предсказывает ответ 5.63 при x, равном нулю. Равенство b₁ = 0.54 означает, что предсказанный ответ возрастает до 0.54 при x, увеличенным на единицу.

Заметьте, что вы можете предоставить y как двумерный массив. Тогда результаты не будут отличаться:

>>> new_model = LinearRegression().fit(x, y.reshape((-1, 1)))
>>> print('intercept:', new_model.intercept_)
intercept: [5.63333333]
>>> print('slope:', new_model.coef_)
slope: [[0.54]]

Как вы видите, пример похож на предыдущий, но в данном случае .intercept_ – одномерный массив с единственным элементом b₀, и .coef_ – двумерный массив с единственным элементом b₁.

Шаг 5: Предскажите ответ

Когда вас устроит ваша модель, вы можете использовать её для прогнозов с текущими или другими данными.

Получите предсказанный ответ, используя .predict():

>>> y_pred = model.predict(x)
>>> print('predicted response:', y_pred, sep='n')
predicted response:
[ 8.33333333 13.73333333 19.13333333 24.53333333 29.93333333 35.33333333]

Применяя .predict(), вы передаёте регрессор в качестве аргумента и получаете соответствующий предсказанный ответ.

Вот почти идентичный способ предсказать ответ:

>>> y_pred = model.intercept_ + model.coef_ * x
>>> print('predicted response:', y_pred, sep='n')
predicted response:
[[ 8.33333333]
 [13.73333333]
 [19.13333333]
 [24.53333333]
 [29.93333333]
 [35.33333333]]

В этом случае вы умножаете каждый элемент массива x с помощью model.coef_ и добавляете model.intercept_ в ваш продукт.

Вывод отличается от предыдущего примера количеством измерений. Теперь предсказанный ответ – это двумерный массив, в отличии от предыдущего случая, в котором он одномерный.

Измените количество измерений x до одного, и увидите одинаковый результат. Для этого замените x на x.reshape(-1), x.flatten() или x.ravel() при умножении с помощью model.coef_.

На практике модель регрессии часто используется для прогнозов. Это значит, что вы можете использовать приспособленные модели для вычисления выходов на базе других, новых входов:

>>> x_new = np.arange(5).reshape((-1, 1))
>>> print(x_new)
[[0]
 [1]
 [2]
 [3]
 [4]]
>>> y_new = model.predict(x_new)
>>> print(y_new)
[5.63333333 6.17333333 6.71333333 7.25333333 7.79333333]

Здесь .predict() применяется на новом регрессоре x_new и приводит к ответу y_new. Этот пример удобно использует arange() из NumPy для генерации массива с элементами от 0 (включительно) до 5 (исключительно) – 0, 1, 2, 3, и 4.

О LinearRegression вы узнаете больше из официальной документации.

Теперь у вас есть своя модель линейной регрессии!

Источник

Нравится Data Science? Другие материалы по теме:

  • 6 советов, которые спасут специалиста Data Science
  • Как изучать Data Science в 2019: ответы на частые вопросы
  • Схема успешного развития data-scientist специалиста в 2019 году
  • Редакция Кодкампа

17 авг. 2022 г.
читать 2 мин


Остаток — это разница между наблюдаемым значением и прогнозируемым значением в регрессионной модели .

Он рассчитывается как:

Остаток = наблюдаемое значение – прогнозируемое значение

Если мы нанесем наблюдаемые значения и наложим подобранную линию регрессии, остатки для каждого наблюдения будут вертикальным расстоянием между наблюдением и линией регрессии:

Пример остатка в статистике

Один тип остатка, который мы часто используем для выявления выбросов в регрессионной модели, известен как стандартизированный остаток .

Он рассчитывается как:

r i = e i / s(e i ) = e i / RSE√ 1-h ii

куда:

  • e i : i -й остаток
  • RSE: остаточная стандартная ошибка модели.
  • h ii : рычаг i -го наблюдения

На практике мы часто считаем выбросом любой стандартизованный остаток с абсолютным значением больше 3.

В этом руководстве представлен пошаговый пример расчета стандартизированных остатков в Python.

Шаг 1: введите данные

Во-первых, мы создадим небольшой набор данных для работы с Python:

import pandas as pd

#create dataset
df = pd.DataFrame({'x': [8, 12, 12, 13, 14, 16, 17, 22, 24, 26, 29, 30],
 'y': [41, 42, 39, 37, 35, 39, 45, 46, 39, 49, 55, 57]})

Шаг 2: Подгонка регрессионной модели

Далее мы подгоним простую модель линейной регрессии :

import statsmodels.api as sm

#define response variable
y = df['y']

#define explanatory variable
x = df['x']

#add constant to predictor variables
x = sm.add_constant (x)

#fit linear regression model
model = sm. OLS (y, x). fit ()

Шаг 3: Рассчитайте стандартизированные остатки

Далее мы рассчитаем стандартизированные остатки модели:

#create instance of influence
influence = model. get_influence ()

#obtain standardized residuals
standardized_residuals = influence. resid_studentized_internal

#display standardized residuals
print(standardized_residuals)

[ 1.40517322 0.81017562 0.07491009 -0.59323342 -1.2482053 -0.64248883
 0.59610905 -0.05876884 -2.11711982 -0.066556 0.91057211 1.26973888]

Из результатов видно, что ни один из стандартизированных остатков не превышает абсолютного значения 3. Таким образом, ни одно из наблюдений не является выбросом.

Шаг 4: Визуализируйте стандартизированные остатки

Наконец, мы можем создать диаграмму рассеяния, чтобы визуализировать значения переменной-предиктора по сравнению со стандартизованными остатками:

import matplotlib.pyplot as plt

plt.scatter (df.x, standardized_residuals)
plt.xlabel('x')
plt.ylabel('Standardized Residuals')
plt.axhline (y=0, color='black', linestyle='--', linewidth=1)
plt.show() 

График стандартизированных остатков в Python

Дополнительные ресурсы

Что такое остатки?
Что такое стандартизированные остатки?
Как рассчитать стандартизованные остатки в R
Как рассчитать стандартизированные остатки в Excel

The following are a set of methods intended for regression in which
the target value is expected to be a linear combination of the features.
In mathematical notation, if (hat{y}) is the predicted
value.

[hat{y}(w, x) = w_0 + w_1 x_1 + … + w_p x_p]

Across the module, we designate the vector (w = (w_1,
…, w_p))
as coef_ and (w_0) as intercept_.

To perform classification with generalized linear models, see
Logistic regression.

1.1.1. Ordinary Least Squares¶

LinearRegression fits a linear model with coefficients
(w = (w_1, …, w_p)) to minimize the residual sum
of squares between the observed targets in the dataset, and the
targets predicted by the linear approximation. Mathematically it
solves a problem of the form:

[min_{w} || X w — y||_2^2]

../_images/sphx_glr_plot_ols_001.png

LinearRegression will take in its fit method arrays X, y
and will store the coefficients (w) of the linear model in its
coef_ member:

>>> from sklearn import linear_model
>>> reg = linear_model.LinearRegression()
>>> reg.fit([[0, 0], [1, 1], [2, 2]], [0, 1, 2])
LinearRegression()
>>> reg.coef_
array([0.5, 0.5])

The coefficient estimates for Ordinary Least Squares rely on the
independence of the features. When features are correlated and the
columns of the design matrix (X) have an approximately linear
dependence, the design matrix becomes close to singular
and as a result, the least-squares estimate becomes highly sensitive
to random errors in the observed target, producing a large
variance. This situation of multicollinearity can arise, for
example, when data are collected without an experimental design.

1.1.1.1. Non-Negative Least Squares¶

It is possible to constrain all the coefficients to be non-negative, which may
be useful when they represent some physical or naturally non-negative
quantities (e.g., frequency counts or prices of goods).
LinearRegression accepts a boolean positive
parameter: when set to True Non-Negative Least Squares are then applied.

1.1.1.2. Ordinary Least Squares Complexity¶

The least squares solution is computed using the singular value
decomposition of X. If X is a matrix of shape (n_samples, n_features)
this method has a cost of
(O(n_{text{samples}} n_{text{features}}^2)), assuming that
(n_{text{samples}} geq n_{text{features}}).

1.1.2. Ridge regression and classification¶

1.1.2.1. Regression¶

Ridge regression addresses some of the problems of
Ordinary Least Squares by imposing a penalty on the size of the
coefficients. The ridge coefficients minimize a penalized residual sum
of squares:

[min_{w} || X w — y||_2^2 + alpha ||w||_2^2]

The complexity parameter (alpha geq 0) controls the amount
of shrinkage: the larger the value of (alpha), the greater the amount
of shrinkage and thus the coefficients become more robust to collinearity.

../_images/sphx_glr_plot_ridge_path_001.png

As with other linear models, Ridge will take in its fit method
arrays X, y and will store the coefficients (w) of the linear model in
its coef_ member:

>>> from sklearn import linear_model
>>> reg = linear_model.Ridge(alpha=.5)
>>> reg.fit([[0, 0], [0, 0], [1, 1]], [0, .1, 1])
Ridge(alpha=0.5)
>>> reg.coef_
array([0.34545455, 0.34545455])
>>> reg.intercept_
0.13636...

Note that the class Ridge allows for the user to specify that the
solver be automatically chosen by setting solver="auto". When this option
is specified, Ridge will choose between the "lbfgs", "cholesky",
and "sparse_cg" solvers. Ridge will begin checking the conditions
shown in the following table from top to bottom. If the condition is true,
the corresponding solver is chosen.

Solver

Condition

‘lbfgs’

The positive=True option is specified.

‘cholesky’

The input array X is not sparse.

‘sparse_cg’

None of the above conditions are fulfilled.

1.1.2.2. Classification¶

The Ridge regressor has a classifier variant:
RidgeClassifier. This classifier first converts binary targets to
{-1, 1} and then treats the problem as a regression task, optimizing the
same objective as above. The predicted class corresponds to the sign of the
regressor’s prediction. For multiclass classification, the problem is
treated as multi-output regression, and the predicted class corresponds to
the output with the highest value.

It might seem questionable to use a (penalized) Least Squares loss to fit a
classification model instead of the more traditional logistic or hinge
losses. However, in practice, all those models can lead to similar
cross-validation scores in terms of accuracy or precision/recall, while the
penalized least squares loss used by the RidgeClassifier allows for
a very different choice of the numerical solvers with distinct computational
performance profiles.

The RidgeClassifier can be significantly faster than e.g.
LogisticRegression with a high number of classes because it can
compute the projection matrix ((X^T X)^{-1} X^T) only once.

This classifier is sometimes referred to as a Least Squares Support Vector
Machines with
a linear kernel.

1.1.2.3. Ridge Complexity¶

This method has the same order of complexity as
Ordinary Least Squares.

1.1.2.4. Setting the regularization parameter: leave-one-out Cross-Validation¶

RidgeCV implements ridge regression with built-in
cross-validation of the alpha parameter. The object works in the same way
as GridSearchCV except that it defaults to Leave-One-Out Cross-Validation:

>>> import numpy as np
>>> from sklearn import linear_model
>>> reg = linear_model.RidgeCV(alphas=np.logspace(-6, 6, 13))
>>> reg.fit([[0, 0], [0, 0], [1, 1]], [0, .1, 1])
RidgeCV(alphas=array([1.e-06, 1.e-05, 1.e-04, 1.e-03, 1.e-02, 1.e-01, 1.e+00, 1.e+01,
      1.e+02, 1.e+03, 1.e+04, 1.e+05, 1.e+06]))
>>> reg.alpha_
0.01

Specifying the value of the cv attribute will trigger the use of
cross-validation with GridSearchCV, for
example cv=10 for 10-fold cross-validation, rather than Leave-One-Out
Cross-Validation.

1.1.3. Lasso¶

The Lasso is a linear model that estimates sparse coefficients.
It is useful in some contexts due to its tendency to prefer solutions
with fewer non-zero coefficients, effectively reducing the number of
features upon which the given solution is dependent. For this reason,
Lasso and its variants are fundamental to the field of compressed sensing.
Under certain conditions, it can recover the exact set of non-zero
coefficients (see
Compressive sensing: tomography reconstruction with L1 prior (Lasso)).

Mathematically, it consists of a linear model with an added regularization term.
The objective function to minimize is:

[min_{w} { frac{1}{2n_{text{samples}}} ||X w — y||_2 ^ 2 + alpha ||w||_1}]

The lasso estimate thus solves the minimization of the
least-squares penalty with (alpha ||w||_1) added, where
(alpha) is a constant and (||w||_1) is the (ell_1)-norm of
the coefficient vector.

The implementation in the class Lasso uses coordinate descent as
the algorithm to fit the coefficients. See Least Angle Regression
for another implementation:

>>> from sklearn import linear_model
>>> reg = linear_model.Lasso(alpha=0.1)
>>> reg.fit([[0, 0], [1, 1]], [0, 1])
Lasso(alpha=0.1)
>>> reg.predict([[1, 1]])
array([0.8])

The function lasso_path is useful for lower-level tasks, as it
computes the coefficients along the full path of possible values.

Note

Feature selection with Lasso

As the Lasso regression yields sparse models, it can
thus be used to perform feature selection, as detailed in
L1-based feature selection.

The following two references explain the iterations
used in the coordinate descent solver of scikit-learn, as well as
the duality gap computation used for convergence control.

1.1.3.1. Setting regularization parameter¶

The alpha parameter controls the degree of sparsity of the estimated
coefficients.

1.1.3.1.1. Using cross-validation¶

scikit-learn exposes objects that set the Lasso alpha parameter by
cross-validation: LassoCV and LassoLarsCV.
LassoLarsCV is based on the Least Angle Regression algorithm
explained below.

For high-dimensional datasets with many collinear features,
LassoCV is most often preferable. However, LassoLarsCV has
the advantage of exploring more relevant values of alpha parameter, and
if the number of samples is very small compared to the number of
features, it is often faster than LassoCV.

lasso_cv_1 lasso_cv_2

1.1.3.1.2. Information-criteria based model selection¶

Alternatively, the estimator LassoLarsIC proposes to use the
Akaike information criterion (AIC) and the Bayes Information criterion (BIC).
It is a computationally cheaper alternative to find the optimal value of alpha
as the regularization path is computed only once instead of k+1 times
when using k-fold cross-validation.

Indeed, these criteria are computed on the in-sample training set. In short,
they penalize the over-optimistic scores of the different Lasso models by
their flexibility (cf. to “Mathematical details” section below).

However, such criteria need a proper estimation of the degrees of freedom of
the solution, are derived for large samples (asymptotic results) and assume the
correct model is candidates under investigation. They also tend to break when
the problem is badly conditioned (e.g. more features than samples).

../_images/sphx_glr_plot_lasso_lars_ic_001.png

Mathematical details

The definition of AIC (and thus BIC) might differ in the literature. In this
section, we give more information regarding the criterion computed in
scikit-learn. The AIC criterion is defined as:

[AIC = -2 log(hat{L}) + 2 d]

where (hat{L}) is the maximum likelihood of the model and
(d) is the number of parameters (as well referred to as degrees of
freedom in the previous section).

The definition of BIC replace the constant (2) by (log(N)):

[BIC = -2 log(hat{L}) + log(N) d]

where (N) is the number of samples.

For a linear Gaussian model, the maximum log-likelihood is defined as:

[log(hat{L}) = — frac{n}{2} log(2 pi) — frac{n}{2} ln(sigma^2) — frac{sum_{i=1}^{n} (y_i — hat{y}_i)^2}{2sigma^2}]

where (sigma^2) is an estimate of the noise variance,
(y_i) and (hat{y}_i) are respectively the true and predicted
targets, and (n) is the number of samples.

Plugging the maximum log-likelihood in the AIC formula yields:

[AIC = n log(2 pi sigma^2) + frac{sum_{i=1}^{n} (y_i — hat{y}_i)^2}{sigma^2} + 2 d]

The first term of the above expression is sometimes discarded since it is a
constant when (sigma^2) is provided. In addition,
it is sometimes stated that the AIC is equivalent to the (C_p) statistic
[12]. In a strict sense, however, it is equivalent only up to some constant
and a multiplicative factor.

At last, we mentioned above that (sigma^2) is an estimate of the
noise variance. In LassoLarsIC when the parameter noise_variance is
not provided (default), the noise variance is estimated via the unbiased
estimator [13] defined as:

[sigma^2 = frac{sum_{i=1}^{n} (y_i — hat{y}_i)^2}{n — p}]

where (p) is the number of features and (hat{y}_i) is the
predicted target using an ordinary least squares regression. Note, that this
formula is valid only when n_samples > n_features.

1.1.3.1.3. Comparison with the regularization parameter of SVM¶

The equivalence between alpha and the regularization parameter of SVM,
C is given by alpha = 1 / C or alpha = 1 / (n_samples * C),
depending on the estimator and the exact objective function optimized by the
model.

1.1.4. Multi-task Lasso¶

The MultiTaskLasso is a linear model that estimates sparse
coefficients for multiple regression problems jointly: y is a 2D array,
of shape (n_samples, n_tasks). The constraint is that the selected
features are the same for all the regression problems, also called tasks.

The following figure compares the location of the non-zero entries in the
coefficient matrix W obtained with a simple Lasso or a MultiTaskLasso.
The Lasso estimates yield scattered non-zeros while the non-zeros of
the MultiTaskLasso are full columns.

multi_task_lasso_1 multi_task_lasso_2

Fitting a time-series model, imposing that any active feature be active at all times.

Mathematically, it consists of a linear model trained with a mixed
(ell_1) (ell_2)-norm for regularization.
The objective function to minimize is:

[min_{W} { frac{1}{2n_{text{samples}}} ||X W — Y||_{text{Fro}} ^ 2 + alpha ||W||_{21}}]

where (text{Fro}) indicates the Frobenius norm

[||A||_{text{Fro}} = sqrt{sum_{ij} a_{ij}^2}]

and (ell_1) (ell_2) reads

[||A||_{2 1} = sum_i sqrt{sum_j a_{ij}^2}.]

The implementation in the class MultiTaskLasso uses
coordinate descent as the algorithm to fit the coefficients.

1.1.5. Elastic-Net¶

ElasticNet is a linear regression model trained with both
(ell_1) and (ell_2)-norm regularization of the coefficients.
This combination allows for learning a sparse model where few of
the weights are non-zero like Lasso, while still maintaining
the regularization properties of Ridge. We control the convex
combination of (ell_1) and (ell_2) using the l1_ratio
parameter.

Elastic-net is useful when there are multiple features that are
correlated with one another. Lasso is likely to pick one of these
at random, while elastic-net is likely to pick both.

A practical advantage of trading-off between Lasso and Ridge is that it
allows Elastic-Net to inherit some of Ridge’s stability under rotation.

The objective function to minimize is in this case

[min_{w} { frac{1}{2n_{text{samples}}} ||X w — y||_2 ^ 2 + alpha rho ||w||_1 +
frac{alpha(1-rho)}{2} ||w||_2 ^ 2}]

../_images/sphx_glr_plot_lasso_coordinate_descent_path_001.png

The class ElasticNetCV can be used to set the parameters
alpha ((alpha)) and l1_ratio ((rho)) by cross-validation.

The following two references explain the iterations
used in the coordinate descent solver of scikit-learn, as well as
the duality gap computation used for convergence control.

1.1.6. Multi-task Elastic-Net¶

The MultiTaskElasticNet is an elastic-net model that estimates sparse
coefficients for multiple regression problems jointly: Y is a 2D array
of shape (n_samples, n_tasks). The constraint is that the selected
features are the same for all the regression problems, also called tasks.

Mathematically, it consists of a linear model trained with a mixed
(ell_1) (ell_2)-norm and (ell_2)-norm for regularization.
The objective function to minimize is:

[min_{W} { frac{1}{2n_{text{samples}}} ||X W — Y||_{text{Fro}}^2 + alpha rho ||W||_{2 1} +
frac{alpha(1-rho)}{2} ||W||_{text{Fro}}^2}]

The implementation in the class MultiTaskElasticNet uses coordinate descent as
the algorithm to fit the coefficients.

The class MultiTaskElasticNetCV can be used to set the parameters
alpha ((alpha)) and l1_ratio ((rho)) by cross-validation.

1.1.7. Least Angle Regression¶

Least-angle regression (LARS) is a regression algorithm for
high-dimensional data, developed by Bradley Efron, Trevor Hastie, Iain
Johnstone and Robert Tibshirani. LARS is similar to forward stepwise
regression. At each step, it finds the feature most correlated with the
target. When there are multiple features having equal correlation, instead
of continuing along the same feature, it proceeds in a direction equiangular
between the features.

The advantages of LARS are:

  • It is numerically efficient in contexts where the number of features
    is significantly greater than the number of samples.

  • It is computationally just as fast as forward selection and has
    the same order of complexity as ordinary least squares.

  • It produces a full piecewise linear solution path, which is
    useful in cross-validation or similar attempts to tune the model.

  • If two features are almost equally correlated with the target,
    then their coefficients should increase at approximately the same
    rate. The algorithm thus behaves as intuition would expect, and
    also is more stable.

  • It is easily modified to produce solutions for other estimators,
    like the Lasso.

The disadvantages of the LARS method include:

  • Because LARS is based upon an iterative refitting of the
    residuals, it would appear to be especially sensitive to the
    effects of noise. This problem is discussed in detail by Weisberg
    in the discussion section of the Efron et al. (2004) Annals of
    Statistics article.

The LARS model can be used via the estimator Lars, or its
low-level implementation lars_path or lars_path_gram.

1.1.8. LARS Lasso¶

LassoLars is a lasso model implemented using the LARS
algorithm, and unlike the implementation based on coordinate descent,
this yields the exact solution, which is piecewise linear as a
function of the norm of its coefficients.

../_images/sphx_glr_plot_lasso_lars_001.png

>>> from sklearn import linear_model
>>> reg = linear_model.LassoLars(alpha=.1)
>>> reg.fit([[0, 0], [1, 1]], [0, 1])
LassoLars(alpha=0.1)
>>> reg.coef_
array([0.6..., 0.        ])

The Lars algorithm provides the full path of the coefficients along
the regularization parameter almost for free, thus a common operation
is to retrieve the path with one of the functions lars_path
or lars_path_gram.

1.1.8.1. Mathematical formulation¶

The algorithm is similar to forward stepwise regression, but instead
of including features at each step, the estimated coefficients are
increased in a direction equiangular to each one’s correlations with
the residual.

Instead of giving a vector result, the LARS solution consists of a
curve denoting the solution for each value of the (ell_1) norm of the
parameter vector. The full coefficients path is stored in the array
coef_path_ of shape (n_features, max_features + 1). The first
column is always zero.

1.1.9. Orthogonal Matching Pursuit (OMP)¶

OrthogonalMatchingPursuit and orthogonal_mp implement the OMP
algorithm for approximating the fit of a linear model with constraints imposed
on the number of non-zero coefficients (ie. the (ell_0) pseudo-norm).

Being a forward feature selection method like Least Angle Regression,
orthogonal matching pursuit can approximate the optimum solution vector with a
fixed number of non-zero elements:

[underset{w}{operatorname{arg,min,}} ||y — Xw||_2^2 text{ subject to } ||w||_0 leq n_{text{nonzero_coefs}}]

Alternatively, orthogonal matching pursuit can target a specific error instead
of a specific number of non-zero coefficients. This can be expressed as:

[underset{w}{operatorname{arg,min,}} ||w||_0 text{ subject to } ||y-Xw||_2^2 leq text{tol}]

OMP is based on a greedy algorithm that includes at each step the atom most
highly correlated with the current residual. It is similar to the simpler
matching pursuit (MP) method, but better in that at each iteration, the
residual is recomputed using an orthogonal projection on the space of the
previously chosen dictionary elements.

1.1.10. Bayesian Regression¶

Bayesian regression techniques can be used to include regularization
parameters in the estimation procedure: the regularization parameter is
not set in a hard sense but tuned to the data at hand.

This can be done by introducing uninformative priors
over the hyper parameters of the model.
The (ell_{2}) regularization used in Ridge regression and classification is
equivalent to finding a maximum a posteriori estimation under a Gaussian prior
over the coefficients (w) with precision (lambda^{-1}).
Instead of setting lambda manually, it is possible to treat it as a random
variable to be estimated from the data.

To obtain a fully probabilistic model, the output (y) is assumed
to be Gaussian distributed around (X w):

[p(y|X,w,alpha) = mathcal{N}(y|X w,alpha)]

where (alpha) is again treated as a random variable that is to be
estimated from the data.

The advantages of Bayesian Regression are:

  • It adapts to the data at hand.

  • It can be used to include regularization parameters in the
    estimation procedure.

The disadvantages of Bayesian regression include:

  • Inference of the model can be time consuming.

1.1.10.1. Bayesian Ridge Regression¶

BayesianRidge estimates a probabilistic model of the
regression problem as described above.
The prior for the coefficient (w) is given by a spherical Gaussian:

[p(w|lambda) =
mathcal{N}(w|0,lambda^{-1}mathbf{I}_{p})]

The priors over (alpha) and (lambda) are chosen to be gamma
distributions, the
conjugate prior for the precision of the Gaussian. The resulting model is
called Bayesian Ridge Regression, and is similar to the classical
Ridge.

The parameters (w), (alpha) and (lambda) are estimated
jointly during the fit of the model, the regularization parameters
(alpha) and (lambda) being estimated by maximizing the
log marginal likelihood. The scikit-learn implementation
is based on the algorithm described in Appendix A of (Tipping, 2001)
where the update of the parameters (alpha) and (lambda) is done
as suggested in (MacKay, 1992). The initial value of the maximization procedure
can be set with the hyperparameters alpha_init and lambda_init.

There are four more hyperparameters, (alpha_1), (alpha_2),
(lambda_1) and (lambda_2) of the gamma prior distributions over
(alpha) and (lambda). These are usually chosen to be
non-informative. By default (alpha_1 = alpha_2 = lambda_1 = lambda_2 = 10^{-6}).

Bayesian Ridge Regression is used for regression:

>>> from sklearn import linear_model
>>> X = [[0., 0.], [1., 1.], [2., 2.], [3., 3.]]
>>> Y = [0., 1., 2., 3.]
>>> reg = linear_model.BayesianRidge()
>>> reg.fit(X, Y)
BayesianRidge()

After being fitted, the model can then be used to predict new values:

>>> reg.predict([[1, 0.]])
array([0.50000013])

The coefficients (w) of the model can be accessed:

>>> reg.coef_
array([0.49999993, 0.49999993])

Due to the Bayesian framework, the weights found are slightly different to the
ones found by Ordinary Least Squares. However, Bayesian Ridge Regression
is more robust to ill-posed problems.

1.1.10.2. Automatic Relevance Determination — ARD¶

The Automatic Relevance Determination (as being implemented in
ARDRegression) is a kind of linear model which is very similar to the
Bayesian Ridge Regression, but that leads to sparser coefficients (w)
[1] [2].

ARDRegression poses a different prior over (w): it drops
the spherical Gaussian distribution for a centered elliptic Gaussian
distribution. This means each coefficient (w_{i}) can itself be drawn from
a Gaussian distribution, centered on zero and with a precision
(lambda_{i}):

[p(w|lambda) = mathcal{N}(w|0,A^{-1})]

with (A) being a positive definite diagonal matrix and
(text{diag}(A) = lambda = {lambda_{1},…,lambda_{p}}).

In contrast to the Bayesian Ridge Regression, each coordinate of
(w_{i}) has its own standard deviation (frac{1}{lambda_i}). The
prior over all (lambda_i) is chosen to be the same gamma distribution
given by the hyperparameters (lambda_1) and (lambda_2).

ARD is also known in the literature as Sparse Bayesian Learning and Relevance
Vector Machine
[3] [4]. For a worked-out comparison between ARD and Bayesian
Ridge Regression, see the example below.

1.1.11. Logistic regression¶

The logistic regression is implemented in LogisticRegression. Despite
its name, it is implemented as a linear model for classification rather than
regression in terms of the scikit-learn/ML nomenclature. The logistic
regression is also known in the literature as logit regression,
maximum-entropy classification (MaxEnt) or the log-linear classifier. In this
model, the probabilities describing the possible outcomes of a single trial
are modeled using a logistic function.

This implementation can fit binary, One-vs-Rest, or multinomial logistic
regression with optional (ell_1), (ell_2) or Elastic-Net
regularization.

Note

Regularization

Regularization is applied by default, which is common in machine
learning but not in statistics. Another advantage of regularization is
that it improves numerical stability. No regularization amounts to
setting C to a very high value.

Note

Logistic Regression as a special case of the Generalized Linear Models (GLM)

Logistic regression is a special case of
Generalized Linear Models with a Binomial / Bernoulli conditional
distribution and a Logit link. The numerical output of the logistic
regression, which is the predicted probability, can be used as a classifier
by applying a threshold (by default 0.5) to it. This is how it is
implemented in scikit-learn, so it expects a categorical target, making
the Logistic Regression a classifier.

1.1.11.1. Binary Case¶

For notational ease, we assume that the target (y_i) takes values in the
set ({0, 1}) for data point (i).
Once fitted, the predict_proba
method of LogisticRegression predicts
the probability of the positive class (P(y_i=1|X_i)) as

[hat{p}(X_i) = operatorname{expit}(X_i w + w_0) = frac{1}{1 + exp(-X_i w — w_0)}.]

As an optimization problem, binary
class logistic regression with regularization term (r(w)) minimizes the
following cost function:

[min_{w} C sum_{i=1}^n left(-y_i log(hat{p}(X_i)) — (1 — y_i) log(1 — hat{p}(X_i))right) + r(w).]

We currently provide four choices for the regularization term (r(w)) via
the penalty argument:

penalty

(r(w))

None

(0)

(ell_1)

(|w|_1)

(ell_2)

(frac{1}{2}|w|_2^2 = frac{1}{2}w^T w)

ElasticNet

(frac{1 — rho}{2}w^T w + rho |w|_1)

For ElasticNet, (rho) (which corresponds to the l1_ratio parameter)
controls the strength of (ell_1) regularization vs. (ell_2)
regularization. Elastic-Net is equivalent to (ell_1) when
(rho = 1) and equivalent to (ell_2) when (rho=0).

1.1.11.2. Multinomial Case¶

The binary case can be extended to (K) classes leading to the multinomial
logistic regression, see also log-linear model.

Note

It is possible to parameterize a (K)-class classification model
using only (K-1) weight vectors, leaving one class probability fully
determined by the other class probabilities by leveraging the fact that all
class probabilities must sum to one. We deliberately choose to overparameterize the model
using (K) weight vectors for ease of implementation and to preserve the
symmetrical inductive bias regarding ordering of classes, see [16]. This effect becomes
especially important when using regularization. The choice of overparameterization can be
detrimental for unpenalized models since then the solution may not be unique, as shown in [16].

Let (y_i in {1, ldots, K}) be the label (ordinal) encoded target variable for observation (i).
Instead of a single coefficient vector, we now have
a matrix of coefficients (W) where each row vector (W_k) corresponds to class
(k). We aim at predicting the class probabilities (P(y_i=k|X_i)) via
predict_proba as:

[hat{p}_k(X_i) = frac{exp(X_i W_k + W_{0, k})}{sum_{l=0}^{K-1} exp(X_i W_l + W_{0, l})}.]

The objective for the optimization becomes

[min_W -C sum_{i=1}^n sum_{k=0}^{K-1} [y_i = k] log(hat{p}_k(X_i)) + r(W).]

Where ([P]) represents the Iverson bracket which evaluates to (0)
if (P) is false, otherwise it evaluates to (1). We currently provide four choices
for the regularization term (r(W)) via the penalty argument:

penalty

(r(W))

None

(0)

(ell_1)

(|W|_{1,1} = sum_{i=1}^nsum_{j=1}^{K}|W_{i,j}|)

(ell_2)

(frac{1}{2}|W|_F^2 = frac{1}{2}sum_{i=1}^nsum_{j=1}^{K} W_{i,j}^2)

ElasticNet

(frac{1 — rho}{2}|W|_F^2 + rho |W|_{1,1})

1.1.11.3. Solvers¶

The solvers implemented in the class LogisticRegression
are “lbfgs”, “liblinear”, “newton-cg”, “newton-cholesky”, “sag” and “saga”:

The solver “liblinear” uses a coordinate descent (CD) algorithm, and relies
on the excellent C++ LIBLINEAR library, which is shipped with
scikit-learn. However, the CD algorithm implemented in liblinear cannot learn
a true multinomial (multiclass) model; instead, the optimization problem is
decomposed in a “one-vs-rest” fashion so separate binary classifiers are
trained for all classes. This happens under the hood, so
LogisticRegression instances using this solver behave as multiclass
classifiers. For (ell_1) regularization sklearn.svm.l1_min_c allows to
calculate the lower bound for C in order to get a non “null” (all feature
weights to zero) model.

The “lbfgs”, “newton-cg” and “sag” solvers only support (ell_2)
regularization or no regularization, and are found to converge faster for some
high-dimensional data. Setting multi_class to “multinomial” with these solvers
learns a true multinomial logistic regression model [5], which means that its
probability estimates should be better calibrated than the default “one-vs-rest”
setting.

The “sag” solver uses Stochastic Average Gradient descent [6]. It is faster
than other solvers for large datasets, when both the number of samples and the
number of features are large.

The “saga” solver [7] is a variant of “sag” that also supports the
non-smooth penalty="l1". This is therefore the solver of choice for sparse
multinomial logistic regression. It is also the only solver that supports
penalty="elasticnet".

The “lbfgs” is an optimization algorithm that approximates the
Broyden–Fletcher–Goldfarb–Shanno algorithm [8], which belongs to
quasi-Newton methods. As such, it can deal with a wide range of different training
data and is therefore the default solver. Its performance, however, suffers on poorly
scaled datasets and on datasets with one-hot encoded categorical features with rare
categories.

The “newton-cholesky” solver is an exact Newton solver that calculates the hessian
matrix and solves the resulting linear system. It is a very good choice for
n_samples >> n_features, but has a few shortcomings: Only (ell_2)
regularization is supported. Furthermore, because the hessian matrix is explicitly
computed, the memory usage has a quadratic dependency on n_features as well as on
n_classes. As a consequence, only the one-vs-rest scheme is implemented for the
multiclass case.

For a comparison of some of these solvers, see [9].

The following table summarizes the penalties supported by each solver:

Solvers

Penalties

‘lbfgs’

‘liblinear’

‘newton-cg’

‘newton-cholesky’

‘sag’

‘saga’

Multinomial + L2 penalty

yes

no

yes

no

yes

yes

OVR + L2 penalty

yes

yes

yes

yes

yes

yes

Multinomial + L1 penalty

no

no

no

no

no

yes

OVR + L1 penalty

no

yes

no

no

no

yes

Elastic-Net

no

no

no

no

no

yes

No penalty (‘none’)

yes

no

yes

yes

yes

yes

Behaviors

Penalize the intercept (bad)

no

yes

no

no

no

no

Faster for large datasets

no

no

no

no

yes

yes

Robust to unscaled datasets

yes

yes

yes

yes

no

no

The “lbfgs” solver is used by default for its robustness. For large datasets
the “saga” solver is usually faster.
For large dataset, you may also consider using SGDClassifier
with loss="log_loss", which might be even faster but requires more tuning.

Note

Feature selection with sparse logistic regression

A logistic regression with (ell_1) penalty yields sparse models, and can
thus be used to perform feature selection, as detailed in
L1-based feature selection.

Note

P-value estimation

It is possible to obtain the p-values and confidence intervals for
coefficients in cases of regression without penalization. The statsmodels
package natively supports this.
Within sklearn, one could use bootstrapping instead as well.

LogisticRegressionCV implements Logistic Regression with built-in
cross-validation support, to find the optimal C and l1_ratio parameters
according to the scoring attribute. The “newton-cg”, “sag”, “saga” and
“lbfgs” solvers are found to be faster for high-dimensional dense data, due
to warm-starting (see Glossary).

1.1.12. Generalized Linear Models¶

Generalized Linear Models (GLM) extend linear models in two ways
[10]. First, the predicted values (hat{y}) are linked to a linear
combination of the input variables (X) via an inverse link function
(h) as

[hat{y}(w, X) = h(Xw).]

Secondly, the squared loss function is replaced by the unit deviance
(d) of a distribution in the exponential family (or more precisely, a
reproductive exponential dispersion model (EDM) [11]).

The minimization problem becomes:

[min_{w} frac{1}{2 n_{text{samples}}} sum_i d(y_i, hat{y}_i) + frac{alpha}{2} ||w||_2^2,]

where (alpha) is the L2 regularization penalty. When sample weights are
provided, the average becomes a weighted average.

The following table lists some specific EDMs and their unit deviance :

Distribution

Target Domain

Unit Deviance (d(y, hat{y}))

Normal

(y in (-infty, infty))

((y-hat{y})^2)

Bernoulli

(y in {0, 1})

(2({y}logfrac{y}{hat{y}}+({1}-{y})logfrac{{1}-{y}}{{1}-hat{y}}))

Categorical

(y in {0, 1, …, k})

(2sum_{i in {0, 1, …, k}} I(y = i) y_text{i}logfrac{I(y = i)}{hat{I(y = i)}})

Poisson

(y in [0, infty))

(2(ylogfrac{y}{hat{y}}-y+hat{y}))

Gamma

(y in (0, infty))

(2(logfrac{y}{hat{y}}+frac{y}{hat{y}}-1))

Inverse Gaussian

(y in (0, infty))

(frac{(y-hat{y})^2}{yhat{y}^2})

The Probability Density Functions (PDF) of these distributions are illustrated
in the following figure,

../_images/poisson_gamma_tweedie_distributions.png

PDF of a random variable Y following Poisson, Tweedie (power=1.5) and Gamma
distributions with different mean values ((mu)). Observe the point
mass at (Y=0) for the Poisson distribution and the Tweedie (power=1.5)
distribution, but not for the Gamma distribution which has a strictly
positive target domain.

The Bernoulli distribution is a discrete probability distribution modelling a
Bernoulli trial — an event that has only two mutually exclusive outcomes.
The Categorical distribution is a generalization of the Bernoulli distribution
for a categorical random variable. While a random variable in a Bernoulli
distribution has two possible outcomes, a Categorical random variable can take
on one of K possible categories, with the probability of each category
specified separately.

The choice of the distribution depends on the problem at hand:

  • If the target values (y) are counts (non-negative integer valued) or
    relative frequencies (non-negative), you might use a Poisson distribution
    with a log-link.

  • If the target values are positive valued and skewed, you might try a Gamma
    distribution with a log-link.

  • If the target values seem to be heavier tailed than a Gamma distribution, you
    might try an Inverse Gaussian distribution (or even higher variance powers of
    the Tweedie family).

  • If the target values (y) are probabilities, you can use the Bernoulli
    distribution. The Bernoulli distribution with a logit link can be used for
    binary classification. The Categorical distribution with a softmax link can be
    used for multiclass classification.

Examples of use cases include:

  • Agriculture / weather modeling: number of rain events per year (Poisson),
    amount of rainfall per event (Gamma), total rainfall per year (Tweedie /
    Compound Poisson Gamma).

  • Risk modeling / insurance policy pricing: number of claim events /
    policyholder per year (Poisson), cost per event (Gamma), total cost per
    policyholder per year (Tweedie / Compound Poisson Gamma).

  • Credit Default: probability that a loan can’t be payed back (Bernouli).

  • Fraud Detection: probability that a financial transaction like a cash transfer
    is a fraudulent transaction (Bernoulli).

  • Predictive maintenance: number of production interruption events per year
    (Poisson), duration of interruption (Gamma), total interruption time per year
    (Tweedie / Compound Poisson Gamma).

  • Medical Drug Testing: probability of curing a patient in a set of trials or
    probability that a patient will experience side effects (Bernoulli).

  • News Classification: classification of news articles into three categories
    namely Business News, Politics and Entertainment news (Categorical).

1.1.12.1. Usage¶

TweedieRegressor implements a generalized linear model for the
Tweedie distribution, that allows to model any of the above mentioned
distributions using the appropriate power parameter. In particular:

  • power = 0: Normal distribution. Specific estimators such as
    Ridge, ElasticNet are generally more appropriate in
    this case.

  • power = 1: Poisson distribution. PoissonRegressor is exposed
    for convenience. However, it is strictly equivalent to
    TweedieRegressor(power=1, link='log').

  • power = 2: Gamma distribution. GammaRegressor is exposed for
    convenience. However, it is strictly equivalent to
    TweedieRegressor(power=2, link='log').

  • power = 3: Inverse Gaussian distribution.

The link function is determined by the link parameter.

Usage example:

>>> from sklearn.linear_model import TweedieRegressor
>>> reg = TweedieRegressor(power=1, alpha=0.5, link='log')
>>> reg.fit([[0, 0], [0, 1], [2, 2]], [0, 1, 2])
TweedieRegressor(alpha=0.5, link='log', power=1)
>>> reg.coef_
array([0.2463..., 0.4337...])
>>> reg.intercept_
-0.7638...

1.1.12.2. Practical considerations¶

The feature matrix X should be standardized before fitting. This ensures
that the penalty treats features equally.

Since the linear predictor (Xw) can be negative and Poisson,
Gamma and Inverse Gaussian distributions don’t support negative values, it
is necessary to apply an inverse link function that guarantees the
non-negativeness. For example with link='log', the inverse link function
becomes (h(Xw)=exp(Xw)).

If you want to model a relative frequency, i.e. counts per exposure (time,
volume, …) you can do so by using a Poisson distribution and passing
(y=frac{mathrm{counts}}{mathrm{exposure}}) as target values
together with (mathrm{exposure}) as sample weights. For a concrete
example see e.g.
Tweedie regression on insurance claims.

When performing cross-validation for the power parameter of
TweedieRegressor, it is advisable to specify an explicit scoring function,
because the default scorer TweedieRegressor.score is a function of
power itself.

1.1.13. Stochastic Gradient Descent — SGD¶

Stochastic gradient descent is a simple yet very efficient approach
to fit linear models. It is particularly useful when the number of samples
(and the number of features) is very large.
The partial_fit method allows online/out-of-core learning.

The classes SGDClassifier and SGDRegressor provide
functionality to fit linear models for classification and regression
using different (convex) loss functions and different penalties.
E.g., with loss="log", SGDClassifier
fits a logistic regression model,
while with loss="hinge" it fits a linear support vector machine (SVM).

1.1.14. Perceptron¶

The Perceptron is another simple classification algorithm suitable for
large scale learning. By default:

  • It does not require a learning rate.

  • It is not regularized (penalized).

  • It updates its model only on mistakes.

The last characteristic implies that the Perceptron is slightly faster to
train than SGD with the hinge loss and that the resulting models are
sparser.

1.1.15. Passive Aggressive Algorithms¶

The passive-aggressive algorithms are a family of algorithms for large-scale
learning. They are similar to the Perceptron in that they do not require a
learning rate. However, contrary to the Perceptron, they include a
regularization parameter C.

For classification, PassiveAggressiveClassifier can be used with
loss='hinge' (PA-I) or loss='squared_hinge' (PA-II). For regression,
PassiveAggressiveRegressor can be used with
loss='epsilon_insensitive' (PA-I) or
loss='squared_epsilon_insensitive' (PA-II).

1.1.16. Robustness regression: outliers and modeling errors¶

Robust regression aims to fit a regression model in the
presence of corrupt data: either outliers, or error in the model.

../_images/sphx_glr_plot_theilsen_001.png

1.1.16.1. Different scenario and useful concepts¶

There are different things to keep in mind when dealing with data
corrupted by outliers:

  • Outliers in X or in y?

    Outliers in the y direction

    Outliers in the X direction

    y_outliers

    X_outliers

  • Fraction of outliers versus amplitude of error

    The number of outlying points matters, but also how much they are
    outliers.

    Small outliers

    Large outliers

    y_outliers

    large_y_outliers

An important notion of robust fitting is that of breakdown point: the
fraction of data that can be outlying for the fit to start missing the
inlying data.

Note that in general, robust fitting in high-dimensional setting (large
n_features) is very hard. The robust models here will probably not work
in these settings.

1.1.16.2. RANSAC: RANdom SAmple Consensus¶

RANSAC (RANdom SAmple Consensus) fits a model from random subsets of
inliers from the complete data set.

RANSAC is a non-deterministic algorithm producing only a reasonable result with
a certain probability, which is dependent on the number of iterations (see
max_trials parameter). It is typically used for linear and non-linear
regression problems and is especially popular in the field of photogrammetric
computer vision.

The algorithm splits the complete input sample data into a set of inliers,
which may be subject to noise, and outliers, which are e.g. caused by erroneous
measurements or invalid hypotheses about the data. The resulting model is then
estimated only from the determined inliers.

../_images/sphx_glr_plot_ransac_001.png

1.1.16.2.1. Details of the algorithm¶

Each iteration performs the following steps:

  1. Select min_samples random samples from the original data and check
    whether the set of data is valid (see is_data_valid).

  2. Fit a model to the random subset (base_estimator.fit) and check
    whether the estimated model is valid (see is_model_valid).

  3. Classify all data as inliers or outliers by calculating the residuals
    to the estimated model (base_estimator.predict(X) - y) — all data
    samples with absolute residuals smaller than or equal to the
    residual_threshold are considered as inliers.

  4. Save fitted model as best model if number of inlier samples is
    maximal. In case the current estimated model has the same number of
    inliers, it is only considered as the best model if it has better score.

These steps are performed either a maximum number of times (max_trials) or
until one of the special stop criteria are met (see stop_n_inliers and
stop_score). The final model is estimated using all inlier samples (consensus
set) of the previously determined best model.

The is_data_valid and is_model_valid functions allow to identify and reject
degenerate combinations of random sub-samples. If the estimated model is not
needed for identifying degenerate cases, is_data_valid should be used as it
is called prior to fitting the model and thus leading to better computational
performance.

1.1.16.3. Theil-Sen estimator: generalized-median-based estimator¶

The TheilSenRegressor estimator uses a generalization of the median in
multiple dimensions. It is thus robust to multivariate outliers. Note however
that the robustness of the estimator decreases quickly with the dimensionality
of the problem. It loses its robustness properties and becomes no
better than an ordinary least squares in high dimension.

1.1.16.3.1. Theoretical considerations¶

TheilSenRegressor is comparable to the Ordinary Least Squares
(OLS)
in terms of asymptotic efficiency and as an
unbiased estimator. In contrast to OLS, Theil-Sen is a non-parametric
method which means it makes no assumption about the underlying
distribution of the data. Since Theil-Sen is a median-based estimator, it
is more robust against corrupted data aka outliers. In univariate
setting, Theil-Sen has a breakdown point of about 29.3% in case of a
simple linear regression which means that it can tolerate arbitrary
corrupted data of up to 29.3%.

../_images/sphx_glr_plot_theilsen_001.png

The implementation of TheilSenRegressor in scikit-learn follows a
generalization to a multivariate linear regression model [14] using the
spatial median which is a generalization of the median to multiple
dimensions [15].

In terms of time and space complexity, Theil-Sen scales according to

[binom{n_{text{samples}}}{n_{text{subsamples}}}]

which makes it infeasible to be applied exhaustively to problems with a
large number of samples and features. Therefore, the magnitude of a
subpopulation can be chosen to limit the time and space complexity by
considering only a random subset of all possible combinations.

1.1.16.4. Huber Regression¶

The HuberRegressor is different to Ridge because it applies a
linear loss to samples that are classified as outliers.
A sample is classified as an inlier if the absolute error of that sample is
lesser than a certain threshold. It differs from TheilSenRegressor
and RANSACRegressor because it does not ignore the effect of the outliers
but gives a lesser weight to them.

../_images/sphx_glr_plot_huber_vs_ridge_001.png

The loss function that HuberRegressor minimizes is given by

[min_{w, sigma} {sum_{i=1}^nleft(sigma + H_{epsilon}left(frac{X_{i}w — y_{i}}{sigma}right)sigmaright) + alpha {||w||_2}^2}]

where

[begin{split}H_{epsilon}(z) = begin{cases}
z^2, & text {if } |z| < epsilon,
2epsilon|z| — epsilon^2, & text{otherwise}
end{cases}end{split}]

It is advised to set the parameter epsilon to 1.35 to achieve 95% statistical efficiency.

1.1.16.5. Notes¶

The HuberRegressor differs from using SGDRegressor with loss set to huber
in the following ways.

  • HuberRegressor is scaling invariant. Once epsilon is set, scaling X and y
    down or up by different values would produce the same robustness to outliers as before.
    as compared to SGDRegressor where epsilon has to be set again when X and y are
    scaled.

  • HuberRegressor should be more efficient to use on data with small number of
    samples while SGDRegressor needs a number of passes on the training data to
    produce the same robustness.

Note that this estimator is different from the R implementation of Robust Regression
(https://stats.oarc.ucla.edu/r/dae/robust-regression/) because the R implementation does a weighted least
squares implementation with weights given to each sample on the basis of how much the residual is
greater than a certain threshold.

1.1.17. Quantile Regression¶

Quantile regression estimates the median or other quantiles of (y)
conditional on (X), while ordinary least squares (OLS) estimates the
conditional mean.

As a linear model, the QuantileRegressor gives linear predictions
(hat{y}(w, X) = Xw) for the (q)-th quantile, (q in (0, 1)).
The weights or coefficients (w) are then found by the following
minimization problem:

[min_{w} {frac{1}{n_{text{samples}}}
sum_i PB_q(y_i — X_i w) + alpha ||w||_1}.]

This consists of the pinball loss (also known as linear loss),
see also mean_pinball_loss,

[begin{split}PB_q(t) = q max(t, 0) + (1 — q) max(-t, 0) =
begin{cases}
q t, & t > 0,
0, & t = 0,
(q-1) t, & t < 0
end{cases}end{split}]

and the L1 penalty controlled by parameter alpha, similar to
Lasso.

As the pinball loss is only linear in the residuals, quantile regression is
much more robust to outliers than squared error based estimation of the mean.
Somewhat in between is the HuberRegressor.

Quantile regression may be useful if one is interested in predicting an
interval instead of point prediction. Sometimes, prediction intervals are
calculated based on the assumption that prediction error is distributed
normally with zero mean and constant variance. Quantile regression provides
sensible prediction intervals even for errors with non-constant (but
predictable) variance or non-normal distribution.

../_images/sphx_glr_plot_quantile_regression_002.png

Based on minimizing the pinball loss, conditional quantiles can also be
estimated by models other than linear models. For example,
GradientBoostingRegressor can predict conditional
quantiles if its parameter loss is set to "quantile" and parameter
alpha is set to the quantile that should be predicted. See the example in
Prediction Intervals for Gradient Boosting Regression.

Most implementations of quantile regression are based on linear programming
problem. The current implementation is based on
scipy.optimize.linprog.

1.1.18. Polynomial regression: extending linear models with basis functions¶

One common pattern within machine learning is to use linear models trained
on nonlinear functions of the data. This approach maintains the generally
fast performance of linear methods, while allowing them to fit a much wider
range of data.

For example, a simple linear regression can be extended by constructing
polynomial features from the coefficients. In the standard linear
regression case, you might have a model that looks like this for
two-dimensional data:

[hat{y}(w, x) = w_0 + w_1 x_1 + w_2 x_2]

If we want to fit a paraboloid to the data instead of a plane, we can combine
the features in second-order polynomials, so that the model looks like this:

[hat{y}(w, x) = w_0 + w_1 x_1 + w_2 x_2 + w_3 x_1 x_2 + w_4 x_1^2 + w_5 x_2^2]

The (sometimes surprising) observation is that this is still a linear model:
to see this, imagine creating a new set of features

[z = [x_1, x_2, x_1 x_2, x_1^2, x_2^2]]

With this re-labeling of the data, our problem can be written

[hat{y}(w, z) = w_0 + w_1 z_1 + w_2 z_2 + w_3 z_3 + w_4 z_4 + w_5 z_5]

We see that the resulting polynomial regression is in the same class of
linear models we considered above (i.e. the model is linear in (w))
and can be solved by the same techniques. By considering linear fits within
a higher-dimensional space built with these basis functions, the model has the
flexibility to fit a much broader range of data.

Here is an example of applying this idea to one-dimensional data, using
polynomial features of varying degrees:

../_images/sphx_glr_plot_polynomial_interpolation_001.png

This figure is created using the PolynomialFeatures transformer, which
transforms an input data matrix into a new data matrix of a given degree.
It can be used as follows:

>>> from sklearn.preprocessing import PolynomialFeatures
>>> import numpy as np
>>> X = np.arange(6).reshape(3, 2)
>>> X
array([[0, 1],
       [2, 3],
       [4, 5]])
>>> poly = PolynomialFeatures(degree=2)
>>> poly.fit_transform(X)
array([[ 1.,  0.,  1.,  0.,  0.,  1.],
       [ 1.,  2.,  3.,  4.,  6.,  9.],
       [ 1.,  4.,  5., 16., 20., 25.]])

The features of X have been transformed from ([x_1, x_2]) to
([1, x_1, x_2, x_1^2, x_1 x_2, x_2^2]), and can now be used within
any linear model.

This sort of preprocessing can be streamlined with the
Pipeline tools. A single object representing a simple
polynomial regression can be created and used as follows:

>>> from sklearn.preprocessing import PolynomialFeatures
>>> from sklearn.linear_model import LinearRegression
>>> from sklearn.pipeline import Pipeline
>>> import numpy as np
>>> model = Pipeline([('poly', PolynomialFeatures(degree=3)),
...                   ('linear', LinearRegression(fit_intercept=False))])
>>> # fit to an order-3 polynomial data
>>> x = np.arange(5)
>>> y = 3 - 2 * x + x ** 2 - x ** 3
>>> model = model.fit(x[:, np.newaxis], y)
>>> model.named_steps['linear'].coef_
array([ 3., -2.,  1., -1.])

The linear model trained on polynomial features is able to exactly recover
the input polynomial coefficients.

In some cases it’s not necessary to include higher powers of any single feature,
but only the so-called interaction features
that multiply together at most (d) distinct features.
These can be gotten from PolynomialFeatures with the setting
interaction_only=True.

For example, when dealing with boolean features,
(x_i^n = x_i) for all (n) and is therefore useless;
but (x_i x_j) represents the conjunction of two booleans.
This way, we can solve the XOR problem with a linear classifier:

>>> from sklearn.linear_model import Perceptron
>>> from sklearn.preprocessing import PolynomialFeatures
>>> import numpy as np
>>> X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
>>> y = X[:, 0] ^ X[:, 1]
>>> y
array([0, 1, 1, 0])
>>> X = PolynomialFeatures(interaction_only=True).fit_transform(X).astype(int)
>>> X
array([[1, 0, 0, 0],
       [1, 0, 1, 0],
       [1, 1, 0, 0],
       [1, 1, 1, 1]])
>>> clf = Perceptron(fit_intercept=False, max_iter=10, tol=None,
...                  shuffle=False).fit(X, y)

And the classifier “predictions” are perfect:

>>> clf.predict(X)
array([0, 1, 1, 0])
>>> clf.score(X, y)
1.0

2 декабря 2017 г.

Теория регрессионного анализа

Модель из категории «обучение с учителем», устанавливающая связь между одним или несколькими независимыми признааками (предикторами) и завсимыми континуальным призаком. В линейной регрессии моделью зависимости является формула прямой линии.

Простая линейная регрессия

Простая (одномерная) линейная регрессия (модель с одним предиктором) аппроксимируется всем известной со школы функцией прямой линии
y=kx+bLarge y=kx+b с той лишь разницей, что теперь нужно добавить случайную ошибку ϵepsilon:
yi=β0+β1xi+ϵiLarge y_i = beta_0 + beta_1 x_i + epsilon_i
где

  • yiy_i — зависимая переменная (отклик)
  • xix_i — известная константа (значение объясняющей переменной, измерянной в i-ом эксперименте)
  • β0beta_0, β1beta_1 — параметры модели (свободный член и угловой коэффициент).
  • ϵiepsilon_i — случайная ошибка

Графически она выглядит следующим образом:

Множественная линейная регрессия

Для простоты примем, что x0=1x_0 = 1. Тогда в общем случае форма линейной регрессии выглядит следующим так:
∀h∈H,h(x⃗)=w0x0+w1x1+w2x2+⋯+wmxm=∑i=0mwixi=x⃗Tw⃗Large begin{array}{rcl} forall h in mathcal{H}, hleft(vec{x}right) = w_0 x_0 + w_1 x_1 + w_2 x_2 + cdots + w_m x_m = sum_{i=0}^m w_i x_i = vec{x}^T vec{w} end{array}

Пример графика для линейной регрессионной модели с двумя независимыми переменными:

Обучение линейной регрессии

Метод наименьших квадратов

Чаще всего линейная регрессия обучается с использованием среднеквадратичной ошибки. В этом случае получаем задачу оптимизации функции стоимости (эмпирического риска) (считаем, что среди признаков есть константный, и поэтому свободный коэффициент не нужен):
L(X,y⃗,w⃗)=12n∑i=1n(yi−x⃗iTw⃗i)2=12n∣y⃗−Xw⃗∣22=12n(y⃗−Xw⃗)T(y⃗−Xw⃗)Large begin{array}{rcl}mathcal{L}left(X, vec{y}, vec{w} right) = frac{1}{2n} sum_{i=1}^n left(y_i — vec{x}_i^T vec{w}_iright)^2 = frac{1}{2n} left| vec{y} — X vec{w} right|_2^2 = frac{1}{2n} left(vec{y} — X vec{w}right)^T left(vec{y} — X vec{w}right) end{array}

Если продифференцировать данный функционал по вектору ww, приравнять к нулю и решить уравнение, то получим явную формулу для решения:

w⃗=(XTX)−1XTy⃗Large vec{w} = left(X^T Xright)^{-1} X^T vec{y}

Полный вывод формулы, для рассчёта коэффициентов

Шпаргалка по матричным производным:
∂∂xxTa=a∂∂xxTAx=(A+AT)x∂∂AxTAy=xyT∂∂xA−1=−A−1∂A∂xA−1large begin{array}{rcl} frac{partial}{partial x} x^T a = a \ frac{partial}{partial x} x^T A x = left(A + A^Tright)x \ frac{partial}{partial A} x^T A y = xy^T\ frac{partial}{partial x} A^{-1} = -A^{-1} frac{partial A}{partial x} A^{-1} end{array}

Вычислим производную функции стоимости:
∂L∂w⃗=∂∂w⃗12n(y⃗Ty⃗−2y⃗TXw⃗+w⃗TXTXw⃗)=12n(−2XTy⃗+2XTXw⃗)large begin{array}{rcl} frac{partial mathcal{L}}{partial vec{w}} = frac{partial}{partial vec{w}} frac{1}{2n} left( vec{y}^T vec{y} -2vec{y}^T X vec{w} + vec{w}^T X^T X vec{w}right) = frac{1}{2n} left(-2 X^T vec{y} + 2X^T X vec{w}right) end{array}

Приравняем к нулю и найдем решение в явном виде:

∂L∂w⃗=0amp;⇔amp;12n(−2XTy⃗+2XTXw⃗)=0amp;⇔amp;−XTy⃗+XTXw⃗=0amp;⇔amp;XTXw⃗=XTy⃗amp;⇔amp;w⃗=(XTX)−1XTy⃗Large begin{array}{rcl} frac{partial mathcal{L}}{partial vec{w}} = 0 &amp;Leftrightarrow&amp; frac{1}{2n} left(-2 X^T vec{y} + 2X^T X vec{w}right) = 0 &amp;Leftrightarrow&amp; -X^T vec{y} + X^T X vec{w} = 0 &amp;Leftrightarrow&amp; X^T X vec{w} = X^T vec{y} &amp;Leftrightarrow&amp; vec{w} = left(X^T Xright)^{-1} X^T vec{y} end{array}

Оценка МНК является лучшей оценкой параметров модели, среди всех линейных и несмещенных оценок, то есть обладающей наименьшей дисперсией (из теоремы Гаусса — Маркова).

Недостатки метода наименьших квадратов
Наличие явной формулы для оптимального вектора весов — это большое преимущество линейной регрессии. Но аналитическое решение не всегда возможно по ряду причин:

  • Обращение матрицы — сложная операция с кубической сложностью от количества признаков. Если в выборке тысячи признаков, то вычисления могут стать слишком трудоёмкими. Решить эту проблему можно путём использования численных методов оптимизации.
  • Матрица XTXX^T X может быть вырожденной (определитель равено 0 → нет обратной матрицы) или плохо обусловленной. В этом случае обращение либо невозможно, либо может привести к неустойчивым результатам. Проблема решается с помощью регуляризации.

Ограничения линейной регрессии

  • Линейность: зависимая переменная может линейно аппроксимировать независимые переменные
  • Нормальность распределения Y и ε
  • Отсутствие избытка влиятельных наблюдений
  • Гомоскадастичность распределения остатков
  • Отсутсвие мультиколлинеарности

Не все эти ограничения должны соблюдаться на 100%, особенно, если Вы занимаетес машинным обучением.

Практическое занятие с Housing Dataset

Применим полученные навыки для анализа данных о ценах на недвижимость в Бостоне, опубликованных в статье 1978 г.

Ещё данные можно подгрузить из втроенной в sklearn коллекции:

import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns %matplotlib inline %config InlineBackend.figure_format = 'svg' from sklearn.datasets import load_boston boston = load_boston() print(boston.DESCR) boston_df = pd.DataFrame(boston.data, columns=boston.feature_names) boston_df['MEDV'] = boston.target boston_df.head()

.. _boston_dataset:

Boston house prices dataset

—————————

**Data Set Characteristics:**

:Number of Instances: 506

:Number of Attributes: 13 numeric/categorical predictive. Median Value (attribute 14) is usually the target.

:Attribute Information (in order):

— CRIM per capita crime rate by town

— ZN proportion of residential land zoned for lots over 25,000 sq.ft.

— INDUS proportion of non-retail business acres per town

— CHAS Charles River dummy variable (= 1 if tract bounds river; 0 otherwise)

— NOX nitric oxides concentration (parts per 10 million)

— RM average number of rooms per dwelling

— AGE proportion of owner-occupied units built prior to 1940

— DIS weighted distances to five Boston employment centres

— RAD index of accessibility to radial highways

— TAX full-value property-tax rate per $10,000

— PTRATIO pupil-teacher ratio by town

— B 1000(Bk — 0.63)^2 where Bk is the proportion of blacks by town

— LSTAT % lower status of the population

— MEDV Median value of owner-occupied homes in $1000’s

:Missing Attribute Values: None

:Creator: Harrison, D. and Rubinfeld, D.L.

This is a copy of UCI ML housing dataset.

https://archive.ics.uci.edu/ml/machine-learning-databases/housing/

This dataset was taken from the StatLib library which is maintained at Carnegie Mellon University.

The Boston house-price data of Harrison, D. and Rubinfeld, D.L. ‘Hedonic

prices and the demand for clean air’, J. Environ. Economics & Management,

vol.5, 81-102, 1978. Used in Belsley, Kuh & Welsch, ‘Regression diagnostics

…’, Wiley, 1980. N.B. Various transformations are used in the table on

pages 244-261 of the latter.

The Boston house-price data has been used in many machine learning papers that address regression

problems.

.. topic:: References

— Belsley, Kuh & Welsch, ‘Regression diagnostics: Identifying Influential Data and Sources of Collinearity’, Wiley, 1980. 244-261.

— Quinlan,R. (1993). Combining Instance-Based and Model-Based Learning. In Proceedings on the Tenth International Conference of Machine Learning, 236-243, University of Massachusetts, Amherst. Morgan Kaufmann.

CRIM ZN INDUS CHAS NOX RM AGE DIS RAD TAX PTRATIO B LSTAT MEDV
0 0.00632 18.0 2.31 0.0 0.538 6.575 65.2 4.0900 1.0 296.0 15.3 396.90 4.98 24.0
1 0.02731 0.0 7.07 0.0 0.469 6.421 78.9 4.9671 2.0 242.0 17.8 396.90 9.14 21.6
2 0.02729 0.0 7.07 0.0 0.469 7.185 61.1 4.9671 2.0 242.0 17.8 392.83 4.03 34.7
3 0.03237 0.0 2.18 0.0 0.458 6.998 45.8 6.0622 3.0 222.0 18.7 394.63 2.94 33.4
4 0.06905 0.0 2.18 0.0 0.458 7.147 54.2 6.0622 3.0 222.0 18.7 396.90 5.33 36.2

Разведывательный анализ

Как всегда начинаем изучение нового набора данных с разглядывания графиков. Визуализируем матрицу корреляций и ввиде тепловой карты:

cols = ['LSTAT', 'INDUS', 'NOX', 'RM', 'MEDV'] hm = sns.heatmap(boston_df[cols].corr(), cbar=True, annot=True)

Регрессионный анализ

Приступим к построению регрессионной модели. Определим зависимые и независимые переменные:

X = boston_df[['LSTAT']].values y = boston_df['MEDV'].values

from sklearn.linear_model import LinearRegression slr = LinearRegression() slr.fit(X, y) y_pred = slr.predict(X) print('Slope: {:.2f}'.format(slr.coef_[0])) print('Intercept: {:.2f}'.format(slr.intercept_))

Slope: -0.95

Intercept: 34.55

PS: эти непонятные символы в фигурных скобках задают вид форматирования строки. В данном случае мы говорим, что хотим вывести число с плавающей точкой с точностью до двух знаков. Подробнее в Format Specification Mini-Language.

plt.scatter(X, y) plt.plot(X, slr.predict(X), color='red', linewidth=2);

Для быстрой визуализации линейной зависимости можно также использовать функцию regplot из seaborn.

sns.regplot(x="LSTAT", y="MEDV", data=boston_df)

Посчитайте линейную регрессионную модель самостоятельно, используя описанные ранее формулу и библиотеку numpy. Проверьте, совпадают ли коэффициенты с результатами sklearn.

Подсказки:

  • «Склеить» два столбца можно при помощи np.hstack.
  • Для получения обратной матрицы можно использовать np.linalg.inv.
  • Матричное умножение осуществляет np.dot.
  • w⃗=(XTX)−1XTy⃗vec{w} = left(X^T Xright)^{-1} X^T vec{y}

Проверка качество модели: практика

Возможные метрики для проверки качества модели в sklearn можно посмотреть в документации, а можно посчитать самим по формулам. Выберем простой путь.

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=0)

slr = LinearRegression() slr.fit(X_train, y_train) y_train_pred = slr.predict(X_train) y_test_pred = slr.predict(X_test)

Поскольку в нашей модели несколько независимых переменных, мы не можем отобразить их зависимость на двумерном пространстве, но можем нанести на график связь между остатками модели и предсказанными значениями, что также поможет нам диагностировать качество модели. Это называется Residuals plot. C его помощью мы можем увидет нелинейность и выбросы, проверить случайность распределения ошибки.

from sklearn.metrics import mean_absolute_error, mean_squared_error, median_absolute_error, r2_score print('MSE train: {:.3f}, test: {:.3f}'.format( mean_squared_error(y_train, y_train_pred), mean_squared_error(y_test, y_test_pred))) print('R^2 train: {:.3f}, test: {:.3f}'.format( r2_score(y_train, y_train_pred), r2_score(y_test, y_test_pred)))

MSE train: 37.934, test: 39.817

R^2 train: 0.552, test: 0.522

plt.scatter(y_train_pred, y_train_pred - y_train, c='blue', marker='o', label='Training data') plt.scatter(y_test_pred, y_test_pred - y_test, c='lightgreen', marker='s', label='Test data') plt.xlabel('Predicted values') plt.ylabel('Residuals') plt.legend(loc='upper left') plt.hlines(y=0, xmin=-10, xmax=50, lw=2, color='red') plt.xlim([-10, 50]) plt.tight_layout()

Другой взгляд на построение статистических моделей — statsmodels

Statsmodels — ещё одна библиотека для построения статистических данных в Python, но выполненная в лучших традийиях R. Посмотрим, сравним реализацию линейных моделей в sklearn и statsmodels.

from sklearn.linear_model import LinearRegression slr = LinearRegression() slr.fit(X, y) y_pred = slr.predict(X) print('Slope: {:.2f}'.format(slr.coef_[0])) print('Intercept: {:.2f}'.format(slr.intercept_))

Slope: -0.95

Intercept: 34.55

import statsmodels.api as sm import statsmodels.formula.api as smf results = smf.ols('MEDV ~ LSTAT', data=df).fit() results.summary()

OLS Regression Results

Dep. Variable: MEDV R-squared: 0.544
Model: OLS Adj. R-squared: 0.543
Method: Least Squares F-statistic: 601.6
Date: Sat, 23 Mar 2019 Prob (F-statistic): 5.08e-88
Time: 09:14:53 Log-Likelihood: -1641.5
No. Observations: 506 AIC: 3287.
Df Residuals: 504 BIC: 3295.
Df Model: 1
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept 34.5538 0.563 61.415 0.000 33.448 35.659
LSTAT -0.9500 0.039 -24.528 0.000 -1.026 -0.874
Omnibus: 137.043 Durbin-Watson: 0.892
Prob(Omnibus): 0.000 Jarque-Bera (JB): 291.373
Skew: 1.453 Prob(JB): 5.36e-64
Kurtosis: 5.319 Cond. No. 29.7

Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

Регуляризация линейных моделей

Переобучение

Регуляризация — это метод для уменьшения степени переобучения модели, а значит, прежде чем мы разберемся, что такое регуляризация, нужно понять суть переобучения (overfitting).

Переобучение дает неплавные кривые прогнозирования, т. е. «нерегулярные». Такие плохие сложные кривые прогнозирования обычно характеризуются весовыми значениями, которые имеют очень большие или очень малые величины. Поэтому один из способов уменьшить степень переобучения состоит в том, чтобы не допускать очень малых или больших весовых значений для модели. В этом и заключается суть регуляризации.

Проблема многомерности: Bias-variance trade-off

https://elitedatascience.com/wp-content/uploads/2017/06/Bias-vs.-Variance-v5.png

Линейная регрессия с большим числом предикторов – комплексная модель и характеризуется:

  • Достаточно высоким смещением
  • Высокой дисперсией

Чем больше предикторов, тем больше риск переобучения модели. Переобучение также связано с размером коэфициентов.

Переобучение – ситуация, в которой обучающая ошибка продолжает снижаться с повышением сложности модели, а тестовая ошибка растет.

Как с этим бороться?

  • Отбор наилучших предикторов
  • Снижение размерности предикторов
  • Регуляризация

Регуляризация — это способ уменьшить сложность модели чтобы предотвратить переобучение или исправить некорректно поставленную задачу. Обычно это достигается добавлением некоторой априорной информации к условию задачи.

В данном случае суть регуляризации состит в том, что мы создаём модель со всеми предикторами, а потом искуственно уменьшаем размер коэффициентов, прибавляя некоторую величину к ошибке.

Ошибка — это то, что минимизируется обучением с помощью одного из примерно десятка численных методов вроде градиентного спуска (gradient descent), итерационного алгоритма Ньютона-Рафсона (iterative Newton-Raphson), L-BFGS, обратного распространения ошибок (back-propagation) и оптимизации роя (swarm optimization).

Чтобы величины весовых значений модели не становились большими, процесс регуляризации штрафует весовые значения добавляя их в вычисление ошибки. Если весовые значения включаются в общую ошибку, которая минимизируется, тогда меньшие весовые значения будут давать меньшие значения ошибки. L1-регуляризация штрафует весовые значения добавлением суммы их абсолютных значений к ошибке.

L2-регуляризация выполняет аналогичную операцию добавлением суммы их квадратов к ошибке. Сравнение L1 и L2.

Ридж регрессия

Ридж-регрессия или гребневая регрессия (ridge regression) — это один из методов понижения размерности. Часто его применяют для борьбы с переизбыточностью данных, когда независимые переменные коррелируют друг с другом (т.е. имеет место мультиколлинеарность). Следствием этого является плохая обусловленность матрицы XTXX^T X и неустойчивость оценок коэффициентов регрессии. Оценки, например, могут иметь неправильный знак или значения, которые намного превосходят те, которые приемлемы из физических или практических соображений.

Когда мы делаем линейную регрессию, то функция потерь выглядела так:
L(X,y⃗,w⃗)=12n∑i=1n(xi⃗Tw⃗−yi)2mathcal{L}left(X, vec{y}, vec{w} right) = frac{1}{2n} sum_{i=1}^n left(vec{x_i}^T vec{w} — y_iright)^2

Пеперь в ридж-регрессии мы добавляем к ней λ∑j=1m∣wj∣lambdasum_{j=1}^m left| w_j right|, где λlambda обозначает размер штрафа. Если λ=0lambda = 0, то это обычная линейная регрессия.

Теперь формула выглядит так:
L(X,y⃗,w⃗)=12n∑i=1n(xi⃗Tw⃗−yi)2+λ∑j=1m∣wj∣Large mathcal{L}left(X, vec{y}, vec{w} right) = frac{1}{2n} sum_{i=1}^n left(vec{x_i}^T vec{w} — y_iright)^2 + lambda sum_{j=1}^m left| w_j right|

  • Чем меньше λlambda, тем выше дисперсия и ниже смещение.

  • Чем больше λlambda, тем ниже дисперсия и выше смещение.

Взяв производную от этой функции, мы получим формулу, которая не имеет аналитического решения, т.е. МНК не подходит. Следовательно, необходимо использовать градиентный спуск.

Лассо регрессия (Least absolute shrinkage and selection operator)

Очень похожа на ридж регрессию. В ней штраф — это сумма модулей значений коэффициентов.

В чем сила ридж и лассо?

• Ридж регрессия снижает размер коэффициентов, а лассо сокращает многие до 0
• Это позволяет снизить размерность (ридж) и выбрать важные предикторы (лассо)
• Работает, когда p > n, где p — число предикторов
• Работает, когда много коллинеарных предикторов
• Обязательно надо делать шкалирование и центрирование, иначе предикторы с высоким стандартным отклонением будут сильно штравоваться.

ElasticNet — комбинация L1 и L2 регуляризации в разных пропорциях.

Регуляризация в sklearn

from sklearn.preprocessing import StandardScaler sc_x = StandardScaler() sc_y = StandardScaler() X_std = sc_x.fit_transform(X) y_std = sc_y.fit_transform(y.reshape(-1, 1)).flatten() X_train_scaled, X_test_scaled, y_train_scaled, y_test_scaled = train_test_split( X_std, y_std, test_size=0.3, random_state=0)

Проверяем, действительно ли всё шкалировалось:

X_train_scaled.std(), X_train_scaled.mean()

(0.9908156118561137, -0.02977480724160985)

from sklearn.linear_model import Lasso lasso = Lasso(alpha=0.1) lasso.fit(X_train_scaled, y_train_scaled) y_train_pred = lasso.predict(X_train_scaled) y_test_pred = lasso.predict(X_test_scaled) print(lasso.coef_) print('MSE train: {:.3f}, test: {:.3f}'.format( mean_squared_error(y_train_scaled, y_train_pred), mean_squared_error(y_test_scaled, y_test_pred))) print('R^2 train: {:.3f}, test: {:.3f}'.format( r2_score(y_train_scaled, y_train_pred), r2_score(y_test_scaled, y_test_pred)))

[-0.64984726]

MSE train: 0.460, test: 0.473

R^2 train: 0.542, test: 0.520

from sklearn.linear_model import Ridge ridge = Ridge(alpha=0.1) ridge.fit(X_train_scaled, y_train_scaled) y_train_pred = ridge.predict(X_train_scaled) y_test_pred = ridge.predict(X_test_scaled) print(ridge.coef_) print('MSE train: {:.3f}, test: {:.3f}'.format( mean_squared_error(y_train_scaled, y_train_pred), mean_squared_error(y_test_scaled, y_test_pred))) print('R^2 train: {:.3f}, test: {:.3f}'.format( r2_score(y_train_scaled, y_train_pred), r2_score(y_test_scaled, y_test_pred)))

[-0.75149352]

MSE train: 0.449, test: 0.472

R^2 train: 0.552, test: 0.522

from sklearn.linear_model import ElasticNet en = ElasticNet(alpha=0.1, l1_ratio=0.5) en.fit(X_train_scaled, y_train_scaled) y_train_pred = en.predict(X_train_scaled) y_test_pred = en.predict(X_test_scaled) print(en.coef_) print('MSE train: {:.3f}, test: {:.3f}'.format( mean_squared_error(y_train_scaled, y_train_pred), mean_squared_error(y_test_scaled, y_test_pred))) print('R^2 train: {:.3f}, test: {:.3f}'.format( r2_score(y_train_scaled, y_train_pred), r2_score(y_test_scaled, y_test_pred)))

[-0.6668167]

MSE train: 0.456, test: 0.471

R^2 train: 0.545, test: 0.522

Полиноминальная регрессия

Не всегда аппроксимация в виде прямой линии является наилучшим выходом. Иногда, стоит отказаться от предположения о наличии такой связи и воспользоваться полиноминальной регрессией.

from sklearn.preprocessing import PolynomialFeatures

X = df[['LSTAT']].values y = df['MEDV'].values regr = LinearRegression() quadratic = PolynomialFeatures(degree=2) cubic = PolynomialFeatures(degree=3) X_quad = quadratic.fit_transform(X) X_cubic = cubic.fit_transform(X) X_fit = np.arange(X.min(), X.max(), 1)[:, np.newaxis] regr = regr.fit(X, y) y_lin_fit = regr.predict(X_fit) linear_r2 = r2_score(y, regr.predict(X)) regr = regr.fit(X_quad, y) y_quad_fit = regr.predict(quadratic.fit_transform(X_fit)) quadratic_r2 = r2_score(y, regr.predict(X_quad)) regr = regr.fit(X_cubic, y) y_cubic_fit = regr.predict(cubic.fit_transform(X_fit)) cubic_r2 = r2_score(y, regr.predict(X_cubic)) plt.scatter(X, y, label='training points', color='lightgray') plt.plot(X_fit, y_lin_fit, label='linear (d=1), $R^2={:.2f}$'.format(linear_r2), color='blue', lw=2, linestyle=':') plt.plot(X_fit, y_quad_fit, label='quadratic (d=2), $R^2={:.2f}$'.format(quadratic_r2), color='red', lw=2, linestyle='-') plt.plot(X_fit, y_cubic_fit, label='cubic (d=3), $R^2={:.2f}$'.format(cubic_r2), color='green', lw=2, linestyle='--') plt.xlabel('% lower status of the population [LSTAT]') plt.ylabel('Price in $1000's [MEDV]') plt.legend(loc='upper right')

Что можно сказать о результатах полиноминальной регрессии? Какую бы модель Вы выбрали?

На мысль о какой функции наводит Вас красный график? Можно ли обойтись без полиномов, одними преобразованиями?

X = df[['LSTAT']].values y = df['MEDV'].values X_log = np.log(X) y_sqrt = np.sqrt(y) X_fit = np.arange(X_log.min()-1, X_log.max()+1, 1)[:, np.newaxis] regr = regr.fit(X_log, y_sqrt) y_lin_fit = regr.predict(X_fit) linear_r2 = r2_score(y_sqrt, regr.predict(X_log)) plt.scatter(X_log, y_sqrt, label='training points', color='lightgray') plt.plot(X_fit, y_lin_fit, label='linear (d=1), $R^2={:.2f}$'.format(linear_r2), color='blue', lw=2) plt.xlabel('log(% lower status of the population [LSTAT])') plt.ylabel('$sqrt{Price ; in ; $1000's [MEDV]}$') plt.legend(loc='lower left')

Как понять, какое преобразование лучше сделать? Обычно, переменную логарифмируют, когда у неё есть длинный и тонкий хвост. Пример — доход. У большинства людей доход не сильно отличается, но есть очень небольшое количество людей с очень высоким доходом. Логарифмическая функция устроена таким образом, что если применяется к y она штрафует большие значения и вытягивает маленькие. Это можно увидеть из графика:

plt.plot(np.arange(1, 101), np.log(np.arange(1, 101)));

А есть ли более формальный критерий, по которому можно понять, как трансформировать переменную?

Есть. Это степенная трансформация Бокса-Кокса.

yiλ={yiλ−1λ,amp;if λ≠0,log⁡(yi),amp;if λ=0.y_i^{lambda} = begin{cases}frac{y_i^lambda-1}{lambda},&amp;text{if } lambda neq 0, log{(y_i)},&amp; text{if } lambda = 0.end{cases}

Она реализована в библиотеке scipy.

from scipy.stats import boxcox y_boxcox = boxcox(df['MEDV'].values)[0] regr = LinearRegression() regr = regr.fit(X_log, y_boxcox) y_lin_fit = regr.predict(X_fit) linear_r2 = r2_score(y_boxcox, regr.predict(X_log)) plt.scatter(X_log, y_sqrt, label='training points', color='lightgray') plt.plot(X_fit, y_lin_fit, label='linear (d=1), $R^2={:.2f}$'.format(linear_r2), color='blue', lw=2) plt.xlabel('log(% lower status of the population [LSTAT])') plt.ylabel('$sqrt{Price ; in ; $1000's [MEDV]}$') plt.legend(loc='lower left')

Использование категориальных переменных в качестве предикторов

Label Encoding

Самый простой и очевидный способ перекодировки — просто заменить строки на числа. В модуле preprocessing библиотеки sklearn именно для этой задачи реализован класс LabelEncoder.

from sklearn import preprocessing le = preprocessing.LabelEncoder() le.fit(["paris", "paris", "tokyo", "amsterdam"]) le.transform(["tokyo", "tokyo", "paris"])

Метод fit этого класса находит все уникальные значения и строит таблицу для соответствия каждой категории некоторому числу, а метод transform непосредственно преобразует значения в числа. После fit у label_encoder будет доступно поле classes_, содержащее все уникальные значения. Можно их пронумеровать и убедиться, что преобразование выполнено верно.

При использовании этого подхода мы всегда должны быть уверены, что признак не может принимать неизвестных ранее значений, иначе вылезет ошибка.

Однако, такое представление имеет мало смысла и, более того, не корректно, поскольку таким образом мы определяем для этой переменной недопустимые математические операции.

One-Hot Encoding

Предположим, что некоторый признак может принимать 10 разных значений. В этом случае One Hot Encoding подразумевает создание 10 признаков, все из которых равны нулю за исключением одного. На позицию, соответствующую численному значению признака мы помещаем 1.

Эта техника реализована в sklearn.preprocessing в классе OneHotEncoder. По умолчанию OneHotEncoder преобразует данные в разреженную матрицу, чтобы не расходовать память на хранение многочисленных нулей. Однако в этом примере размер данных не является для нас проблемой, поэтому мы будем использовать «плотное» представление.

cars_df = pd.DataFrame({"car": ["BMW", "Audi", "BMW", "Mersedes"], "retailer": ["Best cars ever", "Best cars ever", "Best cars ever", "AutoMoto"]}) cars_df

car retailer
0 BMW Best cars ever
1 Audi Best cars ever
2 BMW Best cars ever
3 Mersedes AutoMoto
car_Audi car_BMW car_Mersedes retailer_AutoMoto retailer_Best cars ever
0 0 1 0 0 1
1 1 0 0 0 1
2 0 1 0 0 1
3 0 0 1 1 0

Можно использовать модуль OneHotEncoder из SkLearn, но с ним не так всё просто — необходимо сначала преобразовать значения в числа как в label encoding. Проще использовать LabelBinarizer:

from sklearn import preprocessing lb = preprocessing.LabelBinarizer() lb.fit_transform(cars_df["car"])

array([[0, 1, 0],

[1, 0, 0],

[0, 1, 0],

[0, 0, 1]])

Hashing trick

Для очень больших данных со множеством уникальных признаков, на этом курсе не пригодится.
Подробнее для самостоятельного ознакомления:

  • Jun Wang, Wei Liu, Sanjiv Kumar, Shih-Fu Chang. Learning to Hash for Indexing Big Data — A Survey
  • Открытый курс машинного обучения.

Пример работы с категориальными данными

Пусть это будут данные о престиже различных профессий в США.

import statsmodels.api as sm import statsmodels.formula.api as smf duncan_prestige = sm.datasets.get_rdataset("Duncan", "car")

print(duncan_prestige.__doc__)

+———-+——————-+

| Duncan | R Documentation |

+———-+——————-+

Duncan’s Occupational Prestige Data

————————————

The «Duncan« data frame has 45 rows and 4 columns. Data on the

prestige and other characteristics of 45 U. S. occupations in 1950.

::

Duncan

This data frame contains the following columns:

type

Type of occupation. A factor with the following levels: «prof«,

professional and managerial; «wc«, white-collar; «bc«,

blue-collar.

income

Percent of males in occupation earning $3500 or more in 1950.

education

Percent of males in occupation in 1950 who were high-school

graduates.

prestige

Percent of raters in NORC study rating occupation as excellent or

good in prestige.

Duncan, O. D. (1961) A socioeconomic index for all occupations. In

Reiss, A. J., Jr. (Ed.) *Occupations and Social Status.* Free Press

[Table VI-1].

Fox, J. (2008) *Applied Regression Analysis and Generalized Linear

Models*, Second Edition. Sage.

Fox, J. and Weisberg, S. (2011) *An R Companion to Applied Regression*,

Second Edition, Sage.

duncan_prestige.data.head(5)

type income education prestige
accountant prof 62 86 82
pilot prof 72 76 83
architect prof 75 92 90
author prof 55 90 76
chemist prof 64 86 90

prestige_df = duncan_prestige.data.copy() prestige_dummies = pd.get_dummies(prestige_df.select_dtypes(include=[object])) prestige_df = pd.concat([prestige_df, prestige_dummies], axis=1)

type income education prestige type_bc type_prof type_wc
accountant prof 62 86 82 0 1 0
pilot prof 72 76 83 0 1 0
architect prof 75 92 90 0 1 0
author prof 55 90 76 0 1 0
chemist prof 64 86 90 0 1 0

from sklearn.linear_model import LinearRegression X_prestige = prestige_df.drop(["type", "prestige"], axis=1).values y_prestige = prestige_df['prestige'].values X_train_prestige, X_test_prestige, y_train_prestige, y_test_prestige = train_test_split( X_prestige, y_prestige, test_size=0.3, random_state=0) slr_prestige = LinearRegression() slr_prestige.fit(X_train_prestige, y_train_prestige) y_train_pred = slr_prestige.predict(X_train_prestige) y_test_pred = slr_prestige.predict(X_test_prestige) print('Slope: {}'.format(slr_prestige.coef_)) print('Intercept: {:.2f}'.format(slr_prestige.intercept_))

Slope: [ 0.56397932 0.32027185 -2.14826104 18.72771436 -16.57945333]

Intercept: 2.74

Похожую вещь можно сделать с помощью statsmodels, и про том гораздо проще:

results = smf.ols('prestige ~ education + income + C(type)', data=prestige_df).fit()

OLS Regression Results

Dep. Variable: prestige R-squared: 0.913
Model: OLS Adj. R-squared: 0.904
Method: Least Squares F-statistic: 105.0
Date: Fri, 03 Nov 2017 Prob (F-statistic): 1.17e-20
Time: 14:56:00 Log-Likelihood: -163.65
No. Observations: 45 AIC: 337.3
Df Residuals: 40 BIC: 346.3
Df Model: 4
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept -0.1850 3.714 -0.050 0.961 -7.691 7.321
C(type)[T.prof] 16.6575 6.993 2.382 0.022 2.524 30.791
C(type)[T.wc] -14.6611 6.109 -2.400 0.021 -27.007 -2.315
education 0.3453 0.114 3.040 0.004 0.116 0.575
income 0.5975 0.089 6.687 0.000 0.417 0.778
Omnibus: 10.720 Durbin-Watson: 1.497
Prob(Omnibus): 0.005 Jarque-Bera (JB): 10.285
Skew: 1.013 Prob(JB): 0.00584
Kurtosis: 4.176 Cond. No. 462.

Самостоятельная

Ваша задача — проанализировать результаты European Social Survey шестой волны или World Values Survey. ESS — это сравнительное межстрановое трендовое исследование установок, взглядов, ценностей и поведения населения Европы, которое проводится почти во всех европейских странах каждые два года, начиная с 2002, методом опроса населения 15 лет и старше по случайной репрезентативной выборке населения в каждой стране с помощью личных интервью на дому у респондентов. WVS — тоже весьма похожий исследовательский проект, объединяющий социологов по всему миру, которые изучают ценности и их воздействие на социальную и культурную жизнь. WVS провёл социологические исследования уже в 97 странах.

Продемонструйте навыки использования регресси на этих данных:

  • самостоятельно поставьте исследовательскую задачу, определите зависимую и независимые переменные;
  • визуализируйте эффекты модели и дайте им интерпретацию.

Описание всех переменных из ESS доступно в протоколе исследования, сами данные можно скачать по ссылке. Для переменных из WVS также есть описание на сайте (раздел «Questionnaire») и данные.

import pyreadstat ess, _ = pyreadstat.read_sav('ESS6.sav') ess_ru = ess[ess.cntry == "RU"][["agea", "hinctnta", "eduyrs", "gndr", "fairelc", "meprinf"]] ess_ru.head()

Human brains are built to recognize patterns in the world around us. For example, we observe that if we practice our programming everyday, our related skills grow. But how do we precisely describe this relationship to other people? How can we describe how strong this relationship is? Luckily, we can describe relationships between phenomena, such as practice and skill, in terms of formal mathematical estimations called regressions.

Regressions are one of the most commonly used tools in a data scientist’s kit. When you learn Python or R, you gain the ability to create regressions in single lines of code without having to deal with the underlying mathematical theory. But this ease can cause us to forget to evaluate our regressions to ensure that they are a sufficient enough representation of our data. We can plug our data back into our regression equation to see if the predicted output matches corresponding observed value seen in the data.

The quality of a regression model is how well its predictions match up against actual values, but how do we actually evaluate quality? Luckily, smart statisticians have developed error metrics to judge the quality of a model and enable us to compare regresssions against other regressions with different parameters. These metrics are short and useful summaries of the quality of our data. This article will dive into four common regression metrics and discuss their use cases. There are many types of regression, but this article will focus exclusively on metrics related to the linear regression.

The linear regression is the most commonly used model in research and business and is the simplest to understand, so it makes sense to start developing your intuition on how they are assessed. The intuition behind many of the metrics we’ll cover here extend to other types of models and their respective metrics. If you’d like a quick refresher on the linear regression, you can consult this fantastic blog post or the Linear Regression Wiki page.

A primer on linear regression

In the context of regression, models refer to mathematical equations used to describe the relationship between two variables. In general, these models deal with prediction and estimation of values of interest in our data called outputs. Models will look at other aspects of the data called inputs that we believe to affect the outputs, and use them to generate estimated outputs.

These inputs and outputs have many names that you may have heard before. Inputs can also be called independent variables or predictors, while outputs are also known as responses or dependent variables. Simply speaking, models are just functions where the outputs are some function of the inputs. The linear part of linear regression refers to the fact that a linear regression model is described mathematically in the form: Linear Regression Anatomy If that looks too mathematical, take solace in that linear thinking is particularly intuitive. If you’ve ever heard of “practice makes perfect,” then you know that more practice means better skills; there is some linear relationship between practice and perfection. The regression part of linear regression does not refer to some return to a lesser state. Regression here simply refers to the act of estimating the relationship between our inputs and outputs. In particular, regression deals with the modelling of continuous values (think: numbers) as opposed to discrete states (think: categories).

Taken together, a linear regression creates a model that assumes a linear relationship between the inputs and outputs. The higher the inputs are, the higher (or lower, if the relationship was negative) the outputs are. What adjusts how strong the relationship is and what the direction of this relationship is between the inputs and outputs are our coefficients. The first coefficient without an input is called the intercept, and it adjusts what the model predicts when all your inputs are 0. We will not delve into how these coefficients are calculated, but know that there exists a method to calculate the optimal coefficients, given which inputs we want to use to predict the output.

Given the coefficients, if we plug in values for the inputs, the linear regression will give us an estimate for what the output should be. As we’ll see, these outputs won’t always be perfect. Unless our data is a perfectly straight line, our model will not precisely hit all of our data points. One of the reasons for this is the ϵ (named “epsilon”) term. This term represents error that comes from sources out of our control, causing the data to deviate slightly from their true position. Our error metrics will be able to judge the differences between prediction and actual values, but we cannot know how much the error has contributed to the discrepancy. While we cannot ever completely eliminate epsilon, it is useful to retain a term for it in a linear model.

Comparing model predictions against reality

Since our model will produce an output given any input or set of inputs, we can then check these estimated outputs against the actual values that we tried to predict. We call the difference between the actual value and the model’s estimate a residual. We can calculate the residual for every point in our data set, and each of these residuals will be of use in assessment. These residuals will play a significant role in judging the usefulness of a model.

If our collection of residuals are small, it implies that the model that produced them does a good job at predicting our output of interest. Conversely, if these residuals are generally large, it implies that model is a poor estimator. We technically can inspect all of the residuals to judge the model’s accuracy, but unsurprisingly, this does not scale if we have thousands or millions of data points. Thus, statisticians have developed summary measurements that take our collection of residuals and condense them into a single value that represents the predictive ability of our model. There are many of these summary statistics, each with their own advantages and pitfalls. For each, we’ll discuss what each statistic represents, their intuition and typical use case. We’ll cover:

  • Mean Absolute Error
  • Mean Square Error
  • Mean Absolute Percentage Error
  • Mean Percentage Error

Note: Even though you see the word error here, it does not refer to the epsilon term from above! The error described in these metrics refer to the residuals!

Staying rooted in real data

In discussing these error metrics, it is easy to get bogged down by the various acronyms and equations used to describe them. To keep ourselves grounded, we’ll use a model that I’ve created using the Video Game Sales Data Set from Kaggle. The specifics of the model I’ve created are shown below. Imgur My regression model takes in two inputs (critic score and user score), so it is a multiple variable linear regression. The model took in my data and found that 0.039 and -0.099 were the best coefficients for the inputs.

For my model, I chose my intercept to be zero since I’d like to imagine there’d be zero sales for scores of zero. Thus, the intercept term is crossed out. Finally, the error term is crossed out because we do not know its true value in practice. I have shown it because it depicts a more detailed description of what information is encoded in the linear regression equation.

Rationale behind the model

Let’s say that I’m a game developer who just created a new game, and I want to know how much money I will make. I don’t want to wait, so I developed a model that predicts total global sales (my output) based on an expert critic’s judgment of the game and general player judgment (my inputs). If both critics and players love the game, then I should make more money… right? When I actually get the critic and user reviews for my game, I can predict how much glorious money I’ll make. Currently, I don’t know if my model is accurate or not, so I need to calculate my error metrics to check if I should perhaps include more inputs or if my model is even any good!

Mean absolute error

The mean absolute error (MAE) is the simplest regression error metric to understand. We’ll calculate the residual for every data point, taking only the absolute value of each so that negative and positive residuals do not cancel out. We then take the average of all these residuals. Effectively, MAE describes the typical magnitude of the residuals. If you’re unfamiliar with the mean, you can refer back to this article on descriptive statistics. The formal equation is shown below: MAE Equation The picture below is a graphical description of the MAE. The green line represents our model’s predictions, and the blue points represent our data. MAE

The MAE is also the most intuitive of the metrics since we’re just looking at the absolute difference between the data and the model’s predictions. Because we use the absolute value of the residual, the MAE does not indicate underperformance or overperformance of the model (whether or not the model under or overshoots actual data). Each residual contributes proportionally to the total amount of error, meaning that larger errors will contribute linearly to the overall error. Like we’ve said above, a small MAE suggests the model is great at prediction, while a large MAE suggests that your model may have trouble in certain areas. A MAE of 0 means that your model is a perfect predictor of the outputs (but this will almost never happen).

While the MAE is easily interpretable, using the absolute value of the residual often is not as desirable as squaring this difference. Depending on how you want your model to treat outliers, or extreme values, in your data, you may want to bring more attention to these outliers or downplay them. The issue of outliers can play a major role in which error metric you use.

Calculating MAE against our model

Calculating MAE is relatively straightforward in Python. In the code below, sales contains a list of all the sales numbers, and X contains a list of tuples of size 2. Each tuple contains the critic score and user score corresponding to the sale in the same index. The lm contains a LinearRegression object from scikit-learn, which I used to create the model itself. This object also contains the coefficients. The predict method takes in inputs and gives the actual prediction based off those inputs.

# Perform the intial fitting to get the LinearRegression object
from sklearn import linear_model
lm = linear_model.LinearRegression()
lm.fit(X, sales)

mae_sum = 0
for sale, x in zip(sales, X):
    prediction = lm.predict(x)
    mae_sum += abs(sale - prediction)
mae = mae_sum / len(sales)

print(mae)
>>> [ 0.7602603 ]

Our model’s MAE is 0.760, which is fairly small given that our data’s sales range from 0.01 to about 83 (in millions).

Mean square error

The mean square error (MSE) is just like the MAE, but squares the difference before summing them all instead of using the absolute value. We can see this difference in the equation below. MSE Equation

Consequences of the Square Term

Because we are squaring the difference, the MSE will almost always be bigger than the MAE. For this reason, we cannot directly compare the MAE to the MSE. We can only compare our model’s error metrics to those of a competing model. The effect of the square term in the MSE equation is most apparent with the presence of outliers in our data. While each residual in MAE contributes proportionally to the total error, the error grows quadratically in MSE. This ultimately means that outliers in our data will contribute to much higher total error in the MSE than they would the MAE. Similarly, our model will be penalized more for making predictions that differ greatly from the corresponding actual value. This is to say that large differences between actual and predicted are punished more in MSE than in MAE. The following picture graphically demonstrates what an individual residual in the MSE might look like. MSE Outliers will produce these exponentially larger differences, and it is our job to judge how we should approach them.

The problem of outliers

Outliers in our data are a constant source of discussion for the data scientists that try to create models. Do we include the outliers in our model creation or do we ignore them? The answer to this question is dependent on the field of study, the data set on hand and the consequences of having errors in the first place. For example, I know that some video games achieve superstar status and thus have disproportionately higher earnings. Therefore, it would be foolish of me to ignore these outlier games because they represent a real phenomenon within the data set. I would want to use the MSE to ensure that my model takes these outliers into account more.

If I wanted to downplay their significance, I would use the MAE since the outlier residuals won’t contribute as much to the total error as MSE. Ultimately, the choice between is MSE and MAE is application-specific and depends on how you want to treat large errors. Both are still viable error metrics, but will describe different nuances about the prediction errors of your model.

A note on MSE and a close relative

Another error metric you may encounter is the root mean squared error (RMSE). As the name suggests, it is the square root of the MSE. Because the MSE is squared, its units do not match that of the original output. Researchers will often use RMSE to convert the error metric back into similar units, making interpretation easier. Since the MSE and RMSE both square the residual, they are similarly affected by outliers. The RMSE is analogous to the standard deviation (MSE to variance) and is a measure of how large your residuals are spread out. Both MAE and MSE can range from 0 to positive infinity, so as both of these measures get higher, it becomes harder to interpret how well your model is performing. Another way we can summarize our collection of residuals is by using percentages so that each prediction is scaled against the value it’s supposed to estimate.

Calculating MSE against our model

Like MAE, we’ll calculate the MSE for our model. Thankfully, the calculation is just as simple as MAE.

mse_sum = 0
for sale, x in zip(sales, X):
    prediction = lm.predict(x)
    mse_sum += (sale - prediction)**2
mse = mse_sum / len(sales)

print(mse)
>>> [ 3.53926581 ]

With the MSE, we would expect it to be much larger than MAE due to the influence of outliers. We find that this is the case: the MSE is an order of magnitude higher than the MAE. The corresponding RMSE would be about 1.88, indicating that our model misses actual sale values by about $1.8M.

Mean absolute percentage error

The mean absolute percentage error (MAPE) is the percentage equivalent of MAE. The equation looks just like that of MAE, but with adjustments to convert everything into percentages. MAPE Equation Just as MAE is the average magnitude of error produced by your model, the MAPE is how far the model’s predictions are off from their corresponding outputs on average. Like MAE, MAPE also has a clear interpretation since percentages are easier for people to conceptualize. Both MAPE and MAE are robust to the effects of outliers thanks to the use of absolute value. MAPE

However for all of its advantages, we are more limited in using MAPE than we are MAE. Many of MAPE’s weaknesses actually stem from use division operation. Now that we have to scale everything by the actual value, MAPE is undefined for data points where the value is 0. Similarly, the MAPE can grow unexpectedly large if the actual values are exceptionally small themselves. Finally, the MAPE is biased towards predictions that are systematically less than the actual values themselves. That is to say, MAPE will be lower when the prediction is lower than the actual compared to a prediction that is higher by the same amount. The quick calculation below demonstrates this point. MAPE Bad

We have a measure similar to MAPE in the form of the mean percentage error. While the absolute value in MAPE eliminates any negative values, the mean percentage error incorporates both positive and negative errors into its calculation.

Calculating MAPE against our model

mape_sum = 0
for sale, x in zip(sales, X):
    prediction = lm.predict(x)
    mape_sum += (abs((sale - prediction))/sale)
mape = mape_sum/len(sales)

print(mape)
>>> [ 5.68377867 ]

We know for sure that there are no data points for which there are zero sales, so we are safe to use MAPE. Remember that we must interpret it in terms of percentage points. MAPE states that our model’s predictions are, on average, 5.6% off from actual value.

Mean percentage error

The mean percentage error (MPE) equation is exactly like that of MAPE. The only difference is that it lacks the absolute value operation.

MPE Equation

Even though the MPE lacks the absolute value operation, it is actually its absence that makes MPE useful. Since positive and negative errors will cancel out, we cannot make any statements about how well the model predictions perform overall. However, if there are more negative or positive errors, this bias will show up in the MPE. Unlike MAE and MAPE, MPE is useful to us because it allows us to see if our model systematically underestimates (more negative error) or overestimates (positive error). MPE

If you’re going to use a relative measure of error like MAPE or MPE rather than an absolute measure of error like MAE or MSE, you’ll most likely use MAPE. MAPE has the advantage of being easily interpretable, but you must be wary of data that will work against the calculation (i.e. zeroes). You can’t use MPE in the same way as MAPE, but it can tell you about systematic errors that your model makes.

Calculating MPE against our model

mpe_sum = 0
for sale, x in zip(sales, X):
    prediction = lm.predict(x)
    mpe_sum += ((sale - prediction)/sale)
mpe = mpe_sum/len(sales)

print(mpe)
>>> [-4.77081497]

All the other error metrics have suggested to us that, in general, the model did a fair job at predicting sales based off of critic and user score. However, the MPE indicates to us that it actually systematically underestimates the sales. Knowing this aspect about our model is helpful to us since it allows us to look back at the data and reiterate on which inputs to include that may improve our metrics. Overall, I would say that my assumptions in predicting sales was a good start. The error metrics revealed trends that would have been unclear or unseen otherwise.

Conclusion

We’ve covered a lot of ground with the four summary statistics, but remembering them all correctly can be confusing. The table below will give a quick summary of the acronyms and their basic characteristics.

Acroynm Full Name Residual Operation? Robust To Outliers?
MAE Mean Absolute Error Absolute Value Yes
MSE Mean Squared Error Square No
RMSE Root Mean Squared Error Square No
MAPE Mean Absolute Percentage Error Absolute Value Yes
MPE Mean Percentage Error N/A Yes

All of the above measures deal directly with the residuals produced by our model. For each of them, we use the magnitude of the metric to decide if the model is performing well. Small error metric values point to good predictive ability, while large values suggest otherwise. That being said, it’s important to consider the nature of your data set in choosing which metric to present. Outliers may change your choice in metric, depending on if you’d like to give them more significance to the total error. Some fields may just be more prone to outliers, while others are may not see them so much.

In any field though, having a good idea of what metrics are available to you is always important. We’ve covered a few of the most common error metrics used, but there are others that also see use. The metrics we covered use the mean of the residuals, but the median residual also sees use. As you learn other types of models for your data, remember that intuition we developed behind our metrics and apply them as needed.

Further Resources

If you’d like to explore the linear regression more, Dataquest offers an excellent course on its use and application! We used scikit-learn to apply the error metrics in this article, so you can read the docs to get a better look at how to use them!

  • Dataquest’s course on Linear Regression
  • Scikit-learn and regression error metrics
  • Scikit-learn’s documentation on the LinearRegression object
  • An example use of the LinearRegression object

Learn Python the Right Way.

Learn Python by writing Python code from day one, right in your browser window. It’s the best way to learn Python — see for yourself with one of our 60+ free lessons.

astronaut floating over code

Try Dataquest

В этой серии мы внимательно рассмотрим алгоритм машинного обучения и изучим плюсы и минусы каждого алгоритма. Мы рассмотрим алгоритмы вместе с математикой, лежащей в основе алгоритма.

Во-первых, давайте проясним некоторые основные термины, используемые в машинном обучении.

  • Контролируемый алгоритм ML: Те алгоритмы, которые используют помеченные данные, известны как контролируемые алгоритмы ml. Контролируемые алгоритмы ml широко используются для двух задач: классификации и регрессии.
  • Классификация: Когда задача состоит в том, чтобы классифицировать объекты выборки по определенным категориям (целевая переменная), тогда это называется классификацией. Например, определение того, является ли электронное письмо спамом или нет.
  • Регрессия: когда задача состоит в том, чтобы предсказать непрерывную переменную (целевую переменную), тогда это называется регрессией. Например, прогнозирование цен на жилье.
  • Неконтролируемый алгоритм ML: те алгоритмы, которые используют немаркированные данные, известны как неконтролируемые алгоритмы ml. Для кластеризации используется неконтролируемый алгоритм.
  • Кластеризация: задача поиска групп в заданных немаркированных данных известна как кластеризация.
  • Ошибка: разница между фактическим и прогнозируемым значением.
  • Градиентный спуск: механизм обновления параметров модели таким образом, чтобы генерировать минимальное значение функции ошибки.

Что такое линейная регрессия в машинном обучении?

Линейная регрессия — это тип контролируемого алгоритма машинного обучения, который используется для прогнозирования непрерывной числовой переменной, известной как цель. Это один из самых простых алгоритмов машинного обучения. Он называется «линейным», потому что алгоритм предполагает, что взаимосвязь между входными характеристиками (также известными как независимые переменные) и выходной переменной (также известной как зависимая или целевая переменная) является линейной. Другими словами, алгоритм пытается найти прямую линию (или гиперплоскость в случае нескольких входных объектов), которая наилучшим образом соответствует данным.

Типы линейной регрессии:

Простая линейная регрессия:

Линейная регрессия известна как простая линейная регрессия, когда прогнозирование выходного значения выполняется с использованием одной входной функции. Мы можем провести линию между зависимыми и независимыми переменными в 2D-пространстве, когда задан один входной признак. здесь b0 — точка пересечения, b1 — коэффициент, x1, x2,…, xn — входные признаки, а y — выходная переменная.

Множественная линейная регрессия:

Линейная регрессия известна как множественная линейная регрессия, когда прогнозирование выходной переменной выполняется с использованием нескольких входных признаков. Мы можем нарисовать плоскость между зависимой и независимой переменными в 3D-пространстве, когда заданы только два входных объекта. В более высоких измерениях визуализация становится затруднительной, но интуиция заключается в том, чтобы найти гиперплоскость в более высоких измерениях. здесь b0 — это перехват, а b1, b2, b3, ......., bn-1, bn известны как коэффициенты, а x1, x2,..., xn известны как входные характеристики, а y — переменная результата.

К этому моменту мы поняли, что линейная регрессия пытается построить линейную границу, но как она это делает?

Как он найдет идеальную линию, которая разделяет данные два класса?

Как указано в уравнении, b0 известен как перехват, а b1, b2,...., bn известны как коэффициенты линейной регрессии, и теперь цель состоит в том, чтобы найти ту линейную границу, которая минимизирует функцию ошибки. Функция ошибки представляет собой квадрат суммы разностей между прогнозируемыми и фактическими значениями целевой переменной. Если мы не сведем ошибку в квадрат, то положительные и отрицательные моменты будут компенсировать друг друга.

Нам нужно найти коэффициенты и перехваты для линейной регрессии таким образом, чтобы сумма квадратов ошибок (SSE) была минимизирована. Градиентный спуск — один из самых популярных методов, который используется для нахождения оптимальных коэффициентов для ml и алгоритмов глубокого обучения.

В приведенном ниже разделе мы обучим модель на базе данных страхования, где мы должны спрогнозировать расходы с учетом входных данных: возраст, пол, ИМТ, расходы на больницу, количество прошлых консультаций и т.д.

Вы можете использовать библиотеку sklearn на python для обучения и тестирования модели линейной регрессии. Мы будем использовать набор данных insurance.csv для обучения модели линейной регрессии. Некоторые этапы предварительной обработки выполняются для описания данных, обработки пропущенных значений и проверки допущений линейной регрессии.

Шаг 1: Загрузите все необходимые библиотеки и наборы данных, используя библиотеку pandas.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from statsmodels.stats.outliers_influence import variance_inflation_factor as VIF
from sklearn.metrics import classification_report
insurance=pd.read_csv('new_insurance_data.csv') 
insurance.head()

Случайно выбранные 5 записей из набора данных

Случайно выбранные 5 записей из набора данных

Шаг 2: Проверьте нулевые значения, форму и тип данных переменных:

# checks for non-null entries, size and datatype
insurance.info()

Мы можем отдельно проверить количество нулей для каждой функции, используя df.isna().sum():

insurance.isnull().sum()
# helps me to check for null values

Шаг 3. Заполните пропущенные значения

Мы можем заполнить недостающие значения объектов объектного типа, используя режим, а объектов целочисленного типа — среднее значение или медиану.

# calculating mode for object data type features which will be used to fill missing values.
# We have 3 features which are of object type
print(f"mode of sex feature: {insurance['sex'].mode()[0]}")
print(f"mode of region feature: {insurance['region'].mode()[0]}")
print(f"mode of smoker feature: {insurance['smoker'].mode()[0]}")

# describe() function will give the descriptive statistics for all numerical features
insurance.describe().transpose()

Мы видим, что для числовых признаков среднее и медиана почти одинаковы. Поэтому теперь мы заменим нулевые значения числовых признаков их медианой, а нулевые значения категориальных переменных — их режимом.

for col_name in list(insurance.columns):
    if insurance[col_name].dtypes=='object':
        # filling null values with mode for object type features
        insurance[col_name] = insurance[col_name].fillna(insurance[col_name].mode()[0])
    else:
        # filling null values with mean for numeric type features
        insurance[col_name] = insurance[col_name].fillna(insurance[col_name].median())
# Now the null count for each feature is zero
print("After filling null values:")
print(insurance.isna().sum())

Шаг 4: Анализ выбросов

Мы построим прямоугольную диаграмму для всех числовых характеристик, кроме целевых переменных зарядов.

i = 1
plt.figure(figsize=(16,15))
for col_name in list(insurance.columns):
    # total 9 box plots will be plotted, therefore 3*3 grid is taken
    if((insurance[col_name].dtypes=='int64' or insurance[col_name].dtypes=='float64') and col_name != 'charges'):
        plt.subplot(3,3, i)
        plt.boxplot(insurance[col_name])
        plt.xlabel(col_name)
        plt.ylabel('count')
        plt.title(f"Box plot for {col_name}")
        i += 1
plt.show()

Мы видим, что характеристики ‘bmi’, ‘Hospital_expenditure’ и ‘Number_of_past_hospitalizations’ имеют выбросы. Мы удалим эти выбросы:

outliers_features = ['bmi', 'Hospital_expenditure', 'Anual_Salary', 'past_consultations']
for col_name in outliers_features:
    Q3 = insurance[col_name].quantile(0.75)
    Q1 = insurance[col_name].quantile(0.25)
    IQR = Q3 - Q1
    upper_limit = Q3 + 1.5*IQR
    lower_limit = Q1 - 1.5*IQR
    prev_size = len(insurance)
    insurance = insurance[(insurance[col_name] >= lower_limit) & (insurance[col_name] <= upper_limit)]
    cur_size = len(insurance)
    print(f"dropped {prev_size - cur_size} rows for {col_name}  due to presence of outliers")

Шаг 5: Проверьте корреляцию:

Существует корреляция между age & charges, age & Anual_salary и т. д., поскольку их корреляция больше 0,5.

import seaborn as sns
sns.heatmap(insurance.corr(),cmap='gist_rainbow',annot=True)
plt.show()

Мы проверим наличие мультиколлинеарности среди признаков:

from statsmodels.stats.outliers_influence import variance_inflation_factor 
col_list = []
for col in insurance.columns:
    if ((insurance[col].dtype != 'object') & (col != 'charges') ):#only num cols except for the charges column
        col_list.append(col)

X = insurance[col_list]
vif_data = pd.DataFrame() 
vif_data["feature"] = X.columns 
vif_data["VIF"] = [variance_inflation_factor(X.values, i) for i in range(len(X.columns))] 
print(vif_data)

Мы видим, что функция num_of_steps имеет самую высокую коллинеарность, равную 61,43, поэтому мы удалим функцию num_of_steps и снова проверим оценку VIF.

# deleting num_of_steps feature
insurance.drop('num_of_steps', axis = 1, inplace= True)
from statsmodels.stats.outliers_influence import variance_inflation_factor 
col_list = []
for col in insurance.columns:
    if ((insurance[col].dtype != 'object') & (col != 'charges') ):#only num cols except for the charges column
        col_list.append(col)
X = insurance[col_list]

X = insurance[col_list]
vif_data = pd.DataFrame() 
vif_data["feature"] = X.columns 
vif_data["VIF"] = [variance_inflation_factor(X.values, i) for i in range(len(X.columns))] 
print(vif_data)

После удаления функции num_of_steps age имеет самую высокую коллинеарность, равную 14,63, поэтому мы удалим функцию age и снова проверим оценку VIF.

# deleting age feature
insurance.drop('age', axis = 1, inplace= True)
from statsmodels.stats.outliers_influence import variance_inflation_factor 
col_list = []
for col in insurance.columns:
    if ((insurance[col].dtype != 'object') & (col != 'charges') ):#only num cols except for the charges column
        col_list.append(col)
X = insurance[col_list]

X = insurance[col_list]
vif_data = pd.DataFrame() 
vif_data["feature"] = X.columns 
vif_data["VIF"] = [variance_inflation_factor(X.values, i) for i in range(len(X.columns))] 
print(vif_data)

После удаления функции возраста BMI имеет самую высокую коллинеарность, равную 10,36, поэтому мы удалим BMI и снова проверим показатель VIF.

# deleting bmi feature
insurance.drop('bmi', axis = 1, inplace= True)
from statsmodels.stats.outliers_influence import variance_inflation_factor 
col_list = []
for col in insurance.columns:
    if ((insurance[col].dtype != 'object') & (col != 'charges') ):#only num cols except for the charges column
        col_list.append(col)
X = insurance[col_list]

X = insurance[col_list]
vif_data = pd.DataFrame() 
vif_data["feature"] = X.columns 
vif_data["VIF"] = [variance_inflation_factor(X.values, i) for i in range(len(X.columns))] 
print(vif_data)

Шаг 6: Разделение входных функций и целевой переменной:

x=insurance.loc[:,['children','Claim_Amount','past_consultations','Hospital_expenditure','NUmber_of_past_hospitalizations','Anual_Salary']]
y=insurance.loc[:,'charges']
x_train, x_test, y_train, y_test=train_test_split(x,y,train_size=0.8, random_state=0)
print("length of train dataset: ",len(x_train) )
print("length of test dataset: ",len(x_test) )

Шаг 7: Обучение модели линейной регрессии на наборе поездов и ее оценка на тестовом наборе данных:

from sklearn.linear_model import LinearRegression
from sklearn.metrics import classification_report, recall_score, r2_score, f1_score, accuracy_score

model = LinearRegression()
# train the model
model.fit(x_train, y_train)
print("trained model coefficients:", model.coef_, " and intercept is: ", model.intercept_)
# model.intercept_ is b0 term in linear boundary equation, and model.coef_ is
#  the array of weights assigned to ['children','Claim_Amount','past_consultations','Hospital_expenditure',
#                    'NUmber_of_past_hospitalizations','Anual_Salary'] respectively

y_pred = model.predict(x_test)
error_pred=pd.DataFrame(columns={'Actual_data','Prediction_data'})
error_pred['Prediction_data'] = y_pred
error_pred['Actual_data'] = y_test
error_pred["error"] = y_test - y_pred
sns.distplot(error_pred['error'])
plt.show()

Мы можем построить остаточные графики между фактической целью и остатками или ошибками:

sns.scatterplot(x = y_test,y =  (y_test - y_pred), c = 'g', s = 40)
plt.hlines(y = 0, xmin = 0, xmax=20000)
plt.title("residual plot")
plt.xlabel("actural target")
plt.ylabel("residula error")

Оценка R-квадрата:

R-квадрат известен как коэффициент детерминации. R Squared — это статистическая мера, которая представляет долю дисперсии зависимой переменной, объясненную независимыми переменными в регрессии. Это значение находится в диапазоне от 0 до 1. Значение «1» указывает, что предиктор полностью учитывает все изменения в Y. Значение «0» указывает, что предиктор «x» не учитывает никаких изменений в «y». Значение R-Squared содержит три термина SSE, SSR и SST.

SSE — это сумма квадратов ошибок. Его также называют остаточной суммой квадратов (RSS).

SSR — это сумма квадратов регрессии.

SST (Сумма в квадрате) — это квадрат разницы между наблюдаемой зависимой переменной и ее средним значением.

# check for model performance
print(f'r2 score of trained model: {r2_score(y_pred=y_pred, y_true= y_test)}')

Предположения линейной регрессии

  • Линейная связь: линейная регрессия предполагает линейную связь между прогнозируемой переменной и независимой переменной. Вы можете использовать точечную диаграмму, чтобы визуализировать взаимосвязь между независимой переменной и зависимой переменной в 2D-пространстве.
  • Небольшая мультиколлинеарность или отсутствие мультиколлинеарности между функциями: линейная регрессия предполагает, что функции должны быть независимыми друг от друга, т. Е. Никакой корреляции между функциями. Вы можете использовать функцию VIF, чтобы найти значение мультиколлинеарности признаков. Общее предположение гласит, что если значение признака VIF больше 5, то признаки сильно коррелированы.
  • Однородность: линейная регрессия предполагает, что члены ошибок имеют постоянную дисперсию, т. е. разброс членов ошибок должен быть постоянным. Это предположение можно проверить, построив остаточную диаграмму. Если предположение нарушается, то точки образуют форму воронки, в противном случае они будут постоянными.
  • Нормальность: линейная регрессия предполагает, что каждая функция данного набора данных следует нормальному распределению. Вы можете строить гистограммы и графики KDE для каждой функции, чтобы проверить, нормально ли они распределены или нет.
  • Ошибка: линейная регрессия предполагает, что условия ошибки также должны быть нормально распределены. Вы можете строить гистограммы, а KDE строит графики ошибок, чтобы проверить, нормально ли они распределены или нет.

Вот ссылка GitHub для кода и набора данных.

The following are a set of methods intended for regression in which
the target value is expected to be a linear combination of the features.
In mathematical notation, if (hat{y}) is the predicted
value.

[hat{y}(w, x) = w_0 + w_1 x_1 + … + w_p x_p]

Across the module, we designate the vector (w = (w_1,
…, w_p))
as coef_ and (w_0) as intercept_.

To perform classification with generalized linear models, see
Logistic regression.

1.1.1. Ordinary Least Squares¶

LinearRegression fits a linear model with coefficients
(w = (w_1, …, w_p)) to minimize the residual sum
of squares between the observed targets in the dataset, and the
targets predicted by the linear approximation. Mathematically it
solves a problem of the form:

[min_{w} || X w — y||_2^2]

../_images/sphx_glr_plot_ols_001.png

LinearRegression will take in its fit method arrays X, y
and will store the coefficients (w) of the linear model in its
coef_ member:

>>> from sklearn import linear_model
>>> reg = linear_model.LinearRegression()
>>> reg.fit([[0, 0], [1, 1], [2, 2]], [0, 1, 2])
LinearRegression()
>>> reg.coef_
array([0.5, 0.5])

The coefficient estimates for Ordinary Least Squares rely on the
independence of the features. When features are correlated and the
columns of the design matrix (X) have an approximately linear
dependence, the design matrix becomes close to singular
and as a result, the least-squares estimate becomes highly sensitive
to random errors in the observed target, producing a large
variance. This situation of multicollinearity can arise, for
example, when data are collected without an experimental design.

1.1.1.1. Non-Negative Least Squares¶

It is possible to constrain all the coefficients to be non-negative, which may
be useful when they represent some physical or naturally non-negative
quantities (e.g., frequency counts or prices of goods).
LinearRegression accepts a boolean positive
parameter: when set to True Non-Negative Least Squares are then applied.

1.1.1.2. Ordinary Least Squares Complexity¶

The least squares solution is computed using the singular value
decomposition of X. If X is a matrix of shape (n_samples, n_features)
this method has a cost of
(O(n_{text{samples}} n_{text{features}}^2)), assuming that
(n_{text{samples}} geq n_{text{features}}).

1.1.2. Ridge regression and classification¶

1.1.2.1. Regression¶

Ridge regression addresses some of the problems of
Ordinary Least Squares by imposing a penalty on the size of the
coefficients. The ridge coefficients minimize a penalized residual sum
of squares:

[min_{w} || X w — y||_2^2 + alpha ||w||_2^2]

The complexity parameter (alpha geq 0) controls the amount
of shrinkage: the larger the value of (alpha), the greater the amount
of shrinkage and thus the coefficients become more robust to collinearity.

../_images/sphx_glr_plot_ridge_path_001.png

As with other linear models, Ridge will take in its fit method
arrays X, y and will store the coefficients (w) of the linear model in
its coef_ member:

>>> from sklearn import linear_model
>>> reg = linear_model.Ridge(alpha=.5)
>>> reg.fit([[0, 0], [0, 0], [1, 1]], [0, .1, 1])
Ridge(alpha=0.5)
>>> reg.coef_
array([0.34545455, 0.34545455])
>>> reg.intercept_
0.13636...

Note that the class Ridge allows for the user to specify that the
solver be automatically chosen by setting solver="auto". When this option
is specified, Ridge will choose between the "lbfgs", "cholesky",
and "sparse_cg" solvers. Ridge will begin checking the conditions
shown in the following table from top to bottom. If the condition is true,
the corresponding solver is chosen.

Solver

Condition

‘lbfgs’

The positive=True option is specified.

‘cholesky’

The input array X is not sparse.

‘sparse_cg’

None of the above conditions are fulfilled.

1.1.2.2. Classification¶

The Ridge regressor has a classifier variant:
RidgeClassifier. This classifier first converts binary targets to
{-1, 1} and then treats the problem as a regression task, optimizing the
same objective as above. The predicted class corresponds to the sign of the
regressor’s prediction. For multiclass classification, the problem is
treated as multi-output regression, and the predicted class corresponds to
the output with the highest value.

It might seem questionable to use a (penalized) Least Squares loss to fit a
classification model instead of the more traditional logistic or hinge
losses. However, in practice, all those models can lead to similar
cross-validation scores in terms of accuracy or precision/recall, while the
penalized least squares loss used by the RidgeClassifier allows for
a very different choice of the numerical solvers with distinct computational
performance profiles.

The RidgeClassifier can be significantly faster than e.g.
LogisticRegression with a high number of classes because it can
compute the projection matrix ((X^T X)^{-1} X^T) only once.

This classifier is sometimes referred to as a Least Squares Support Vector
Machines with
a linear kernel.

1.1.2.3. Ridge Complexity¶

This method has the same order of complexity as
Ordinary Least Squares.

1.1.2.4. Setting the regularization parameter: leave-one-out Cross-Validation¶

RidgeCV implements ridge regression with built-in
cross-validation of the alpha parameter. The object works in the same way
as GridSearchCV except that it defaults to Leave-One-Out Cross-Validation:

>>> import numpy as np
>>> from sklearn import linear_model
>>> reg = linear_model.RidgeCV(alphas=np.logspace(-6, 6, 13))
>>> reg.fit([[0, 0], [0, 0], [1, 1]], [0, .1, 1])
RidgeCV(alphas=array([1.e-06, 1.e-05, 1.e-04, 1.e-03, 1.e-02, 1.e-01, 1.e+00, 1.e+01,
      1.e+02, 1.e+03, 1.e+04, 1.e+05, 1.e+06]))
>>> reg.alpha_
0.01

Specifying the value of the cv attribute will trigger the use of
cross-validation with GridSearchCV, for
example cv=10 for 10-fold cross-validation, rather than Leave-One-Out
Cross-Validation.

1.1.3. Lasso¶

The Lasso is a linear model that estimates sparse coefficients.
It is useful in some contexts due to its tendency to prefer solutions
with fewer non-zero coefficients, effectively reducing the number of
features upon which the given solution is dependent. For this reason,
Lasso and its variants are fundamental to the field of compressed sensing.
Under certain conditions, it can recover the exact set of non-zero
coefficients (see
Compressive sensing: tomography reconstruction with L1 prior (Lasso)).

Mathematically, it consists of a linear model with an added regularization term.
The objective function to minimize is:

[min_{w} { frac{1}{2n_{text{samples}}} ||X w — y||_2 ^ 2 + alpha ||w||_1}]

The lasso estimate thus solves the minimization of the
least-squares penalty with (alpha ||w||_1) added, where
(alpha) is a constant and (||w||_1) is the (ell_1)-norm of
the coefficient vector.

The implementation in the class Lasso uses coordinate descent as
the algorithm to fit the coefficients. See Least Angle Regression
for another implementation:

>>> from sklearn import linear_model
>>> reg = linear_model.Lasso(alpha=0.1)
>>> reg.fit([[0, 0], [1, 1]], [0, 1])
Lasso(alpha=0.1)
>>> reg.predict([[1, 1]])
array([0.8])

The function lasso_path is useful for lower-level tasks, as it
computes the coefficients along the full path of possible values.

Note

Feature selection with Lasso

As the Lasso regression yields sparse models, it can
thus be used to perform feature selection, as detailed in
L1-based feature selection.

The following two references explain the iterations
used in the coordinate descent solver of scikit-learn, as well as
the duality gap computation used for convergence control.

1.1.3.1. Setting regularization parameter¶

The alpha parameter controls the degree of sparsity of the estimated
coefficients.

1.1.3.1.1. Using cross-validation¶

scikit-learn exposes objects that set the Lasso alpha parameter by
cross-validation: LassoCV and LassoLarsCV.
LassoLarsCV is based on the Least Angle Regression algorithm
explained below.

For high-dimensional datasets with many collinear features,
LassoCV is most often preferable. However, LassoLarsCV has
the advantage of exploring more relevant values of alpha parameter, and
if the number of samples is very small compared to the number of
features, it is often faster than LassoCV.

lasso_cv_1 lasso_cv_2

1.1.3.1.2. Information-criteria based model selection¶

Alternatively, the estimator LassoLarsIC proposes to use the
Akaike information criterion (AIC) and the Bayes Information criterion (BIC).
It is a computationally cheaper alternative to find the optimal value of alpha
as the regularization path is computed only once instead of k+1 times
when using k-fold cross-validation.

Indeed, these criteria are computed on the in-sample training set. In short,
they penalize the over-optimistic scores of the different Lasso models by
their flexibility (cf. to “Mathematical details” section below).

However, such criteria need a proper estimation of the degrees of freedom of
the solution, are derived for large samples (asymptotic results) and assume the
correct model is candidates under investigation. They also tend to break when
the problem is badly conditioned (e.g. more features than samples).

../_images/sphx_glr_plot_lasso_lars_ic_001.png

Mathematical details

The definition of AIC (and thus BIC) might differ in the literature. In this
section, we give more information regarding the criterion computed in
scikit-learn. The AIC criterion is defined as:

[AIC = -2 log(hat{L}) + 2 d]

where (hat{L}) is the maximum likelihood of the model and
(d) is the number of parameters (as well referred to as degrees of
freedom in the previous section).

The definition of BIC replace the constant (2) by (log(N)):

[BIC = -2 log(hat{L}) + log(N) d]

where (N) is the number of samples.

For a linear Gaussian model, the maximum log-likelihood is defined as:

[log(hat{L}) = — frac{n}{2} log(2 pi) — frac{n}{2} ln(sigma^2) — frac{sum_{i=1}^{n} (y_i — hat{y}_i)^2}{2sigma^2}]

where (sigma^2) is an estimate of the noise variance,
(y_i) and (hat{y}_i) are respectively the true and predicted
targets, and (n) is the number of samples.

Plugging the maximum log-likelihood in the AIC formula yields:

[AIC = n log(2 pi sigma^2) + frac{sum_{i=1}^{n} (y_i — hat{y}_i)^2}{sigma^2} + 2 d]

The first term of the above expression is sometimes discarded since it is a
constant when (sigma^2) is provided. In addition,
it is sometimes stated that the AIC is equivalent to the (C_p) statistic
[12]. In a strict sense, however, it is equivalent only up to some constant
and a multiplicative factor.

At last, we mentioned above that (sigma^2) is an estimate of the
noise variance. In LassoLarsIC when the parameter noise_variance is
not provided (default), the noise variance is estimated via the unbiased
estimator [13] defined as:

[sigma^2 = frac{sum_{i=1}^{n} (y_i — hat{y}_i)^2}{n — p}]

where (p) is the number of features and (hat{y}_i) is the
predicted target using an ordinary least squares regression. Note, that this
formula is valid only when n_samples > n_features.

1.1.3.1.3. Comparison with the regularization parameter of SVM¶

The equivalence between alpha and the regularization parameter of SVM,
C is given by alpha = 1 / C or alpha = 1 / (n_samples * C),
depending on the estimator and the exact objective function optimized by the
model.

1.1.4. Multi-task Lasso¶

The MultiTaskLasso is a linear model that estimates sparse
coefficients for multiple regression problems jointly: y is a 2D array,
of shape (n_samples, n_tasks). The constraint is that the selected
features are the same for all the regression problems, also called tasks.

The following figure compares the location of the non-zero entries in the
coefficient matrix W obtained with a simple Lasso or a MultiTaskLasso.
The Lasso estimates yield scattered non-zeros while the non-zeros of
the MultiTaskLasso are full columns.

multi_task_lasso_1 multi_task_lasso_2

Fitting a time-series model, imposing that any active feature be active at all times.

Mathematically, it consists of a linear model trained with a mixed
(ell_1) (ell_2)-norm for regularization.
The objective function to minimize is:

[min_{W} { frac{1}{2n_{text{samples}}} ||X W — Y||_{text{Fro}} ^ 2 + alpha ||W||_{21}}]

where (text{Fro}) indicates the Frobenius norm

[||A||_{text{Fro}} = sqrt{sum_{ij} a_{ij}^2}]

and (ell_1) (ell_2) reads

[||A||_{2 1} = sum_i sqrt{sum_j a_{ij}^2}.]

The implementation in the class MultiTaskLasso uses
coordinate descent as the algorithm to fit the coefficients.

1.1.5. Elastic-Net¶

ElasticNet is a linear regression model trained with both
(ell_1) and (ell_2)-norm regularization of the coefficients.
This combination allows for learning a sparse model where few of
the weights are non-zero like Lasso, while still maintaining
the regularization properties of Ridge. We control the convex
combination of (ell_1) and (ell_2) using the l1_ratio
parameter.

Elastic-net is useful when there are multiple features that are
correlated with one another. Lasso is likely to pick one of these
at random, while elastic-net is likely to pick both.

A practical advantage of trading-off between Lasso and Ridge is that it
allows Elastic-Net to inherit some of Ridge’s stability under rotation.

The objective function to minimize is in this case

[min_{w} { frac{1}{2n_{text{samples}}} ||X w — y||_2 ^ 2 + alpha rho ||w||_1 +
frac{alpha(1-rho)}{2} ||w||_2 ^ 2}]

../_images/sphx_glr_plot_lasso_coordinate_descent_path_001.png

The class ElasticNetCV can be used to set the parameters
alpha ((alpha)) and l1_ratio ((rho)) by cross-validation.

The following two references explain the iterations
used in the coordinate descent solver of scikit-learn, as well as
the duality gap computation used for convergence control.

1.1.6. Multi-task Elastic-Net¶

The MultiTaskElasticNet is an elastic-net model that estimates sparse
coefficients for multiple regression problems jointly: Y is a 2D array
of shape (n_samples, n_tasks). The constraint is that the selected
features are the same for all the regression problems, also called tasks.

Mathematically, it consists of a linear model trained with a mixed
(ell_1) (ell_2)-norm and (ell_2)-norm for regularization.
The objective function to minimize is:

[min_{W} { frac{1}{2n_{text{samples}}} ||X W — Y||_{text{Fro}}^2 + alpha rho ||W||_{2 1} +
frac{alpha(1-rho)}{2} ||W||_{text{Fro}}^2}]

The implementation in the class MultiTaskElasticNet uses coordinate descent as
the algorithm to fit the coefficients.

The class MultiTaskElasticNetCV can be used to set the parameters
alpha ((alpha)) and l1_ratio ((rho)) by cross-validation.

1.1.7. Least Angle Regression¶

Least-angle regression (LARS) is a regression algorithm for
high-dimensional data, developed by Bradley Efron, Trevor Hastie, Iain
Johnstone and Robert Tibshirani. LARS is similar to forward stepwise
regression. At each step, it finds the feature most correlated with the
target. When there are multiple features having equal correlation, instead
of continuing along the same feature, it proceeds in a direction equiangular
between the features.

The advantages of LARS are:

  • It is numerically efficient in contexts where the number of features
    is significantly greater than the number of samples.

  • It is computationally just as fast as forward selection and has
    the same order of complexity as ordinary least squares.

  • It produces a full piecewise linear solution path, which is
    useful in cross-validation or similar attempts to tune the model.

  • If two features are almost equally correlated with the target,
    then their coefficients should increase at approximately the same
    rate. The algorithm thus behaves as intuition would expect, and
    also is more stable.

  • It is easily modified to produce solutions for other estimators,
    like the Lasso.

The disadvantages of the LARS method include:

  • Because LARS is based upon an iterative refitting of the
    residuals, it would appear to be especially sensitive to the
    effects of noise. This problem is discussed in detail by Weisberg
    in the discussion section of the Efron et al. (2004) Annals of
    Statistics article.

The LARS model can be used via the estimator Lars, or its
low-level implementation lars_path or lars_path_gram.

1.1.8. LARS Lasso¶

LassoLars is a lasso model implemented using the LARS
algorithm, and unlike the implementation based on coordinate descent,
this yields the exact solution, which is piecewise linear as a
function of the norm of its coefficients.

../_images/sphx_glr_plot_lasso_lars_001.png

>>> from sklearn import linear_model
>>> reg = linear_model.LassoLars(alpha=.1)
>>> reg.fit([[0, 0], [1, 1]], [0, 1])
LassoLars(alpha=0.1)
>>> reg.coef_
array([0.6..., 0.        ])

The Lars algorithm provides the full path of the coefficients along
the regularization parameter almost for free, thus a common operation
is to retrieve the path with one of the functions lars_path
or lars_path_gram.

1.1.8.1. Mathematical formulation¶

The algorithm is similar to forward stepwise regression, but instead
of including features at each step, the estimated coefficients are
increased in a direction equiangular to each one’s correlations with
the residual.

Instead of giving a vector result, the LARS solution consists of a
curve denoting the solution for each value of the (ell_1) norm of the
parameter vector. The full coefficients path is stored in the array
coef_path_ of shape (n_features, max_features + 1). The first
column is always zero.

1.1.9. Orthogonal Matching Pursuit (OMP)¶

OrthogonalMatchingPursuit and orthogonal_mp implement the OMP
algorithm for approximating the fit of a linear model with constraints imposed
on the number of non-zero coefficients (ie. the (ell_0) pseudo-norm).

Being a forward feature selection method like Least Angle Regression,
orthogonal matching pursuit can approximate the optimum solution vector with a
fixed number of non-zero elements:

[underset{w}{operatorname{arg,min,}} ||y — Xw||_2^2 text{ subject to } ||w||_0 leq n_{text{nonzero_coefs}}]

Alternatively, orthogonal matching pursuit can target a specific error instead
of a specific number of non-zero coefficients. This can be expressed as:

[underset{w}{operatorname{arg,min,}} ||w||_0 text{ subject to } ||y-Xw||_2^2 leq text{tol}]

OMP is based on a greedy algorithm that includes at each step the atom most
highly correlated with the current residual. It is similar to the simpler
matching pursuit (MP) method, but better in that at each iteration, the
residual is recomputed using an orthogonal projection on the space of the
previously chosen dictionary elements.

1.1.10. Bayesian Regression¶

Bayesian regression techniques can be used to include regularization
parameters in the estimation procedure: the regularization parameter is
not set in a hard sense but tuned to the data at hand.

This can be done by introducing uninformative priors
over the hyper parameters of the model.
The (ell_{2}) regularization used in Ridge regression and classification is
equivalent to finding a maximum a posteriori estimation under a Gaussian prior
over the coefficients (w) with precision (lambda^{-1}).
Instead of setting lambda manually, it is possible to treat it as a random
variable to be estimated from the data.

To obtain a fully probabilistic model, the output (y) is assumed
to be Gaussian distributed around (X w):

[p(y|X,w,alpha) = mathcal{N}(y|X w,alpha)]

where (alpha) is again treated as a random variable that is to be
estimated from the data.

The advantages of Bayesian Regression are:

  • It adapts to the data at hand.

  • It can be used to include regularization parameters in the
    estimation procedure.

The disadvantages of Bayesian regression include:

  • Inference of the model can be time consuming.

1.1.10.1. Bayesian Ridge Regression¶

BayesianRidge estimates a probabilistic model of the
regression problem as described above.
The prior for the coefficient (w) is given by a spherical Gaussian:

[p(w|lambda) =
mathcal{N}(w|0,lambda^{-1}mathbf{I}_{p})]

The priors over (alpha) and (lambda) are chosen to be gamma
distributions, the
conjugate prior for the precision of the Gaussian. The resulting model is
called Bayesian Ridge Regression, and is similar to the classical
Ridge.

The parameters (w), (alpha) and (lambda) are estimated
jointly during the fit of the model, the regularization parameters
(alpha) and (lambda) being estimated by maximizing the
log marginal likelihood. The scikit-learn implementation
is based on the algorithm described in Appendix A of (Tipping, 2001)
where the update of the parameters (alpha) and (lambda) is done
as suggested in (MacKay, 1992). The initial value of the maximization procedure
can be set with the hyperparameters alpha_init and lambda_init.

There are four more hyperparameters, (alpha_1), (alpha_2),
(lambda_1) and (lambda_2) of the gamma prior distributions over
(alpha) and (lambda). These are usually chosen to be
non-informative. By default (alpha_1 = alpha_2 = lambda_1 = lambda_2 = 10^{-6}).

Bayesian Ridge Regression is used for regression:

>>> from sklearn import linear_model
>>> X = [[0., 0.], [1., 1.], [2., 2.], [3., 3.]]
>>> Y = [0., 1., 2., 3.]
>>> reg = linear_model.BayesianRidge()
>>> reg.fit(X, Y)
BayesianRidge()

After being fitted, the model can then be used to predict new values:

>>> reg.predict([[1, 0.]])
array([0.50000013])

The coefficients (w) of the model can be accessed:

>>> reg.coef_
array([0.49999993, 0.49999993])

Due to the Bayesian framework, the weights found are slightly different to the
ones found by Ordinary Least Squares. However, Bayesian Ridge Regression
is more robust to ill-posed problems.

1.1.10.2. Automatic Relevance Determination — ARD¶

The Automatic Relevance Determination (as being implemented in
ARDRegression) is a kind of linear model which is very similar to the
Bayesian Ridge Regression, but that leads to sparser coefficients (w)
[1] [2].

ARDRegression poses a different prior over (w): it drops
the spherical Gaussian distribution for a centered elliptic Gaussian
distribution. This means each coefficient (w_{i}) can itself be drawn from
a Gaussian distribution, centered on zero and with a precision
(lambda_{i}):

[p(w|lambda) = mathcal{N}(w|0,A^{-1})]

with (A) being a positive definite diagonal matrix and
(text{diag}(A) = lambda = {lambda_{1},…,lambda_{p}}).

In contrast to the Bayesian Ridge Regression, each coordinate of
(w_{i}) has its own standard deviation (frac{1}{lambda_i}). The
prior over all (lambda_i) is chosen to be the same gamma distribution
given by the hyperparameters (lambda_1) and (lambda_2).

ARD is also known in the literature as Sparse Bayesian Learning and Relevance
Vector Machine
[3] [4]. For a worked-out comparison between ARD and Bayesian
Ridge Regression, see the example below.

1.1.11. Logistic regression¶

The logistic regression is implemented in LogisticRegression. Despite
its name, it is implemented as a linear model for classification rather than
regression in terms of the scikit-learn/ML nomenclature. The logistic
regression is also known in the literature as logit regression,
maximum-entropy classification (MaxEnt) or the log-linear classifier. In this
model, the probabilities describing the possible outcomes of a single trial
are modeled using a logistic function.

This implementation can fit binary, One-vs-Rest, or multinomial logistic
regression with optional (ell_1), (ell_2) or Elastic-Net
regularization.

Note

Regularization

Regularization is applied by default, which is common in machine
learning but not in statistics. Another advantage of regularization is
that it improves numerical stability. No regularization amounts to
setting C to a very high value.

Note

Logistic Regression as a special case of the Generalized Linear Models (GLM)

Logistic regression is a special case of
Generalized Linear Models with a Binomial / Bernoulli conditional
distribution and a Logit link. The numerical output of the logistic
regression, which is the predicted probability, can be used as a classifier
by applying a threshold (by default 0.5) to it. This is how it is
implemented in scikit-learn, so it expects a categorical target, making
the Logistic Regression a classifier.

1.1.11.1. Binary Case¶

For notational ease, we assume that the target (y_i) takes values in the
set ({0, 1}) for data point (i).
Once fitted, the predict_proba
method of LogisticRegression predicts
the probability of the positive class (P(y_i=1|X_i)) as

[hat{p}(X_i) = operatorname{expit}(X_i w + w_0) = frac{1}{1 + exp(-X_i w — w_0)}.]

As an optimization problem, binary
class logistic regression with regularization term (r(w)) minimizes the
following cost function:

[min_{w} C sum_{i=1}^n left(-y_i log(hat{p}(X_i)) — (1 — y_i) log(1 — hat{p}(X_i))right) + r(w).]

We currently provide four choices for the regularization term (r(w)) via
the penalty argument:

penalty

(r(w))

None

(0)

(ell_1)

(|w|_1)

(ell_2)

(frac{1}{2}|w|_2^2 = frac{1}{2}w^T w)

ElasticNet

(frac{1 — rho}{2}w^T w + rho |w|_1)

For ElasticNet, (rho) (which corresponds to the l1_ratio parameter)
controls the strength of (ell_1) regularization vs. (ell_2)
regularization. Elastic-Net is equivalent to (ell_1) when
(rho = 1) and equivalent to (ell_2) when (rho=0).

1.1.11.2. Multinomial Case¶

The binary case can be extended to (K) classes leading to the multinomial
logistic regression, see also log-linear model.

Note

It is possible to parameterize a (K)-class classification model
using only (K-1) weight vectors, leaving one class probability fully
determined by the other class probabilities by leveraging the fact that all
class probabilities must sum to one. We deliberately choose to overparameterize the model
using (K) weight vectors for ease of implementation and to preserve the
symmetrical inductive bias regarding ordering of classes, see [16]. This effect becomes
especially important when using regularization. The choice of overparameterization can be
detrimental for unpenalized models since then the solution may not be unique, as shown in [16].

Let (y_i in {1, ldots, K}) be the label (ordinal) encoded target variable for observation (i).
Instead of a single coefficient vector, we now have
a matrix of coefficients (W) where each row vector (W_k) corresponds to class
(k). We aim at predicting the class probabilities (P(y_i=k|X_i)) via
predict_proba as:

[hat{p}_k(X_i) = frac{exp(X_i W_k + W_{0, k})}{sum_{l=0}^{K-1} exp(X_i W_l + W_{0, l})}.]

The objective for the optimization becomes

[min_W -C sum_{i=1}^n sum_{k=0}^{K-1} [y_i = k] log(hat{p}_k(X_i)) + r(W).]

Where ([P]) represents the Iverson bracket which evaluates to (0)
if (P) is false, otherwise it evaluates to (1). We currently provide four choices
for the regularization term (r(W)) via the penalty argument:

penalty

(r(W))

None

(0)

(ell_1)

(|W|_{1,1} = sum_{i=1}^nsum_{j=1}^{K}|W_{i,j}|)

(ell_2)

(frac{1}{2}|W|_F^2 = frac{1}{2}sum_{i=1}^nsum_{j=1}^{K} W_{i,j}^2)

ElasticNet

(frac{1 — rho}{2}|W|_F^2 + rho |W|_{1,1})

1.1.11.3. Solvers¶

The solvers implemented in the class LogisticRegression
are “lbfgs”, “liblinear”, “newton-cg”, “newton-cholesky”, “sag” and “saga”:

The solver “liblinear” uses a coordinate descent (CD) algorithm, and relies
on the excellent C++ LIBLINEAR library, which is shipped with
scikit-learn. However, the CD algorithm implemented in liblinear cannot learn
a true multinomial (multiclass) model; instead, the optimization problem is
decomposed in a “one-vs-rest” fashion so separate binary classifiers are
trained for all classes. This happens under the hood, so
LogisticRegression instances using this solver behave as multiclass
classifiers. For (ell_1) regularization sklearn.svm.l1_min_c allows to
calculate the lower bound for C in order to get a non “null” (all feature
weights to zero) model.

The “lbfgs”, “newton-cg” and “sag” solvers only support (ell_2)
regularization or no regularization, and are found to converge faster for some
high-dimensional data. Setting multi_class to “multinomial” with these solvers
learns a true multinomial logistic regression model [5], which means that its
probability estimates should be better calibrated than the default “one-vs-rest”
setting.

The “sag” solver uses Stochastic Average Gradient descent [6]. It is faster
than other solvers for large datasets, when both the number of samples and the
number of features are large.

The “saga” solver [7] is a variant of “sag” that also supports the
non-smooth penalty="l1". This is therefore the solver of choice for sparse
multinomial logistic regression. It is also the only solver that supports
penalty="elasticnet".

The “lbfgs” is an optimization algorithm that approximates the
Broyden–Fletcher–Goldfarb–Shanno algorithm [8], which belongs to
quasi-Newton methods. As such, it can deal with a wide range of different training
data and is therefore the default solver. Its performance, however, suffers on poorly
scaled datasets and on datasets with one-hot encoded categorical features with rare
categories.

The “newton-cholesky” solver is an exact Newton solver that calculates the hessian
matrix and solves the resulting linear system. It is a very good choice for
n_samples >> n_features, but has a few shortcomings: Only (ell_2)
regularization is supported. Furthermore, because the hessian matrix is explicitly
computed, the memory usage has a quadratic dependency on n_features as well as on
n_classes. As a consequence, only the one-vs-rest scheme is implemented for the
multiclass case.

For a comparison of some of these solvers, see [9].

The following table summarizes the penalties supported by each solver:

Solvers

Penalties

‘lbfgs’

‘liblinear’

‘newton-cg’

‘newton-cholesky’

‘sag’

‘saga’

Multinomial + L2 penalty

yes

no

yes

no

yes

yes

OVR + L2 penalty

yes

yes

yes

yes

yes

yes

Multinomial + L1 penalty

no

no

no

no

no

yes

OVR + L1 penalty

no

yes

no

no

no

yes

Elastic-Net

no

no

no

no

no

yes

No penalty (‘none’)

yes

no

yes

yes

yes

yes

Behaviors

Penalize the intercept (bad)

no

yes

no

no

no

no

Faster for large datasets

no

no

no

no

yes

yes

Robust to unscaled datasets

yes

yes

yes

yes

no

no

The “lbfgs” solver is used by default for its robustness. For large datasets
the “saga” solver is usually faster.
For large dataset, you may also consider using SGDClassifier
with loss="log_loss", which might be even faster but requires more tuning.

Note

Feature selection with sparse logistic regression

A logistic regression with (ell_1) penalty yields sparse models, and can
thus be used to perform feature selection, as detailed in
L1-based feature selection.

Note

P-value estimation

It is possible to obtain the p-values and confidence intervals for
coefficients in cases of regression without penalization. The statsmodels
package natively supports this.
Within sklearn, one could use bootstrapping instead as well.

LogisticRegressionCV implements Logistic Regression with built-in
cross-validation support, to find the optimal C and l1_ratio parameters
according to the scoring attribute. The “newton-cg”, “sag”, “saga” and
“lbfgs” solvers are found to be faster for high-dimensional dense data, due
to warm-starting (see Glossary).

1.1.12. Generalized Linear Models¶

Generalized Linear Models (GLM) extend linear models in two ways
[10]. First, the predicted values (hat{y}) are linked to a linear
combination of the input variables (X) via an inverse link function
(h) as

[hat{y}(w, X) = h(Xw).]

Secondly, the squared loss function is replaced by the unit deviance
(d) of a distribution in the exponential family (or more precisely, a
reproductive exponential dispersion model (EDM) [11]).

The minimization problem becomes:

[min_{w} frac{1}{2 n_{text{samples}}} sum_i d(y_i, hat{y}_i) + frac{alpha}{2} ||w||_2^2,]

where (alpha) is the L2 regularization penalty. When sample weights are
provided, the average becomes a weighted average.

The following table lists some specific EDMs and their unit deviance :

Distribution

Target Domain

Unit Deviance (d(y, hat{y}))

Normal

(y in (-infty, infty))

((y-hat{y})^2)

Bernoulli

(y in {0, 1})

(2({y}logfrac{y}{hat{y}}+({1}-{y})logfrac{{1}-{y}}{{1}-hat{y}}))

Categorical

(y in {0, 1, …, k})

(2sum_{i in {0, 1, …, k}} I(y = i) y_text{i}logfrac{I(y = i)}{hat{I(y = i)}})

Poisson

(y in [0, infty))

(2(ylogfrac{y}{hat{y}}-y+hat{y}))

Gamma

(y in (0, infty))

(2(logfrac{y}{hat{y}}+frac{y}{hat{y}}-1))

Inverse Gaussian

(y in (0, infty))

(frac{(y-hat{y})^2}{yhat{y}^2})

The Probability Density Functions (PDF) of these distributions are illustrated
in the following figure,

../_images/poisson_gamma_tweedie_distributions.png

PDF of a random variable Y following Poisson, Tweedie (power=1.5) and Gamma
distributions with different mean values ((mu)). Observe the point
mass at (Y=0) for the Poisson distribution and the Tweedie (power=1.5)
distribution, but not for the Gamma distribution which has a strictly
positive target domain.

The Bernoulli distribution is a discrete probability distribution modelling a
Bernoulli trial — an event that has only two mutually exclusive outcomes.
The Categorical distribution is a generalization of the Bernoulli distribution
for a categorical random variable. While a random variable in a Bernoulli
distribution has two possible outcomes, a Categorical random variable can take
on one of K possible categories, with the probability of each category
specified separately.

The choice of the distribution depends on the problem at hand:

  • If the target values (y) are counts (non-negative integer valued) or
    relative frequencies (non-negative), you might use a Poisson distribution
    with a log-link.

  • If the target values are positive valued and skewed, you might try a Gamma
    distribution with a log-link.

  • If the target values seem to be heavier tailed than a Gamma distribution, you
    might try an Inverse Gaussian distribution (or even higher variance powers of
    the Tweedie family).

  • If the target values (y) are probabilities, you can use the Bernoulli
    distribution. The Bernoulli distribution with a logit link can be used for
    binary classification. The Categorical distribution with a softmax link can be
    used for multiclass classification.

Examples of use cases include:

  • Agriculture / weather modeling: number of rain events per year (Poisson),
    amount of rainfall per event (Gamma), total rainfall per year (Tweedie /
    Compound Poisson Gamma).

  • Risk modeling / insurance policy pricing: number of claim events /
    policyholder per year (Poisson), cost per event (Gamma), total cost per
    policyholder per year (Tweedie / Compound Poisson Gamma).

  • Credit Default: probability that a loan can’t be payed back (Bernouli).

  • Fraud Detection: probability that a financial transaction like a cash transfer
    is a fraudulent transaction (Bernoulli).

  • Predictive maintenance: number of production interruption events per year
    (Poisson), duration of interruption (Gamma), total interruption time per year
    (Tweedie / Compound Poisson Gamma).

  • Medical Drug Testing: probability of curing a patient in a set of trials or
    probability that a patient will experience side effects (Bernoulli).

  • News Classification: classification of news articles into three categories
    namely Business News, Politics and Entertainment news (Categorical).

1.1.12.1. Usage¶

TweedieRegressor implements a generalized linear model for the
Tweedie distribution, that allows to model any of the above mentioned
distributions using the appropriate power parameter. In particular:

  • power = 0: Normal distribution. Specific estimators such as
    Ridge, ElasticNet are generally more appropriate in
    this case.

  • power = 1: Poisson distribution. PoissonRegressor is exposed
    for convenience. However, it is strictly equivalent to
    TweedieRegressor(power=1, link='log').

  • power = 2: Gamma distribution. GammaRegressor is exposed for
    convenience. However, it is strictly equivalent to
    TweedieRegressor(power=2, link='log').

  • power = 3: Inverse Gaussian distribution.

The link function is determined by the link parameter.

Usage example:

>>> from sklearn.linear_model import TweedieRegressor
>>> reg = TweedieRegressor(power=1, alpha=0.5, link='log')
>>> reg.fit([[0, 0], [0, 1], [2, 2]], [0, 1, 2])
TweedieRegressor(alpha=0.5, link='log', power=1)
>>> reg.coef_
array([0.2463..., 0.4337...])
>>> reg.intercept_
-0.7638...

1.1.12.2. Practical considerations¶

The feature matrix X should be standardized before fitting. This ensures
that the penalty treats features equally.

Since the linear predictor (Xw) can be negative and Poisson,
Gamma and Inverse Gaussian distributions don’t support negative values, it
is necessary to apply an inverse link function that guarantees the
non-negativeness. For example with link='log', the inverse link function
becomes (h(Xw)=exp(Xw)).

If you want to model a relative frequency, i.e. counts per exposure (time,
volume, …) you can do so by using a Poisson distribution and passing
(y=frac{mathrm{counts}}{mathrm{exposure}}) as target values
together with (mathrm{exposure}) as sample weights. For a concrete
example see e.g.
Tweedie regression on insurance claims.

When performing cross-validation for the power parameter of
TweedieRegressor, it is advisable to specify an explicit scoring function,
because the default scorer TweedieRegressor.score is a function of
power itself.

1.1.13. Stochastic Gradient Descent — SGD¶

Stochastic gradient descent is a simple yet very efficient approach
to fit linear models. It is particularly useful when the number of samples
(and the number of features) is very large.
The partial_fit method allows online/out-of-core learning.

The classes SGDClassifier and SGDRegressor provide
functionality to fit linear models for classification and regression
using different (convex) loss functions and different penalties.
E.g., with loss="log", SGDClassifier
fits a logistic regression model,
while with loss="hinge" it fits a linear support vector machine (SVM).

1.1.14. Perceptron¶

The Perceptron is another simple classification algorithm suitable for
large scale learning. By default:

  • It does not require a learning rate.

  • It is not regularized (penalized).

  • It updates its model only on mistakes.

The last characteristic implies that the Perceptron is slightly faster to
train than SGD with the hinge loss and that the resulting models are
sparser.

1.1.15. Passive Aggressive Algorithms¶

The passive-aggressive algorithms are a family of algorithms for large-scale
learning. They are similar to the Perceptron in that they do not require a
learning rate. However, contrary to the Perceptron, they include a
regularization parameter C.

For classification, PassiveAggressiveClassifier can be used with
loss='hinge' (PA-I) or loss='squared_hinge' (PA-II). For regression,
PassiveAggressiveRegressor can be used with
loss='epsilon_insensitive' (PA-I) or
loss='squared_epsilon_insensitive' (PA-II).

1.1.16. Robustness regression: outliers and modeling errors¶

Robust regression aims to fit a regression model in the
presence of corrupt data: either outliers, or error in the model.

../_images/sphx_glr_plot_theilsen_001.png

1.1.16.1. Different scenario and useful concepts¶

There are different things to keep in mind when dealing with data
corrupted by outliers:

  • Outliers in X or in y?

    Outliers in the y direction

    Outliers in the X direction

    y_outliers

    X_outliers

  • Fraction of outliers versus amplitude of error

    The number of outlying points matters, but also how much they are
    outliers.

    Small outliers

    Large outliers

    y_outliers

    large_y_outliers

An important notion of robust fitting is that of breakdown point: the
fraction of data that can be outlying for the fit to start missing the
inlying data.

Note that in general, robust fitting in high-dimensional setting (large
n_features) is very hard. The robust models here will probably not work
in these settings.

1.1.16.2. RANSAC: RANdom SAmple Consensus¶

RANSAC (RANdom SAmple Consensus) fits a model from random subsets of
inliers from the complete data set.

RANSAC is a non-deterministic algorithm producing only a reasonable result with
a certain probability, which is dependent on the number of iterations (see
max_trials parameter). It is typically used for linear and non-linear
regression problems and is especially popular in the field of photogrammetric
computer vision.

The algorithm splits the complete input sample data into a set of inliers,
which may be subject to noise, and outliers, which are e.g. caused by erroneous
measurements or invalid hypotheses about the data. The resulting model is then
estimated only from the determined inliers.

../_images/sphx_glr_plot_ransac_001.png

1.1.16.2.1. Details of the algorithm¶

Each iteration performs the following steps:

  1. Select min_samples random samples from the original data and check
    whether the set of data is valid (see is_data_valid).

  2. Fit a model to the random subset (base_estimator.fit) and check
    whether the estimated model is valid (see is_model_valid).

  3. Classify all data as inliers or outliers by calculating the residuals
    to the estimated model (base_estimator.predict(X) - y) — all data
    samples with absolute residuals smaller than or equal to the
    residual_threshold are considered as inliers.

  4. Save fitted model as best model if number of inlier samples is
    maximal. In case the current estimated model has the same number of
    inliers, it is only considered as the best model if it has better score.

These steps are performed either a maximum number of times (max_trials) or
until one of the special stop criteria are met (see stop_n_inliers and
stop_score). The final model is estimated using all inlier samples (consensus
set) of the previously determined best model.

The is_data_valid and is_model_valid functions allow to identify and reject
degenerate combinations of random sub-samples. If the estimated model is not
needed for identifying degenerate cases, is_data_valid should be used as it
is called prior to fitting the model and thus leading to better computational
performance.

1.1.16.3. Theil-Sen estimator: generalized-median-based estimator¶

The TheilSenRegressor estimator uses a generalization of the median in
multiple dimensions. It is thus robust to multivariate outliers. Note however
that the robustness of the estimator decreases quickly with the dimensionality
of the problem. It loses its robustness properties and becomes no
better than an ordinary least squares in high dimension.

1.1.16.3.1. Theoretical considerations¶

TheilSenRegressor is comparable to the Ordinary Least Squares
(OLS)
in terms of asymptotic efficiency and as an
unbiased estimator. In contrast to OLS, Theil-Sen is a non-parametric
method which means it makes no assumption about the underlying
distribution of the data. Since Theil-Sen is a median-based estimator, it
is more robust against corrupted data aka outliers. In univariate
setting, Theil-Sen has a breakdown point of about 29.3% in case of a
simple linear regression which means that it can tolerate arbitrary
corrupted data of up to 29.3%.

../_images/sphx_glr_plot_theilsen_001.png

The implementation of TheilSenRegressor in scikit-learn follows a
generalization to a multivariate linear regression model [14] using the
spatial median which is a generalization of the median to multiple
dimensions [15].

In terms of time and space complexity, Theil-Sen scales according to

[binom{n_{text{samples}}}{n_{text{subsamples}}}]

which makes it infeasible to be applied exhaustively to problems with a
large number of samples and features. Therefore, the magnitude of a
subpopulation can be chosen to limit the time and space complexity by
considering only a random subset of all possible combinations.

1.1.16.4. Huber Regression¶

The HuberRegressor is different to Ridge because it applies a
linear loss to samples that are classified as outliers.
A sample is classified as an inlier if the absolute error of that sample is
lesser than a certain threshold. It differs from TheilSenRegressor
and RANSACRegressor because it does not ignore the effect of the outliers
but gives a lesser weight to them.

../_images/sphx_glr_plot_huber_vs_ridge_001.png

The loss function that HuberRegressor minimizes is given by

[min_{w, sigma} {sum_{i=1}^nleft(sigma + H_{epsilon}left(frac{X_{i}w — y_{i}}{sigma}right)sigmaright) + alpha {||w||_2}^2}]

where

[begin{split}H_{epsilon}(z) = begin{cases}
z^2, & text {if } |z| < epsilon, \
2epsilon|z| — epsilon^2, & text{otherwise}
end{cases}end{split}]

It is advised to set the parameter epsilon to 1.35 to achieve 95% statistical efficiency.

1.1.16.5. Notes¶

The HuberRegressor differs from using SGDRegressor with loss set to huber
in the following ways.

  • HuberRegressor is scaling invariant. Once epsilon is set, scaling X and y
    down or up by different values would produce the same robustness to outliers as before.
    as compared to SGDRegressor where epsilon has to be set again when X and y are
    scaled.

  • HuberRegressor should be more efficient to use on data with small number of
    samples while SGDRegressor needs a number of passes on the training data to
    produce the same robustness.

Note that this estimator is different from the R implementation of Robust Regression
(https://stats.oarc.ucla.edu/r/dae/robust-regression/) because the R implementation does a weighted least
squares implementation with weights given to each sample on the basis of how much the residual is
greater than a certain threshold.

1.1.17. Quantile Regression¶

Quantile regression estimates the median or other quantiles of (y)
conditional on (X), while ordinary least squares (OLS) estimates the
conditional mean.

As a linear model, the QuantileRegressor gives linear predictions
(hat{y}(w, X) = Xw) for the (q)-th quantile, (q in (0, 1)).
The weights or coefficients (w) are then found by the following
minimization problem:

[min_{w} {frac{1}{n_{text{samples}}}
sum_i PB_q(y_i — X_i w) + alpha ||w||_1}.]

This consists of the pinball loss (also known as linear loss),
see also mean_pinball_loss,

[begin{split}PB_q(t) = q max(t, 0) + (1 — q) max(-t, 0) =
begin{cases}
q t, & t > 0, \
0, & t = 0, \
(q-1) t, & t < 0
end{cases}end{split}]

and the L1 penalty controlled by parameter alpha, similar to
Lasso.

As the pinball loss is only linear in the residuals, quantile regression is
much more robust to outliers than squared error based estimation of the mean.
Somewhat in between is the HuberRegressor.

Quantile regression may be useful if one is interested in predicting an
interval instead of point prediction. Sometimes, prediction intervals are
calculated based on the assumption that prediction error is distributed
normally with zero mean and constant variance. Quantile regression provides
sensible prediction intervals even for errors with non-constant (but
predictable) variance or non-normal distribution.

../_images/sphx_glr_plot_quantile_regression_002.png

Based on minimizing the pinball loss, conditional quantiles can also be
estimated by models other than linear models. For example,
GradientBoostingRegressor can predict conditional
quantiles if its parameter loss is set to "quantile" and parameter
alpha is set to the quantile that should be predicted. See the example in
Prediction Intervals for Gradient Boosting Regression.

Most implementations of quantile regression are based on linear programming
problem. The current implementation is based on
scipy.optimize.linprog.

1.1.18. Polynomial regression: extending linear models with basis functions¶

One common pattern within machine learning is to use linear models trained
on nonlinear functions of the data. This approach maintains the generally
fast performance of linear methods, while allowing them to fit a much wider
range of data.

For example, a simple linear regression can be extended by constructing
polynomial features from the coefficients. In the standard linear
regression case, you might have a model that looks like this for
two-dimensional data:

[hat{y}(w, x) = w_0 + w_1 x_1 + w_2 x_2]

If we want to fit a paraboloid to the data instead of a plane, we can combine
the features in second-order polynomials, so that the model looks like this:

[hat{y}(w, x) = w_0 + w_1 x_1 + w_2 x_2 + w_3 x_1 x_2 + w_4 x_1^2 + w_5 x_2^2]

The (sometimes surprising) observation is that this is still a linear model:
to see this, imagine creating a new set of features

[z = [x_1, x_2, x_1 x_2, x_1^2, x_2^2]]

With this re-labeling of the data, our problem can be written

[hat{y}(w, z) = w_0 + w_1 z_1 + w_2 z_2 + w_3 z_3 + w_4 z_4 + w_5 z_5]

We see that the resulting polynomial regression is in the same class of
linear models we considered above (i.e. the model is linear in (w))
and can be solved by the same techniques. By considering linear fits within
a higher-dimensional space built with these basis functions, the model has the
flexibility to fit a much broader range of data.

Here is an example of applying this idea to one-dimensional data, using
polynomial features of varying degrees:

../_images/sphx_glr_plot_polynomial_interpolation_001.png

This figure is created using the PolynomialFeatures transformer, which
transforms an input data matrix into a new data matrix of a given degree.
It can be used as follows:

>>> from sklearn.preprocessing import PolynomialFeatures
>>> import numpy as np
>>> X = np.arange(6).reshape(3, 2)
>>> X
array([[0, 1],
       [2, 3],
       [4, 5]])
>>> poly = PolynomialFeatures(degree=2)
>>> poly.fit_transform(X)
array([[ 1.,  0.,  1.,  0.,  0.,  1.],
       [ 1.,  2.,  3.,  4.,  6.,  9.],
       [ 1.,  4.,  5., 16., 20., 25.]])

The features of X have been transformed from ([x_1, x_2]) to
([1, x_1, x_2, x_1^2, x_1 x_2, x_2^2]), and can now be used within
any linear model.

This sort of preprocessing can be streamlined with the
Pipeline tools. A single object representing a simple
polynomial regression can be created and used as follows:

>>> from sklearn.preprocessing import PolynomialFeatures
>>> from sklearn.linear_model import LinearRegression
>>> from sklearn.pipeline import Pipeline
>>> import numpy as np
>>> model = Pipeline([('poly', PolynomialFeatures(degree=3)),
...                   ('linear', LinearRegression(fit_intercept=False))])
>>> # fit to an order-3 polynomial data
>>> x = np.arange(5)
>>> y = 3 - 2 * x + x ** 2 - x ** 3
>>> model = model.fit(x[:, np.newaxis], y)
>>> model.named_steps['linear'].coef_
array([ 3., -2.,  1., -1.])

The linear model trained on polynomial features is able to exactly recover
the input polynomial coefficients.

In some cases it’s not necessary to include higher powers of any single feature,
but only the so-called interaction features
that multiply together at most (d) distinct features.
These can be gotten from PolynomialFeatures with the setting
interaction_only=True.

For example, when dealing with boolean features,
(x_i^n = x_i) for all (n) and is therefore useless;
but (x_i x_j) represents the conjunction of two booleans.
This way, we can solve the XOR problem with a linear classifier:

>>> from sklearn.linear_model import Perceptron
>>> from sklearn.preprocessing import PolynomialFeatures
>>> import numpy as np
>>> X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
>>> y = X[:, 0] ^ X[:, 1]
>>> y
array([0, 1, 1, 0])
>>> X = PolynomialFeatures(interaction_only=True).fit_transform(X).astype(int)
>>> X
array([[1, 0, 0, 0],
       [1, 0, 1, 0],
       [1, 1, 0, 0],
       [1, 1, 1, 1]])
>>> clf = Perceptron(fit_intercept=False, max_iter=10, tol=None,
...                  shuffle=False).fit(X, y)

And the classifier “predictions” are perfect:

>>> clf.predict(X)
array([0, 1, 1, 0])
>>> clf.score(X, y)
1.0

tl;dr

not with scikit-learn, but you can compute this manually with some linear algebra. i do this for your example below.

also here’s a jupyter notebook with this code: https://gist.github.com/grisaitis/cf481034bb413a14d3ea851dab201d31

what and why

the standard errors of your estimates are just the square root of the variances of your estimates. what’s the variance of your estimate? if you assume your model has gaussian error, it’s:

Var(beta_hat) = inverse(X.T @ X) * sigma_squared_hat

and then the standard error of beta_hat[i] is Var(beta_hat)[i, i] ** 0.5.

All you have to compute sigma_squared_hat. This is the estimate of your model’s gaussian error. This is not known a priori but can be estimated with the sample variance of your residuals.

Also you need to add an intercept term to your data matrix. Scikit-learn does this automatically with the LinearRegression class. So to compute this yourself you need to add that to your X matrix or dataframe.

how

Starting after your code,

show your scikit-learn results

print(model.intercept_)
print(model.coef_)
[-0.28671532]
[[ 0.17501115 -0.6928708   0.22336584]]

reproduce this with linear algebra

N = len(X)
p = len(X.columns) + 1  # plus one because LinearRegression adds an intercept term

X_with_intercept = np.empty(shape=(N, p), dtype=np.float)
X_with_intercept[:, 0] = 1
X_with_intercept[:, 1:p] = X.values

beta_hat = np.linalg.inv(X_with_intercept.T @ X_with_intercept) @ X_with_intercept.T @ y.values
print(beta_hat)
[[-0.28671532]
 [ 0.17501115]
 [-0.6928708 ]
 [ 0.22336584]]

compute standard errors of the parameter estimates

y_hat = model.predict(X)
residuals = y.values - y_hat
residual_sum_of_squares = residuals.T @ residuals
sigma_squared_hat = residual_sum_of_squares[0, 0] / (N - p)
var_beta_hat = np.linalg.inv(X_with_intercept.T @ X_with_intercept) * sigma_squared_hat
for p_ in range(p):
    standard_error = var_beta_hat[p_, p_] ** 0.5
    print(f"SE(beta_hat[{p_}]): {standard_error}")
SE(beta_hat[0]): 0.2468580488280805
SE(beta_hat[1]): 0.2965501221823944
SE(beta_hat[2]): 0.3518847753610169
SE(beta_hat[3]): 0.3250760291745124

confirm with statsmodels

import statsmodels.api as sm
ols = sm.OLS(y.values, X_with_intercept)
ols_result = ols.fit()
ols_result.summary()
...
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
const         -0.2867      0.247     -1.161      0.290      -0.891       0.317
x1             0.1750      0.297      0.590      0.577      -0.551       0.901
x2            -0.6929      0.352     -1.969      0.096      -1.554       0.168
x3             0.2234      0.325      0.687      0.518      -0.572       1.019
==============================================================================

yay, done!

Понравилась статья? Поделить с друзьями:
  • Стандартная ошибка модели значения
  • Стандартная ошибка среднего пример
  • Стандартная ошибка разности выборочных средних
  • Стандартная ошибка для коэффициента корреляции
  • Стандартная ошибка среднего показывает