У меня есть следующее 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 Ожидаемый результат указан в последних двух блоках кода.
Можно было бы сгруппировать по «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
, спасибо :-)
Может быть, яснее? unique(dt[, .SD[ (uniqueN(V2) > 1), ], by = "V1"])
@r2evans Это чисто, но я думаю, что .I
будет быстрее, чем в моих предыдущих экспериментах с тестами.
Полезно знать, спасибо. Мой опыт работы с data.table
еще не дает интуитивного представления о сравнительной производительности. Улучшение на ~ 10% (медиана) — это неплохо, интересно, как оно масштабируется до «реальных» (не маленьких) наборов данных.
@ r2evans В этот вопрос есть тест относительно большой таблицы. Другой вариант (не то чтобы нужно больше) unique(dt[, if (uniqueN(V2) > 1) .SD, by = "V1"])
Это работает как шарм, конечно. Однако мне трудно понять, как это работает... Как работает .I[uniqueN(V2) > 1]
? uniqueN(V2) > 1
возвращает TRUE
(поскольку существует более одного уникального значения), но при заключении в .I[]
(также известном как seq_along
) оно становится 1, 2, ... nrows
. Это другой оператор >
, о котором я не знаю?
@Wasabi Что он делает, так это uniqueN(V2) > 1
получает логический вектор длины 1 для каждой группы «V1». Путем переноса на .I
он дает индекс строки всех строк для этого V1. извлеките этот столбец индекса строки $i1
и используйте его в i
для подстановки строк и переноса с помощью unique
Также одна 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))]
.
Что я хочу знать: со всем этим кодовым гольфом base-R вы сохраняете один пробел посередине? ;-)
Им нужно сохранить лишние пробелы для всего этого обязательного отступа? (Полное раскрытие: я тоже использую python, просто не так хорошо/вокально.)
@WeNYoBen, меня бы очень впечатлила версия этого панды :-P
@r2evans держите пространство - это хорошее поведение при кодировании, которое я должен улучшить :-), df[df.groupby('V1').V2.transform('nunique').gt(1)].drop_duplicates()
Использование оператора 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
Каков ваш ожидаемый результат