Как определить и подсчитать элементы пересечения в R

У меня есть фрейм данных, который показывает принадлежность к трем цветовым классам. Числа относятся к уникальным идентификаторам. Один идентификатор может быть частью одной или нескольких групп.

dat <- data.frame(BLUE = c(1, 2, 3, 4, 6, NA),
                  RED = c(2, 3, 6, 7, 9, 13),
                  GREEN = c(4, 6, 8, 9, 10, 11))

или для наглядности:

BLUE  RED  GREEN
1     2    4
2     3    6
3     6    8
4     7    9
6     9    10
NA    13   11

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

COLOR                TOTAL  IDs (reference only, not needed in final output)
RED                  2      (7, 13)
BLUE                 1      (1)
GREEN                3      (8, 10, 11)
RED, BLUE            3      (2, 3, 6)
RED, GREEN           2      (6, 9)
BLUE, GREEN          2      (4, 6)
RED, BLUE, GREEN     1      (6)

Кто-нибудь знает эффективный способ сделать это в R? Спасибо!


person DJC    schedule 26.09.2019    source источник
comment
Почему 7 и 13 ID для красного?   -  person NelsonGon    schedule 26.09.2019
comment
Число 2 относится к количеству идентификаторов, которые выделены красным и только красным цветом. Поскольку 7 и 13 выделены красным, а не синим или зеленым цветом, общее количество красных равно 2. Все остальные числа, выделенные красным, появляются в других группах. 7 и 13 — это просто случайные числа, которые я создал для иллюстративных целей, но они действуют так же, как идентификаторы появляются в реальном наборе данных (т. е. некоторые находятся только в одной группе, а некоторые — в нескольких группах).   -  person DJC    schedule 26.09.2019
comment
Почему 6 указано в RED, BLUE, GREEN, а также все 3 попарные группы? Я интерпретировал это как то, что вы хотите, чтобы каждый идентификатор учитывался только один раз (это его максимальная группа).   -  person ClancyStats    schedule 26.09.2019
comment
Не уверен, что я понимаю вопрос здесь, но 6 перечислены в красном, синем и зеленом ряду по отдельности, так как мне нужно определить, сколько идентификаторов было во всех трех группах одновременно.   -  person DJC    schedule 26.09.2019


Ответы (2)


library(dplyr)
library(tidyr)

cbind(dat, row = 1:6) %>% 
  gather(COLOR, IDs, -row) %>% 
  group_by(IDs) %>% 
  nest(COLOR, .key="COLOR") %>% 
  mutate(COLOR = sapply(COLOR, as.character)) %>% 
  drop_na %>% 
  group_by(COLOR) %>% 
  add_count(name="TOTAL") %>% 
  group_by(COLOR, TOTAL) %>% 
  nest(IDs, .key = "IDs") %>% 
  as.data.frame

#>                       COLOR TOTAL       IDs
#> 1                      BLUE     1         1
#> 2          c("BLUE", "RED")     2      2, 3
#> 3        c("BLUE", "GREEN")     1         4
#> 4 c("BLUE", "RED", "GREEN")     1         6
#> 5                       RED     2     7, 13
#> 6         c("RED", "GREEN")     1         9
#> 7                     GREEN     3 8, 10, 11


Есть более традиционный способ работы с NA в пакете venn:

library(purrr)
library(magrittr)
library(venn)

as.list(dat) %>%
  map(discard, is.na) %>%
  compact() %>% 
  venn() %>% 
  print

    #>                BLUE RED GREEN counts
    #>                   0   0     0      0
    #> GREEN             0   0     1      3
    #> RED               0   1     0      2
    #> RED:GREEN         0   1     1      1
    #> BLUE              1   0     0      1
    #> BLUE:GREEN        1   0     1      1
    #> BLUE:RED          1   1     0      2
    #> BLUE:RED:GREEN    1   1     1      1

Существует много других пакетов для диаграммы venn в R в соответствии с этим ответом.

Например, пакет VennDiagram::venn.diagram имеет переменную na, которая получает stop, remove и none. Итак, здесь мы будем использовать remove; однако это даст нам только диаграмму, а не таблицу. Вы можете изучить другие возможности в других пакетах.

