Numpy: как работать с нормальным распределением в Python-расчетах

Пройдите тест, узнайте какой профессии подходите

Я предпочитаю
0%
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы

Для кого эта статья:

  • Аналитики данных и специалисты в области Data Science
  • Студенты и начинающие профессионалы, желающие освоить статистику и программирование на Python
  • Исследователи и практикующие ученые, использующие статистический анализ в своих проектах

    Нормальное распределение — безусловный царь статистического анализа. Если вы занимаетесь Data Science, машинным обучением или научными исследованиями, столкновение с гауссовым распределением неизбежно. NumPy превращает работу с ним из математического кошмара в элегантный Python-код. В 2025 году, когда объёмы обрабатываемых данных выросли экспоненциально, умение эффективно моделировать и анализировать нормально распределённые величины стало критическим навыком любого уважающего себя аналитика. 📊 Давайте погрузимся в мир NumPy и освоим весь арсенал инструментов для укрощения колоколообразных кривых!

Хотите превратить свою любовь к числам в профессию с зарплатой от 100 000 рублей? Освойте библиотеку NumPy и другие инструменты анализа данных на Курсе «Аналитик данных» с нуля от Skypro. Наши студенты учатся не просто генерировать нормальные распределения, а решать реальные бизнес-задачи с помощью статистического анализа. Начните карьеру в аналитике данных прямо сейчас!

Основы нормального распределения в NumPy: функции и методы

Нормальное (или гауссово) распределение — фундамент статистики и теории вероятностей. NumPy, как основная библиотека для научных вычислений в Python, предоставляет мощный инструментарий для работы с ним.

Математически нормальное распределение описывается функцией плотности вероятности:

f(x) = (1 / (σ * sqrt(2π))) * e^(-(x-μ)²/(2σ²))

где μ — среднее значение (математическое ожидание), а σ — стандартное отклонение.

В NumPy работа с нормальным распределением сосредоточена в модуле numpy.random. Основные функции, с которыми нужно ознакомиться:

  • numpy.random.normal() — генерация случайных чисел из нормального распределения
  • numpy.random.standard_normal() — генерация чисел из стандартного нормального распределения (μ=0, σ=1)
  • scipy.stats.norm — класс для работы с нормальным распределением (требуется дополнительная библиотека SciPy)
  • numpy.random.Generator.normal() — современный генератор случайных чисел из API NumPy 2.0+

Начнем с простого примера. Допустим, нам нужно сгенерировать одно случайное число из нормального распределения со средним 0 и стандартным отклонением 1:

Python
Скопировать код
import numpy as np

# Генерация одного случайного числа
random_number = np.random.normal(0, 1)
print(random_number) # Вывод будет разным при каждом запуске

А теперь сгенерируем массив из 1000 случайных чисел с параметрами μ=100 и σ=15:

Python
Скопировать код
import numpy as np

# Генерация массива случайных чисел
samples = np.random.normal(loc=100, scale=15, size=1000)

# Базовая статистика по сгенерированным данным
print(f"Среднее: {np.mean(samples):.2f}")
print(f"Стандартное отклонение: {np.std(samples):.2f}")
print(f"Минимум: {np.min(samples):.2f}")
print(f"Максимум: {np.max(samples):.2f}")

Сравним классические и новые методы генерации нормального распределения в NumPy:

МетодСинтаксисКомментарииРекомендуется для NumPy
Классический методnp.random.normal(loc, scale, size)Использует устаревший генератор MT19937До версии 1.17
Новый APIrng = np.random.Generator()<br>rng.normal(loc, scale, size)Использует современный генератор PCG64Версии 1.17 и новее
Использование SciPyfrom scipy import stats<br>stats.norm.rvs(loc, scale, size)Более полный набор статистических функцийЛюбая

Новый API для генерации случайных чисел в NumPy обеспечивает лучшие статистические свойства и производительность. Рекомендуется использовать его в проектах с 2025 года:

Python
Скопировать код
import numpy as np

# Создаем генератор с явным указанием seed для воспроизводимости
rng = np.random.Generator(np.random.PCG64(12345))

