NumPy и Pandas: в чём разница и когда что использовать

Проверь себя · 1/3разбор после ответа
Пусть выполнено a = [1, 2] и b = [1, 2]. Какие значения дадут выражения a == b и a is b?

Зачем понимать разницу

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

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

Разобраться с этим стоит не ради собеседования, а чтобы перестать брать неподходящий инструмент. Когда вы крутите матрицы и считаете линейную алгебру через Pandas, вы теряете скорость на ровном месте. Когда вы чистите CSV с датами и пропусками через голый NumPy, вы вручную пишете то, что в Pandas делается одним методом. Ниже разберём обе библиотеки по отдельности, сравним их по структурам данных и производительности и покажем, как переходить между ними.

Что такое NumPy

NumPy (Numerical Python) — это фундамент научных вычислений в Python. Главная сущность библиотеки — ndarray (n-dimensional array), однородный многомерный массив фиксированного типа. «Однородный» здесь ключевое слово: все элементы массива имеют один и тот же тип данных, например int64 или float64, и лежат в памяти непрерывным блоком. Именно из-за этого NumPy работает на порядки быстрее обычных списков Python.

import numpy as np

arr = np.array([1, 2, 3, 4, 5])
print(arr.dtype)           # int64 (на 64-битных системах)
print(arr.mean())          # 3.0
print(arr * 2)             # [ 2  4  6  8 10]

matrix = np.array([[1, 2], [3, 4]])
print(matrix.shape)        # (2, 2)
print(matrix.sum(axis=0))  # [4 6] — сумма по столбцам

Обратите внимание на выражение arr * 2. Мы не писали цикл — операция применилась сразу ко всему массиву. Это и есть векторизация: одно действие над целым массивом вместо поэлементного перебора в Python. Под капотом перебор всё равно происходит, но он выполняется в скомпилированном C-коде, без накладных расходов интерпретатора на каждый шаг.

import numpy as np

# Питоновский цикл — каждый шаг проходит через интерпретатор
nums = list(range(1_000_000))
squares_slow = [x ** 2 for x in nums]

# Векторизация NumPy — тот же результат, но цикл на C-уровне
arr = np.arange(1_000_000)
squares_fast = arr ** 2

NumPy особенно силён там, где данные по своей природе числовые и однородные: линейная алгебра, статистика, обработка сигналов и изображений (картинка — это массив пикселей), генерация случайных чисел. Многие библиотеки машинного обучения, включая scikit-learn, принимают на вход именно NumPy-массивы.

import numpy as np

# Решение системы линейных уравнений Ax = b
A = np.array([[1, 2], [3, 4]])
b = np.array([5, 6])
x = np.linalg.solve(A, b)
print(x)  # [-4.   4.5]

Что такое Pandas

Pandas решает другую задачу — работу с табличными разнотипными данными, то есть с тем, что вы привыкли видеть в Excel или в результатах SQL-запроса. Две основные структуры здесь — Series (один столбец с метками) и DataFrame (таблица из столбцов). В отличие от NumPy, разные колонки DataFrame могут иметь разные типы: строка с именем, целое число с возрастом, дробное число с зарплатой — всё в одной таблице.

import pandas as pd

df = pd.DataFrame({
    'name': ['Анна', 'Борис', 'Вика'],
    'age': [25, 30, 28],
    'salary': [80000, 120000, 95000],
})

print(df.dtypes)
# name      object
# age        int64
# salary     int64
# dtype: object

print(df[df['salary'] > 90000])
#     name  age  salary
# 1  Борис   30  120000
# 2   Вика   28   95000

Кроме гетерогенности у Pandas есть две вещи, которых нет в NumPy: осмысленные метки и встроенная логика анализа данных. У каждой строки и колонки есть индекс (имя), а не только числовая позиция, поэтому к данным можно обращаться по названию. А методы вроде groupby, merge, pivot_table, fillna, read_csv закрывают типичный цикл работы аналитика — загрузил, почистил, сгруппировал, посчитал — без ручного кода.

import pandas as pd

orders = pd.DataFrame({
    'user_id': [1, 1, 2, 2, 3],
    'amount': [100, 250, 90, 310, 70],
})

by_user = orders.groupby('user_id')['amount'].agg(['sum', 'count', 'mean'])
print(by_user)
#          sum  count   mean
# user_id
# 1        350      2  175.0
# 2        400      2  200.0
# 3         70      1   70.0

По сути Pandas — это SQL и Excel внутри Python, плюс удобная работа с датами, текстом и пропусками. Если вы уже знаете SQL, многие операции переносятся почти один в один — об этом есть отдельный разбор в Pandas vs SQL.

Ключевые отличия

