Дисперсия NumPy vs Pandas: что такое ddof и какую формулу выбрать | AiManual
AiManual Logo Ai / Manual.
15 Мар 2026 Гайд

Почему дисперсия в NumPy и Pandas разная: объяснение bias=ddof и как правильно выбрать формулу

Подробное объяснение, почему NumPy и Pandas считают дисперсию по-разному. Разбираем параметр ddof, формулы и как избежать ошибок в анализе данных.

Почему эта цифра не совпадает?

Вы в два клика посчитали дисперсию для одного столбца. Сначала через NumPy. Потом через Pandas. И получили две разные цифры.

Первая мысль – баг. Проверяете данные, пересчитываете, ищете опечатку. Ее нет. Результаты упорно различаются. Знакомо? Это не ошибка. Это принципиально разные формулы, и если не понять разницу – можно наломать дров в любом анализе, от A/B-теста до проверки гипотез.

Вот наглядный пример, который сводит с ума новичков. Запустите этот код прямо сейчас.

import numpy as np
import pandas as pd

data = [2, 4, 6, 8, 10]
arr = np.array(data)
series = pd.Series(data)

print(f"NumPy variance: {np.var(arr):.2f}")      # 8.00
print(f"Pandas variance: {series.var():.2f}")    # 10.00

Вот оно. Дисперсия отличается. На целых 25%. Кого слушать? За ответом нужно залезть в статистику лет на 50 назад.

Две формулы дисперсии: простая правда и неудобная правда

В школе нам давали самую простую формулу. Сумма квадратов отклонений от среднего, деленная на количество элементов. Это смещенная (biased) дисперсия.

Тип дисперсии Формула Когда использовать
Смещенная (Population) σ² = Σ(xᵢ - μ)² / N Когда ваши данные – это ВСЯ генеральная совокупность. (Например, зарплаты всех сотрудников компании).
Несмещенная (Sample) s² = Σ(xᵢ - x̄)² / (N - 1) Когда ваши данные – ВЫБОРКА из большой генеральной совокупности. (Например, 1000 пользователей из миллионов).

Ключевая разница в знаменателе. Деление на N или на N - 1. Этот "-1" – не прихоть, а поправка Бесселя. Она компенсирует тот факт, что при расчете по выборке мы используем выборочное среднее (x̄), а не истинное среднее генеральной совокупности (μ). Использование x̄ делает оценку дисперсии чуть заниженной, и деление на N-1 эту "смещенность" убирает.

💡
Параметр ddof расшифровывается как Delta Degrees of Freedom – дельта степеней свободы. Это число, которое вычитается из знаменателя. При ddof=0 знаменатель = N. При ddof=1 знаменатель = N - 1.

NumPy и Pandas: война мировоззрений

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

  • NumPy (np.var): по умолчанию ddof=0. Считает, что вы работаете с чистыми массивами чисел и сами решите, какая формула нужна. Это позиция «мы даем базовую математику».
  • Pandas (.var()): по умолчанию ddof=1. Исходит из того, что DataFrame или Series – это почти всегда выборка из реальных данных. Это позиция «мы работаем со статистикой в data science».

1 Прямое сравнение: где собака зарыта

# Создаем одинаковые данные
data = np.random.randn(100)  # 100 значений из стандартного нормального распределения
df = pd.DataFrame({'col': data})

# Считаем дисперсию тремя способами с явным указанием ddof
var_numpy_default = np.var(data)               # ddof=0
var_numpy_unbiased = np.var(data, ddof=1)      # ddof=1
var_pandas_default = df['col'].var()           # ddof=1
var_pandas_biased = df['col'].var(ddof=0)      # ddof=0

print(f"NumPy (ddof=0): {var_numpy_default:.4f}")
print(f"NumPy (ddof=1): {var_numpy_unbiased:.4f}")
print(f"Pandas (ddof=1): {var_pandas_default:.4f}")
print(f"Pandas (ddof=0): {var_pandas_biased:.4f}")

Теперь видно: np.var(data) и df['col'].var(ddof=0) дают одинаковый результат. Так же, как np.var(data, ddof=1) и df['col'].var(). Вся магия – в одной строке.

Самая частая ошибка – сравнивать результаты np.var(arr) и series.var() без понимания, что это разные формулы. Это как сравнивать километры и мили, не зная о коэффициенте.

Практическое правило: какую дисперсию выбрать?

