web-dev-qa-db-fra.com

Réduire / concaténer / agréger une colonne en une seule chaîne séparée par des virgules dans chaque groupe

Je veux agréger une colonne dans un bloc de données selon deux variables de regroupement et séparer les valeurs individuelles par une virgule.

Voici quelques données:

data <- data.frame(A = c(rep(111, 3), rep(222, 3)), B = rep(1:2, 3), C = c(5:10))
data
#     A B  C
# 1 111 1  5
# 2 111 2  6
# 3 111 1  7
# 4 222 2  8
# 5 222 1  9
# 6 222 2 10    

"A" et "B" sont des variables de regroupement, et "C" est la variable que je veux réduire en une chaîne character séparée par des virgules. J'ai essayé:

library(plyr)
ddply(data, .(A,B), summarise, test = list(C))

    A B  test
1 111 1  5, 7
2 111 2     6
3 222 1     9
4 222 2 8, 10

mais quand j'ai essayé de convertir la colonne de test en character, cela devient comme ceci:

ddply(data, .(A,B), summarise, test = as.character(list(C)))
#     A B     test
# 1 111 1  c(5, 7)
# 2 111 2        6
# 3 222 1        9
# 4 222 2 c(8, 10)

Comment conserver le format character et les séparer par une virgule? Par exemple, la ligne 1 ne doit être que "5,7", et non pas comme c (5,7).

52
linp

Voici quelques options utilisant toString, une fonction utilitaire de Nice qui concatène les chaînes avec des virgules. Si vous ne voulez pas de virgules, vous pouvez utiliser paste() avec l'argument collapse à la place.

data.table

# alternative using data.table
library(data.table)
as.data.table(data)[, toString(C), by = list(A, B)]

agrégat Ceci n'utilise aucun paquet:

# alternative using aggregate from the stats package in the core of R
aggregate(C ~., data, toString)

sqldf

Et voici une alternative en utilisant la fonction SQL group_concat en utilisant package sqldf :

library(sqldf)
sqldf("select A, B, group_concat(C) C from data group by A, B", method = "raw")

dplyr Une alternative dplyr:

library(dplyr)
data %>%
  group_by(A, B) %>%
  summarise(test = toString(C)) %>%
  ungroup()

plyr

# plyr
library(plyr)
ddply(data, .(A,B), summarize, C = toString(C))
59
G. Grothendieck

Changez où vous mettez as.character:

> out <- ddply(data, .(A, B), summarise, test = list(as.character(C)))
> str(out)
'data.frame':   4 obs. of  3 variables:
 $ A   : num  111 111 222 222
 $ B   : int  1 2 1 2
 $ test:List of 4
  ..$ : chr  "5" "7"
  ..$ : chr "6"
  ..$ : chr "9"
  ..$ : chr  "8" "10"
> out
    A B  test
1 111 1  5, 7
2 111 2     6
3 222 1     9
4 222 2 8, 10

Notez cependant que chaque élément est toujours en fait un caractère distinct, pas une chaîne de caractères unique. Autrement dit, ce n'est pas une chaîne réelle qui ressemble à "5, 7", mais plutôt deux caractères, "5" et "7", que R affiche avec une virgule entre eux.

Comparez avec ce qui suit:

> out2 <- ddply(data, .(A, B), summarise, test = paste(C, collapse = ", "))
> str(out2)
'data.frame':   4 obs. of  3 variables:
 $ A   : num  111 111 222 222
 $ B   : int  1 2 1 2
 $ test: chr  "5, 7" "6" "9" "8, 10"
> out
    A B  test
1 111 1  5, 7
2 111 2     6
3 222 1     9
4 222 2 8, 10

La solution comparable dans la base R est, bien sûr, aggregate:

> A1 <- aggregate(C ~ A + B, data, function(x) c(as.character(x)))
> str(A1)
'data.frame':   4 obs. of  3 variables:
 $ A: num  111 222 111 222
 $ B: int  1 1 2 2
 $ C:List of 4
  ..$ 0: chr  "5" "7"
  ..$ 1: chr "9"
  ..$ 2: chr "6"
  ..$ 3: chr  "8" "10"
> A2 <- aggregate(C ~ A + B, data, paste, collapse = ", ")
> str(A2)
'data.frame':   4 obs. of  3 variables:
 $ A: num  111 222 111 222
 $ B: int  1 1 2 2
 $ C: chr  "5, 7" "9" "6" "8, 10"
13

Voici la solution stringr/tidyverse:

library(tidyverse)
library(stringr)

data <- data.frame(A = c(rep(111, 3), rep(222, 3)), B = rep(1:2, 3), C = c(5:10))


data %>%
 group_by(A, B) %>%
 summarize(text = str_c(C, collapse = ", "))

# A tibble: 4 x 3
# Groups:   A [2]
      A     B test 
  <dbl> <int> <chr>
1   111     1 5, 7 
2   111     2 6    
3   222     1 9    
4   222     2 8, 10
4
Ben G