# Генерируем выборку из нормального распределения
samples = rng.normal(loc=0, scale=1, size=1000)
Кинга Идем в IT: пошаговый план для смены профессии

Генерация случайных чисел с помощью numpy.random.normal

Александр Петров, ведущий аналитик данных

Однажды я работал над проектом по прогнозированию финансовых рисков. Нам требовалось смоделировать тысячи сценариев изменения цен акций для расчета Value at Risk. Традиционно финансисты использовали Excel для таких симуляций, но при объеме данных в миллионы точек это занимало часы.

Я переписал модель с использованием NumPy, применив всего одну строку кода для генерации всех сценариев:

Python
Скопировать код
price_scenarios = initial_price * np.exp(np.random.normal(mean_return, volatility, (num_scenarios, num_days)))

Время расчета сократилось с 3 часов до 12 секунд. Клиент был в шоке, когда увидел результат. NumPy буквально спас проект, а я получил премию. С тех пор я никогда не использую циклы там, где можно применить векторизованные операции NumPy.

Функция numpy.random.normal — центральный инструмент для генерации случайных чисел из нормального распределения. Она имеет простой, но гибкий интерфейс:

numpy.random.normal(loc=0.0, scale=1.0, size=None)

Где:

  • loc — математическое ожидание (μ) распределения
  • scale — стандартное отклонение (σ) распределения
  • size — размер и форма выходного массива

Если параметр size не указан, функция возвращает одно случайное число. Если указан, возвращается массив указанной формы. Это удобно для генерации многомерных данных:

Python
Скопировать код
import numpy as np

# Генерация матрицы 3x3 случайных чисел 
random_matrix = np.random.normal(loc=5, scale=2, size=(3, 3))
print(random_matrix)

Для работы со стандартным нормальным распределением (μ=0, σ=1) есть специализированная функция, которая работает заметно быстрее:

Python
Скопировать код
import numpy as np

# Генерация 1,000,000 чисел из стандартного нормального распределения
standard_samples = np.random.standard_normal(1000000)

Сравнение времени выполнения разных методов генерации случайных чисел:

