У меня есть сценарий, в котором пользователь хочет применить несколько фильтров к объекту Pandas DataFrame или Series. По сути, я хочу эффективно объединить в цепочку несколько операций фильтрации (операций сравнения), которые задаются пользователем во время выполнения.
Фильтры должны быть аддитивными (то есть каждый применяемый фильтр должен сужать результаты).
В настоящее время я использую, reindex()
но при этом каждый раз создается новый объект и копируются базовые данные (если я правильно понимаю документацию). Таким образом, это может быть действительно неэффективным при фильтрации больших серий или DataFrame.
Я думаю, что лучше использовать apply()
, map()
или что-то подобное. Я новичок в Pandas, поэтому все еще пытаюсь осмыслить все.
TL; DR
Я хочу взять словарь следующей формы и применить каждую операцию к данному объекту Series и вернуть «отфильтрованный» объект Series.
relops = {'>=': [1], '<=': [1]}
Длинный пример
Я начну с примера того, что у меня есть на данный момент, и просто с фильтрации одного объекта Series. Ниже представлена функция, которую я сейчас использую:
def apply_relops(series, relops):
"""
Pass dictionary of relational operators to perform on given series object
"""
for op, vals in relops.iteritems():
op_func = ops[op]
for val in vals:
filtered = op_func(series, val)
series = series.reindex(series[filtered])
return series
Пользователь предоставляет словарь с операциями, которые он хочет выполнить:
>>> df = pandas.DataFrame({'col1': [0, 1, 2], 'col2': [10, 11, 12]})
>>> print df
>>> print df
col1 col2
0 0 10
1 1 11
2 2 12
>>> from operator import le, ge
>>> ops ={'>=': ge, '<=': le}
>>> apply_relops(df['col1'], {'>=': [1]})
col1
1 1
2 2
Name: col1
>>> apply_relops(df['col1'], relops = {'>=': [1], '<=': [1]})
col1
1 1
Name: col1
Опять же, «проблема» с моим вышеупомянутым подходом заключается в том, что я думаю, что существует много, возможно, ненужного копирования данных для промежуточных шагов.
Кроме того, я хотел бы расширить это, чтобы переданный словарь мог включать столбцы для оператора и фильтровать весь DataFrame на основе входного словаря. Однако я предполагаю, что все, что работает для серии, можно легко расширить до DataFrame.
df.query
иpd.eval
кажутся подходящими для вашего варианта использования. Для получения информации оpd.eval()
семействе функций, их функциях и вариантах использования, пожалуйста, посетите Dynamic Expression Evaluation в pandas с помощью pd.eval () .Ответы:
Pandas (и numpy) допускают логическое индексирование , которое будет намного эффективнее:
In [11]: df.loc[df['col1'] >= 1, 'col1'] Out[11]: 1 1 2 2 Name: col1 In [12]: df[df['col1'] >= 1] Out[12]: col1 col2 1 1 11 2 2 12 In [13]: df[(df['col1'] >= 1) & (df['col1'] <=1 )] Out[13]: col1 col2 1 1 11
Если вы хотите написать для этого вспомогательные функции, рассмотрите что-нибудь в этом роде:
In [14]: def b(x, col, op, n): return op(x[col],n) In [15]: def f(x, *b): return x[(np.logical_and(*b))] In [16]: b1 = b(df, 'col1', ge, 1) In [17]: b2 = b(df, 'col1', le, 1) In [18]: f(df, b1, b2) Out[18]: col1 col2 1 1 11
Обновление: pandas 0.13 имеет метод запроса для таких случаев использования, при условии, что имена столбцов являются действительными идентификаторами, следующие работы (и могут быть более эффективными для больших фреймов, поскольку он использует numexpr за кулисами):
In [21]: df.query('col1 <= 1 & 1 <= col1') Out[21]: col1 col2 1 1 11
источник
df[(ge(df['col1'], 1) & le(df['col1'], 1)]
. Проблема для меня в том, что словарь с фильтрами может содержать множество операторов, и объединение их в цепочку является громоздким. Может быть, я мог бы добавить каждый промежуточный логический массив в большой массив, а затем просто использоватьmap
для примененияand
к ним оператора?f()
нужно брать,*b
а не простоb
? Это так, что пользовательf()
может по-прежнему использовать необязательныйout
параметрlogical_and()
? Это приводит к еще одному небольшому побочному вопросу. В чем преимущество / компромисс в производительности при передаче массива через поout()
сравнению с использованием массива, возвращенного изlogical_and()
? Еще раз спасибо!*b
Необходимо потому , что вы передаете два массиваb1
и ,b2
и вы должны распаковать их при вызовеlogical_and
. Однако остаётся другой вопрос. Есть ли преимущество в производительности при передаче массива черезout
параметр вlogical_and()
vs, просто используя его возвращаемое значение?Сочетание условий создает длинные линии, которые не приветствуются pep8. Использование метода .query заставляет использовать строки, что является мощным, но непифоничным и не очень динамичным.
Когда каждый из фильтров установлен, один подход
import numpy as np import functools def conjunction(*conditions): return functools.reduce(np.logical_and, conditions) c_1 = data.col1 == True c_2 = data.col2 < 64 c_3 = data.col3 != 4 data_filtered = data[conjunction(c1,c2,c3)]
np.logical работает быстро, но не принимает более двух аргументов, которые обрабатываются functools.reduce.
Обратите внимание, что это все еще имеет некоторые избыточности: a) сокращение не происходит на глобальном уровне b) каждое из отдельных условий выполняется для всех исходных данных. Тем не менее, я ожидаю, что это будет достаточно эффективно для многих приложений и будет очень читабельным.
Вы также можете создать дизъюнкцию (в которой должно выполняться только одно из условий), используя
np.logical_or
вместо этого:import numpy as np import functools def disjunction(*conditions): return functools.reduce(np.logical_or, conditions) c_1 = data.col1 == True c_2 = data.col2 < 64 c_3 = data.col3 != 4 data_filtered = data[disjunction(c1,c2,c3)]
источник
c_1
,c_2
,c_3
, ...c_n
в списке, а затем передать ,data[conjunction(conditions_list)]
но получаю сообщение об ошибкеValueError: Item wrong length 5 instead of 37.
также пытался ,data[conjunction(*conditions_list)]
но я получить другой результат , чемdata[conjunction(c_1, c_2, c_3, ... c_n )]
, не уверен , что происходит.data[conjunction(*conditions_list)]
действительно работает после упаковки фреймов данных в список и распаковки списка на местеdf[f_2 & f_3 & f_4 & f_5 ]
withf_2 = df["a"] >= 0
и т. д. Нет необходимости в этой функции ... (хотя хорошее использование функции высшего порядка ...)Самое простое из всех решений:
Использование:
filtered_df = df[(df['col1'] >= 1) & (df['col1'] <= 5)]
Другой пример. Чтобы отфильтровать фрейм данных для значений, принадлежащих февралю 2018 г., используйте приведенный ниже код.
filtered_df = df[(df['year'] == 2018) & (df['month'] == 2)]
источник
Начиная с обновления pandas 0.22 , доступны следующие параметры сравнения:
и многое другое. Эти функции возвращают логический массив. Посмотрим, как их можно использовать:
# sample data df = pd.DataFrame({'col1': [0, 1, 2,3,4,5], 'col2': [10, 11, 12,13,14,15]}) # get values from col1 greater than or equals to 1 df.loc[df['col1'].ge(1),'col1'] 1 1 2 2 3 3 4 4 5 5 # where co11 values is better 0 and 2 df.loc[df['col1'].between(0,2)] col1 col2 0 0 10 1 1 11 2 2 12 # where col1 > 1 df.loc[df['col1'].gt(1)] col1 col2 2 2 12 3 3 13 4 4 14 5 5 15
источник
Почему бы этого не сделать?
def filt_spec(df, col, val, op): import operator ops = {'eq': operator.eq, 'neq': operator.ne, 'gt': operator.gt, 'ge': operator.ge, 'lt': operator.lt, 'le': operator.le} return df[ops[op](df[col], val)] pandas.DataFrame.filt_spec = filt_spec
Демо:
df = pd.DataFrame({'a': [1,2,3,4,5], 'b':[5,4,3,2,1]}) df.filt_spec('a', 2, 'ge')
Результат:
a b 1 2 4 2 3 3 3 4 2 4 5 1
Вы можете видеть, что столбец «a» был отфильтрован, где a> = 2.
Это немного быстрее (время набора, а не производительность), чем связывание операторов. Конечно, вы можете поместить импорт в начало файла.
источник
e также может выбирать строки на основе значений столбца, которых нет в списке или какой-либо итерации. Мы создадим логическую переменную, как и раньше, но теперь мы инвертируем логическую переменную, поместив ~ впереди.
Например
list = [1, 0] df[df.col1.isin(list)]
источник
Если вы хотите проверить любое / все несколько столбцов на предмет значения, вы можете сделать:
df[(df[['HomeTeam', 'AwayTeam']] == 'Fulham').any(axis=1)]
источник