#python #pandas #dataframe #numpy
Вопрос:
Я читал сообщения SOF о том, как создать поле, содержащее количество дубликатов, которые содержит строка в фрейме данных pandas. Не используя никаких других библиотек, я попытался написать функцию, которая делает это, и она работает с небольшими объектами фреймов данных; однако для больших объектов это занимает слишком много времени и потребляет слишком много памяти.
Это и есть функция:
def count_duplicates(dataframe):
function = lambda x: dataframe.to_numpy().tolist().count(x.to_list()) - 1
return dataframe.apply(function, axis=1)
Я сделал dir
в массив numpy из DataFrame.to_numpy
функции, и я не видел функции, похожей на эту list.count
функцию. Причина, по которой это занимает так много времени, заключается в том, что для каждой строки необходимо сравнить строку со всеми строками в массиве numpy. Я хотел бы гораздо более эффективный способ сделать это, даже если он не использует фрейм данных pandas. Я чувствую, что должен быть простой способ сделать это с помощью numpy
, но я просто недостаточно знаком. Я уже некоторое время тестирую различные подходы, и это приводит к множеству ошибок. Я собираюсь продолжать тестировать различные подходы, но почувствовал, что сообщество может предложить лучший способ.
Спасибо вам за вашу помощь.
Вот пример фрейма данных:
one two
0 1 1
1 2 2
2 3 3
3 1 1
Я бы использовал его вот так:
d['duplicates'] = count_duplicates(d)
Результирующий кадр данных является:
one two duplicates
0 1 1 1
1 2 2 0
2 3 3 0
3 1 1 1
Проблема в том, что фактический кадр данных будет содержать 1,4 миллиона строк, и каждая лямбда занимает в среднем 0,148558 секунды, что при умножении на 1,4 миллиона строк составляет около 207981,459 секунды или 57,772 часа. Мне нужен гораздо более быстрый способ сделать это.
Еще раз спасибо вам.
Я обновил функцию, которая ускоряет процесс:
def _counter(series_to_count, list_of_lists):
return list_of_lists.count(series_to_count.to_list()) - 1
def count_duplicates(dataframe):
df_list = dataframe.to_numpy().tolist()
return dataframe.apply(_counter, args=(df_list,), axis=1)
Это занимает всего 29,487 секунды. Узким местом было преобразование фрейма данных при каждом вызове функции.
Я все еще заинтересован в оптимизации этого. Я бы хотел сократить это до 2-3 секунд, если это вообще возможно. Может быть, это и не так, но я хотел бы убедиться, что это произойдет как можно быстрее.
Еще раз спасибо вам.
Комментарии:
1. не могли бы вы, пожалуйста, предоставить образец фрейма данных и желаемый результат.
2. Я привел пример и некоторые данные о текущем времени выполнения и расчетном времени выполнения с текущим кодом.
3. Я отредактировал функцию, и она оптимизировала ее, но для завершения 1,4 миллиона строк все равно требуется около 29 секунд. Я все еще ищу лучший способ, если вы можете помочь.
Ответ №1:
Вот векторизованный способ сделать это. Для 1,4 миллиона строк, в среднем 140 дубликатов для каждой строки, это занимает менее 0,05 секунды. Когда дубликатов вообще нет, это занимает около 0,4 секунды.
d['duplicates'] = d.groupby(['one', 'two'], sort=False)['one'].transform('size') - 1
На вашем примере:
>>> d
one two duplicates
0 1 1 1
1 2 2 0
2 3 3 0
3 1 1 1
Скорость
Относительно высокая частота дубликатов:
n = 1_400_000
d = pd.DataFrame(np.random.randint(0, 100, size=(n, 2)), columns='one two'.split())
%timeit d.groupby(['one', 'two'], sort=False)['one'].transform('size') - 1
# 48.3 ms ± 110 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
# how many duplicates on average?
>>> (d.groupby(['one', 'two'], sort=False)['one'].transform('size') - 1).mean()
139.995841
# (as expected: n / 100**2)
Никаких дубликатов
n = 1_400_000
d = pd.DataFrame(np.arange(2 * n).reshape(-1, 2), columns='one two'.split())
%timeit d.groupby(['one', 'two'], sort=False)['one'].transform('size') - 1
# 389 ms ± 1.55 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)