МетодВремя выполнения (1M чисел)Расход памятиТочность статистических свойств
np.random.normal(0, 1, 1000000)~100 мс8.0 МБХорошая
np.random.standard_normal(1000000)~80 мс8.0 МБХорошая
rng.normal(0, 1, 1000000 (новый API)~70 мс8.0 МБОтличная
Python цикл с random.gauss()~2000 мс8.0 МБХорошая

Для обеспечения воспроизводимости результатов используйте параметр seed:

Python
Скопировать код
import numpy as np

# Устанавливаем seed для воспроизводимости
np.random.seed(42)

# Теперь результаты будут одинаковыми при каждом запуске
random_numbers = np.random.normal(0, 1, 5)
print(random_numbers) # Всегда одни и те же числа

В версиях NumPy 1.17+ рекомендуется использовать новый API генерации случайных чисел:

Python
Скопировать код
import numpy as np

# Создаем генератор случайных чисел
rng = np.random.default_rng(seed=42)

# Используем его для генерации нормального распределения
random_numbers = rng.normal(0, 1, 5)
print(random_numbers)

Обратите внимание, что функция numpy.random.normal генерирует псевдослучайные числа, соответствующие нормальному распределению. Для криптографических целей она непригодна! 🔐

Визуализация нормального распределения в NumPy и Matplotlib

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

Начнем с генерации данных и построения гистограммы:

Python
Скопировать код
import numpy as np
import matplotlib.pyplot as plt

# Генерация 10,000 случайных чисел
data = np.random.normal(loc=0, scale=1, size=10000)

# Построение гистограммы
plt.figure(figsize=(10, 6))
plt.hist(data, bins=50, alpha=0.7, density=True, color='skyblue', edgecolor='black')

# Добавление теоретической кривой нормального распределения
x = np.linspace(-4, 4, 1000)
y = (1 / (1 * np.sqrt(2 * np.pi))) * np.exp(-(x – 0)**2 / (2 * 1**2))
plt.plot(x, y, 'r-', linewidth=2)

plt.title('Гистограмма и функция плотности вероятности\nнормального распределения')
plt.xlabel('Значение')
plt.ylabel('Плотность')
plt.grid(alpha=0.3)
plt.savefig('normal_distribution.png', dpi=300, bbox_inches='tight')
plt.show()

Для сравнения нескольких распределений с разными параметрами удобно использовать subplots:

Python
Скопировать код
import numpy as np
import matplotlib.pyplot as plt

# Создаем фигуру с подграфиками
fig, axs = plt.subplots(2, 2, figsize=(12, 10))

# Разные параметры для нормальных распределений
params = [(0, 1), (0, 2), (3, 1), (-2, 0.5)]
titles = ['Стандартное N(0,1)', 'N(0,2) – увеличенное σ', 
'N(3,1) – сдвинутое μ', 'N(-2,0.5) – сдвинутое и суженное']

# Заполняем подграфики
for i, (ax, (mu, sigma)) in enumerate(zip(axs.flat, params)):
# Генерируем данные
data = np.random.normal(mu, sigma, 5000)

# Строим гистограмму
ax.hist(data, bins=30, density=True, alpha=0.7, color='skyblue')

# Добавляем теоретическую кривую
x = np.linspace(mu – 4*sigma, mu + 4*sigma, 1000)
y = (1 / (sigma * np.sqrt(2 * np.pi))) * np.exp(-(x – mu)**2 / (2 * sigma**2))
ax.plot(x, y, 'r-', linewidth=2)

# Настраиваем график
ax.set_title(titles[i])
ax.set_xlabel('Значение')
ax.set_ylabel('Плотность')
ax.grid(alpha=0.3)

plt.tight_layout()
plt.show()

Для оценки соответствия данных нормальному распределению рекомендую использовать Q-Q plot (квантиль-квантильный график):

Python
Скопировать код
import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as stats

# Генерация данных
normal_data = np.random.normal(0, 1, 1000) # Нормальное распределение
uniform_data = np.random.uniform(-3, 3, 1000) # Равномерное распределение

# Создаем фигуру с подграфиками
fig, axs = plt.subplots(1, 2, figsize=(15, 6))

# Q-Q график для нормальных данных
stats.probplot(normal_data, dist="norm", plot=axs[0])
axs[0].set_title('Q-Q Plot: Нормальные данные')

# Q-Q график для равномерных данных
stats.probplot(uniform_data, dist="norm", plot=axs[1])
axs[1].set_title('Q-Q Plot: Равномерные данные')

plt.tight_layout()
plt.show()

Визуализация функции плотности вероятности и кумулятивной функции распределения:

Python
Скопировать код
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

# Создаем диапазон значений
x = np.linspace(-4, 4, 1000)

# Вычисляем значения PDF и CDF
pdf = stats.norm.pdf(x)
cdf = stats.norm.cdf(x)

# Создаем график
fig, ax1 = plt.subplots(figsize=(10, 6))

# Строим PDF
color = 'tab:blue'
ax1.plot(x, pdf, color=color)
ax1.fill_between(x, pdf, alpha=0.2, color=color)
ax1.set_xlabel('Значение')
ax1.set_ylabel('Плотность вероятности', color=color)
ax1.tick_params(axis='y', labelcolor=color)
ax1.grid(alpha=0.3)

# Создаем вторую ось Y для CDF
ax2 = ax1.twinx()
color = 'tab:red'
ax2.plot(x, cdf, color=color)
ax2.set_ylabel('Кумулятивная вероятность', color=color)
ax2.tick_params(axis='y', labelcolor=color)

# Добавляем заголовок
plt.title('Функция плотности вероятности (PDF) и\nкумулятивная функция распределения (CDF)')

plt.tight_layout()
plt.show()

Трехмерная визуализация многомерного нормального распределения тоже возможна с помощью NumPy и Matplotlib:

Python
Скопировать код
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D

# Функция для вычисления PDF двумерного нормального распределения
def multivariate_normal_pdf(x, y, mu_x=0, mu_y=0, sigma_x=1, sigma_y=1, rho=0):
z = np.exp(-0.5 / (1 – rho**2) * ((x – mu_x)**2 / sigma_x**2 + 
(y – mu_y)**2 / sigma_y**2 – 
2 * rho * (x – mu_x) * (y – mu_y) / (sigma_x * sigma_y)))
return z / (2 * np.pi * sigma_x * sigma_y * np.sqrt(1 – rho**2))

# Создаем сетку точек
x = np.linspace(-3, 3, 100)
y = np.linspace(-3, 3, 100)
X, Y = np.meshgrid(x, y)

# Вычисляем PDF
Z = multivariate_normal_pdf(X, Y, mu_x=0, mu_y=0, sigma_x=1, sigma_y=1, rho=0.5)

# Строим 3D поверхность
fig = plt.figure(figsize=(12, 10))
ax = fig.add_subplot(111, projection='3d')
surf = ax.plot_surface(X, Y, Z, cmap=cm.viridis, linewidth=0, antialiased=True)

ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Плотность вероятности')
ax.set_title('Двумерное нормальное распределение (корреляция = 0.5)')
fig.colorbar(surf, shrink=0.5, aspect=5)

plt.tight_layout()
plt.show()

Эти примеры демонстрируют мощь NumPy и Matplotlib для визуализации нормального распределения. Графики не только помогают понять теоретические аспекты, но и анализировать реальные данные на соответствие нормальному закону. 📈

Практические задачи статистики с NumPy: z-оценки и вероятности

Марина Соколова, инженер-статистик

В 2023 году наша команда разрабатывала систему контроля качества для фармацевтического производства. Основной проблемой был поиск аномалий в серийном производстве таблеток — их вес должен соответствовать строгим нормам.

Традиционные методы "трех сигм" давали много ложных срабатываний. Изучив распределение данных, я обнаружила, что оно очень близко к нормальному, но с небольшой асимметрией. Вместо стандартной формулы я написала динамическую систему вычисления z-оценок с использованием NumPy:

Python
Скопировать код
# Вместо простого (x – mean) / std
robust_z = (data – np.median(data)) / (np.percentile(data, 75) – np.percentile(data, 25)) * 0.7413
anomalies = data[np.abs(robust_z) > 3.5]

Коэффициент 0.7413 сделал метрику сопоставимой с классическими z-оценками, но устойчивой к выбросам. После внедрения этого алгоритма количество ложных срабатываний снизилось на 87%, а выявление реальных дефектов улучшилось на 15%. Компания сэкономила более $500,000 в первый год за счет снижения брака и возвратов.

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

Рассмотрим наиболее востребованные статистические задачи:

1. Расчёт z-оценок (z-scores)

Z-оценка показывает, на сколько стандартных отклонений значение отличается от среднего. Это универсальный способ нормализации данных:

Python
Скопировать код
import numpy as np

# Исходные данные
data = np.array([23, 25, 28, 30, 31, 32, 34, 38, 40, 45])

# Расчёт z-оценок
mean = np.mean(data)
std = np.std(data, ddof=1) # ddof=1 для несмещенной оценки
z_scores = (data – mean) / std

print(f"Исходные данные: {data}")
print(f"Z-оценки: {z_scores}")

# Идентификация выбросов (|z| > 2)
outliers = data[np.abs(z_scores) > 2]
print(f"Выбросы: {outliers}")

2. Вычисление вероятностей и квантилей

Для расчета вероятностей в нормальном распределении используем SciPy, который основан на NumPy:

Python
Скопировать код
import numpy as np
from scipy import stats

# Параметры распределения
mu = 100 # среднее
sigma = 15 # стандартное отклонение

# Вероятность P(X < 80)
prob_less_than_80 = stats.norm.cdf(80, loc=mu, scale=sigma)
print(f"P(X < 80) = {prob_less_than_80:.4f}")

# Вероятность P(X > 120)
prob_greater_than_120 = 1 – stats.norm.cdf(120, loc=mu, scale=sigma)
print(f"P(X > 120) = {prob_greater_than_120:.4f}")

# Вероятность P(90 < X < 110)
prob_between_90_110 = stats.norm.cdf(110, loc=mu, scale=sigma) – stats.norm.cdf(90, loc=mu, scale=sigma)
print(f"P(90 < X < 110) = {prob_between_90_110:.4f}")

3. Нахождение доверительных интервалов

Доверительный интервал для среднего значения выборки:

Python
Скопировать код
import numpy as np
from scipy import stats

# Генерируем выборку
np.random.seed(42)
sample = np.random.normal(loc=100, scale=15, size=30)

# Рассчитываем параметры
sample_mean = np.mean(sample)
sample_std = np.std(sample, ddof=1)
n = len(sample)

# 95% доверительный интервал
confidence = 0.95
alpha = 1 – confidence
t_critical = stats.t.ppf(1 – alpha/2, n-1) # t-распределение для малых выборок
margin_of_error = t_critical * sample_std / np.sqrt(n)

confidence_interval = (sample_mean – margin_of_error, sample_mean + margin_of_error)

print(f"Среднее выборки: {sample_mean:.2f}")
print(f"{confidence*100}% доверительный интервал: ({confidence_interval[0]:.2f}, {confidence_interval[1]:.2f})")

4. Проверка нормальности распределения данных

Для проверки гипотезы о нормальности распределения используем тесты Шапиро-Уилка или Колмогорова-Смирнова:

Python
Скопировать код
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

# Генерируем данные
np.random.seed(42)
normal_data = np.random.normal(0, 1, 100)
uniform_data = np.random.uniform(-2, 2, 100)

# Тест Шапиро-Уилка
shapiro_normal = stats.shapiro(normal_data)
shapiro_uniform = stats.shapiro(uniform_data)

print("Тест Шапиро-Уилка для нормальных данных:")
print(f"Статистика: {shapiro_normal.statistic:.4f}, p-value: {shapiro_normal.pvalue:.4f}")
print("Вывод: данные", "нормальны" if shapiro_normal.pvalue > 0.05 else "не нормальны")

print("\nТест Шапиро-Уилка для равномерных данных:")
print(f"Статистика: {shapiro_uniform.statistic:.4f}, p-value: {shapiro_uniform.pvalue:.4f}")
print("Вывод: данные", "нормальны" if shapiro_uniform.pvalue > 0.05 else "не нормальны")

# Визуализация с помощью Q-Q графиков
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))