Забудьте про то, что использует библиотека по умолчанию. Спросите себя:

  1. Это вся генеральная совокупность? У вас есть данные по КАЖДОМУ элементу изучаемой группы. Пример: результаты всех выпускников школы за год, температура в городе за каждый день месяца. Используйте ddof=0 (смещенную оценку).
  2. Это выборка из большой совокупности? Вы опросили 1000 человек из миллионов пользователей, взяли 100 транзакций из миллионов. Используйте ddof=1 (несмещенную оценку). Именно ее ждут большинство статистических тестов.

2 Пример из реальной жизни: A/B-тест

Представьте, вы проводите A/B-тест для нового функционала. У вас есть выборка пользователей в группах А и Б. Вы оцениваете разницу средних. Для расчета стандартной ошибки и t-статистики вам нужна дисперсия выборки.

# Группа A (контрольная)
group_a = df[df['group'] == 'A']['metric'].values
# Группа B (тестовая)
group_b = df[df['group'] == 'B']['metric'].values

# ПРАВИЛЬНО для выборки: использовать несмещенную оценку (ddof=1)
var_a = np.var(group_a, ddof=1)
var_b = np.var(group_b, ddof=1)
# или через pandas, что по умолчанию даст тот же результат
# var_a = df.loc[df['group'] == 'A', 'metric'].var()

# Используем var_a и var_b для расчета t-статистики...

Использование ddof=0 здесь занизило бы оценку дисперсии и могло бы привести к ложному обнаружению статистически значимого эффекта. Опасно.

Эпидемия тихой ошибки: где еще прячется ddof?

Проблема не заканчивается на .var(). Параметр ddof всплывает в других местах, про которые часто забывают.

# Стандартное отклонение (std) – корень из дисперсии. Те же правила!
print(np.std(data))           # ddof=0
print(np.std(data, ddof=1))   # ddof=1
print(df['col'].std())        # ddof=1
print(df['col'].std(ddof=0))  # ddof=0

# Функция np.cov() для ковариационной матрицы
# Её параметр ddof тоже по умолчанию равен 1, если rowvar=False!
# Будьте внимательны, читайте доки.

Чек-лист: как никогда не ошибиться с дисперсией

  • Всегда явно указывайте ddof. Не полагайтесь на значения по умолчанию, особенно если пишете библиотечный код или ноутбук для коллег. np.var(data, ddof=1) читается лучше, чем просто np.var(data).
  • Пишите комментарии. «Здесь используем дисперсию выборки (ddof=1), так как данные – случайная подвыборка из лога».
  • Проверяйте свои цепочки вычислений. Если вы считаете дисперсию через Pandas, а потом используете ее в формуле, написанной под NumPy, убедитесь, что ddof согласован. Эта проблема часто ломает расчеты объяснимости моделей.
  • Тестируйте на простых данных. Как в самом начале статьи. Посчитайте вручную, убедитесь, что библиотека возвращает ожидаемое.

Частые вопросы (FAQ)

Что делать, если я не знаю, выборка у меня или генеральная совокупность?

В 95% случаев в data science и анализе данных вы работаете с выборками. Логи, записи о транзакциях, активность пользователей – это почти всегда срез. Смело используйте ddof=1 (несмещенную оценку). Это более консервативный и общепринятый в статистике выбор. Для работы с реальными данными это правило спасет от излишнего оптимизма.

Почему просто не сделать один стандарт для всех?

Исторически так сложилось. NumPy старше и ближе к научным вычислениям (физика, инженерия), где часто работают с полными наборами измерений. Pandas младше и создан для анализа неполных, «грязных» бизнес-данных. Конфликт неизбежен. Похожие разногласия есть и в других инструментах, например, когда сравнивают Pandas и PySpark.

Влияет ли это на машинное обучение?

Косвенно. Например, при стандартизации признаков (StandardScaler из sklearn) используется несмещенная оценка дисперсии (ddof=1). Если вы будете пересчитывать scaling вручную через NumPy с ddof=0, получите другие коэффициенты. Всегда сверяйтесь с документацией библиотек.

Итог простой, но важный: ddof – не техническая деталь, а статистическое решение. Следующий раз, когда будете считать дисперсию, остановитесь на секунду и спросите: «А что эти данные на самом деле представляют?». Эта привычка сэкономит вам часы отладки и сделает ваши выводы на порядок надежнее.

Подписаться на канал