Ускорение Pandas в 100 раз: векторные операции вместо циклов в 2026 | AiManual
AiManual Logo Ai / Manual.
08 Мар 2026 Гайд

Как ускорить Pandas в 100 раз: отказываемся от циклов в пользу векторных операций

Практический гайд по отказу от циклов в Pandas. Узнайте, как векторизация ускоряет код в 100 раз, с примерами и пошаговым планом на 2026 год.

Ваш код на Pandas ползет как улитка? Скорее всего, вы пишете циклы

Вижу это постоянно. Разработчик берет красивый, чистый датасет, загружает его в DataFrame... и пишет for row in df.iterrows(). Через час он приходит ко мне с вопросом, почему обработка 100 тысяч строк занимает три века. Ответ прост: вы используете Pandas не по назначению. Это не Excel с макросами. Это векторный движок, построенный на NumPy и C. И если вы не щелкаете векторными операциями как семечками, вы проиграли еще до старта.

Факт на 08.03.2026: Pandas 2.7.2 под капотом — это уже не просто обертка над NumPy. В последних версиях команда внедрила поддержку PyArrow в качестве бэкенда по умолчанию для строковых и nullable-типов. Это меняет правила игры, но основной принцип неизменен: циклы на Python уровне убивают производительность наповал.

Почему цикл for — это смертный приговор для скорости?

Представьте, что вам нужно пересчитать миллион зарплат с учетом бонуса. В цикле это выглядит так: взять одну строку из DataFrame (это уже создание Python-объекта), достать из нее значение (еще один вызов), прибавить бонус (математика в Python), записать обратно (создание нового объекта и вставка). На каждую строку — десятки операций на медленном интерпретируемом языке.

Векторная операция делает все иначе. Она говорит низкоуровневому движку (часто написанному на C или Fortran): "Эй, вот массив чисел и константа, сложи их побыстрее, используя SIMD-инструкции процессора". Движок загружает пачку чисел в кэш CPU, выполняет одну инструкцию для всей пачки сразу и записывает результат обратно в непрерывный блок памяти. Нулевые накладные расходы на создание Python-объектов для каждой строки. Разница — как между доставкой пиццы курьером на велосипеде и отправкой целой фуры пиццы по автостраде.

# КАК НЕ НАДО ДЕЛАТЬ (медленный ад)
import pandas as pd
import numpy as np

# Генерируем тестовые данные (актуально для pandas 2.7.2)
df = pd.DataFrame({'salary': np.random.uniform(50000, 150000, 1_000_000)})
bonus = 5000

# Грехопадение в чистом виде
new_salaries = []
for index, row in df.iterrows():  # Этот метод сам по себе медленный
    new_salaries.append(row['salary'] + bonus)
df['salary_with_bonus'] = new_salaries  # Еще одно копирование
# КАК НАДО ДЕЛАТЬ (молниеносно)
df['salary_with_bonus'] = df['salary'] + bonus  # Вот и вся магия

Второй вариант работает не в 2 и не в 10 раз быстрее. На миллионе строк разница составит ~500 миллисекунд против ~50 секунд. Это тот самый заветный ускорение в 100 раз. И это не преувеличение.

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

Главная проблема новичков — они мысленно представляют данные как таблицу в Excel и обрабатывают их построчно. Pandas хочет, чтобы вы думали о колонках как о цельных векторах (numpy-массивах). Ваша задача — выразить преобразование в виде операций над этими векторами: сложение, умножение, сравнение, агрегация.

💡
Если вы ловите себя на мысли "мне нужно применить сложную функцию к каждой строке", остановитесь. Сначала спросите: "Можно ли это выразить через комбинацию векторных операций?" В 95% случаев ответ — да. Для оставшихся 5% есть df.apply() с аккуратным использованием или Numba.

1 Диагностика: найдите свои циклы с помощью py-spy

Прежде чем что-то ускорять, нужно найти виновника. Не доверяйте догадкам. Используйте профайлер. Мой любимый инструмент — py-spy, потому что он не требует модификации кода и показывает, что происходит на уровне системы.

# Установите и запустите профайлер (актуальная версия на 2026 - 0.3.17)
py-spy record -o profile.svg -- python my_slow_pandas_script.py

Визуализация покажет, что 90% времени ваш процессор торчит в функциях __getitem__, iterrows или в вашем собственном цикле. Подробнее об этом я писал в статье про профилирование с py-spy. Это первый шаг к исцелению.

2 Переписывание: базовые паттерны замены циклов

Давайте разберем три самых частых сценария и как их векторизовать.

Сценарий 1: Арифметические операции и условные присваивания

Вместо проверки условия в цикле используйте np.where() или df.where()/df.mask().

# Старый код (медленный)
for i in range(len(df)):
    if df.loc[i, 'age'] >= 18:
        df.loc[i, 'status'] = 'adult'
    else:
        df.loc[i, 'status'] = 'minor'