# Q-Q графики
stats.probplot(normal_data, plot=ax1)
ax1.set_title('Q-Q Plot: Нормальные данные')

stats.probplot(uniform_data, plot=ax2)
ax2.set_title('Q-Q Plot: Равномерные данные')

plt.tight_layout()
plt.show()

5. Центральная предельная теорема на практике

Демонстрация центральной предельной теоремы с помощью NumPy:

Python
Скопировать код
import numpy as np
import matplotlib.pyplot as plt

# Генерируем равномерное распределение
np.random.seed(42)

# Создаем пустые списки для хранения средних значений
means_n2 = []
means_n10 = []
means_n30 = []

# Количество экспериментов
num_experiments = 1000

# Проводим эксперименты
for _ in range(num_experiments):
# Генерируем выборки разного размера
sample_n2 = np.random.uniform(0, 10, 2)
sample_n10 = np.random.uniform(0, 10, 10)
sample_n30 = np.random.uniform(0, 10, 30)

# Сохраняем средние значения
means_n2.append(np.mean(sample_n2))
means_n10.append(np.mean(sample_n10))
means_n30.append(np.mean(sample_n30))

# Визуализация результатов
fig, axs = plt.subplots(1, 3, figsize=(15, 5))

# Гистограммы для разных размеров выборки
axs[0].hist(means_n2, bins=30, alpha=0.7, color='skyblue', edgecolor='black')
axs[0].set_title('Средние для n=2')
axs[0].set_xlabel('Среднее значение')
axs[0].set_ylabel('Частота')

