web-dev-qa-db-fra.com

Remplacement conditionnel des valeurs dans un data.frame

J'essaie de comprendre comment remplacer de manière conditionnelle des valeurs dans un cadre de données sans utiliser de boucle. Mon bloc de données est structuré comme suit:

> df
          a b est
1  11.77000 2   0
2  10.90000 3   0
3  10.32000 2   0
4  10.96000 0   0
5   9.90600 0   0
6  10.70000 0   0
7  11.43000 1   0
8  11.41000 2   0
9  10.48512 4   0
10 11.19000 0   0

et la sortie dput est la suivante:

structure(list(a = c(11.77, 10.9, 10.32, 10.96, 9.906, 10.7, 
11.43, 11.41, 10.48512, 11.19), b = c(2, 3, 2, 0, 0, 0, 1, 2, 
4, 0), est = c(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)), .Names = c("a", 
"b", "est"), row.names = c(NA, -10L), class = "data.frame")

Ce que je veux faire, c'est vérifier la valeur de b. Si b est 0, je souhaite définir est sur une valeur de a. Je comprends que df$est[df$b == 0] <- 23 définira toutes les valeurs de est à 23, lorsque b==0. Ce que je ne comprends pas, c'est comment définir est sur une valeur de a lorsque cette condition est vraie. Par exemple:

df$est[df$b == 0] <- (df$a - 5)/2.533 

donne l'avertissement suivant:

Warning message:
In df$est[df$b == 0] <- (df$a - 5)/2.533 :
  number of items to replace is not a multiple of replacement length

Existe-t-il un moyen de passer la cellule concernée plutôt que le vecteur?

61
djq

Comme vous indexez conditionnellement df$est, vous devez également indexer de manière conditionnelle le vecteur de remplacement df$a:

index <- df$b == 0
df$est[index] <- (df$a[index] - 5)/2.533 

Bien entendu, la variable index n'est que temporaire et je l'utilise pour rendre le code un peu plus lisible. Vous pouvez l'écrire en une étape:

df$est[df$b == 0] <- (df$a[df$b == 0] - 5)/2.533 

Pour une lisibilité encore meilleure, vous pouvez utiliser within:

df <- within(df, est[b==0] <- (a[b==0]-5)/2.533)

Les résultats, quelle que soit la méthode choisie:

df
          a b      est
1  11.77000 2 0.000000
2  10.90000 3 0.000000
3  10.32000 2 0.000000
4  10.96000 0 2.352941
5   9.90600 0 1.936834
6  10.70000 0 2.250296
7  11.43000 1 0.000000
8  11.41000 2 0.000000
9  10.48512 4 0.000000
10 11.19000 0 2.443743

Comme d'autres l'ont fait remarquer, une solution alternative dans votre exemple consiste à utiliser ifelse.

72
Andrie

Essayez data.table l'opérateur := de /:

DT = as.data.table(df)
DT[b==0, est := (a-5)/2.533]

C'est rapide et court. Voir ces questions liées pour plus d'informations sur :=:

Pourquoi data.table a-t-il été défini :=

Quand dois-je utiliser l'opérateur := dans data.table

Comment supprimer des colonnes d'un data.frame

R self reference

27
Matt Dowle

Voici une approche. ifelse est vectorisé et vérifie toutes les lignes pour une valeur nulle de b et remplace est par (a - 5)/2.53 si c'est le cas.

df <- transform(df, est = ifelse(b == 0, (a - 5)/2.53, est))
17
Ramnath

Le R-inferno , ou la documentation de base de R, expliquera pourquoi utiliser df $ * n’est pas la meilleure approche ici. À partir de la page d'aide pour "[":

"L’indexation par [est similaire aux vecteurs atomiques et sélectionne une liste des éléments spécifiés. [[do. x $ name est équivalent à x [["name", exact = FALSE]]. De plus, le comportement de correspondance partielle de [[peut être contrôlé à l'aide de l'argument exact. "

Je recommande d'utiliser la notation [row,col] à la place. Exemple: 

Rgames: foo   
         x    y z  
   [1,] 1e+00 1 0  
   [2,] 2e+00 2 0  
   [3,] 3e+00 1 0  
   [4,] 4e+00 2 0  
   [5,] 5e+00 1 0  
   [6,] 6e+00 2 0  
   [7,] 7e+00 1 0  
   [8,] 8e+00 2 0  
   [9,] 9e+00 1 0  
   [10,] 1e+01 2 0  
Rgames: foo<-as.data.frame(foo)

Rgames: foo[foo$y==2,3]<-foo[foo$y==2,1]
Rgames: foo
       x y     z
1  1e+00 1 0e+00
2  2e+00 2 2e+00
3  3e+00 1 0e+00
4  4e+00 2 4e+00
5  5e+00 1 0e+00
6  6e+00 2 6e+00
7  7e+00 1 0e+00
8  8e+00 2 8e+00
9  9e+00 1 0e+00
10 1e+01 2 1e+01
6
Carl Witthoft

Une autre option serait d’utiliser case_when

require(dplyr)

transform(df, est = case_when(
    b == 0 ~ (a - 5)/2.53, 
    TRUE   ~ est 
))

Cette solution devient encore plus pratique si plus de 2 cas doivent être distingués, car elle permet d’éviter les constructions imbriquées if_else.

4
Holger Brandl

Voici ma solution avec une autre version pour résoudre mon problème avec if et max en lignes.

my.assign <- function(col1, col2, col3){
                       if(col2==0) {col3 <- col1} else {
                       col3 <- 0
                      }
              }

my.max <- function(col1, col2, col3){
                     if(col1 >= 10 ) {max_r <- max(col2, col3, na.rm=TRUE)} 
                         else { max_r <- col2 }
              }


df$est <- with(df,mapply(my.assign,col1=a, col2=b, col3=est))
df$max_row <- with(df,mapply(my.max,col1=a, col2=b, col3=est))

> df
      a b    est max_row
1  11.77000 2  0.000    2.00
2  10.90000 3  0.000    3.00
3  10.32000 2  0.000    2.00
4  10.96000 0 10.960   10.96
5   9.90600 0  9.906    0.00
6  10.70000 0 10.700   10.70
7  11.43000 1  0.000    1.00
8  11.41000 2  0.000    2.00
9  10.48512 4  0.000    4.00
10 11.19000 0 11.190   11.19
0
A. Suliman