web-dev-qa-db-fra.com

Mutate plusieurs colonnes dans un dataframe

J'ai un ensemble de données qui ressemble à ceci.

bankname    bankid  year    totass  cash    bond    loans
Bank A      1       1881    244789  7250    20218   29513
Bank B      2       1881    195755  10243   185151  2800
Bank C      3       1881    107736  13357   177612  NA
Bank D      4       1881    170600  35000   20000   5000
Bank E      5       1881    3200000 351266  314012  NA

et je veux calculer des ratios basés sur les bilans des banques. et je veux que l'ensemble de données ressemble à ceci

bankname    bankid  year    totass  cash    bond    loans   CashtoAsset BondtoAsset LoanstoAsset
Bank A      1       1881    2447890 7250    202100  951300  0.002   0.082   0.388
Bank B      2       1881    195755  10243   185151  2800    0.052   0.945   0.014
Bank C      3       1881    107736  13357   177612  NA  0.123   1.648585431 NA
Bank D      4       1881    170600  35000   20000   5000    0.205   0.117   0.029
Bank E      5       1881    32000000    351266  314012  NA  0.0109  0.009   NA

Voici le code pour répliquer les données

bankname <- c("Bank A","Bank B","Bank C","Bank D","Bank E")
bankid <- c( 1, 2,  3,  4,  5)
year<- c( 1881, 1881,   1881,   1881,   1881)
totass  <- c(244789,    195755, 107736, 170600, 32000000)
cash<-c(7250,10243,13357,35000,351266)
bond<-c(20218,185151,177612,20000,314012)
loans<-c(29513,2800,NA,5000,NA)
bankdata<-data.frame(bankname, bankid,year,totass, cash, bond, loans)

Premièrement, je me suis débarrassé des NA dans les bilans.

cols <- c("totass", "cash", "bond", "loans")
bankdata[cols][is.na(bankdata[cols])] <- 0

Puis je calcule des ratios

library(dplyr)
bankdata<-mutate(bankdata,CashtoAsset = cash/totass)
bankdata<-mutate(bankdata,BondtoAsset = bond/totass)
bankdata<-mutate(bankdata,loanstoAsset =loans/totass)

Mais, au lieu de calculer tous ces ratios ligne par ligne, je souhaite créer un look permettant de faire tout cela à la fois. À Stata, je ferais

foreach x of varlist cash bond loans {
by bankid: gen `x'toAsset = `x'/ totass
}

Comment je ferais ça?

14
H Park

Mise à jour (à partir du 2 décembre 2017)

Depuis que j'ai répondu à cette question, je me suis rendu compte que certains SO utilisateurs vérifiaient cette réponse. Le paquet dplyr a changé depuis. Par conséquent, je laisse la mise à jour suivante. J'espère que cela aidera certains utilisateurs de R à apprendre à utiliser mutate_at().

mutate_each() est maintenant obsolète. Vous souhaitez utiliser mutate_at() à la place. Vous pouvez spécifier les colonnes auxquelles vous souhaitez appliquer votre fonction dans .vars. Une solution consiste à utiliser vars(). Une autre consiste à utiliser un vecteur de caractères contenant des noms de colonnes, auquel vous souhaitez appliquer votre fonction personnalisée dans .fun. L'autre consiste à spécifier des colonnes avec des nombres (par exemple, 5: 7 dans ce cas). Notez que si vous utilisez une colonne pour group_by(), vous devez modifier le nombre de positions de colonne. Jetez un oeil sur cette question .

bankdata %>%
mutate_at(.funs = funs(toAsset = ./totass), .vars = vars(cash:loans))

bankdata %>%
mutate_at(.funs = funs(toAsset = ./totass), .vars = c("cash", "bond", "loans"))

bankdata %>%
mutate_at(.funs = funs(toAsset = ./totass), .vars = 5:7)

#  bankname bankid year   totass   cash   bond loans cash_toAsset bond_toAsset loans_toAsset
#1   Bank A      1 1881   244789   7250  20218 29513   0.02961734  0.082593581    0.12056506
#2   Bank B      2 1881   195755  10243 185151  2800   0.05232561  0.945830247    0.01430359
#3   Bank C      3 1881   107736  13357 177612    NA   0.12397899  1.648585431            NA
#4   Bank D      4 1881   170600  35000  20000  5000   0.20515826  0.117233294    0.02930832
#5   Bank E      5 1881 32000000 351266 314012    NA   0.01097706  0.009812875            NA

