Самая быстрая альтернатива pd.groupby.filter при фильтрации по размеру группы

У меня есть очень огромный DataFrame с миллионами строк и около 20-30 столбцов с различными типами данных, например. строка, число, даты и т. д.

df

index           t1      num1    float1  ...          str2  
0       2014-10-21      3456     0.000  ...  ayzkcxtoScUy             
1       2014-10-21      2453     0.000  ...  jZygJWtxyVnS             
...            ...       ...       ...  ...           ...           
n-1     2020-11-06    708735   670.818  ...  UWVhmKCfmzVj             
n       2020-11-06     70630   670.817  ...  EvhreYZotqVS             

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

ПЕРВЫЙ НАИВНЫЙ ПОДХОД

Я искал и пытался использовать этот ответ: Как выбрать строки в фрейме данных Pandas, где значение появляется более одного раза

lst = ["t1", "str1", "num1", "str2", "num2"]
df = df.groupby(lst).filter(lambda x: len(x.index) > 1).reset_index(drop=True)

который действительно работает так, как ожидалось. Мой DataFrame df теперь отфильтрован от всех строк, которые встречаются в группах, имеющих размер 1. Проблема заключается во времени по отношению к размерам моего DataFrame с использованием метода фильтрации, который занимает слишком много времени. Чтобы представить это в перспективе, группировка по этим демонстрационным столбцам даст около 165 000 групп с 2,5 миллионами строк DataFrame, около трети этих групп имеют размер 1. Мне пришлось прервать выполнение этого скрипта, потому что это заняло бы возраст. Далее я попытался вдохновиться этой ссылкой Как повысить производительность работы фильтра pandas GroupBy?, но не смог заставить его работать с map, потому что я группирую в DataFrame, а не в Series. При использовании метода transform производительность ухудшилась.

ПРИМЕЧАНИЕ

При дальнейшем расследовании я обнаружил, что возникла проблема при использовании filter в DataFrame, который имеет столбцы datetime64[ns, UTC] и/или datetime64[ns]. Я использовал Del df[x] для удаления всех этих трех столбцов, что увеличило производительность метода фильтрации примерно на треть. Все еще недостаточно, но достаточно важно, чтобы упомянуть об этом здесь, особенно когда мне нужны эти столбцы и я не могу их просто удалить.

ВТОРОЙ «УМНЫЙ» ПОДХОД

Затем я попытался использовать умную индексацию своих данных, чтобы вообще обойти использование группировки, фильтрации или преобразования, используя .value_counts() из ссылки 1.

vc = df[lst].value_counts()
vc_index = vc.index[vc.gt(1)]
df = data[data[lst].isin(vc_index)]

Я получаю количество значений vc, чтобы найти все индексы со значением 1, а затем создать MultiIndex new_index только с нужными индексами (которые являются count > 1). После этого я попытался отфильтровать свой df с помощью .isin(), как в ссылке 1, которая установила все значения df в NaN/NaT. Я застрял здесь - я не уверен, что я сделал не так.

df

index      t1      num1    float1  ...    str2  
0         NaT       NaN       NaN  ...     NaN             
1         NaT       NaN       NaN  ...     NaN             
...       ...       ...       ...  ...     ...           
n-1       NaT       NaN       NaN  ...     NaN             
n         NaT       NaN       NaN  ...     NaN             

В другой попытке я попытался использовать метод pd.index.difference()

vc = data[lst].value_counts()
df = data.set_index(keys=lst)
df.index = df.index.difference(other=vc.index[vc.gt(1)])

но это только дало мне TypeError: '<' not supported between instances of 'float' and 'str'.

Честно говоря, я не знаю, что здесь лучше, сижу над этой проблемой уже два дня. Я даже думал о распараллеливании (может быть, с Dask?), но я не уверен, как это использовать, так как я никогда не работал с этим. Помощь будет высоко оценена.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
0
816
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Для этого конкретного случая использования (группа с количеством > 1) duplicated работает намного быстрее:

df[df.duplicated(lst, keep=False)]
# 231 ms ± 10.2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Другой вариант, не такой быстрый, но значительно быстрее, чем filter, и в целом работает: groupby().transform('size'):

df[df.groupby(lst)['t1'].transform('size')>1]
# 554 ms ± 108 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

По сравнению с:

df.groupby(lst).filter(lambda x: len(x) > 1)
# CPU times: user 38.8 s, sys: 482 ms, total: 39.3 s
# Wall time: 39.4 s

Спасибо! Ваш ответ отлично работает! 😊

CptSnuggles 22.12.2020 15:07

Решение от @Quang Hoang работает очень хорошо. Некоторые тесты, которые я сделал с моим набором данных:

(rs = строки df, ngrps = df.groupby(lst).ngroups)

method   100k rs/82.488 ngrps  200k rs/164.466 ngrps  400k rs/331.351 ngrps  800k rs/672.905 ngrps  1.600k rs/1.351.525 ngrps

duplicated     0:00:00.031236         0:00:00.078112         0:00:00.181825         0:00:00.331095             0:00:00.683959
transform      0:00:00.062507         0:00:00.109386         0:00:00.261506         0:00:00.528166             0:00:01.029606
filter         0:00:09.039214         0:00:18.422355         0:00:37.372117         0:01:15.531945             0:02:32.075144

Он довольно хорошо масштабируется с использованием дублирования, но имейте в виду: если в столбцах, которые находятся внутри вашего списка, есть значения NaN (по которым вы хотите сгруппировать, lst в моем примере), дублирование не удалит их.

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