r: extraer combinaciones únicas de una larga lista de variables binarias

CorePress2024-01-25  12

Tengo un marco de datos que contiene una larga lista de variables binarias. Cada fila representa a un participante y las columnas representan si un participante tomó una determinada decisión (1) o no (0). Para simplificar, digamos que solo hay cuatro variables binarias y 6 participantes.

df <- data.frame(a = c(0,1,0,1,0,1),
                 b = c(1,1,1,1,0,1),
                 c = c(0,0,0,1,1,1),
                 d = c(1,1,0,0,0,0))

>df

#   a b c d
# 1 0 1 0 1
# 2 1 1 0 1
# 3 0 1 0 0
# 4 1 1 1 0
# 5 0 0 1 0
# 6 1 1 1 0

En el marco de datos, quiero crear una lista de columnas que reflejen cada combinación única de variables en df (es decir, abc, abd, bcd, cda). Luego, para cada fila, quiero agregar el valor "1" si la fila contiene la combinación particular correspondiente a la columna. Entonces, si el participante obtuvo 1 en "a", "b" y "c", y 0 en "d" tendría una puntuación de 1 en la columna "abc" recién creada, pero 0 en las otras columnas. Lo ideal seríase parece a esto.

>df_updated

#   a b c d abc abd bcd cda
# 1 0 1 0 1   0   0   0   0
# 2 1 1 0 1   0   1   0   0
# 3 0 1 0 0   0   0   0   0
# 4 1 1 1 0   1   0   0   0
# 5 0 0 1 0   0   0   0   0
# 6 1 1 1 0   0   0   0   0

El objetivo final es tener una idea de la frecuencia de cada una de las combinaciones, así poder ordenarlas desde las elegidas con más frecuencia hasta las elegidas con menos frecuencia. He estado pensando en este tema durante días, pero no pude encontrar una respuesta adecuada. Agradecería mucho la ayuda.

1

A decir verdad, no estoy seguro de cómo el resultado esperado le brinda información nueva y útil sobre los datos sin procesar. Ha convertido 4 columnas de entrada en 4 columnas de salida que son:-en mi opinión--incluso más difícil de entender que los datos sin procesar. ¿Qué pasa si tienes 5, 6 o 10 columnas de entrada? El número de combinaciones de salida diferentes de esos campos aumentará muy rápidamente.

Simón

28/03/2021 a las 11:02

2

A mí esto me parece que quizás estés buscando "conjuntos de elementos". Si es así, apriori::arules es una herramienta eficiente, especialmente si tendrá más que solo combinaciones de tres (como lo menciona @Simon). Véase, por ejemplo. contar cConjuntos comunes de artículos entre diferentes clientes, donde ya se tiene una "matriz de incidencia binaria". Ajuste el soporte y minlen como desee.

- Henrik

28/03/2021 a las 11:14

1

Para obtener más información, consulte la bonita viñeta de Arules

- Henrik

28/03/2021 a las 11:25

¿Por qué no escribes una solución basada en esto? Sería bueno verla

- Sirio

28/03/2021 a las 13:46

@Sirius Gracias, de hecho, pero si OP realmente quiere conjuntos de elementos, la pregunta sería un duplicado del enlace (y entonces no es necesario reiterar esa respuesta aquí). Y si OP quiere exactamente lo que se describe en la pregunta, ya hay algunas respuestas. Saludos

- Henrik

28/03/2021 a las 14:12



------------------------------------

¿Algo como esto?

funCombn <- function(data){
  f <- function(x, data){
    data <- data[x]
    list(
      name = paste(x, collapse = ""),
      vec = apply(data, 1, function(x) +all(as.logical(x)))
    )
  }
  
  res <- combn(names(df), 3, f, simplify = FALSE, data = df)
  out <- do.call(cbind.data.frame, lapply(res, '[[', 'vec'))
  names(out) <- sapply(res, '[[', 'name')
  cbind(data, out)
}

funCombn(df)
#  a b c d abc abd acd bcd
#1 0 1 0 1   0   0   0   0
#2 1 1 0 1   0   1   0   0
#3 0 1 0 0   0   0   0   0
#4 1 1 1 0   1   0   0   0
#5 0 0 1 0   0   0   0   0
#6 1 1 1 0   1   0   0   0



