У меня есть очень огромный 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?), но я не уверен, как это использовать, так как я никогда не работал с этим. Помощь будет высоко оценена.
Для этого конкретного случая использования (группа с количеством > 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
Решение от @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
в моем примере), дублирование не удалит их.
Спасибо! Ваш ответ отлично работает! 😊