Ремарка:
В данной статье представлен авторский метод нахождения t-критерия
, разработанный в процессе решения более крупной задачи. Этот метод не претендует на эффективность или универсальность в различных условиях применения, а представляет собой исключительно личный подход к решению конкретной задачи. Автор делится своим опытом и методологией работы, не утверждая их общепризнанной пригодности или оптимальности в других контекстах.
t-критерий:
t-критерий является ключевым инструментом статистического анализа, используемым для проверки гипотез о средних значениях двух выборок или для оценки значимости различий между группами данных. Этот критерий основан на распределении Стьюдента и позволяет определить, насколько значимы различия между выборочными средними, учитывая их стандартные ошибки. Значение t-критерия вычисляется как отношение разности между средними значениями выборок к их стандартной ошибке разности.
(t-распределение) используется в статистике для оценки доверительных интервалов и проверки гипотез о средних значениях, когда объем выборки мал или параметры генеральной совокупности неизвестны. Функция плотности вероятности t-распределения определяется следующей формулой:
где t
- t-критерй, n
- число степеней свободы
Основные факторы, от которых зависит t-критерий:
Уровень значимости
α
: Уровень значимости определяет вероятность ошибки первого рода (отвержение верной нулевой гипотезы).Число степеней свободы
n
: Это параметр, зависящий от размера выборки и определяющий форму распределения Стьюдента.
то есть в ствою очередь t
- это можно считать (не в строгом смысле) как функцию от α
и n
:
вырозить програмно эту зависимость я ставлю в данной статье своей задачей в виде функции ta.
//"интерфейс" этой функции
def ta(a: Double, n: Int): Double = ???
// где a и n доверительный интервал и число степеней свободы соответственно
для нахождения t-критерия мне понадобилось два алгоритма ACM295 и ACM209
ACM209:
Алгоритм ACM209 представляет собой численное приближение для вычисления функции распределения Гаусса, также известной как нормальное распределение. Этот алгоритм был предложен для быстрого и точного вычисления значений интеграла от функции плотности вероятности нормального распределения. даный алгоритм всплывает в данном контексте поскольку t-распределение и распределения гауса "связаны" и в предельном случе при t-распределение
стремиться к гаусовскому и в последсвии все сведется к вычислению разницы между t-распределением
и гаусовым.
//ACM 209 реализация на scala
def ACM209(z: Double): Double = {
var y: Double = 0.0
var p: Double = 0.0
var w: Double = 0.0
if (z == 0.0)
p = 0.0
else {
y = Math.abs(z) / 2
if (y >= 3.0) {
p = 1.0
} else if (y < 1.0) {
w = y * y
p = ((((((((0.000124818987 * w
- 0.001075204047) * w + 0.005198775019) * w
- 0.019198292004) * w + 0.059054035642) * w
- 0.151968751364) * w + 0.319152932694) * w
- 0.5319230073) * w + 0.797884560593) * y * 2.0
} else {
y = y - 2.0
p = (((((((((((((-0.000045255659 * y
+ 0.00015252929) * y - 0.000019538132) * y
- 0.000676904986) * y + 0.001390604284) * y
- 0.00079462082) * y - 0.002034254874) * y
+ 0.006549791214) * y - 0.010557625006) * y
+ 0.011630447319) * y - 0.009279453341) * y
+ 0.005353579108) * y - 0.002141268741) * y
+ 0.000535310849) * y + 0.999936657524
}
}
if (z > 0.0)
(p + 1.0) / 2
else
(1.0 - p) / 2
}
Реализация алгоритма на языке Scala, представленная выше, включает несколько ключевых шагов.
Сначала вычисляется абсолютное значение входного параметра z
, и в зависимости от его величины алгоритм выбирает одну из нескольких веток вычислений. Если y
(половина абсолютного значения z
) больше или равно 3, вероятность р
устанавливается равной 1. В случае y
меньше 1, применяется полиномиальная аппроксимация для вычисления p
с использованием ряда коэффициентов. Для значений y
между 1 и 3 используется другая полиномиальная аппроксимация, включающая другой набор коэффициентов. Наконец, в зависимости от знака z
, полученное значение p
корректируется для возвращения результата, представляющего собой значение функции распределения Гаусса для данного z
.
ACM395:
Алгоритм ACM395 реализует численное приближение для вычисления значений функции распределения t-статистики. Эта функция возвращает параметр p
:уровнем значимости или же вероятность ошибки первого рода. предстовляет собой функцию от числа степеней свободы n
и t-критерия
:
//ACM 395 моя реализация на scala
def ACM395(t: Double, n: Int): Double = {
var a = 0.0
var b = 0.0
var y = 0.0
val tSq = t * t
y = tSq / n
b = y + 1.0
if (y > 1.0E-6) y = Math.log(b)
a = n - 0.5
b = 48.0 * a * a
y = a * y
y = (((((-0.4 * y - 3.3) * y - 24.0) * y - 85.5) /
(0.8 * y * y + 100.0 + b) + y + 3.0) / b + 1.0) *
Math.sqrt(y)
2.0 * ACM209(-y)
}
После вычисления p
, функция ACM395 возвращает , что представляет собой вычисление значения функции распределения t-статистики с использованием функции ACM209 для вычисления вероятности.
Вычисление доверительной вероятности
α
собой предстовляет
// в общем тут нечего коментрировать
def alpfa(ta: Double, n: Int): Double = {
1 - ACM395(ta, n)
}
Вычисляем t-критерий:
α
принмает значения от 0
до 1
и при n = const
выполняется следующие свойство: при
так как мы ищем ищем значение t при определеном α0 = const
то введем еще функцию и при фиксированом n = const
вырождается в функцию одной переменой α'(t, n = const) = α'(t) и тогда α'(t) примает значения (-α0, 1 -α0) и сохраняется свойство: α'(t1) < α'(t2) при t1 < t2
//так я програмно отразила паралельный перенос
def alphaParallel(fun:(Double, Int) => Double, delta: Double): (Double, Int) => Double = {
(t: Double, n: Int) => {
fun(t, n) - delta
}
}
//так я програмно вырозила вырождение функции двух переменых в функциию одной
//n = const
//то что я и назвала фиксированным n
def alpfaConst(fun: (Double, Int) => Double, n: Int): Double => Double = {
t: Double => fun(t, n)
}
val parAlpha = alphaParallel(alpfa, a0)
val conAlpha = alpfaConst(parAlpha, n) // собственно α'(t)
выше обозначеный свойства позволяют применить теорему Больцено-Коши
и найти
но для этого нужно знать интервал в котором находиться t-критерий
для α0
и делаю это перебором интервалов по длиной в 1 пока не найду необходимый
//метод для нахождения интервала
def findInterval(fun: Double => Double): (Double, Double) = {
@tailrec
def finder(fun: Double => Double, min: Double, max: Double): (Double, Double) = {
fun(min)*fun(max) match {
case value if value < 0 => (min, max)
case value if value > 0 => finder(fun, min + 1, max + 1)
}
}
finder(fun, 0, 1)
}
val interval = findInterval(conAlpha)
когда интервал найден можно применить метод половинго деления
//метод реализующий половинное деление
def bisection(tol: Double = 1e-10): (Double => Double, (Double, Double)) => Double = {
(f: Double => Double, interval: (Double, Double)) => {
@tailrec
def loop(interval: (Double, Double)): Double = {
val (a, b) = interval
val midpoint = (a + b) / 2
val fMid = f(midpoint)
if (fMid == 0 || (b - a) / 2 < tol) midpoint
else if (f(a) * fMid < 0) loop(a, midpoint)
else loop(midpoint, b)
}
loop(interval)
}
}
bisection()(conAlpha, interval)
и того суммарно код метода выглядит так
def ta(a0: Double, n: Int): Double = {
def alphaParallel(fun:(Double, Int) => Double, delta: Double): (Double, Int) => Double = {
(t: Double, n: Int) => {
fun(t, n) - delta
}
}
def alpfaConst(fun: (Double, Int) => Double, n: Int): Double => Double = {
t: Double => fun(t, n)
}
def findInterval(fun: Double => Double): (Double, Double) = {
@tailrec
def finder(fun: Double => Double, min: Double, max: Double): (Double, Double) = {
fun(min)*fun(max) match {
case value if value < 0 => (min, max)
case value if value > 0 => finder(fun, min + 1, max + 1)
}
}
finder(fun, 0, 1)
}
def bisection(tol: Double = 1e-10): (Double => Double, (Double, Double)) => Double = {
(f: Double => Double, interval: (Double, Double)) => {
@tailrec
def loop(interval: (Double, Double)): Double = {
val (a, b) = interval
val midpoint = (a + b) / 2
val fMid = f(midpoint)
if (fMid == 0 || (b - a) / 2 < tol) midpoint
else if (f(a) * fMid < 0) loop(a, midpoint)
else loop(midpoint, b)
}
loop(interval)
}
val parAlpha = alphaParallel(alpfa, a0)
val conAlpha = alpfaConst(parAlpha, n)
val interval = findInterval(conAlpha)
bisection()(conAlpha, interval)
}
Результат:
сравним табличные значения и те что нагенерил мой метод
| 0.95 | 0.99 | 0.999 |
1 | 12,70 | 63,65 | 636,61 |
2 | 4,303 | 9,925 | 31,602 |
3 | 3,182 | 5,841 | 12,923 |
4 | 2,776 | 4,604 | 8,610 |
5 | 2,571 | 4,032 | 6,869 |
6 | 2,447 | 3,707 | 5,959 |
7 | 2,365 | 3,499 | 5,408 |
8 | 2,306 | 3,355 | 5,041 |
9 | 2,262 | 3,250 | 4,781 |
10 | 2,228 | 3,169 | 4,587 |
и вот то что мой метод выдал:
заметим что чем больше n и меньше α0
тем меньше разница с табличным значением.
P.S. код можно глянуть тут