идентифицированное различное расположение значений по группам в data.frame

#r #dataframe

#r #фрейм данных

Вопрос:

У меня есть большой фрейм данных, который имеет в качестве основной организации одну строку с группами одинаковой длины (в примере с игрушкой 3).

 df <- data.frame(groups = c("gr1","gr1","gr1","gr2","gr2","gr2","gr3","gr3","gr3"),
               no = c(1, 2, 3, 1, 2, 3, 1, 2, 3),
               colA = c("a", "b", "c", "a", "b", "c", "a", "b", "c"),
               colB = c("a", "b", "c", "X_", "b", "c", "a", "b", "c"),
               colC = c("a", "b", "c", "X_", "b", "c", "c", "b", "a"))

df

> df
>   groups no colA colB colC
> 1    gr1  1    a    a    a
> 2    gr1  2    b    b    b
> 3    gr1  3    c    c    c
> 4    gr2  1    a   X_   X_
> 5    gr2  2    b    b    b
> 6    gr2  3    c    c    c
> 7    gr3  1    a    a    c
> 8    gr3  2    b    b    b
> 9    gr3  3    c    c    a
 

Я хочу определить для каждого столбца, какая группа является первым примером уникального расположения значений. Таким образом, для colA он должен возвращать (T, F, F), поскольку все три группы идентичны, поэтому только первая группа является 1-й уникальной. Для ColB он должен возвращать (T, T, F), поскольку существуют две разные группы, и только 3-я идентична 1-й. И для ColC это должно быть (T, T, T), поскольку порядок элементов имеет значение.

Таким образом, конечным результатом может быть такая матрица

        colA  colB  colC
> gr1     T     T     T
> gr2     F     T     T
> gr3     F     F     T

 

Я думаю, что мог бы разобраться в этом, разбив фрейм данных на пары group и colA / B / B, определить, какие из них идентичны, сохранить результаты в векторе, а затем повторно собрать всю сделку. Но я вижу массу циклов for, и мне трудно думать о том, как это векторизовать. Я немного использую dplyr, но пока не вижу, как это может помочь.

Может быть, есть достойный способ разархивировать каждый из столбцов на основе групп, а затем выполнить сравнение по соответствующим подмножествам новых (и более коротких) столбцов?


Отредактировано для добавления:

Возможно, group_by %>% summary — это способ добраться до этого. Если сводка может по существу объединить все значения в группе для каждого столбца в действительно длинную строку, я мог бы тогда увидеть, какое из них отличается для каждой группы?


Второе редактирование:

Я добрался до:

 d1 <- df %>% group_by(groups) %>% summarise(colB = paste(unique(colB), collapse = ', ')) %>% distinct(colB)
 

который выдает

 > # A tibble: 2 x 1
>   colB    
>   <chr>   
> 1 a, b, c 
> 2 X_, b, c
 

Он идентифицирует отдельные группы, но теперь мне нужно выяснить, как сравнить его с остальным полным столбцом, чтобы получить T / F для каждой группы.

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

1. Отличный улов. Спасибо.

Ответ №1:

Вот базовый подход R. :

 cols <- grep('col', names(df))
cbind(unique(df[1]), sapply(df[cols], function(x) 
      !duplicated(by(x, df$groups, paste0, collapse = '-'))))

#  groups  colA  colB colC
#1    gr1  TRUE  TRUE TRUE
#4    gr2 FALSE  TRUE TRUE
#7    gr3 FALSE FALSE TRUE
 

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

1. Потрясающе, @Ronak. Как всегда удивительно быстро. И еще один отличный опыт обучения. Я начну распаковывать каждую часть вложенного кода, чтобы понять, что на самом деле делает каждый шаг.

Ответ №2:

Ваша идея суммирования точна:

 df %>%
  group_by(groups) %>%
  summarize(across(starts_with("col"), paste, collapse = ""), .groups = "drop") %>%
  mutate(across(starts_with("col"), ~!duplicated(.)))
# # A tibble: 3 x 4
#   groups colA  colB  colC 
#   <chr>  <lgl> <lgl> <lgl>
# 1 gr1    TRUE  TRUE  TRUE 
# 2 gr2    FALSE TRUE  TRUE 
# 3 gr3    FALSE FALSE TRUE 
 

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

1. Это действительно волшебно, как быстро вы, ребята, можете это понять. Мне все равно потребуется некоторое время, чтобы даже понять строку кода. Надеюсь, вы не возражаете, что я ставлю галочку для ответа «базовый R», поскольку я думаю, что было бы предпочтительнее, если другие люди наткнутся на вопрос.

2. Да, не беспокойтесь — какой ответ принять, зависит исключительно от вас, и Ронак, как всегда, превосходен. Если это поможет вам понять код, все три ответа идентичны по методам, просто используя base/dplyr/data.table . Мы paste объединяем все столбцы (хотя toString() не нужно указывать collapse аргумент для вставки — он делает это по умолчанию), затем мы все используем !duplicated() для получения результата.

3. На самом !duplicated() деле это ключ. После unique и distinct это еще один отличный инструмент. К сожалению, я все еще не там. Мне потребовалось около 20 минут, чтобы выяснить, как заменить starts_with() in с помощью vector именами столбцов, которые я хочу сравнить (это all_of() я, наконец, понял). И после всего этого я получил обратно полностью пустую таблицу, что, я полагаю, связано с тем, что все столбцы являются либо множителями, либо целыми числами, а не символами. Поэтому мне придется преобразовать в символ где-то посередине.

4. all_of() работает с именами столбцов в кавычках, c() работает с именами столбцов без кавычек. Таким образом, вы могли бы использовать all_of("colA", "colB") или c(colA, colB) .

5. Имена столбцов были в векторе, причем каждый элемент в векторе был строкой. Итак, all_of(col_names) дал мне правильные результаты.

Ответ №3:

С помощью «data.table» вы можете попробовать:

 library(data.table)

cols <- c("colA", "colB", "colC")
fun <- function(x) !duplicated(x)
as.data.table(df)[, lapply(.SD, toString), groups, .SDcols = cols][
  , (cols) := lapply(.SD, fun), .SDcols = cols][]
#    groups  colA  colB colC
# 1:    gr1  TRUE  TRUE TRUE
# 2:    gr2 FALSE  TRUE TRUE
# 3:    gr3 FALSE FALSE TRUE