------------------------------------

Opción Base R usando combn:

n <- 3
cbind(df, do.call(cbind, combn(names(df), n, function(x) {
  setNames(data.frame(as.integer(rowSums(df[x] == 1) == n)), 
           paste0(x, collapse = ''))
}, simplify = FALSE))) -> result

result

#  a b c d abc abd acd bcd
#1 0 1 0 1   0   0   0   0
#2 1 1 0 1   0   1   0   0
#3 0 1 0 0   0   0   0   0
#4 1 1 1 0   1   0   0   0
#5 0 0 1 0   0   0   0   0
#6 1 1 1 0   1   0   0   0

Utilizando combn crea todas las combinaciones de nombres de columnas tomando n columnas a la vez. Para cada una de esas combinaciones, asigne 1 a aquellas filas donde las 3 combinaciones son 1 o 0 en caso contrario.



------------------------------------

Si solo estás buscando una frecuencia de las combinaciones (y no es necesario que vuelvan a estar en los datos originales), entonces puedes usar algo como esto:

df <- data.frame(a = c(0,1,0,1,0,1),
                 b = c(1,1,1,1,0,1),
                 c = c(0,0,0,1,1,1),
                 d = c(1,1,0,0,0,0))
n <- names(df)
out <- sapply(n, function(x)ifelse(df[[x]] == 1, x, ""))
combs <- apply(out, 1, paste, collapse="")
sort(table(combs))
# combs
# abd   b  bd   c abc 
#   1   1   1   1   2 



------------------------------------

Bien, usemos tus datos, incluida una fila sin unos:

df <- data.frame(
  a = c(0,1,0,1,0,1,0),
  b = c(1,1,1,1,0,1,0),
  c = c(0,0,0,1,1,1,0),
  d = c(1,1,0,0,0,0,0)
)

Ahora quiero pegar todos los nombres de las columnas juntas si tienen un 1, y luego convertirla en una tabla amplia (para que todas tengan una columna para una combinación). Por supuesto, lleno todos los NA resultantes con ceros.

df2 <- df %>%
  dplyr::mutate(
    combination = paste0(
      ifelse(a == 1, "a", ""),    # There is possibly a way to automate this as well using across()
      ifelse(b == 1, "b", ""),
      ifelse(c == 1, "c", ""),
      ifelse(d == 1, "d", "")
    ),
    combination = ifelse(
      combination == "",
      "nothing",
      paste0("comb_", combination)
    ),
    value = ifelse(
      is.na(combination),
      0,
      1
    ),
    i = dplyr::row_number()
  ) %>%
  tidyr::pivot_wider(
    names_from = combination,
    values_from = value,
    names_repair = "unique"
  ) %>%
  replace(., is.na(.), 0) %>%
  dplyr::select(-i)

Como desea ordenar el df original por frecuencia, puede crear un resumen de todas las combinaciones.iones (excluyendo aquellos sin nada completado). Luego, simplemente conviértalo en una tabla larga y extraiga la columna para cada combinación (ordenada por frecuencia) de la tabla.

comb_in_order <- df2 %>%
  dplyr::select(
    -tidyselect::any_of(
      c(
        names(df),
        "nothing"     # I think you want these last.
      )
    )
  ) %>%
  dplyr::summarise(
    dplyr::across(
      .cols = tidyselect::everything(),
      .fns = sum
    )
  ) %>%
  tidyr::pivot_longer(
    cols = tidyselect::everything(),
    names_to = "combination",
    values_to = "frequency"
  ) %>%
  dplyr::arrange(
    dplyr::desc(frequency)
  ) %>%
  dplyr::pull(combination)

Lo único que queda entonces es reconstruir el df original por estos después de ordenar por columnas.

df2 %>%
  dplyr::arrange(
    across(
      tidyselect::any_of(comb_in_order),
      desc
    )
  ) %>%
  dplyr::select(
    tidyselect::any_of(names(df))
  )

Esto debería funcionar para todas las combinaciones posibles.

Su guía para un futuro mejor - libreflare
Su guía para un futuro mejor - libreflare