Pandas: удалить последовательные дубликаты
Какой самый эффективный способ удалить только последовательные дубликаты в pandas?
drop_duplicates дает следующее:
In [3]: a = pandas.Series([1,2,2,3,2], index=[1,2,3,4,5])
In [4]: a.drop_duplicates()
Out[4]:
1 1
2 2
4 3
dtype: int64
Но я хочу это:
In [4]: a.something()
Out[4]:
1 1
2 2
4 3
5 2
dtype: int64
Ответы
Ответ 1
Используйте shift
:
a.loc[a.shift(-1) != a]
Out[3]:
1 1
3 2
4 3
5 2
dtype: int64
Таким образом, в приведенном выше примере используются логические критерии, мы сравниваем блок данных с рамкой данных, сдвинутой на -1 строк, чтобы создать маску
Другим методом является использование diff
:
In [82]:
a.loc[a.diff() != 0]
Out[82]:
1 1
2 2
4 3
5 2
dtype: int64
Но это медленнее исходного метода, если у вас большое количество строк.
Обновление
Благодаря Bjarke Ebert для указания тонкой ошибки я должен использовать shift(1)
или просто shift()
, поскольку по умолчанию это период 1, это возвращает первое последовательное значение:
In [87]:
a.loc[a.shift() != a]
Out[87]:
1 1
2 2
4 3
5 2
dtype: int64
Обратите внимание на разницу в значениях индекса, спасибо @BjarkeEbert!
Ответ 2
Вот обновление, которое позволит работать с несколькими столбцами. Используйте ".any(axis = 1)", чтобы объединить результаты из каждого столбца:
cols = ["col1","col2","col3"]
de_dup = a[cols].loc[(a[cols].shift() != a[cols]).any(axis=1)]
Ответ 3
Поскольку мы идем most efficient way
, то есть производительностью, давайте использовать данные массива для использования NumPy. Мы будем срезать одноразовые срезы и сравниваем, подобно методу сдвига, обсуждаемому ранее в @EdChum post
. Но с нарезкой NumPy мы получим один-единственный массив, поэтому нам нужно объединить элемент True
в начале, чтобы выбрать первый элемент, и, следовательно, у нас будет такая реализация,
def drop_consecutive_duplicates(a):
ar = a.values
return a[np.concatenate(([True],ar[:-1]!= ar[1:]))]
Пример прогона -
In [149]: a
Out[149]:
1 1
2 2
3 2
4 3
5 2
dtype: int64
In [150]: drop_consecutive_duplicates(a)
Out[150]:
1 1
2 2
4 3
5 2
dtype: int64
Сроки на больших массивах, сравнивающие @EdChum solution
-
In [142]: a = pd.Series(np.random.randint(1,5,(1000000)))
In [143]: %timeit a.loc[a.shift() != a]
100 loops, best of 3: 12.1 ms per loop
In [144]: %timeit drop_consecutive_duplicates(a)
100 loops, best of 3: 11 ms per loop
In [145]: a = pd.Series(np.random.randint(1,5,(10000000)))
In [146]: %timeit a.loc[a.shift() != a]
10 loops, best of 3: 136 ms per loop
In [147]: %timeit drop_consecutive_duplicates(a)
10 loops, best of 3: 114 ms per loop
Итак, некоторые улучшения!
Получите мощный импульс только для ценностей!
Если нужны только значения, мы могли бы получить мощный импульс, просто индексируя данные массива, например,
def drop_consecutive_duplicates(a):
ar = a.values
return ar[np.concatenate(([True],ar[:-1]!= ar[1:]))]
Пример прогона -
In [170]: a = pandas.Series([1,2,2,3,2], index=[1,2,3,4,5])
In [171]: drop_consecutive_duplicates(a)
Out[171]: array([1, 2, 3, 2])
Сроки -
In [173]: a = pd.Series(np.random.randint(1,5,(10000000)))
In [174]: %timeit a.loc[a.shift() != a]
10 loops, best of 3: 137 ms per loop
In [175]: %timeit drop_consecutive_duplicates(a)
10 loops, best of 3: 61.3 ms per loop