web-dev-qa-db-fra.com

Appliquer la fonction sur un sous-ensemble de colonnes (.SDcols) tout en appliquant une fonction différente sur une autre colonne (au sein des groupes)

Ceci est très similaire à une question appliquant une fonction commune à plusieurs colonnes d'un data.table uning .SDcolsréponse complète ici .

La différence est que je voudrais appliquer simultanément une fonction différente sur une autre colonne qui ne fait pas partie du .SD sous-ensemble. Je poste un exemple simple ci-dessous pour montrer ma tentative de résoudre le problème:

dt = data.table(grp = sample(letters[1:3],100, replace = TRUE),
                v1 = rnorm(100), 
                v2 = rnorm(100), 
                v3 = rnorm(100))
sd.cols = c("v2", "v3")
dt.out = dt[, list(v1 = sum(v1),  lapply(.SD,mean)), by = grp, .SDcols = sd.cols]

Donne l'erreur suivante:

Error in `[.data.table`(dt, , list(v1 = sum(v1), lapply(.SD, mean)), by = grp,  
: object 'v1' not found

Maintenant, cela a du sens parce que le v1 La colonne n'est pas incluse dans le sous-ensemble de colonnes qui doit être évalué en premier. J'ai donc exploré davantage en l'incluant dans mon sous-ensemble de colonnes:

sd.cols = c("v1","v2", "v3")
dt.out = dt[, list(sum(v1), lapply(.SD,mean)), by = grp, .SDcols = sd.cols]

Maintenant, cela ne provoque pas d'erreur mais fournit une réponse contenant 9 lignes (pour 3 groupes), la somme étant répétée trois fois dans la colonne V1 et les moyennes des 3 colonnes (comme prévu mais non souhaitées) placées dans V2 comme indiqué ci-dessous:

> dt.out 
   grp        V1                  V2
1:   c -1.070608 -0.0486639841313638
2:   c -1.070608  -0.178154270921521
3:   c -1.070608  -0.137625003604012
4:   b -2.782252 -0.0794929150464099
5:   b -2.782252  -0.149529237116445
6:   b -2.782252   0.199925178109264
7:   a  6.091355   0.141659419355985
8:   a  6.091355 -0.0272192037753071
9:   a  6.091355 0.00815760216214876

Solution de contournement en 2 étapes

Il est clairement possible de résoudre le problème en plusieurs étapes en calculant le mean par groupe pour le sous-ensemble de colonnes et en le joignant au sum par groupe pour la colonne unique comme suit:

dt.out1 = dt[, sum(v1), by = grp]
dt.out2 = dt[, lapply(.SD,mean), by = grp, .SDcols = sd.cols]
dt.out = merge(dt.out1, dt.out2, by = "grp")

> dt.out
   grp        V1         v2           v3
1:   a  6.091355 -0.0272192  0.008157602
2:   b -2.782252 -0.1495292  0.199925178
3:   c -1.070608 -0.1781543 -0.137625004

Je suis sûr que c'est une chose assez simple qui me manque, merci d'avance pour tout conseil.

33
Matt Weller

Mise à jour: Problème # 495 est résolu maintenant avec ce récent commit , nous pouvons maintenant le faire ça va:

require(data.table) # v1.9.7+
set.seed(1L)
dt = data.table(grp = sample(letters[1:3],100, replace = TRUE),
                v1 = rnorm(100), 
                v2 = rnorm(100), 
                v3 = rnorm(100))
sd.cols = c("v2", "v3")
dt.out = dt[, list(v1 = sum(v1),  lapply(.SD,mean)), by = grp, .SDcols = sd.cols]

Notez cependant que dans ce cas, v2 Serait renvoyé sous forme de liste. C'est parce que vous faites list(val, list()) efficacement. Ce que vous avez l'intention de faire est peut-être:

dt[, c(list(v1=sum(v1)), lapply(.SD, mean)), by=grp, .SDcols = sd.cols]
#    grp        v1          v2         v3
# 1:   a -6.440273  0.16993940  0.2173324
# 2:   b  4.304350 -0.02553813  0.3381612
# 3:   c  0.377974 -0.03828672 -0.2489067

Voir l'historique pour une réponse plus ancienne.

28
Arun

Essaye ça:

dt[,list(sum(v1), mean(v2), mean(v3)), by=grp]

Dans data.table, L'utilisation de list() dans le deuxième argument vous permet de décrire un ensemble de colonnes qui aboutissent au data.table Final.

Pour ce que ça vaut, .SD Peut être assez lent [^ 1], donc vous voudrez peut-être l'éviter à moins que vous n'ayez vraiment besoin de toutes les données fournies dans le sous-ensemble data.table Comme vous le feriez pour plus fonction sophistiquée.

Une autre option, si vous avez plusieurs colonnes pour .SDcols Serait de faire la fusion sur une seule ligne en utilisant la syntaxe de fusion data.table.

Par exemple:

dt[, sum(v1), by=grp][dt[,lapply(.SD,mean), by=grp, .SDcols=sd.cols]]

Pour utiliser le merge de data.table, Vous devez d'abord utiliser setkey() sur votre data.table Afin qu'il sache comment faire correspondre les choses.

Donc, vraiment, vous avez d'abord besoin:

setkey(dt, grp)

Ensuite, vous pouvez utiliser la ligne ci-dessus pour produire un résultat équivalent.

[^ 1]: Je trouve cela particulièrement vrai lorsque votre nombre de groupes approche le nombre de lignes totales. Par exemple, cela peut se produire lorsque votre clé est un ID individuel et que de nombreux individus n'ont qu'une ou deux observations.

8
JBecker