Урок по треку Python для анализа данных 7 мин

Производительность и память в pandas

Векторизация против apply и циклов, category, downcast, Copy-on-Write, когда брать Polars.

pandas быстр, пока вы работаете целыми колонками. Как только код идёт по строкам, он замедляется в десятки и сотни раз. На собесе middle и senior обязательно спросят, как ускорить медленный ноутбук и как уместить данные в память.

Векторизация

Векторизованная операция применяется ко всей колонке сразу, цикл выполняется внутри NumPy на C.

# медленно: Python-вызов на каждую строку
df['total'] = df.apply(lambda r: r['qty'] * r['price'], axis=1)

# быстро: одна операция над массивами
df['total'] = df['qty'] * df['price']

На миллионе строк разница обычно в сотни раз. Иерархия скорости, от лучшего к худшему:

  1. встроенные векторные операции pandas и NumPy;
  2. методы .str, .dt, map по словарю;
  3. apply по колонке (Series.apply);
  4. apply(axis=1), iterrows и обычный цикл.

itertuples заметно быстрее iterrows, но это всё равно цикл в Python.

Условия без apply

import numpy as np

df['size'] = np.where(df['amount'] > 1000, 'big', 'small')

df['tier'] = np.select(
    [df['amount'] < 100, df['amount'] < 1000],
    ['low', 'mid'],
    default='high',
)

np.select проверяет условия по порядку, как цепочка if/elif/else. Для интервалов есть pd.cut, для маппинга кодов map со словарём.

Память: типы данных

Проверка: df.memory_usage(deep=True) или df.info(memory_usage='deep'). deep=True важен для строк: без него pandas показывает только размер указателей.

Способы ужать таблицу:

  • category для строк с небольшим числом уникальных значений (город, статус, платформа). Хранится словарь значений и маленькие целые коды. Экономия бывает в десятки раз, а groupby по категориям быстрее.
  • downcast чисел: pd.to_numeric(s, downcast='integer') выберет минимальный подходящий тип (int8, int16...). Вместо float64 часто хватает float32.
  • читать только нужное: usecols, dtype прямо в read_csv.

Ловушка category: при конкатенации или merge таблиц с разными наборами категорий колонка может молча стать обычной строкой. И category бесполезна, если почти все значения уникальны.

Copy-on-Write в pandas 3.0

В pandas 3.0 по умолчанию включён режим Copy-on-Write. Главное следствие: любой объект, полученный из DataFrame (колонка, срез, результат фильтра), ведёт себя как независимая копия. Изменить исходную таблицу через него нельзя.

df = pd.DataFrame({'a': [1, 2]})
col = df['a']
col.iloc[0] = 100
df['a'].tolist()   # [1, 2] в pandas 3.0 (в 2.x было [100, 2])

Цепочное присваивание df[df['a'] > 1]['b'] = 0 в pandas 3.0 никогда не меняет df. Правильно одной операцией: df.loc[df['a'] > 1, 'b'] = 0. Знаменитое предупреждение SettingWithCopyWarning ушло вместе со старым поведением.

Бонус CoW: копии создаются лениво, только при записи. Цепочки методов вроде df.rename(...).fillna(...).query(...) больше не копируют данные на каждом шаге.

Когда pandas мало

  • Polars: колоночный движок на Rust, многопоточный, с ленивыми запросами. На группировках и join по десяткам миллионов строк обычно в разы быстрее pandas.
  • DuckDB: SQL прямо по DataFrame, CSV и Parquet, тоже многопоточный, удобен для тяжёлых агрегатов.
  • Spark: когда данные не помещаются на одну машину.

На собесе хороший ответ звучит так: сначала профилирую и убираю циклы, ужимаю типы, и только потом меняю инструмент.

Как искать узкое место

  • %timeit в Jupyter для одной строки, %%time для ячейки.
  • %prun или line_profiler, чтобы найти медленную функцию.
  • Проверьте, не вызывается ли apply или iterrows внутри цикла, и не растёт ли DataFrame через concat в цикле.

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

  • apply(axis=1) для арифметики, которую можно записать колонками.
  • iterrows с присвоением в df.loc[i, ...] внутри цикла: медленно вдвойне.
  • Строки вместо category на сотнях миллионов строк.
  • memory_usage() без deep=True, и строки «ничего не весят».
  • Цепочное присваивание, которое в pandas 3.0 ничего не меняет.

Что спросят на собесе

  • Почему apply(axis=1) медленный и чем его заменить?
  • Как уменьшить память, которую занимает DataFrame?
  • Когда category помогает, а когда вредит?
  • Что такое Copy-on-Write и что поменялось в pandas 3.0?
  • Когда стоит переходить на Polars, DuckDB или Spark?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.