web-dev-qa-db-fra.com

Comment utiliser map of purrr with dplyr

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. 

  • list1 = toutes les colonnes portant le nombre 1
  • list2 = toutes les colonnes portant le nombre 2

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.

10
user30276

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
10
akrun

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))
4
docendo discimus

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
3
Moody_Mudskipper

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.

2
Lorenzo G

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
Tung

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.

1
G. Grothendieck
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
1
Phil

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
0
dabsingh