axs[1].hist(means_n10, bins=30, alpha=0.7, color='skyblue', edgecolor='black')
axs[1].set_title('Средние для n=10')
axs[1].set_xlabel('Среднее значение')

axs[2].hist(means_n30, bins=30, alpha=0.7, color='skyblue', edgecolor='black')
axs[2].set_title('Средние для n=30')
axs[2].set_xlabel('Среднее значение')

plt.tight_layout()
plt.show()

# Проверка нормальности для n=30
shapiro_test = stats.shapiro(means_n30)
print(f"Тест Шапиро-Уилка для средних при n=30:")
print(f"Статистика: {shapiro_test.statistic:.4f}, p-value: {shapiro_test.pvalue:.4f}")
print("Вывод: распределение средних", "нормально" if shapiro_test.pvalue > 0.05 else "не нормально")

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

Нужен совет по выбору карьеры в сфере данных? Ваши результаты с z-оценкой выше среднего в математике и программировании? Пройдите Тест на профориентацию от Skypro, чтобы узнать, подходит ли вам карьера аналитика данных. Мы используем продвинутые статистические модели, включая нормальное распределение, для точного определения ваших сильных сторон и оптимальной карьерной траектории. Тест займет всего 3 минуты, но может изменить вашу жизнь!

Оптимизация расчетов нормального распределения в NumPy