Главных линий различия три: структура данных, типы и работа с пропусками. NumPy оперирует однородным числовым массивом без меток, Pandas — таблицей с разнотипными именованными колонками. Из этого вытекает почти всё остальное.

Критерий NumPy Pandas
Основная структура ndarray (массив) DataFrame и Series (таблица)
Типы данных Однородные, один тип на массив Разнородные, свой тип у каждой колонки
Индексация По числовой позиции По именованным меткам строк и колонок
Пропуски Только NaN во float, без удобных методов Встроенно: NaN, fillna, dropna, isna
Группировка и сводные Нет из коробки groupby, pivot_table, merge
Чтение файлов Базовое (loadtxt, genfromtxt) CSV, Excel, SQL, JSON, Parquet
Сильная сторона Скорость на чистой математике Удобство табличного анализа и ETL

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

import numpy as np
import pandas as pd

s = pd.Series([10, np.nan, 30, np.nan, 50])
print(s.mean())              # 30.0 — NaN пропускается по умолчанию
print(s.fillna(0).tolist())  # [10.0, 0.0, 30.0, 0.0, 50.0]
print(s.dropna().tolist())   # [10.0, 30.0, 50.0]

arr = np.array([10, np.nan, 30, np.nan, 50])
print(arr.mean())            # nan — обычное среднее «заражается» NaN
print(np.nanmean(arr))       # 30.0 — нужен отдельный метод nanmean

Производительность: что быстрее

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

При этом сравнивать «NumPy против Pandas» по скорости не всегда корректно — обычно их не выбирают для одной и той же задачи. Гораздо важнее другое сравнение: и NumPy, и Pandas в разы быстрее обычного питоновского цикла, потому что переносят вычисления на C-уровень и работают с непрерывной памятью. Векторизованная операция вместо for-цикла или iterrows — это типично порядок величины ускорения, а иногда и два.

import numpy as np
import pandas as pd

df = pd.DataFrame({'a': np.arange(1_000_000)})

# Медленно: построчный обход в Python
total = 0
for _, row in df.iterrows():
    total += row['a'] ** 2

# Быстро: векторизация — операция уходит в NumPy под капотом
total_fast = (df['a'] ** 2).sum()

Практический вывод простой. Не переписывайте Pandas на NumPy преждевременно: для большинства аналитических задач Pandas достаточно быстр, а читаемость кода важнее микросекунд. К NumPy спускайтесь точечно — когда узкое место действительно в тяжёлой математике над числовым массивом. Если же тормозит сам Pandas, чаще помогает не переход на NumPy, а правильные типы и векторизация — это разобрано в оптимизации производительности Pandas.

Закрепи Python для аналитика
200+ задач по pandas, numpy и работе с данными — с разборами
Тренировать Python в Telegram

Как Pandas использует NumPy

Pandas не альтернатива NumPy, а надстройка над ним. Каждая числовая колонка DataFrame физически хранится как NumPy-массив, и многие операции Pandas просто делегируются движку NumPy. Поэтому знание NumPy не «дополнительная» тема для аналитика, а понимание того, как Pandas устроен изнутри.

import pandas as pd

df = pd.DataFrame({'a': [1, 2, 3]})
print(type(df['a'].to_numpy()))   # <class 'numpy.ndarray'>
print(df['a'].to_numpy())         # [1 2 3]

Эта связь объясняет и поведение, которое иначе кажется случайным. Например, почему Series с целыми числами превращается во float, как только в нём появляется NaN: у целочисленного NumPy-массива нет значения для «пусто», а NaN существует только во float, поэтому Pandas повышает тип всей колонки. Понимая нижний слой, такие вещи перестают удивлять.

Переход между NumPy и Pandas

На практике вы постоянно ходите между двумя мирами: загрузили таблицу в Pandas, почистили, а затем отдали числовой массив в модель машинного обучения, которая ждёт NumPy. Переход в обе стороны делается явными методами.

import numpy as np
import pandas as pd

df = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]})

# Pandas → NumPy
arr = df['a'].to_numpy()   # рекомендуемый способ
arr_legacy = df['a'].values  # старый способ, тот же массив
mat = df.to_numpy()          # весь DataFrame → 2D-массив

# NumPy → Pandas
data = np.array([[1, 2], [3, 4]])
back = pd.DataFrame(data, columns=['x', 'y'])
print(back)
#    x  y
# 0  1  2
# 1  3  4

Два практических момента. Во-первых, для получения массива из колонки предпочитайте to_numpy(), а не атрибут .values — он считается современным и явным API, тогда как .values оставлен для обратной совместимости. Во-вторых, если применить to_numpy() к DataFrame с разнотипными колонками (строки и числа вместе), результат получит общий тип object — и вместе с ним потеряется скорость NumPy. Поэтому к массиву обычно приводят однородную числовую часть таблицы, а не всё подряд.