person M--    schedule 26.09.2019
comment
Согласованный. Не круто, кто это сделал. Извините за все - у меня есть дополнения к решениям @M и @tmfmnk. В обоих ваших решениях такие комбинации, как КРАСНЫЙ:ЗЕЛЕНЫЙ = 1 или КРАСНЫЙ:СИНИЙ = 2, но разве они не должны возвращаться как 2 и 3 соответственно? Может я схожу с ума, но пересчитал и вот как должно возвращать нет? - person DJC; 26.09.2019
comment
@DJC Нет, потому что 6 находится в Red, Green, Blue. Вы считаете это более одного раза. Если мы хотим следовать вашей логике, кроме Red, Green = 2 и Red, Blue = 3, Blue, Green также должно быть 2, а не 1. См. график, который у меня есть выше для пояснения. p.s. в одном комментарии можно отметить только одного человека. tmfmnk не был уведомлен об этом. - person M--; 26.09.2019
comment
Спасибо за внимание. Можно ли как-то считать так, как я описываю? Несмотря на то, что я знаю, что логически, если что-то находится в RBG, то, соответственно, оно находится в RB, RG и BG, мне все равно понадобятся эти две пары, несмотря ни на что. Является ли самый простой способ просто взять эту общую комбинацию одеял для всех групп и просто добавить ее ко всем другим группам комбинаций, но не к отдельным цветам? Большое спасибо, кстати. Очень ценю вашу помощь с этим - person DJC; 26.09.2019
comment
@DJC нужно время, чтобы подумать об этом. Но я думаю, что могу взломать tidyverse решение, чтобы оно работало так, как вы хотите. - person M--; 26.09.2019
comment
Я уверен, что это можно сделать с помощью tidyverse, однако я не думаю, что это можно осмысленно решить с помощью подхода диаграммы Венна. - person tmfmnk; 26.09.2019
comment
Спасибо. Прямо сейчас я изменил ваш код, как показано ниже. Мой подход заключается в проверке наличия двух или более двоеточий в COLOR. Если есть, я хочу проиндексировать эту сумму, а затем добавить это число к любому ЦВЕТУ с одним двоеточием:. Это может быть сложно, учитывая, что на самом деле групп 5, но посмотрим, ха-ха. - person DJC; 26.09.2019
comment
dat %›% собрать(ЦВЕТ, ID) %›% вложить(ЦВЕТ, .key=ЦВЕТ) %›% мутировать(ЦВЕТ = sapply(ЦВЕТ, as.character), ЦВЕТ = str_replace_all(ЦВЕТ, [^[:alnum: ]], ), COLOR = gsub(c , , COLOR) %›% str_trim, COLOR = gsub( , :, COLOR)) %›% drop_na %›% group_by(COLOR) %›% add_count(name=TOTAL) % ›% отличные(ЦВЕТ, ВСЕГО) %›% мутировать(ТЕСТ = str_detect(ЦВЕТ, :)) - person DJC; 26.09.2019
comment
@DJC вопрос. Если вы хотите rgb в rg, bg, rb, то хотите ли вы, чтобы все они были в r, b и g, а также чтобы они были равны 6 (или 5 для синего, поскольку он имеет один NA). Я пытаюсь сказать, что ваша логика непоследовательна. - person M--; 26.09.2019
comment
Я так не думаю. Я хочу, чтобы это применялось только к комбинациям, а не к отдельным категориям, поэтому я ищу наличие точки с запятой, указывающей на несколько категорий. Может я просто сам запутался :/ - person DJC; 26.09.2019
comment
@DJC Я не говорил о его реализации. Я говорил, что это концептуально ошибочно, поскольку вы дважды подсчитываете взаимодействия, но исключаете их из отдельных лиц. В любом случае, рад, что это сработало для вас в конце концов. - person M--; 27.09.2019

Вы можете использовать библиотеку venn (особенно подходит для ситуаций, когда в ваших данных нет NA):

venn_table <- venn(as.list(dat))

               BLUE RED GREEN counts
                  0   0     0      0
GREEN             0   0     1      3
RED               0   1     0      2
RED:GREEN         0   1     1      1
BLUE              1   0     0      2
BLUE:GREEN        1   0     1      1
BLUE:RED          1   1     0      2
BLUE:RED:GREEN    1   1     1      1

А также:

attr(venn_table, "intersections")

$GREEN
[1]  8 10 11

$RED
[1]  7 13

$`RED:GREEN`
[1] 9

$BLUE
[1]  1 NA

$`BLUE:GREEN`
[1] 4

$`BLUE:RED`
[1] 2 3

$`BLUE:RED:GREEN`
[1] 6

Чтобы включить также идентификаторы:

data.frame(venn_table[2:nrow(venn_table), ],
           ID = do.call("rbind", lapply(attr(venn_table, "intersections"), paste0, collapse = ",")))

               BLUE RED GREEN counts      ID
GREEN             0   0     1      3 8,10,11
RED               0   1     0      2    7,13
RED:GREEN         0   1     1      1       9
BLUE              1   0     0      2    1,NA
BLUE:GREEN        1   0     1      1       4
BLUE:RED          1   1     0      2     2,3
BLUE:RED:GREEN    1   1     1      1       6

Один из способов борьбы с NA:

venn_table2 <- data.frame(venn_table[2:nrow(venn_table), length(venn_table), drop = FALSE],
                          ID = do.call("rbind", lapply(attr(venn_table, "intersections"), paste0, collapse = ",")))

counts <- venn_table2[1] - with(venn_table2, lengths(regmatches(ID, gregexpr("NA", ID))))

               counts
GREEN               3
RED                 2
RED:GREEN           1
BLUE                1
BLUE:GREEN          1
BLUE:RED            2
BLUE:RED:GREEN      1

И более элегантный способ справиться с NA может быть (на основе комментария @M--):

print(venn(Map(function(x) x[!is.na(x)], as.list(dat))))

               BLUE RED GREEN counts
                  0   0     0      0
GREEN             0   0     1      3
RED               0   1     0      2
RED:GREEN         0   1     1      1
BLUE              1   0     0      1
BLUE:GREEN        1   0     1      1
BLUE:RED          1   1     0      2
BLUE:RED:GREEN    1   1     1      1
person tmfmnk    schedule 26.09.2019
comment
@M, спасибо, что указали на это. На самом деле мне не нужен столбец идентификаторов в конечном выводе, так что не беспокойтесь об этом. Этот метод намного проще, но я заметил, что он считает NA синим цветом как уникальное значение (таким образом, общее количество синего равно 2, когда оно должно быть 1), есть идеи, как это исправить? - person DJC; 26.09.2019