В моем фрейме данных каждая строка представляет набор кодов с горячим кодированием, поэтому в фрейме данных содержится большое количество логических столбцов.

Я хочу выбрать все строки, которые содержат подмножество кодов, то есть все строки, в которых значения для данного набора столбцов равны True.
Пример набора может быть:
code_selection = {"H045027", "S100031", "G121001", "S456005", "M743110"}
Моя первая попытка опирается на DataFrame.query
и создает строку запроса из заданного набора:
def filter_codeset_1(codesets_onehot, code_selection):
"""Return only code sets that contain all of the codes in the code selection"""
query_string = " & ".join(code_selection)
return codesets_onehot.query(query_string)
Это работает для небольших наборов, но занимает довольно много времени (время стены: 31,8 с).Для больших наборов происходит сбой с ошибкой памяти:
MemoryError Traceback (most recent call last)
<ipython-input-86-8fb45d40b678> in <module>
----> 1 filtered = filter_codeset(codesets_onehot, code_selection)
<ipython-input-71-ca3fccfa21ba> in filter_codeset(codesets_onehot, code_selection)
2 """Return only code sets that contain all of the codes in the code selection"""
3 query_string = " & ".join(code_selection)
----> 4 return codesets_onehot.query(query_string)
~/anaconda3/lib/python3.7/site-packages/pandas/core/frame.py in query(self, expr, inplace, **kwargs)
2845 kwargs['level'] = kwargs.pop('level', 0) + 1
2846 kwargs['target'] = None
-> 2847 res = self.eval(expr, **kwargs)
2848
2849 try:
~/anaconda3/lib/python3.7/site-packages/pandas/core/frame.py in eval(self, expr, inplace, **kwargs)
2960 kwargs['target'] = self
2961 kwargs['resolvers'] = kwargs.get('resolvers', ()) + tuple(resolvers)
-> 2962 return _eval(expr, inplace=inplace, **kwargs)
2963
2964 def select_dtypes(self, include=None, exclude=None):
~/anaconda3/lib/python3.7/site-packages/pandas/core/computation/eval.py in eval(expr, parser, engine, truediv, local_dict, global_dict, resolvers, level, target, inplace)
294 eng = _engines[engine]
295 eng_inst = eng(parsed_expr)
--> 296 ret = eng_inst.evaluate()
297
298 if parsed_expr.assigner is None:
~/anaconda3/lib/python3.7/site-packages/pandas/core/computation/engines.py in evaluate(self)
74
75 # make sure no names in resolvers and locals/globals clash
---> 76 res = self._evaluate()
77 return _reconstruct_object(self.result_type, res, self.aligned_axes,
78 self.expr.terms.return_type)
~/anaconda3/lib/python3.7/site-packages/pandas/core/computation/engines.py in _evaluate(self)
121 truediv = scope['truediv']
122 _check_ne_builtin_clash(self.expr)
--> 123 return ne.evaluate(s, local_dict=scope, truediv=truediv)
124 except KeyError as e:
125 # python 3 compat kludge
~/anaconda3/lib/python3.7/site-packages/numexpr/necompiler.py in evaluate(ex, local_dict, global_dict, out, order, casting, **kwargs)
814 expr_key = (ex, tuple(sorted(context.items())))
815 if expr_key not in _names_cache:
--> 816 _names_cache[expr_key] = getExprNames(ex, context)
817 names, ex_uses_vml = _names_cache[expr_key]
818 arguments = getArguments(names, local_dict, global_dict)
~/anaconda3/lib/python3.7/site-packages/numexpr/necompiler.py in getExprNames(text, context)
705
706 def getExprNames(text, context):
--> 707 ex = stringToExpression(text, {}, context)
708 ast = expressionToAST(ex)
709 input_order = getInputOrder(ast, None)
~/anaconda3/lib/python3.7/site-packages/numexpr/necompiler.py in stringToExpression(s, types, context)
282 else:
283 flags = 0
--> 284 c = compile(s, '<expr>', 'eval', flags)
285 # make VariableNode's for the names
286 names = {}
MemoryError:
Какие у меня есть варианты для более масштабируемой реализации (запрос сотен тысяч строк с наборами сотен кодов за не более нескольких секунд)?Это должно быть возможно сделать очень эффективно, так как в основном для каждой строки необходимо выбрать фиксированный набор логических значений и связать их с and
.
Вот альтернативные реализации, в том числе предложенные в ответах:
def filter_codeset_2(codesets_onehot, code_selection):
column_mask = codesets_onehot.columns.isin(code_selection)
return codesets_onehot[codesets_onehot.apply(lambda row: row[column_mask].all(), axis=1)]
Кажется, что работает, но занимает еще больше времени: Время на стену: 1мин 22 с
def filter_codesets_3(codesets_onehot, code_selection):
codesets_onehot = codesets_onehot.reset_index(drop=True)
return codesets_onehot.loc[[set(codesets_onehot.columns[i]) == code_selection for i in codesets_onehot.values],:]
Требуется больше времени для получения пустого результата: Время на стену: 1 мин 5 с
def filter_codesets_4(codesets_onehot, code_selection):
columns_of_interest = list(code_selection)
len_coi = len(columns_of_interest)
return codesets_onehot.loc[codesets_onehot[columns_of_interest].sum(axis=1) == len_coi]
Это работает и примерно так же быстро, как и первая версия: Время на стене: 28,7 с.Преимущество состоит в том, что он может запрашивать большие наборы без ошибок памяти.
def filter_codesets_5(codesets_onehot, code_selection):
return codesets_onehot[codesets_onehot[list(code_selection)].all(1)]
Работает, прост и лаконичен и занимает: Время стены: 30 с.Полагаю, трудно быть ниже этого времени только с пандами.