En résumé, je veux créer plusieurs nouvelles colonnes dans un cadre de données en fonction des calculs de différentes paires de colonnes dans le cadre de données.
Les données se présentent comme suit:
df <- data.frame(a1 = c(1:5),
b1 = c(4:8),
c1 = c(10:14),
a2 = c(9:13),
b2 = c(3:7),
c2 = c(15:19))
df
a1 b1 c1 a2 b2 c2
1 4 10 9 3 15
2 5 11 10 4 16
3 6 12 11 5 17
4 7 13 12 6 18
5 8 14 13 7 19
La sortie est supposée ressembler à ceci:
a1 b1 c1 a2 b2 c2 sum_a sum_b sum_c
1 4 10 9 3 15 10 7 25
2 5 11 10 4 16 12 9 27
4 7 13 12 6 18 16 13 31
5 8 14 13 7 19 18 15 33
Je peux y arriver en utilisant dplyr en effectuant un travail manuel de la manière suivante:
df %>% rowwise %>% mutate(sum_a = sum(a1, a2),
sum_b = sum(b1, b2),
sum_c = sum(c1, c2)) %>%
as.data.frame()
Donc, ce qui est fait est: prenez les colonnes contenant la lettre "a", calculez la somme par ligne et créez une nouvelle colonne avec la somme nommée sum_ [lettre]. Répétez l'opération pour les colonnes avec des lettres différentes.
Cela fonctionne, cependant, si j’ai un grand ensemble de données avec 300 paires de colonnes différentes, l’entrée manuelle serait importante, car je devrais écrire 300 appels mutés.
Je suis récemment tombé sur le paquet R "purrr" et je suppose que cela résoudrait mon problème de faire ce que je veux de manière plus automatisée.
En particulier, je pense pouvoir utiliser purrr: map2 auquel je donne deux listes de noms de colonnes.
Ensuite, je pourrais calculer la somme de chaque entrée de liste correspondante, sous la forme de:
map2(list1, list2, ~mutate(sum))
Cependant, je ne suis pas en mesure de trouver le meilleur moyen d’aborder ce problème avec purrr. Je suis plutôt nouveau dans l'utilisation de purrr, alors j'apprécierais vraiment toute aide sur cette question.
Voici une option avec purrr
. Nous obtenons le préfixe unique
de la names
du jeu de données ('nm1'), utilisons map
(à partir de purrr
) pour parcourir les noms uniques, select
la colonne qui matches
le préfixe de 'nm1', ajoute les lignes à l'aide de reduce
et le lier les colonnes (bind_cols
) avec le jeu de données d'origine
library(tidyverse)
nm1 <- names(df) %>%
substr(1, 1) %>%
unique
nm1 %>%
map(~ df %>%
select(matches(.x)) %>%
reduce(`+`)) %>%
set_names(paste0("sum_", nm1)) %>%
bind_cols(df, .)
# a1 b1 c1 a2 b2 c2 sum_a sum_b sum_c
#1 1 4 10 9 3 15 10 7 25
#2 2 5 11 10 4 16 12 9 27
#3 3 6 12 11 5 17 14 11 29
#4 4 7 13 12 6 18 16 13 31
#5 5 8 14 13 7 19 18 15 33
Si vous souhaitez envisager une approche de base R, voici comment procéder:
cbind(df, lapply(split.default(df, substr(names(df), 0,1)), rowSums))
# a1 b1 c1 a2 b2 c2 a b c
#1 1 4 10 9 3 15 10 7 25
#2 2 5 11 10 4 16 12 9 27
#3 3 6 12 11 5 17 14 11 29
#4 4 7 13 12 6 18 16 13 31
#5 5 8 14 13 7 19 18 15 33
Il divise les données par colonnes en une liste, en fonction de la première lettre du nom de chaque colonne (a, b ou c).
Si vous avez un grand nombre de colonnes et devez différencier tous les caractères, à l'exception des chiffres à la fin du nom de chaque colonne, vous pouvez modifier l'approche suivante:
cbind(df, lapply(split.default(df, sub("\\d+$", "", names(df))), rowSums))
en base R, toutes vectorisées:
nms <- names(df)
df[paste0("sum_",unique(gsub("[1-9]","",nms)))] <-
df[endsWith(nms,"1")] + df[endsWith(nms,"2")]
# a1 b1 c1 a2 b2 c2 sum_a sum_b sum_c
# 1 1 4 10 9 3 15 10 7 25
# 2 2 5 11 10 4 16 12 9 27
# 3 3 6 12 11 5 17 14 11 29
# 4 4 7 13 12 6 18 16 13 31
# 5 5 8 14 13 7 19 18 15 33
Pour une solution astucieuse, vérifiez ceci:
library(tidyr)
library(dplyr)
df %>%
rownames_to_column(var = 'row') %>%
gather(a1:c2, key = 'key', value = 'value') %>%
extract(key, into = c('col.base', 'col.index'), regex = '([a-zA-Z]+)([0-9]+)') %>%
group_by(row, col.base) %>%
summarize(.sum = sum(value)) %>%
spread(col.base, .sum) %>%
bind_cols(df, .) %>%
select(-row)
Fondamentalement, je collectionne toutes les paires de colonnes avec leurs valeurs sur toutes les lignes, sépare le nom de la colonne en deux parties, calcule la somme des lignes pour les colonnes ayant la même lettre et le remet en forme large.
Une autre solution qui divise df
par des nombres que l’on utilise Reduce
pour calculer sum
library(tidyverse)
df %>%
split.default(., substr(names(.), 2, 3)) %>%
Reduce('+', .) %>%
set_names(paste0("sum_", substr(names(.), 1, 1))) %>%
cbind(df, .)
#> a1 b1 c1 a2 b2 c2 sum_a sum_b sum_c
#> 1 1 4 10 9 3 15 10 7 25
#> 2 2 5 11 10 4 16 12 9 27
#> 3 3 6 12 11 5 17 14 11 29
#> 4 4 7 13 12 6 18 16 13 31
#> 5 5 8 14 13 7 19 18 15 33
Créé le 2018-04-13 par le package reprex (v0.2.0).
1) dplyr/tidyr Convertir en format long, récapituler et reconvertir en format large:
library(dplyr)
library(tidyr)
DF %>%
mutate(Row = 1:n()) %>%
gather(colname, value, -Row) %>%
group_by(g = gsub("\\d", "", colname), Row) %>%
summarize(sum = sum(value)) %>%
ungroup %>%
mutate(g = paste("sum", g, sep = "_")) %>%
spread(g, sum) %>%
arrange(Row) %>%
cbind(DF, .) %>%
select(-Row)
donnant:
a1 b1 c1 a2 b2 c2 sum_a sum_b sum_c
1 1 4 10 9 3 15 10 7 25
2 2 5 11 10 4 16 12 9 27
3 4 7 13 12 6 18 16 13 31
4 5 8 14 13 7 19 18 15 33
2) base utilisant la multiplication matricielle
nms
est un vecteur de noms de colonnes sans les chiffres et précédés de sum_
. u
est un vecteur de ses éléments uniques. Formez une matrice logique en utilisant outer
à partir de celle qui, multipliée par DF
, donne les sommes; Enfin, liez-le à l'entrée.
nms <- gsub("(\\D+)\\d", "sum_\\1", names(DF))
u <- unique(nms)
sums <- as.matrix(DF) %*% outer(nms, setNames(u, u), "==")
cbind(DF, sums)
donnant:
a1 b1 c1 a2 b2 c2 sum_a sum_b sum_c
1 1 4 10 9 3 15 10 7 25
2 2 5 11 10 4 16 12 9 27
3 4 7 13 12 6 18 16 13 31
4 5 8 14 13 7 19 18 15 33
3) base avec tapply
En utilisant nms
de (2), appliquez tapply à chaque ligne:
cbind(DF, t(apply(DF, 1, tapply, nms, sum)))
donnant:
a1 b1 c1 a2 b2 c2 sum_a sum_b sum_c
1 1 4 10 9 3 15 10 7 25
2 2 5 11 10 4 16 12 9 27
3 4 7 13 12 6 18 16 13 31
4 5 8 14 13 7 19 18 15 33
Vous voudrez peut-être remplacer nms par factor(nms, levels = unique(nms))
dans l'expression ci-dessus si les noms ne sont pas dans l'ordre croissant.
df %>%
mutate(sum_a = pmap_dbl(select(., starts_with("a")), sum),
sum_b = pmap_dbl(select(., starts_with("b")), sum),
sum_c = pmap_dbl(select(., starts_with("c")), sum))
a1 b1 c1 a2 b2 c2 sum_a sum_b sum_c
1 1 4 10 9 3 15 10 7 25
2 2 5 11 10 4 16 12 9 27
3 3 6 12 11 5 17 14 11 29
4 4 7 13 12 6 18 16 13 31
5 5 8 14 13 7 19 18 15 33
Une approche légèrement différente en utilisant la base R:
cbind(df, lapply(unique(gsub("\\d+","", colnames(df))), function(li) {
set_names(data.frame(V = apply(df[grep(li, colnames(df), val = T)], FUN = sum, MARGIN = 1)), paste0("sum_", li))
}))
# a1 b1 c1 a2 b2 c2 sum_a sum_b sum_c
#1 1 4 10 9 3 15 10 7 25
#2 2 5 11 10 4 16 12 9 27
#3 3 6 12 11 5 17 14 11 29
#4 4 7 13 12 6 18 16 13 31
#5 5 8 14 13 7 19 18 15 33