# Новый код (быстрый)
df['status'] = np.where(df['age'] >= 18, 'adult', 'minor')
# Или так (чистый pandas)
df['status'] = 'minor'
df.loc[df['age'] >= 18, 'status'] = 'adult'

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

Сценарий 2: "Применить сложную функцию к каждой строке"

Часто нужно вычислить что-то на основе нескольких колонок. Используйте df.apply() с параметром axis=1? Стоп. Это все еще цикл, только замаскированный. Сначала попробуйте векторизовать.

# Цель: создать полное имя из имени и фамилии
# Плохо (но лучше, чем for loop)
df['full_name'] = df.apply(lambda row: f"{row['first_name']} {row['last_name']}", axis=1)

# Отлично (полностью векторизовано)
df['full_name'] = df['first_name'] + ' ' + df['last_name']
# В pandas 2.7.2 строковые операции работают на удивление шустро благодаря PyArrow.

Если функция действительно сложная (например, содержит ветвления, обращения к внешним библиотекам), тогда apply может быть единственным выходом. Но перед этим проверьте, нет ли уже векторной реализации в NumPy или SciPy.

Сценарий 3: Групповые вычисления и скользящие окна

Здесь циклы — это абсолютное зло. Pandas создан для этого.

# Худшее, что можно сделать
for department in df['department'].unique():
    department_mean = df[df['department'] == department]['salary'].mean()
    # ...

# Правильный путь
department_stats = df.groupby('department')['salary'].agg(['mean', 'std', 'count']).reset_index()
# Для скользящего среднего: df['rolling_avg'] = df['value'].rolling(window=7).mean()

Нюанс 2026 года: В последних версиях Pandas (2.6+) появились экспериментальные методы для "отложенных" групповых операций, которые еще больше снижают потребление памяти. Но для скорости groupby уже оптимизирован до предела.

3 Когда циклы все же неизбежны: спасательные круги

Бывает, что логика настолько запутанная, что векторизовать ее за разумное время нельзя. Или данные настолько велики, что даже векторизованные операции не помещаются в память. Что тогда?

  • Используйте Numba или Cython: Если вычисления числовые, декоротатор @njit от Numba может скомпилировать ваш цикл в машинный код. Резкий рост сложности, но и резкий рост скорости. О других способах перевода Python в нативный код читайте в отдельном материале.
  • Разбейте данные на чанки: Вместо одного цикла по 10 миллионам строк, сделайте цикл по чанкам по 100k и внутри каждого чанка используйте векторизованные операции. Это компромисс между памятью и удобством.
  • Уходите в распределенные системы: Если ваш датасет измеряется терабайтами, Pandas — не ваш выбор. Взгляните на Pandas против PySpark или на Ray для распределенных вычислений.

Распространенные ошибки, которые сведут на нет все ваши усилия

Ошибка Почему это плохо Как исправить
Цепочка .loc в цикле Каждый вызов .loc — это поиск по индексу. В цикле это O(n²). Собрать индексы в список, затем один раз сделать df.loc[index_list, column] = values.
Копирование DataFrame в каждой итерации Неизбежно приводит к утечке памяти и замедлению. Изменять данные на месте или использовать .assign() для создания нового DF.
Использование df.iterrows() для модификации данных Не гарантирует запись обратно в оригинальный DataFrame. Чудовищно медленно. Просто не делайте этого. Вообще. Никогда.

Вопросы, которые вы боялись задать (но они всех мучают)

А что, если моя функция требует обращения к API или чтения файла для каждой строки?

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

Правда ли, что df.apply() с axis=0 (по колонкам) быстрее, чем с axis=1?

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

Я слышал про .itertuples(). Это быстрее, чем .iterrows()?

Да, itertuples() возвращает именованные кортежи (namedtuples), создание которых дешевле, чем объектов Series. Но это все еще цикл на Python. Это как выбрать между ехать на больном осле или идти пешком. Выбор есть, но оба варианта — не автомобиль.

И последний совет, который вы не найдете в официальной документации. Иногда самый быстрый способ что-то сделать в Pandas — это не делать этого в Pandas. Для некоторых операций, особенно сложных преобразований целых таблиц, может оказаться выгоднее конвертировать DataFrame в массив NumPy, сделать все операции там (они все равно векторные) и собрать обратно. Это грязно, но на очень больших данных может дать дополнительный прирост в 10-20% за счет снятия накладных расходов индексации Pandas. Но это уже высший пилотаж. Сначала научитесь ездить на автомобиле, прежде чем пытаться настроить его двигатель для гонок.

Помните, самый дорогой ресурс — это время процессора, потраченное впустую. Цикл по DataFrame — это и есть его растрата. Избавьтесь от этой привычки, и ваши пайплайны данных перестанут быть узким местом. А если упираетесь в пределы одного компьютера, посмотрите в сторону стриминга терабайтных датасетов или распределенных фреймворков. Но это уже совсем другая история.

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