Когда использовать NumPy, а когда Pandas

Правило выбора простое и хорошо звучит на собеседовании: берите NumPy для численных вычислений над однородными данными и Pandas для табличного анализа и ETL. Если задача описывается как «матрицы, векторы, линейная алгебра, обработка изображений, тензоры под модель» — это NumPy. Если как «загрузить файл, почистить, сгруппировать, посчитать метрики, соединить таблицы, поработать с датами» — это Pandas.

NumPy уместен, когда данные числовые и однородные, а на первом месте стоит скорость: быстрые поэлементные операции, линейная алгебра, статистика, генерация случайных выборок через np.random, подготовка признаков в виде матрицы для scikit-learn. Pandas уместен, когда данные табличные и разнотипные: чтение и очистка CSV или выгрузок из базы, группировки и сводные таблицы, объединение источников, работа с временными рядами, разведочный анализ (EDA).

import pandas as pd

# Типичная задача аналитика: помесячная выручка из CSV — это Pandas
orders = pd.read_csv('orders.csv')
monthly = (orders
    .assign(month=pd.to_datetime(orders['date']).dt.to_period('M'))
    .groupby('month')['revenue']
    .agg(['sum', 'count', 'mean']))

Для аналитика данных рабочая пропорция смещена в сторону Pandas: 90% повседневных задач — это таблицы. NumPy подключается точечно. Лучший способ закрепить разницу — не читать про неё, а решать задачи руками: разобрать векторизацию, группировки и пропуски на живых примерах можно в тренажёре по Python для аналитика, где код пишется и проверяется сразу.

Частые ошибки

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

Обходить DataFrame циклом через iterrows или apply там, где работает векторизация. Построчный обход прогоняет каждую строку через интерпретатор Python и убивает главное преимущество обеих библиотек. Почти всегда есть векторизованная альтернатива, которая быстрее на порядок.

Забывать, что to_numpy() на разнотипном DataFrame возвращает массив типа object. Такой массив теряет скорость NumPy и ведёт себя непредсказуемо в числовых операциях. Приводите к массиву только однородную числовую часть таблицы.

Путать поведение с пропусками. В Pandas агрегаты по умолчанию игнорируют NaN, а в голом NumPy mean по массиву с NaN вернёт nan — для пропусков нужны отдельные методы np.nanmean, np.nansum. Перенос привычки из одной библиотеки в другую даёт тихие баги.

Класть в один ndarray разнородные данные. Как только в массиве смешиваются строки и числа, NumPy делает его типом object, и вся C-скорость исчезает. Для разнотипных данных существует Pandas — не нужно гнуть NumPy под чужую задачу.

Связанные темы

FAQ

В чём главная разница между NumPy и Pandas?

NumPy работает с однородными числовыми массивами (ndarray) и заточен под быстрые вычисления, а Pandas — с табличными разнотипными данными (DataFrame и Series), у которых есть именованные индексы и встроенные методы для группировки, объединения и пропусков. Pandas при этом построен поверх NumPy.

Что быстрее — NumPy или Pandas?

На чистых числовых операциях NumPy быстрее, потому что у Pandas есть накладные расходы на индексы, метки и проверку типов. Но обычно их не выбирают для одной задачи: и тот, и другой в разы быстрее питоновского цикла за счёт векторизации, а Pandas для табличного анализа достаточно быстр.

Нужно ли знать NumPy, если я работаю только с Pandas?

Базово да. Pandas хранит данные в NumPy-массивах и делегирует ему часть операций, поэтому знание NumPy объясняет поведение Pandas — например, почему колонка целых чисел становится float при появлении NaN. Плюс NumPy нужен при работе с scikit-learn и кастомными вычислениями.

Можно ли конвертировать DataFrame в NumPy-массив?

Да. Используйте df.to_numpy() для всей таблицы или df['col'].to_numpy() для одной колонки. Атрибут .values делает то же самое, но to_numpy() считается современным API. На разнотипной таблице результат получит тип object, поэтому к массиву обычно приводят однородную числовую часть.

.values или .to_numpy() — что использовать?

Предпочитайте to_numpy(): это явный и рекомендуемый метод. .values оставлен для обратной совместимости и возвращает тот же массив, но в новом коде лучше его не использовать.

Pandas действительно построен на NumPy?

Да. Числовые колонки DataFrame физически хранятся как NumPy-массивы, и многие операции Pandas под капотом вызывают NumPy. Это два слоя одного стека: NumPy — низкоуровневый движок вычислений, Pandas — высокоуровневый интерфейс для анализа данных над ним.