Удаление или изменение последовательных повторяющихся строк

#python #pandas #duplicates

#python #панды #дубликаты

Вопрос:

Предположим, у нас есть фрейм данных с двумя типами данных: float и ndarray (форма всегда (2,) ):

 data = [
    0.1, np.array([1.0, 0.1]), np.array([1.0, 0.1]),
    np.array([1.0, 0.1]), 0.1, 0.1, np.array([0.1, 1.0]), 1.0
]
df = pd.DataFrame(
    {'A': data,}, index=[0., 1., 2.0, 2.6, 3., 3.2, 3.4, 4.0]
)
 
 x   | A
----|-------------
0.0 | 1.0
1.0 | [1.0, 0.1]
2.0 | [1.0, 0.1]
2.6 | [1.0, 0.1]
3.0 | 0.1
3.2 | 0.1
3.4 | [0.1, 1.0]
4.0 | 1.0
 

Я хотел бы обработать последовательные дубликаты, чтобы:

  1. Отбрасывайте повторения, если они равны float s (сохраняя первое в «группе»);
  2. Изменение каждого элемента в «группе» с использованием всех значений индекса этой группы, если эти элементы являются ndarray s.

Ожидаемый результат для данного примера будет примерно таким (здесь я попытался пропорционально разделить диапазон [1., 0.1] на три области):

 x   | A
----|-------------
0.0 | 1.0
1.0 | [1.0, 0.55]
2.0 | [0.55, 0.28]
2.6 | [0.28, 0.1]
3.0 | 0.1
3.4 | [0.1, 1.0]
4.0 | 1.0
 

Для начала я попытался использовать df != df.shift() для поиска дубликатов, но это вызвало бы ошибку при копировании float ndarry и не «группировало» более 2 элементов.

Я также пытался groupby(by=function) , где функция проверяет dtype элемента, но, похоже groupby , в этом случае это влияет на индекс.

Очевидно, что я могу перебирать строки и отслеживать повторения, но это не очень элегантно (эффективно).

У вас есть какие-либо предложения?

Комментарии:

1. По какому правилу вы меняете [1.0, 0.1] на [1.0, 0.55] (значения для index == 1.0 )? Тот же вопрос для 2 следующих строк.

2. Ну, здесь это не очень важно (для примера подойдет любое правило). Здесь я пытался пропорционально увеличить split диапазон [1.0, 0.1] .

Ответ №1:

Шаг 1: удаление последовательных равных чисел с плавающей запятой

Чтобы проверить, являются ли 2 элемента строки равными числам с плавающей запятой, определите следующую функцию:

 def equalFloats(row):
    if (type(row.A).__name__ == 'float') and (type(row.B).__name__ == 'float'):
        return row.A == row.B
    return False
 

Затем временно добавьте в df столбец B, содержащий предыдущее значение
в столбце:

 df['B'] = df.A.shift()
 

И чтобы удалить последовательные числа с плавающей запятой в столбце (а также удалить
столбец B), запустите:

 df = df[~df.apply(equalFloats, axis=1)][['A']]
 

В настоящее время df содержит:

               A
0.0         0.1
1.0  [1.0, 0.1]
2.0  [1.0, 0.1]
2.6  [1.0, 0.1]
3.0         0.1
3.4  [0.1, 1.0]
4.0           1
4.5         2.1
 

Чтобы проверить, что последовательные, но разные числа с плавающей запятой не удаляются,
я добавил строку с индексом 4.5 и значением 2.1. Как вы видите, он не был удален.

Шаг 2: преобразование массивов в столбец

Определите другую функцию:

 def step2(row):
    if type(row.A).__name__ == 'ndarray':
        arr = row.A
        arr[1] = arr.sum() / row.name
        return row
    return row
 

(row.name является значением индекса текущей строки).

Затем примените его:

 df = df.apply(step2, axis=1)
 

Результат:

                               A
0.0                         0.1
1.0                  [1.0, 2.1]
2.0                [1.0, 0.775]
2.6   [1.0, 0.5473372781065089]
3.0                         0.1
3.4  [0.1, 0.12456747404844293]
4.0                           1
4.5                         2.1
 

Если вы хотите, измените формулу на шаге 2 на любую другую по вашему выбору.

Отредактируйте следующие комментарии

Я определил df как:

               A
0.0         0.1
1.0  [1.0, 0.1]
2.0  [1.0, 0.1]
2.6  [1.0, 0.1]
3.0         0.1
3.1         0.1
3.2         0.1
3.4  [0.1, 1.0]
4.0           1
4.5         2.1
 

Он содержит 3 последовательных значения 0.1.
Обратите внимание, что вы не написали о том, сколько последовательных
значений содержит такая «группа».

Обе функции могут быть определены также с помощью isinstance:

 def equalFloats(row):
    if isinstance(row.A, float) and isinstance(row.B, float):
        return row.A == row.B
    return False

def step2(row):
    if isinstance(row.A, np.ndarray):
        arr = row.A
        arr[1] = arr.sum() / row.name
        return row
    return row
 

Затем после запуска:

 df['B'] = df.A.shift()
df = df[~df.apply(equalFloats, axis=1)][['A']]
df = df.apply(step2, axis=1)
 

Результат:

                              A
0.0                        0.1
1.0                 [1.0, 1.1]
2.0                [1.0, 0.55]
2.6  [1.0, 0.4230769230769231]
3.0                        0.1
3.4  [0.1, 0.3235294117647059]
4.0                          1
4.5                        2.1
 

Как вы можете видеть, из последовательности из 3 значений 0.1 осталось
только первое.

Комментарии:

1. Спасибо за ответ. Мне нравится идея рассматривать разные dtype s отдельно. Однако в вашем ответе отсутствует важный момент на шаге 2: совместная обработка последовательных дубликатов, поскольку значения должны зависеть от: 1) количества повторений; 2) значений индекса (или, точнее, диапазона значений индекса).

2. Кроме того, почему вы выбрали .__name__ == 'float' над isinstance(x, float) ?