Когда использовать DataFrame.eval() против pandas.eval() или python eval()

У меня есть несколько десятков условий (например, foo > bar), что мне нужно оценить на ~1 мм строк DataFrameи самый краткий способ написать это - сохранить эти условия в виде списка строк и создать DataFrame логических результатов (одна строка на запись х один столбец на условие). (Пользовательский ввод не оценивается.)

В поисках преждевременной оптимизации я пытаюсь определить, должен ли я написать эти условия для оценки в рамках DataFrame (например, df.eval("foo > bar") или просто оставьте это Python, как в eval("df.foo > df.bar")

Согласно документации по повышению производительности eval:

Вы не должны использовать eval() для простых выражений или для выражений, включающих небольшие DataFrames. На самом деле, eval() на много порядков медленнее для небольших выражений / объектов, чем обычный старый Python. Хорошее практическое правило - использовать eval(), только когда у вас есть DataFrame с более чем 10000 строк.

Было бы неплохо иметь возможность использовать df.eval("foo > bar") синтаксис, потому что мой список был бы немного более читабельным, но я никогда не смогу найти случай, когда он не медленнее оценивать. В документации приведены примеры того, где pandas.eval() быстрее чем питон eval() (что соответствует моему опыту), но ни один для DataFrame.eval() (который указан как "Экспериментальный").

Например, DataFrame.eval() все еще явный неудачник в непростом выражении на большом DataFrame:

import pandas as pd
import numpy as np
import numexpr
import timeit

someDf = pd.DataFrame({'a':np.random.uniform(size=int(1e6)), 'b':np.random.uniform(size=int(1e6))})

%timeit -n100 someDf.eval("a**b - a*b > b**a - b/a") # DataFrame.eval() on notional expression
%timeit -n100 eval("someDf['a']**someDf['b'] - someDf['a']*someDf['b'] > someDf['b']**someDf['a'] - someDf['b']/someDf['a']")
%timeit -n100 pd.eval("someDf.a**someDf.b - someDf.a*someDf.b > someDf.b**someDf.a - someDf.b/someDf.a")

100 loops, best of 3: 29.9 ms per loop
100 loops, best of 3: 18.7 ms per loop
100 loops, best of 3: 15.4 ms per loop

Таково преимущество DataFrame.eval() просто в упрощении ввода, или мы можем определить обстоятельства, когда использование этого метода на самом деле быстрее?

Существуют ли какие-либо другие рекомендации относительно того, когда использовать какие eval()? (Я знаю, что pandas.eval() не поддерживает полный набор операций.)

pd.show_versions()

INSTALLED VERSIONS
------------------
commit: None
python: 3.5.1.final.0
python-bits: 64
OS: Windows
OS-release: 7
machine: AMD64
processor: Intel64 Family 6 Model 63 Stepping 2, GenuineIntel
byteorder: little
LC_ALL: None
LANG: en_US

pandas: 0.18.0
nose: 1.3.7
pip: 8.1.2
setuptools: 20.3
Cython: 0.23.4
numpy: 1.10.4
scipy: 0.17.0
statsmodels: None
xarray: None
IPython: 4.1.2
sphinx: 1.3.1
patsy: 0.4.0
dateutil: 2.5.3
pytz: 2016.2
blosc: None
bottleneck: 1.0.0
tables: 3.2.2
numexpr: 2.5
matplotlib: 1.5.1
openpyxl: 2.3.2
xlrd: 0.9.4
xlwt: 1.0.0
xlsxwriter: 0.8.4
lxml: 3.6.0
bs4: 4.4.1
html5lib: None
httplib2: None
apiclient: None
sqlalchemy: 1.0.12
pymysql: None
psycopg2: None
jinja2: 2.8
boto: 2.39.0

1 ответ

Решение

Итак, является ли преимущество DataFrame.eval() просто в упрощении ввода или мы можем определить обстоятельства, когда использование этого метода действительно быстрее?

Исходный код для DataFrame.eval() показывает, что он на самом деле просто создает аргументы для передачи в pd.eval():

def eval(self, expr, inplace=None, **kwargs):

    inplace = validate_bool_kwarg(inplace, 'inplace')
    resolvers = kwargs.pop('resolvers', None)
    kwargs['level'] = kwargs.pop('level', 0) + 1
    if resolvers is None:
        index_resolvers = self._get_index_resolvers()
        resolvers = dict(self.iteritems()), index_resolvers
    if 'target' not in kwargs:
        kwargs['target'] = self
    kwargs['resolvers'] = kwargs.get('resolvers', ()) + tuple(resolvers)
    return _eval(expr, inplace=inplace, **kwargs)

Где _eval() - это просто псевдоним для pd.eval (), который импортируется в начале модуля:

from pandas.core.computation.eval import eval as _eval

Так что все, что вы можете сделать с df.eval()Вы могли бы сделать с pd.eval() + несколько дополнительных строк для настройки. Как сейчас обстоят дела, df.eval() никогда не бывает строго быстрее, чем pd.eval(), Но это не значит, что не может быть случаев, когда df.eval() так же хорошо, как pd.eval()Еще удобнее писать.

Тем не менее, после игры с %prun магия кажется, что вызов df.eval() в df._get_index_resolvers() добавляет немного времени на df.eval() метод. В конечном счете, _get_index_resolvers() в конечном итоге вызывает .copy() метод numpy.ndarrayчто и приводит к замедлению. В то же время, pd.eval() звонит numpy.ndarray.copy() в какой-то момент, но это занимает незначительное количество времени (по крайней мере, на моей машине).

Короче говоря, кажется, что df.eval() имеет тенденцию быть медленнее, чем pd.eval() потому что под капотом это просто pd.eval() с дополнительными шагами, и эти шаги нетривиальны.

Другие вопросы по тегам