1. Генератор нормального распределения (Box-Muller)
csharp
using System; using System.Collections.Generic; using System.Linq; public class NormalDistributionGenerator {, private readonly Random _random; private readonly double _mean; private readonly double _stdDev; private bool _hasSpareValue; private double _spareValue; public NormalDistributionGenerator(double mean = 0, double stdDev = 1, int? seed = null) { _mean = mean; _stdDev = stdDev; _random = seed.HasValue ? new Random(seed.Value) : new Random(); _hasSpareValue = false; _spareValue = 0; } // Метод Box-Muller для генерации нормального распределения public double Next() { if (_hasSpareValue) { _hasSpareValue = false; return _spareValue * _stdDev + _mean; } double u, v, s; do { u = _random.NextDouble() * 2 - 1; // [-1, 1] v = _random.NextDouble() * 2 - 1; // [-1, 1] s = u * u + v * v; } while (s >= 1 || s == 0); s = Math.Sqrt(-2.0 * Math.Log(s) / s); _spareValue = v * s; _hasSpareValue = true; return u * s * _stdDev + _mean; } public double[] GenerateSample(int size) { var sample = new double[size]; for (int i = 0; i < size; i++) { sample[i] = Next(); } return sample; } }
2. Статистические функции для тестов
csharp
public static class StatisticalFunctions { // Функция ошибок (Error Function) public static double Erf(double x) { // approximation using Abramowitz and Stegun formula 7.1.26 double a1 = 0.254829592; double a2 = -0.284496736; double a3 = 1.421413741; double a4 = -1.453152027; double a5 = 1.061405429; double p = 0.3275911; int sign = x < 0 ? -1 : 1; x = Math.Abs(x); double t = 1.0 / (1.0 + p * x); double y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.Exp(-x * x); return sign * y; } // Нормальная функция распределения (CDF) public static double NormalCdf(double x, double mean = 0, double stdDev = 1) { return 0.5 * (1 + Erf((x - mean) / (stdDev * Math.Sqrt(2)))); } // Гамма-функция public static double Gamma(double x) { // Простая реализация через ланчос if (x <= 0) throw new ArgumentException("x must be positive"); double[] p = { 676.5203681218851, -1259.1392167224028, 771.32342877765313, -176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7 }; if (x < 0.5) return Math.PI / (Math.Sin(Math.PI * x) * Gamma(1 - x)); x -= 1; double a = 0.99999999999980993; double t = x + 7.5; for (int i = 0; i < p.Length; i++) { a += p[i] / (x + i + 1); } return Math.Sqrt(2 * Math.PI) * Math.Pow(t, x + 0.5) * Math.Exp(-t) * a; } // Неполная гамма-функция public static double IncompleteGamma(double s, double x) { // Реализация через ряд if (x < 0) return double.NaN; if (x == 0) return 0; const int maxIterations = 100; const double epsilon = 1e-10; double term = 1.0 / s; double sum = term; for (int k = 1; k < maxIterations; k++) { term *= x / (s + k); sum += term; if (Math.Abs(term) < epsilon) break; } return Math.Pow(x, s) * Math.Exp(-x) * sum; } // Функция хи-квадрат распределения public static double ChiSquareCdf(double x, double degreesOfFreedom) { if (x <= 0) return 0; return IncompleteGamma(degreesOfFreedom / 2, x / 2) / Gamma(degreesOfFreedom / 2); } }
3. Тест Колмогорова-Смирнова
csharp
public class KolmogorovSmirnovTest { public static (double Statistic, double PValue) Test(double[] sample, double mean, double stdDev) { var sortedSample = sample.OrderBy(x => x).ToArray(); int n = sortedSample.Length; double dPlus = 0; double dMinus = 0; for (int i = 0; i < n; i++) { double empirical = (i + 1.0) / n; double theoretical = StatisticalFunctions.NormalCdf(sortedSample[i], mean, stdDev); dPlus = Math.Max(dPlus, empirical - theoretical); dMinus = Math.Max(dMinus, theoretical - (double)i / n); } double d = Math.Max(dPlus, dMinus); double pValue = CalculatePValue(d, n); return (d, pValue); } private static double CalculatePValue(double d, int n) { // Аппроксимация p-value для теста Колмогорова-Смирнова if (n <= 0) return 1; double sqrtN = Math.Sqrt(n); double x = d * (sqrtN + 0.12 + 0.11 / sqrtN); if (x <= 0) return 1; if (x >= 3.0) return 0; double pValue = 0; for (int k = 1; k <= 100; k++) { pValue += Math.Exp(-2 * k * k * x * x); } return 2 * pValue; } }
4. Тест хи-квадрат
csharp
public class ChiSquareTest { public static (double Statistic, double PValue, int DegreesOfFreedom) Test( double[] sample, double mean, double stdDev, int bins = 10) { if (bins < 2) throw new ArgumentException("Number of bins must be at least 2"); // Определяем границы бинов var min = sample.Min(); var max = sample.Max(); var binWidth = (max - min) / bins; // Наблюдаемые частоты var observed = new int[bins]; foreach (var value in sample) { int binIndex = Math.Min((int)((value - min) / binWidth), bins - 1); observed[binIndex]++; } // Ожидаемые частоты для нормального распределения var expected = new double[bins]; for (int i = 0; i < bins; i++) { double left = min + i * binWidth; double right = min + (i + 1) * binWidth; expected[i] = sample.Length * ( StatisticalFunctions.NormalCdf(right, mean, stdDev) - StatisticalFunctions.NormalCdf(left, mean, stdDev) ); } // Вычисляем статистику хи-квадрат double chiSquare = 0; for (int i = 0; i < bins; i++) { if (expected[i] > 0) { chiSquare += Math.Pow(observed[i] - expected[i], 2) / expected[i]; } } // Степени свободы: bins - 1 - 2 (два параметра оценены из данных) int degreesOfFreedom = bins - 3; double pValue = 1 - StatisticalFunctions.ChiSquareCdf(chiSquare, degreesOfFreedom); return (chiSquare, pValue, degreesOfFreedom); } }
5. Тест Шапиро-Уилка (упрощенная версия)
csharp
public class ShapiroWilkTest { // Коэффициенты для теста Шапиро-Уилка (для n <= 50) private static readonly Dictionary<int, double[]> Coefficients = new() { [3] = new double[] { 0.7071 }, [4] = new double[] { 0.6872, 0.1677 }, [5] = new double[] { 0.6646, 0.2413 }, [6] = new double[] { 0.6431, 0.2806, 0.0875 }, [7] = new double[] { 0.6233, 0.3031, 0.1401 }, [8] = new double[] { 0.6052, 0.3164, 0.1743, 0.0561 }, [9] = new double[] { 0.5888, 0.3244, 0.1976, 0.0947 }, [10] = new double[] { 0.5739, 0.3291, 0.2141, 0.1224 }, // Для демонстрации - в реальности нужны все коэффициенты до 50 }; public static (double Statistic, double PValue) Test(double[] sample) { var sorted = sample.OrderBy(x => x).ToArray(); int n = sorted.Length; if (n < 3 || n > 50) { throw new ArgumentException("Shapiro-Wilk test requires sample size between 3 and 50"); } if (!Coefficients.ContainsKey(n)) { throw new ArgumentException($"Coefficients not available for sample size {n}"); } // Вычисляем статистику W double w = CalculateWStatistic(sorted, n); // Аппроксимируем p-value (упрощенно) double pValue = ApproximatePValue(w, n); return (w, pValue); } private static double CalculateWStatistic(double[] sorted, int n) { var a = Coefficients[n]; int k = n % 2 == 0 ? n / 2 : (n - 1) / 2; double numerator = 0; for (int i = 0; i < k; i++) { numerator += a[i] * (sorted[n - 1 - i] - sorted[i]); } double denominator = 0; double mean = sorted.Average(); foreach (var x in sorted) { denominator += Math.Pow(x - mean, 2); } return Math.Pow(numerator, 2) / denominator; } private static double ApproximatePValue(double w, int n) { // Упрощенная аппроксимация p-value // В реальной реализации используются табличные значения if (w > 0.95) return 0.9; if (w > 0.90) return 0.7; if (w > 0.85) return 0.5; if (w > 0.80) return 0.3; if (w > 0.75) return 0.1; return 0.01; } }
6. Полная система тестирования
csharp
public class NormalityTester { private readonly double[] _sample; private readonly double _mean; private readonly double _stdDev; public NormalityTester(double[] sample) { _sample = sample ?? throw new ArgumentNullException(nameof(sample)); _mean = sample.Average(); _stdDev = CalculateStdDev(sample, _mean); } private double CalculateStdDev(double[] values, double mean) { double sumSquares = 0; foreach (var value in values) { sumSquares += Math.Pow(value - mean, 2); } return Math.Sqrt(sumSquares / (values.Length - 1)); } public void RunAllTests(double significanceLevel = 0.05) { Console.WriteLine("=== ТЕСТИРОВАНИЕ НОРМАЛЬНОСТИ РАСПРЕДЕЛЕНИЯ ==="); Console.WriteLine($"Размер выборки: {_sample.Length}"); Console.WriteLine($"Выборочное среднее: {_mean:F4}"); Console.WriteLine($"Выборочное СКО: {_stdDev:F4}"); Console.WriteLine($"Уровень значимости: {significanceLevel}"); Console.WriteLine(); // Тест Колмогорова-Смирнова var (ksStat, ksPValue) = KolmogorovSmirnovTest.Test(_sample, _mean, _stdDev); Console.WriteLine("1. ТЕСТ КОЛМОГОРОВА-СМИРНОВА:"); Console.WriteLine($" Статистика: {ksStat:F4}"); Console.WriteLine($" P-value: {ksPValue:F4}"); Console.WriteLine($" Вывод: {(ksPValue > significanceLevel ? "Нормальность НЕ отвергается" : "Нормальность ОТВЕРГАЕТСЯ")}"); Console.WriteLine(); // Тест хи-квадрат var (chiStat, chiPValue, df) = ChiSquareTest.Test(_sample, _mean, _stdDev, bins: 10); Console.WriteLine("2. ТЕСТ ХИ-КВАДРАТ:"); Console.WriteLine($" Статистика: {chiStat:F4}"); Console.WriteLine($" P-value: {chiPValue:F4}"); Console.WriteLine($" Степени свободы: {df}"); Console.WriteLine($" Вывод: {(chiPValue > significanceLevel ? "Нормальность НЕ отвергается" : "Нормальность ОТВЕРГАЕТСЯ")}"); Console.WriteLine(); // Тест Шапиро-Уилка (для небольших выборок) if (_sample.Length >= 3 && _sample.Length <= 50) { try { var (swStat, swPValue) = ShapiroWilkTest.Test(_sample); Console.WriteLine("3. ТЕСТ ШАПИРО-УИЛКА:"); Console.WriteLine($" Статистика: {swStat:F4}"); Console.WriteLine($" P-value: {swPValue:F4}"); Console.WriteLine($" Вывод: {(swPValue > significanceLevel ? "Нормальность НЕ отвергается" : "Нормальность ОТВЕРГАЕТСЯ")}"); } catch (Exception ex) { Console.WriteLine($"3. ТЕСТ ШАПИРО-УИЛКА: {ex.Message}"); } } else { Console.WriteLine("3. ТЕСТ ШАПИРО-УИЛКА: Неприменим для данной выборки (требуется 3 ≤ n ≤ 50)"); } Console.WriteLine(); Console.WriteLine("=== СВОДКА ==="); Console.WriteLine($"Выборка {(IsNormal(ksPValue, chiPValue, significanceLevel) ? "соответствует" : "НЕ соответствует")} нормальному распределению"); } private bool IsNormal(double ksPValue, double chiPValue, double significanceLevel) { return ksPValue > significanceLevel && chiPValue > significanceLevel; } }
7. Пример использования
csharp
class Program { static void Main() { // Генерируем выборку нормального распределения var generator = new NormalDistributionGenerator(mean: 10, stdDev: 2, seed: 42); double[] sample = generator.GenerateSample(1000); // Выводим описательную статистику Console.WriteLine("Сгенерированная выборка:"); Console.WriteLine($"Минимум: {sample.Min():F4}"); Console.WriteLine($"Максимум: {sample.Max():F4}"); Console.WriteLine($"Среднее: {sample.Average():F4}"); Console.WriteLine($"Медиана: {CalculateMedian(sample):F4}"); Console.WriteLine($"СКО: {CalculateStdDev(sample):F4}"); Console.WriteLine(); // Тестируем на нормальность var tester = new NormalityTester(sample); tester.RunAllTests(significanceLevel: 0.05); // Дополнительно: тест с заведомо ненормальной выборкой Console.WriteLine("\n" + new string('=', 50)); Console.WriteLine("ТЕСТ С НЕНОРМАЛЬНОЙ ВЫБОРКОЙ (равномерное распределение):"); var uniformSample = GenerateUniformSample(1000, 5, 15); var uniformTester = new NormalityTester(uniformSample); uniformTester.RunAllTests(significanceLevel: 0.05); } static double CalculateMedian(double[] values) { var sorted = values.OrderBy(x => x).ToArray(); int n = sorted.Length; return n % 2 == 0 ? (sorted[n/2 - 1] + sorted[n/2]) / 2 : sorted[n/2]; } static double CalculateStdDev(double[] values) { double mean = values.Average(); double sumSquares = values.Sum(x => Math.Pow(x - mean, 2)); return Math.Sqrt(sumSquares / (values.Length - 1)); } static double[] GenerateUniformSample(int size, double min, double max) { var random = new Random(42); var sample = new double[size]; for (int i = 0; i < size; i++) { sample[i] = min + random.NextDouble() * (max - min); } return sample; } }
Особенности реализации:
Box-Muller алгоритм - эффективная генерация нормальных чисел
Три статистических теста - комплексная проверка нормальности
Обработка граничных случаев - проверка размеров выборок
Визуализация результатов - понятные выводы
Сравнение с ненормальным распределением - демонстрация работы тестов
Это полная система для генерации и тестирования нормальных распределений! 🎯
Комментариев нет:
Отправить комментарий