J'ai volontairement donné toAsset à la fonction personnalisée dans .fun, car cela m'aidera à organiser les nouveaux noms de colonne. Auparavant, j'utilisais rename(). Mais je pense qu'il est beaucoup plus facile de nettoyer les noms de colonnes avec gsub() dans l'approche actuelle. Si le résultat ci-dessus est enregistré en tant que out, vous souhaitez exécuter le code suivant afin de supprimer _ dans les noms de colonne.

names(out) <- gsub(names(out), pattern = "_", replacement = "")

Réponse originale

Je pense que vous pouvez économiser un peu de frappe de cette manière avec dplyr. L'inconvénient est que vous écrasez de l'argent, des obligations et des prêts.

bankdata %>%
    group_by(bankname) %>%
    mutate_each(funs(whatever = ./totass), cash:loans)

#  bankname bankid year   totass       cash        bond      loans
#1   Bank A      1 1881   244789 0.02961734 0.082593581 0.12056506
#2   Bank B      2 1881   195755 0.05232561 0.945830247 0.01430359
#3   Bank C      3 1881   107736 0.12397899 1.648585431         NA
#4   Bank D      4 1881   170600 0.20515826 0.117233294 0.02930832
#5   Bank E      5 1881 32000000 0.01097706 0.009812875         NA

Si vous préférez le résultat escompté, je pense qu’il est nécessaire de taper. La partie renommée semble être quelque chose que vous devez faire.

bankdata %>%
    group_by(bankname) %>%
    summarise_each(funs(whatever = ./totass), cash:loans) %>%
    rename(cashtoAsset = cash, bondtoAsset = bond, loanstoAsset = loans) -> ana;
    ana %>%
    merge(bankdata,., by = "bankname")

#  bankname bankid year   totass   cash   bond loans cashtoAsset bondtoAsset loanstoAsset
#1   Bank A      1 1881   244789   7250  20218 29513  0.02961734 0.082593581   0.12056506
#2   Bank B      2 1881   195755  10243 185151  2800  0.05232561 0.945830247   0.01430359
#3   Bank C      3 1881   107736  13357 177612    NA  0.12397899 1.648585431           NA
#4   Bank D      4 1881   170600  35000  20000  5000  0.20515826 0.117233294   0.02930832
#5   Bank E      5 1881 32000000 351266 314012    NA  0.01097706 0.009812875           NA
33
jazzurro

Apply et cbind 

cbind(bankdata,apply(bankdata[,5:7],2, function(x) x/bankdata$totass))
names(bankdata)[8:10] <- paste0(names(bankdata)[5:7], 'toAssest’)

> bankdata
  bankname bankid year   totass   cash   bond loans cashtoAssest bondtoAssest loanstoAssest
1   Bank A      1 1881   244789   7250  20218 29513   0.02961734  0.082593581    0.12056506
2   Bank B      2 1881   195755  10243 185151  2800   0.05232561  0.945830247    0.01430359
3   Bank C      3 1881   107736  13357 177612    NA   0.12397899  1.648585431            NA
4   Bank D      4 1881   170600  35000  20000  5000   0.20515826  0.117233294    0.02930832
5   Bank E      5 1881 32000000 351266 314012    NA   0.01097706  0.009812875            NA
3
hvollmeier

Voici une solution data.table.

library(data.table)
setDT(bankdata)
bankdata[, paste0(names(bankdata)[5:7], "toAsset") := 
           lapply(.SD, function(x) x/totass), .SDcols=5:7]
bankdata
#    bankname bankid year   totass   cash   bond loans cashtoAsset bondtoAsset loanstoAsset
# 1:   Bank A      1 1881   244789   7250  20218 29513  0.02961734 0.082593581   0.12056506
# 2:   Bank B      2 1881   195755  10243 185151  2800  0.05232561 0.945830247   0.01430359
# 3:   Bank C      3 1881   107736  13357 177612     0  0.12397899 1.648585431   0.00000000
# 4:   Bank D      4 1881   170600  35000  20000  5000  0.20515826 0.117233294   0.02930832
# 5:   Bank E      5 1881 32000000 351266 314012     0  0.01097706 0.009812875   0.00000000
2
KFB

C’est l’un des gros inconvénients de dplyr: pour autant que je sache, il n’existe aucun moyen simple de l’utiliser par programme plutôt que de manière interactive, sans une sorte de «bidouille» comme l’idiot déplorable eval(parse(text=foo)).

L'approche la plus simple est la même que dans la méthode Stata, mais la manipulation de chaîne est un peu plus détaillée dans R que dans Stata (ou dans tout autre langage de script, d'ailleurs).

