В этом ответе рассматриваются различные функции и функции, предлагаемые pd.eval
, df.query
и df.eval
.
Настройка
Примеры будут включать эти кадры данных (если не указано иное).
np.random.seed(0)
df1 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
df2 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
df3 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
df4 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
pandas.eval
- «Отсутствует руководство»
Примечание
Из трех функцийобсуждается, pd.eval
является наиболее важным.df.eval
и df.query
звоните pd.eval
под капот.Поведение и использование более или менее согласованно для всех трех функций с некоторыми незначительными семантическими вариациями, которые будут выделены позже.В этом разделе будут представлены функциональные возможности, которые являются общими для всех трех функций - это включает (но не ограничивается) разрешенный синтаксис, правила приоритета и аргументы ключевых слов.
pd.eval
может вычислять арифметические выражения, которые могут состоять из переменных и / или литералов.Эти выражения должны быть переданы в виде строк.Итак, , чтобы ответить на вопрос , как указано, вы можете сделать
x = 5
pd.eval("df1.A + (df1.B * x)")
Несколько вещей, на которые следует обратить внимание:
- Все выражение представляет собой строку
df1
, df2
и x
относятся к переменным в глобальном пространстве имен, они выбираются eval
при синтаксическом анализе выражения - Доступ к определенным столбцам осуществляется с помощью средства доступа к атрибутуиндекс.Вы также можете использовать
"df1['A'] + (df1['B'] * x)"
для того же эффекта.
Я расскажу о конкретной проблеме переназначения в разделе, объясняющем атрибут target=...
ниже.Но сейчас приведем более простые примеры допустимых операций с pd.eval
:
pd.eval("df1.A + df2.A") # Valid, returns a pd.Series object
pd.eval("abs(df1) ** .5") # Valid, returns a pd.DataFrame object
... и так далее.Условные выражения также поддерживаются таким же образом.Приведенные ниже операторы являются действительными выражениями и будут обработаны механизмом.
pd.eval("df1 > df2")
pd.eval("df1 > 5")
pd.eval("df1 < df2 and df3 < df4")
pd.eval("df1 in [1, 2, 3]")
pd.eval("1 < 2 < 3")
Список всех поддерживаемых функций и синтаксиса можно найти в документации .В итоге,
- Арифметические операции, за исключением операторов левого (
<<
) и правого (>>
), например, df + 2 * pi / s ** 4 % 42
- the_golden_ratio - Операции сравнения, включая цепные сравнения, например,
2 < df < df2
- Логические операции, например,
df < df2 and df3 < df4
или not df_bool
list
и tuple
литералы, например, [1, 2]
или (1, 2)
- Доступ к атрибутам, например,
df.a
- Выражения нижнего индекса, например,
df[0]
- Простая оценка переменной, например,
pd.eval('df')
(это не очень полезно) - Математические функции: sin, cos, exp, log, expm1, log1p, sqrt, sinh, cosh, tanh, arcsin, arccos, arctan, arccosh, arcsinh, arctanh, abs и arctan2.
В этом разделе документации также указаны синтаксические правила, которые не поддерживаются, включая литералы set
/ dict
, операторы if-else, циклы и выражения, а также выражения генератора.
FromИз списка очевидно, что вы также можете передавать выражения, включающие индекс, такие как
pd.eval('df1.A * (df1.index > 1)')
ParВыбор ser: parser=...
аргумент
pd.eval
поддерживает два различных параметра синтаксического анализатора при разборе строки выражения для генерации синтаксического дерева: pandas
и python
.Основное различие между ними выделено немного отличающимися правилами приоритета.
При использовании парсера по умолчанию pandas
перегруженные побитовые операторы &
и |
, которые реализуют векторизованные операции И и ИЛИ с объектами панд, будутимеют тот же приоритет операторов, что и and
и or
.Итак,
pd.eval("(df1 > df2) & (df3 < df4)")
будет таким же, как
pd.eval("df1 > df2 & df3 < df4")
# pd.eval("df1 > df2 & df3 < df4", parser='pandas')
, а также так же, как
pd.eval("df1 > df2 and df3 < df4")
Здесь скобки необходимы.Чтобы сделать это условно, пареням потребуется переопределить более высокий приоритет побитовых операторов:
(df1 > df2) & (df3 < df4)
Без этого мы получим
df1 > df2 & df3 < df4
ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
Используйте parser='python'
, если выхотите поддерживать согласованность с фактическими правилами приоритета операторов python при оценке строки.
pd.eval("(df1 > df2) & (df3 < df4)", parser='python')
Другое различие между двумя типами синтаксических анализаторов заключается в семантике операторов ==
и !=
с узлами списков и кортежей, которые имеют семантику, аналогичную in
и not in
соответственно, при использовании 'pandas'
парсер.Например,
pd.eval("df1 == [1, 2, 3]")
Действителен и будет работать с той же семантикой, что и
pd.eval("df1 in [1, 2, 3]")
OTOH, pd.eval("df1 == [1, 2, 3]", parser='python')
выдаст ошибку NotImplementedError
.
Backend Selection: engine=...
аргумент
Есть два варианта - numexpr
(по умолчанию) и python
.Опция numexpr
использует серверную часть numbersxpr , оптимизированную для производительности.
С бэкэндом 'python'
ваше выражение оценивается аналогично простой передаче выражения в функцию eval
python.У вас есть возможность делать больше внутри выражений, таких как, например, строковые операции.
df = pd.DataFrame({'A': ['abc', 'def', 'abacus']})
pd.eval('df.A.str.contains("ab")', engine='python')
0 True
1 False
2 True
Name: A, dtype: bool
К сожалению, этот метод предлагает преимущества производительности no по сравнению с numexpr
, и естьочень мало мер безопасности для обеспечения того, чтобы опасные выражения не оценивались, поэтому ИСПОЛЬЗУЙТЕ НА СВОЙ СТРАХ И РИСК !Как правило, не рекомендуется менять этот параметр на 'python'
, если вы не знаете, что делаете.
local_dict
и global_dict
arguments
Иногда полезно предоставить значения для переменных, используемых внутри выражений, но в настоящее время не определенных в вашем пространстве имен.Вы можете передать словарь в local_dict
Например,
pd.eval("df1 > thresh")
UndefinedVariableError: name 'thresh' is not defined
Сбой, потому что thresh
не определен.Однако это работает:
pd.eval("df1 > thresh", local_dict={'thresh': 10})
Это полезно, когда у вас есть переменные для подачи из словаря.В качестве альтернативы, с двигателем 'python'
вы можете просто сделать это:
mydict = {'thresh': 5}
# Dictionary values with *string* keys cannot be accessed without
# using the 'python' engine.
pd.eval('df1 > mydict["thresh"]', engine='python')
Но это может быть на намного медленнее, чем с использованием механизма 'numexpr'
и передачей словарядо local_dict
или global_dict
.Надеемся, что это должно стать убедительным аргументом для использования этих параметров.
Аргумент target
(+ inplace
) и выражения назначения
Это не часто является обязательным требованием, посколькуобычно это более простые способы сделать это, но вы можете присвоить результат pd.eval
объекту, который реализует __getitem__
, например dict
s, и (как вы уже догадались) DataFrames.
Рассмотрим пример из вопроса
x = 5
df2['D'] = df1['A'] + (df1['B'] * x)
Чтобы присвоить столбцу "D" значение df2
, мы делаем
pd.eval('D = df1.A + (df1.B * x)', target=df2)
A B C D
0 5 9 8 5
1 4 3 0 52
2 5 0 2 22
3 8 1 3 48
4 3 7 0 42
Это не модификация df2
на месте (но ее можно ... читать дальше).Рассмотрим другой пример:
pd.eval('df1.A + df2.A')
0 10
1 11
2 7
3 16
4 10
dtype: int32
Если вы хотите (например) назначить это обратно в DataFrame, вы можете использовать аргумент target
следующим образом:
df = pd.DataFrame(columns=list('FBGH'), index=df1.index)
df
F B G H
0 NaN NaN NaN NaN
1 NaN NaN NaN NaN
2 NaN NaN NaN NaN
3 NaN NaN NaN NaN
4 NaN NaN NaN NaN
df = pd.eval('B = df1.A + df2.A', target=df)
# Similar to
# df = df.assign(B=pd.eval('df1.A + df2.A'))
df
F B G H
0 NaN 10 NaN NaN
1 NaN 11 NaN NaN
2 NaN 7 NaN NaN
3 NaN 16 NaN NaN
4 NaN 10 NaN NaN
Если выхотел выполнить мутацию на месте df
, установить inplace=True
.
pd.eval('B = df1.A + df2.A', target=df, inplace=True)
# Similar to
# df['B'] = pd.eval('df1.A + df2.A')
df
F B G H
0 NaN 10 NaN NaN
1 NaN 11 NaN NaN
2 NaN 7 NaN NaN
3 NaN 16 NaN NaN
4 NaN 10 NaN NaN
Если inplace
установлен без цели, ValueError
повышается.
Несмотря на то, что с аргументом target
забавно играть, вам редко понадобится его использовать.
Если вы хотите сделать это с df.eval
, вы должны использовать выражение, включающее в себя присваивание:
df = df.eval("B = @df1.A + @df2.A")
# df.eval("B = @df1.A + @df2.A", inplace=True)
df
F B G H
0 NaN 10 NaN NaN
1 NaN 11 NaN NaN
2 NaN 7 NaN NaN
3 NaN 16 NaN NaN
4 NaN 10 NaN NaN
Примечание
Одним из непреднамеренных применений pd.eval
является разбор буквенных строк способом, очень похожим на ast.literal_eval
:
pd.eval("[1, 2, 3]")
array([1, 2, 3], dtype=object)
Можеттакже анализирует вложенные списки с помощью механизма 'python'
:
pd.eval("[[1, 2, 3], [4, 5], [10]]", engine='python')
[[1, 2, 3], [4, 5], [10]]
и списков строк:
pd.eval(["[1, 2, 3]", "[4, 5]", "[10]"], engine='python')
[[1, 2, 3], [4, 5], [10]]
Однако проблема заключается в списках длиной более 100:
pd.eval(["[1]"] * 100, engine='python') # Works
pd.eval(["[1]"] * 101, engine='python')
AttributeError: 'PandasExprVisitor' object has no attribute 'visit_Ellipsis'
Более подробную информацию можно найти в данной ошибке, ее причинах, исправлениях и способах ее устранения здесь .
DataFrame.eval
- сопоставление с pandas.eval
Как уже упоминалось выше, df.eval
вызывает pd.eval
под капотом.Исходный код v0.23 показывает это:
def eval(self, expr, inplace=False, **kwargs):
from pandas.core.computation.eval import eval as _eval
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 <b>_eval(expr, inplace=inplace, **kwargs)</b>
eval
создает аргументы, выполняет небольшую проверку и передает аргументы pd.eval
.
Более подробно вы можете прочитать: когда использовать DataFrame.eval () или pandas.eval () или python eval ()
Различия в использовании
Выражения с помощью DataFrames v / s Series Выражения
Для динамических запросов, связанных со всеми фреймами данных, вы должны предпочесть pd.eval
.Например, не существует простого способа указать эквивалент pd.eval("df1 + df2")
при вызове df1.eval
или df2.eval
.
Указание имен столбцов
Другой другойОсновное отличие состоит в том, как осуществляется доступ к столбцам.Например, чтобы добавить два столбца «A» и «B» в df1
, вы должны вызвать pd.eval
со следующим выражением:
pd.eval("df1.A + df1.B")
Для df.eval вам нужно только указать столбецИмена:
df1.eval("A + B")
Поскольку в контексте df1
ясно, что «A» и «B» относятся к именам столбцов.
Вы также можете ссылаться на индекс и столбцы, используя index
(если только индекс не назван, в этом случае вы бы использовали имя).
df1.eval("A + index")
Или, в более общем случае, для любого DataFrame с индексом, имеющим 1 или более уровней, вы можете ссылаться на уровень индекса k th в выражении, используя переменную "ilevel_k" , что означает " i ndex на уровень k ".Таким образом, приведенное выше выражение может быть записано как df1.eval("A + ilevel_0")
.
Эти правила также применяются к query
.
Доступ к переменным в локальном / глобальном пространстве имен
Переменным, указанным внутри выражений, должен предшествовать символ "@", чтобы избежать путаницы с именами столбцов.
A = 5
df1.eval("A > @A")
То же самое относится к query
.
Идет безговоря, что имена ваших столбцов должны соответствовать правилам, чтобы действительные имена идентификаторов в python были доступны внутри eval
.См. здесь для ознакомления со списком правил именования идентификаторов.
Многострочные запросы и присвоения
Малоизвестный факт, что eval
поддерживает многострочныйвыражения, которые имеют дело с назначением.Например, чтобы создать два новых столбца «E» и «F» в df1 на основе некоторых арифметических операций над некоторыми столбцами и третий столбец «G» на основе ранее созданных «E» и «F», мы можем сделать
df1.eval("""
E = A + B
F = @df2.A + @df2.B
G = E >= F
""")
A B C D E F G
0 5 0 3 3 5 14 False
1 7 9 3 5 16 7 True
2 2 4 7 6 6 5 True
3 8 8 1 6 16 9 True
4 7 7 8 1 14 10 True
... Отлично!Однако обратите внимание, что это не поддерживается query
.
eval
v / s query
- Заключительное слово
Это помогает думать о df.query
как офункция, которая использует pd.eval
в качестве подпрограммы.
Как правило, query
(как следует из названия) используется для оценки условных выражений (т. Е. Выражений, которые приводят к значениям True / False) и возвращают строки, соответствующие результату True
.Затем результат выражения передается в loc
(в большинстве случаев), чтобы вернуть строки, которые удовлетворяют выражению.Согласно документации,
Результат оценки этого выражения сначала передается в DataFrame.loc
, а если это не удается из-за многомерного ключа (например, DataFrame), то результат будет переданDataFrame.__getitem__()
.
Этот метод использует функцию pandas.eval()
верхнего уровня для оценки переданного запроса.
С точки зрения сходства, query
и df.eval
являютсяоба одинаково обращаются к именам столбцов и переменным.
Это ключевое различие между ними, как упомянуто выше, заключается в том, как они обрабатывают результат выражения.Это становится очевидным, когда вы фактически запускаете выражение через эти две функции.Например, рассмотрим
df1.A
0 5
1 7
2 2
3 8
4 7
Name: A, dtype: int32
df1.B
0 9
1 3
2 0
3 1
4 7
Name: B, dtype: int32
Чтобы получить все строки, где "A"> = "B" в df1
, мы будем использовать eval
следующим образом:
m = df1.eval("A >= B")
m
0 True
1 False
2 False
3 True
4 True
dtype: bool
m
представляет промежуточный результат, полученный путем вычисления выражения «A> = B».Затем мы используем маску для фильтрации df1
:
df1[m]
# df1.loc[m]
A B C D
0 5 0 3 3
3 8 8 1 6
4 7 7 8 1
Однако при query
промежуточный результат «m» напрямую передается в loc
, поэтому при query
вы простонужно сделать
df1.query("A >= B")
A B C D
0 5 0 3 3
3 8 8 1 6
4 7 7 8 1
Производительность мудрая, она точно такая же.
df1_big = pd.concat([df1] * 100000, ignore_index=True)
%timeit df1_big[df1_big.eval("A >= B")]
%timeit df1_big.query("A >= B")
14.7 ms ± 33.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
14.7 ms ± 24.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Но последний более лаконичен и выражает ту же операцию за один шаг.
Обратите внимание, что вы также можете делать странные вещи с query
, как это (скажем, возвращать все строки, проиндексированные df1.index)
df1.query("index")
# Same as df1.loc[df1.index] # Pointless,... I know
A B C D
0 5 0 3 3
1 7 9 3 5
2 2 4 7 6
3 8 8 1 6
4 7 7 8 1
Но не надо.
Итог: используйте query
при запросе или фильтрации строк на основе условного выражения.