Условие
Объясни, чем 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').
Подводные камни
queryчувствителен к именам колонок: пробелы и спецсимволы ломают парсер — нужноdf.query('my col> 0')через бэктики.- Внешние переменные требуют префикса
@—df.query('a > threshold')упадёт, нужно'a > @threshold'. numexprподдерживает не все операции: нетisin, нет строковых методов.queryвозвращает копию (как и маска),inplace=Trueнет — экономии памяти ноль, только скорость.- На маленьких df разница в скорости незаметна; не оптимизируйте преждевременно.
Эталонный ответ
query/eval используют numexpr и быстрее булевой маски на больших df (>~250k строк) за счёт исполнения в C без промежуточных копий. На малых df смысла нет.