Эффективное использование NumPy для работы с нормальным распределением может значительно ускорить ваши вычисления. Рассмотрим основные стратегии оптимизации. 🚀

1. Векторизация вместо циклов

Главное правило оптимизации в NumPy — избегать циклов Python в пользу векторизованных операций:

Python
Скопировать код
import numpy as np
import time

# Неэффективный подход с циклом Python
def slow_gaussian(x, mu, sigma, size):
result = np.zeros(size)
for i in range(size):
result[i] = np.exp(-0.5 * ((x[i] – mu) / sigma)**2) / (sigma * np.sqrt(2 * np.pi))
return result

# Векторизованный подход
def fast_gaussian(x, mu, sigma):
return np.exp(-0.5 * ((x – mu) / sigma)**2) / (sigma * np.sqrt(2 * np.pi))

# Тестирование производительности
size = 1000000
x = np.linspace(-5, 5, size)

# Измеряем время медленного подхода
start_time = time.time()
slow_result = slow_gaussian(x, 0, 1, size)
slow_time = time.time() – start_time

# Измеряем время быстрого подхода
start_time = time.time()
fast_result = fast_gaussian(x, 0, 1)
fast_time = time.time() – start_time

print(f"Время медленного подхода: {slow_time:.4f} секунд")
print(f"Время быстрого подхода: {fast_time:.4f} секунд")
print(f"Ускорение: {slow_time / fast_time:.2f}x")

2. Использование функции SciPy вместо "ручных" формул

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

Python
Скопировать код
import numpy as np
import time
from scipy import stats

# Ручная реализация CDF нормального распределения
def manual_normal_cdf(x, mu, sigma):
return 0.5 * (1 + np.erf((x – mu) / (sigma * np.sqrt(2))))

# Тестирование производительности
size = 1000000
x = np.linspace(-5, 5, size)

# Ручная реализация
start_time = time.time()
manual_result = manual_normal_cdf(x, 0, 1)
manual_time = time.time() – start_time

# SciPy реализация
start_time = time.time()
scipy_result = stats.norm.cdf(x, 0, 1)
scipy_time = time.time() – start_time

print(f"Время ручной реализации: {manual_time:.4f} секунд")
print(f"Время SciPy реализации: {scipy_time:.4f} секунд")
print(f"Ускорение: {manual_time / scipy_time:.2f}x")

# Проверка точности
max_diff = np.max(np.abs(manual_result – scipy_result))
print(f"Максимальная абсолютная разница: {max_diff:.2e}")

3. Параллельная генерация случайных чисел

Для больших объёмов данных можно использовать параллельные вычисления:

Python
Скопировать код
import numpy as np
import time
from concurrent.futures import ProcessPoolExecutor
import multiprocessing

def generate_chunk(args):
size, seed = args
rng = np.random.Generator(np.random.PCG64(seed))
return rng.normal(0, 1, size)

def parallel_normal(total_size, n_jobs=None):
if n_jobs is None:
n_jobs = multiprocessing.cpu_count()

chunk_size = total_size // n_jobs
seeds = np.random.randint(0, 2**32, n_jobs) # Разные seed для каждого процесса

with ProcessPoolExecutor(max_workers=n_jobs) as executor:
chunks = list(executor.map(generate_chunk, [(chunk_size, seed) for seed in seeds]))

return np.concatenate(chunks)

# Тестирование производительности
total_size = 50000000 # 50 миллионов чисел

# Последовательная генерация
start_time = time.time()
sequential_result = np.random.normal(0, 1, total_size)
sequential_time = time.time() – start_time

# Параллельная генерация
start_time = time.time()
parallel_result = parallel_normal(total_size)
parallel_time = time.time() – start_time

print(f"Время последовательной генерации: {sequential_time:.4f} секунд")
print(f"Время параллельной генерации: {parallel_time:.4f} секунд")
print(f"Ускорение: {sequential_time / parallel_time:.2f}x")

4. Оптимизация памяти при работе с большими массивами

При работе с большими выборками из нормального распределения память может стать узким местом:

Python
Скопировать код
import numpy as np

# Неэффективное использование памяти
def inefficient_memory(size):
# Создаем 3 больших массива в памяти одновременно
x = np.random.normal(0, 1, size)
y = x * 2 + 1 # Линейное преобразование
z = np.exp(y) # Нелинейное преобразование
return z

# Эффективное использование памяти
def efficient_memory(size, chunk_size=1000000):
# Обрабатываем данные по частям
result = np.zeros(size)
for i in range(0, size, chunk_size):
end = min(i + chunk_size, size)
chunk = np.random.normal(0, 1, end – i)
chunk = np.exp(chunk * 2 + 1) # Все преобразования за один проход
result[i:end] = chunk
return result

# Для больших объемов данных в ограниченной памяти
# efficient_memory(10**9) будет работать, когда inefficient_memory(10**9) вызовет MemoryError

5. Сравнение различных генераторов случайных чисел

В NumPy 2.0 доступны разные генераторы случайных чисел с различными характеристиками скорости и качества:

ГенераторСкоростьСтатистическое качествоПериодИспользование
PCG64ВысокаяОтличное2^128По умолчанию в новом API
MT19937СредняяХорошее2^19937-1Старый API (np.random.normal)
PhiloxОчень высокаяОтличное2^256Параллельные вычисления
SFC64Экстремально высокаяОчень хорошее~2^64Производительность на первом месте
Python
Скопировать код
import numpy as np
import time

# Сравнение различных генераторов
generators = {
'PCG64': np.random.Generator(np.random.PCG64(42)),
'MT19937': np.random.Generator(np.random.MT19937(42)),
'Philox': np.random.Generator(np.random.Philox(42)),
'SFC64': np.random.Generator(np.random.SFC64(42))
}

size = 50000000 # 50 миллионов чисел

for name, gen in generators.items():
start_time = time.time()
_ = gen.normal(0, 1, size)
elapsed = time.time() – start_time
print(f"Генератор {name}: {elapsed:.4f} секунд")

6. Jit-компиляция для экстремальной производительности

Для максимальной производительности при работе с нормальным распределением можно использовать Numba:

Python
Скопировать код
import numpy as np
import time
from numba import njit

# Обычная функция NumPy
def numpy_pdf(x, mu, sigma):
return np.exp(-0.5 * ((x – mu) / sigma)**2) / (sigma * np.sqrt(2 * np.pi))

# Jit-скомпилированная функция
@njit
def numba_pdf(x, mu, sigma):
return np.exp(-0.5 * ((x – mu) / sigma)**2) / (sigma * np.sqrt(2 * np.pi))

# Тестирование производительности
size = 10000000
x = np.linspace(-5, 5, size)

# Прогрев для Numba (первый запуск включает компиляцию)
_ = numba_pdf(np.array([0\.0]), 0.0, 1.0)

# NumPy
start_time = time.time()
numpy_result = numpy_pdf(x, 0, 1)
numpy_time = time.time() – start_time

# Numba
start_time = time.time()
numba_result = numba_pdf(x, 0, 1)
numba_time = time.time() – start_time

print(f"Время NumPy: {numpy_time:.4f} секунд")
print(f"Время Numba: {numba_time:.4f} секунд")
print(f"Ускорение: {numpy_time / numba_time:.2f}x")

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

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