Собесов

Сценарий: pandas query и eval — когда дают выигрыш

PythonВекторизация и performanceСредняяMiddle

Условие

Объясни, чем df.query("a > 0 and b < 10") отличается от df[(df.a > 0) & (df.b < 10)]. Когда query/eval быстрее, а когда наоборот медленнее?

Решение

Что под капотом

  • df.query(expr) парсит строку и при размере > ~250k строк передаёт исполнение в numexpr — он считает выражение в нативном C параллельно по чанкам и без промежуточных копий.
  • Boolean-маска (df.a>0)&(df.b<10) всегда создаёт две промежуточные Series и потом ещё одну (&).

Бенчмарк

import pandas as pd
import numpy as np
 
df = pd.DataFrame({'a': np.random.randn(10_000_000), 'b': np.random.randn(10_000_000)})
 
# Маска
%timeit df[(df['a'] > 0) & (df['b'] < 0)]
 
# Query — обычно в 1.5-3 раза быстрее на больших df
%timeit df.query('a > 0 and b < 0')
 
# eval для арифметики
df.eval('c = a + b * 2', inplace=True)

Когда query медленнее

  • Маленькие df (<10k) — оверхед на парсинг строки больше выигрыша.
  • Сложные выражения с вызовом методов (a.str.contains(...)) — query этого не умеет.
  • Переменные из окружения — нужно @var: df.query('a > @threshold').

Подводные камни

  1. query чувствителен к именам колонок: пробелы и спецсимволы ломают парсер — нужно df.query('my col > 0') через бэктики.
  2. Внешние переменные требуют префикса @df.query('a > threshold') упадёт, нужно 'a > @threshold'.
  3. numexpr поддерживает не все операции: нет isin, нет строковых методов.
  4. query возвращает копию (как и маска), inplace=True нет — экономии памяти ноль, только скорость.
  5. На маленьких df разница в скорости незаметна; не оптимизируйте преждевременно.

Эталонный ответ

query/eval используют numexpr и быстрее булевой маски на больших df (>~250k строк) за счёт исполнения в C без промежуточных копий. На малых df смысла нет.

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти