Удаление строк массива на основе определенных совпадений между элементами

#arrays #numpy

#массивы #numpy

Вопрос:

Рассмотрим небольшой образец массива целых чисел из 6 столбцов:

 import numpy as np

J = np.array([[1, 3, 1, 3, 2, 5],
              [2, 6, 3, 4, 2, 6],
              [1, 7, 2, 5, 2, 5],
              [4, 2, 8, 3, 8, 2],
              [0, 3, 0, 3, 0, 3],
              [2, 2, 3, 3, 2, 3],
              [4, 3, 4, 3, 3, 4]) 
  

Я хочу удалить, из J:

а) все строки, в которых первая и вторая ПАРЫ элементов являются точными совпадениями (это удаляет строки типа [1,3, 1,3, 2,5])

б) все строки, в которых вторая и третья ПАРЫ элементов являются точными совпадениями (это удаляет строки, подобные [1,7, 2,5, 2,5])

Совпадения между любыми другими парами разрешены.

У меня есть решение, приведенное ниже, но оно обрабатывается в два этапа. Если есть более прямой, более чистый или более легко расширяемый подход, мне было бы очень интересно.

 K = J[~(np.logical_and(J[:,0] == J[:,2], J[:,1] == J[:,3]))]
L = K[~(np.logical_and(K[:,2] == J[:,4], K[:,3] == K[:,5]))]
  

K удаляет 1-ю, 5-ю и 7-ю строки из J, оставляя

         K =  [[2, 6, 3, 4, 2, 6],
              [1, 7, 2, 5, 2, 5],
              [4, 2, 8, 3, 8, 2],
              [2, 2, 3, 3, 2, 3]])
  

L удаляет 2-ю строку из K, давая окончательный результат.

         L =  [[2, 6, 3, 4, 2, 6],
              [4, 2, 8, 3, 8, 2],
              [2, 2, 3, 3, 2, 3]])
  

Я надеюсь на эффективное решение, потому что, изучая эту проблему, мне нужно распространить эти идеи на 8-столбчатые массивы, где
Я удаляю строки, имеющие точные совпадения между 1-й и 2-й ПАРАМИ, 2-й и 3-й ПАРАМИ и 3-й и 4-й ПАРАМИ.

Ответ №1:

Поскольку мы проверяем соседние пары на равенство, различие в 3D измененных данных, по-видимому, было бы одним из способов сделать это для более чистого векторизованного —

 # a is input array
In [117]: b = a.reshape(a.shape[0],-1,2)

In [118]: a[~(np.diff(b,axis=1)==0).all(2).any(1)]
Out[118]: 
array([[2, 6, 3, 4, 2, 6],
       [4, 2, 8, 3, 8, 2],
       [2, 2, 3, 3, 2, 3]])
  

Если вы хотите повысить производительность, пропустите различие и перейдите к разделенному равенству —

 In [142]: a[~(b[:,:-1] == b[:,1:]).all(2).any(1)]
Out[142]: 
array([[2, 6, 3, 4, 2, 6],
       [4, 2, 8, 3, 8, 2],
       [2, 2, 3, 3, 2, 3]])
  

Общее количество столбцов

Также распространяется на общее количество столбцов —

 In [156]: a
Out[156]: 
array([[1, 3, 1, 3, 2, 5, 1, 3, 1, 3, 2, 5],
       [2, 6, 3, 4, 2, 6, 2, 6, 3, 4, 2, 6],
       [1, 7, 2, 5, 2, 5, 1, 7, 2, 5, 2, 5],
       [4, 2, 8, 3, 8, 2, 4, 2, 8, 3, 8, 2],
       [0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3],
       [2, 2, 3, 3, 2, 3, 2, 2, 3, 3, 2, 3],
       [4, 3, 4, 3, 3, 4, 4, 3, 4, 3, 3, 4]])

In [158]: b = a.reshape(a.shape[0],-1,2)

In [159]: a[~(b[:,:-1] == b[:,1:]).all(2).any(1)]
Out[159]: 
array([[4, 2, 8, 3, 8, 2, 4, 2, 8, 3, 8, 2],
       [2, 2, 3, 3, 2, 3, 2, 2, 3, 3, 2, 3]])
  

Конечно, мы предполагаем, что количество столбцов позволяет выполнять сопряжение.

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

1. Возможно, было бы лучше использовать axis=-1 последовательно вместо axis=2 и axis=1 .

2. @MateenUlhaq Это просто более точно. Из-за более высоких значений dims мы в любом случае добавили бы больше двоеточий .

3. Ваше 2-е решение, в частности, очень эффективно. Как относительный новичок, не могли бы вы просто показать, что это применимо к регистру из 8 столбцов? (то есть: устранение 1-2-х пар совпадений, 2-3-х пар совпадений, 3-4-х пар совпадений)

4. @user109387 Это работает для любого количества столбцов без каких-либо изменений в коде. Или вы хотите, чтобы я показал это в качестве примера выполнения?

5. Добавлен @user109387.

Ответ №2:

То, что у вас есть, вполне разумно. Вот что я бы написал:

 def eliminate_pairs(x: np.ndarray) -> np.ndarray:
    first_second = (x[:, 0] == x[:, 2]) amp; (x[:, 1] == x[:, 3])
    second_third = (x[:, 1] == x[:, 3]) amp; (x[:, 2] == x[:, 4])
    return x[~(first_second | second_third)]
  

Вы также могли бы применить теорему Деморгана и исключить дополнительную not операцию, но это менее важно, чем ясность.

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

1. Если бы это было расширено, скажем, до массива из 8 столбцов, где у нас есть первый-второй, второй-третий и третий-четвертый, как бы вы обработали оператор Return?

2. @user109387 В этот момент я бы переключился на что-то похожее на ответ Дивакара и просто сравнил с помощью срезов после изменения формы (что необходимо для наложения структуры «сопряжения»).

Ответ №3:

Давайте попробуем цикл:

 mask = False
for i in range(0,3,2):
    mask = (J[:,i:i 2]==J[:,i 2:i 4]).all(1) | mask

J[~mask]
  

Вывод:

 array([[2, 6, 3, 4, 2, 6],
       [4, 2, 8, 3, 8, 2],
       [2, 2, 3, 3, 2, 3]])