Удалить *все* повторяющиеся строки, если нет "похожей" строки

У меня есть следующее data.table:

library(data.table)
dt = data.table(c(1, 1, 1, 2, 2, 2, 2, 3, 4),
                c(4, 4, 4, 5, 5, 6, 7, 4, 5))
   V1 V2
1:  1  4
2:  1  4
3:  1  4
4:  2  5
5:  2  5
6:  2  6
7:  2  7
8:  3  4
9:  4  5

Я хочу изучить различные значения V2 для данного V1. Однако, если все значения V2 для данного V1 одинаковы, это меня не интересует, поэтому я хочу удалить такие строки.

Глядя на приведенный выше пример, первые три строки совершенно идентичны (V1=1, V2=4), поэтому я хочу их удалить.

Однако следующие четыре строки включают две одинаковые строки и другие с другим V2. В этом случае я хочу показать три возможных значения V2 с учетом V1 = 2: (2, 5), (2, 6) и (2, 7).

Последние две строки имеют уникальные V1: они подпадают под категорию «все строки совершенно идентичны», поэтому их также следует удалить.

Лучшее, что я мог придумать, показано в этот ответ:

dt[!duplicated(dt) & !duplicated(dt, fromLast = TRUE), ]
   V1 V2
1:  2  6
2:  2  7
3:  3  4
4:  4  5

Что, очевидно, неудовлетворительно: он удаляет пару (2,5), поскольку она дублируется, и сохраняет пары (3,4) и (4,5), поскольку они уникальны и, следовательно, не отмечены ни одним проходом duplicated().

Другим вариантом будет просто вызов

unique(dt)
   V1 V2
1:  1  4
2:  2  5
3:  2  6
4:  2  7
5:  3  4
6:  4  5

Но он сохраняет пары (1,4), (3,4), (4,5), которые я хочу удалить.

В конце концов, результат, который я ищу:

   V1 V2
1:  2  5
2:  2  6
3:  2  7

Хотя допустим и любой другой формат, например:

   V1 V2.1 V2.2 V2.3
1:  2    5    6    7

(который показывает возможные значения V2 для каждого «интересного» V1)

Я не могу понять, как отличить случай (1,4) (все строки одинаковые) от случая (2,5) (есть дубликаты, но есть и другие строки с тем же V1, поэтому мы должны удалить дубликат (2,5), но оставить одну копию ).

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

Каков ваш ожидаемый результат

akrun 23.05.2019 23:14

@akrun Ожидаемый результат указан в последних двух блоках кода.

Wasabi 23.05.2019 23:15
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
2
115
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Можно было бы сгруппировать по «V1», получить индекс группы, длина уникальных элементов которой больше 1, а затем взять unique

unique(dt[dt[, .(i1 = .I[uniqueN(V2) > 1]), V1]$i1])
#   V1 V2
#1:  2  5
#2:  2  6
#3:  2  7

Или, как упомянул @r2evans

unique(dt[, .SD[(uniqueN(V2) > 1)], by = "V1"])

ПРИМЕЧАНИЕ. Набор данных OP — это data.table, а методы data.table — это естественный способ сделать это.


Если нам нужна опция tidyverse, сравнимая с указанной выше опцией data.table

library(dplyr)
dt %>%
   group_by(V1) %>% 
   filter(n_distinct(V2) > 1) %>% 
   distinct()

Я как раз собирался опубликовать unique(dt[,.SD[ (length(unique(V2)) > 1), ], by = "V1" ]) ... и только сейчас узнал о uniqueN, спасибо :-)

r2evans 23.05.2019 23:20

Может быть, яснее? unique(dt[, .SD[ (uniqueN(V2) > 1), ], by = "V1"])

r2evans 23.05.2019 23:21

@r2evans Это чисто, но я думаю, что .I будет быстрее, чем в моих предыдущих экспериментах с тестами.

akrun 23.05.2019 23:22

Полезно знать, спасибо. Мой опыт работы с data.table еще не дает интуитивного представления о сравнительной производительности. Улучшение на ~ 10% (медиана) — это неплохо, интересно, как оно масштабируется до «реальных» (не маленьких) наборов данных.

r2evans 23.05.2019 23:23

@ r2evans В этот вопрос есть тест относительно большой таблицы. Другой вариант (не то чтобы нужно больше) unique(dt[, if (uniqueN(V2) > 1) .SD, by = "V1"])

IceCreamToucan 23.05.2019 23:29

Это работает как шарм, конечно. Однако мне трудно понять, как это работает... Как работает .I[uniqueN(V2) > 1]? uniqueN(V2) > 1 возвращает TRUE (поскольку существует более одного уникального значения), но при заключении в .I[] (также известном как seq_along) оно становится 1, 2, ... nrows. Это другой оператор >, о котором я не знаю?

Wasabi 23.05.2019 23:43

@Wasabi Что он делает, так это uniqueN(V2) > 1 получает логический вектор длины 1 для каждой группы «V1». Путем переноса на .I он дает индекс строки всех строк для этого V1. извлеките этот столбец индекса строки $i1 и используйте его в i для подстановки строк и переноса с помощью unique

akrun 23.05.2019 23:45

Также одна dplyr возможность:

dt %>%
 group_by(V1) %>%
 filter(n_distinct(V2) != 1 & !duplicated(V2))

     V1    V2
  <dbl> <dbl>
1     2     5
2     2     6
3     2     7

Или:

dt %>%
 group_by(V1) %>%
 filter(n_distinct(V2) != 1) %>%
 group_by(V1, V2) %>%
 slice(1)

В вашем случае с базой R

dt[ave(dt$V2,dt$V1,FUN=function(x) length(unique(x)))>1&!duplicated(dt)]
   V1 V2
1:  2  5
2:  2  6
3:  2  7

Небольшое изменение, чтобы сделать его более понятным, может быть dt[with(dt,ave(V2,V1,FUN=function(x) length(unique(x)))>1&!duplicated(dt))].

tmfmnk 23.05.2019 23:35

Что я хочу знать: со всем этим кодовым гольфом base-R вы сохраняете один пробел посередине? ;-)

r2evans 23.05.2019 23:54

Им нужно сохранить лишние пробелы для всего этого обязательного отступа? (Полное раскрытие: я тоже использую python, просто не так хорошо/вокально.)

r2evans 23.05.2019 23:59

@WeNYoBen, меня бы очень впечатлила версия этого панды :-P

r2evans 23.05.2019 23:59

@r2evans держите пространство - это хорошее поведение при кодировании, которое я должен улучшить :-), df[df.groupby('V1').V2.transform('nunique').gt(1)].drop_dupl‌​icates()

BENY 24.05.2019 00:21

Использование оператора if позволяет сделать его более кратким и, возможно, более удобным:

dt[, if (uniqueN(V2) > 1) unique(V2), by = V1]

#    V1 V1
# 1:  2  5
# 2:  2  6
# 3:  2  7

Но не смог правильно назвать имена столбцов...

Немного менее краткие решения:

dt[, .(V2 = if (uniqueN(V2) > 1) unique(V2) else numeric(0)), by = V1]

dt[, .SD[if (uniqueN(V2) > 1) !duplicated(V2)], by = V1]

#    V1 V2
# 1:  2  5
# 2:  2  6
# 3:  2  7

Другие вопросы по теме