Подтвердить что ты не робот

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
4b9b3361

Ответ 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