for (x in c("cash", "bond", "loans")) {
  bankdata[sprintf("%stoAsset", x)] <- bankdata[x] / bankdata$totass  # or, equivalently, bankdata["totass"] for a consistent "look"
  ## can also replace `sprintf("%stoAsset", x)` with `paste0(c(x, "toAsset"))` or even `paste(x, "toAsset", collapse="") depending on what makes more sense to you.
}

Pour rendre le tout plus semblable à Stata, vous pouvez envelopper le tout dans within comme suit:

bankdata <- within(bankdata, for (x in c("cash", "bond", "loans")) {
  assign(x, get(x) / totass)
})

mais cela implique quelques manipulations avec les fonctions get et assign qui ne sont pas aussi sûres à utiliser en général, bien que dans votre cas ce ne soit probablement pas un gros problème. Par exemple, je ne recommanderais pas d'essayer des trucs similaires avec dplyr, parce que dplyr abuse des fonctionnalités d'évaluation non standard de R et qu'il cause probablement plus de problèmes qu'il n'en vaut la peine. Pour une solution plus rapide et probablement supérieure, consultez le paquetage data.table qui (je pense) vous permettrait d’utiliser la syntaxe en boucle de type Stata, mais avec une vitesse de type dplyr. Découvrez la vignette du paquet sur CRAN.

De plus, êtes-vous vraiment très sûr de vouloir réaffecter les entrées NA à 0?

1
shadowtalker

Essayer:

for(i in 5:7){
     bankdata[,(i+3)] = bankdata[,i]/bankdata[,4]
}
names(bankdata)[(5:7)+3] =  paste0(names(bankdata)[5:7], 'toAssest')

Sortie: 

bankdata
  bankname bankid year   totass   cash   bond loans cashtoAssest bondtoAssest loanstoAssest
1   Bank A      1 1881   244789   7250  20218 29513   0.02961734  0.082593581    0.12056506
2   Bank B      2 1881   195755  10243 185151  2800   0.05232561  0.945830247    0.01430359
3   Bank C      3 1881   107736  13357 177612     0   0.12397899  1.648585431    0.00000000
4   Bank D      4 1881   170600  35000  20000  5000   0.20515826  0.117233294    0.02930832
5   Bank E      5 1881 32000000 351266 314012     0   0.01097706  0.009812875    0.00000000
0
rnso

Vous pourriez rendre cela un peu plus difficile que nécessaire. Essayez ceci et voyez si cela donne ce dont vous avez besoin.

bankdata$CashtoAsset <- bankdata$cash / bankdata$totass
bankdata$BondtoAsset <- bankdata$bond / bankdata$totass
bankdata$loantoAsset <- bankdata$loans / bankdata$totass
bankdata

Donne ceci:

bankname bankid year   totass   cash   bond loans CashtoAsset BondtoAsset loantoAsset 
1   Bank A      1 1881   244789   7250  20218 29513  0.02961734 0.082593581  0.12056506 
2   Bank B      2 1881   195755  10243 185151  2800  0.05232561 0.945830247  0.01430359 
3   Bank C      3 1881   107736  13357 177612     0  0.12397899 1.648585431  0.00000 
4   Bank D      4 1881   170600  35000  20000  5000  0.20515826 0.117233294  0.02930832 
5   Bank E      5 1881 32000000 351266 314012     0  0.01097706 0.009812875  0.00000000

Cela devrait vous aider à démarrer dans la bonne direction.

0
Matt Jolly