Help pour réécrire une boucle for

Postez ici vos questions, réponses, commentaires ou suggestions - Les sujets seront ultérieurement répartis dans les archives par les modérateurs

Modérateur : Groupe des modérateurs

Jean-Emmanuel Longueville
Messages : 310
Enregistré le : 23 Fév 2011, 08:03

Help pour réécrire une boucle for

Messagepar Jean-Emmanuel Longueville » 14 Mar 2023, 15:00

Bonjour,
J'ai un dataframe assez conséquent sur ce dataframe je cherche à faire une nouvelle colonne par comparaison d'une date (colonne date_j du df) à deux vecteur d'intervaux construit précédement. Quand j'essaie un mutate et des if_else le résultat n'est pas convaincant.
Auriez vous une piste ?

Code : Tout sélectionner


for 
(i in 1:nrow(mlickstestPRPun)){
  dat <-ymd(pull(mlickstestPRPun[i, "date_j"]))
  if (sum(dat %within% intpr)>0){#On teste si y a une valeur true dans le vecteur FALSE=0 TRUE=1
    mlickstestPRPun[i,"testgrp2"] <- "postpr"
  }else if(sum(dat %within% intpu)>0){
    mlickstestPRPun[i,"testgrp2"] <- "postpu"
  }else{
    mlickstestPRPun[i,"testgrp2-"] <- "at"
  }
}

Merci
Jean-Emmanuel
Ingénieur d'étude LNEC

Pierre-Yves Berrard
Messages : 1029
Enregistré le : 12 Jan 2016, 23:30

Re: Help pour réécrire une boucle for

Messagepar Pierre-Yves Berrard » 14 Mar 2023, 15:12

Bonjour,
Avec un case_when ?
(difficile d'en dire plus sans exemple reproductible)
PY

Maxime Deniaux
Messages : 68
Enregistré le : 11 Fév 2022, 22:49
Contact :

Re: Help pour réécrire une boucle for

Messagepar Maxime Deniaux » 14 Mar 2023, 15:18

Et qu'est-ce qui n'est pas convaincant aussi. Manque des valeurs ? Des valeurs fausses ?

Nous faut un peu plus de détail !

Logez Maxime
Messages : 3138
Enregistré le : 26 Sep 2006, 11:35

Re: Help pour réécrire une boucle for

Messagepar Logez Maxime » 15 Mar 2023, 15:03

Bonjour,

A priori la meilleure chose à faire ici est de ne pas faire de boucle mais de vectoriser. Toutes les comparaisons que tu fais une à une peuvent être fait sur l'ensemble de tes données d'un seul coup ce qui te facilitera la tache et devrait réduire le temps de calcul.

Code : Tout sélectionner

# un tableau fictif
x <- seq(ymd("2000-01-01"), ymd("2002-12-01"), by = "month")
x <- as.character(x)
mlickstestPRPun <- data.frame(date_j = x)
intpr <- interval(ymd("2001-01-01"), ymd("2002-01-01"))
intpu <- interval(ymd("2002-02-01"), ymd("2002-10-01"))

dat <- ymd(mlickstestPRPun$date_j)
auxi <- rep("at", nrow(mlickstestPRPun))
auxi[dat %within% intpu] <- "postpu"
auxi[dat %within% intpr] <- "postpr"

mlickstestPRPun$testgrp2 <- auxi
Conseil : dans une boucle ne répète pas sans arrêt des opérations qui peuvent être fait en une seule fois en dehors de celle-ci. Par exemple, il vaut mieux transformer toutes tes dates en une seule fois avant la boucle, que de transformer chaque date avec ymd à chaque itération.

EDIT :
avec le case_when :

Code : Tout sélectionner

mlickstestPRPun %>% mutate(date_j = ymd(date_j),
  testgrp2 = case_when(date_j %within% intpr ~ "postpr",
    date_j %within% intpu ~ "postpu",
    TRUE ~ "at")

Cordialement,
Maxime

Jean-Emmanuel Longueville
Messages : 310
Enregistré le : 23 Fév 2011, 08:03

Re: Help pour réécrire une boucle for

Messagepar Jean-Emmanuel Longueville » 16 Mar 2023, 10:33

Merci Maxime pour ta réponse qui clarifie bien les choses et comme l'ordre est inchanger j'ai juste à faire un cbind entre le vecteur final et mon df initial ?
Ah oui le case_when une fonction super puissante...
Merci beaucoup pour vos réponses.
Malheureusement j'ai toujours un petit soucis voici un ecm qui permet de le présenter :

Code : Tout sélectionner

# un tableau fictif
<- seq(ymd("2000-01-01"), ymd("2001-12-01"), by = "month")
<- as.character(x)

licksdf <- data.frame(date_j = x)
intpr <- c(interval(ymd("2001-01-01"), ymd("2001-02-01")),interval(ymd("2002-10-01"), ymd("2002-12-01")))
intpu <- c(interval(ymd("2001-10-01"), ymd("2001-12-01")),interval(ymd("2002-01-01"), ymd("2002-02-01")))

#Nouvelle méthode avec le case_when
mlickstestPRPun <- licksdf |>
  mutate(date_j = ymd(date_j),
         date_wjd = lubridate::wday(date_j, label=TRUE, abbr= FALSE),
         testgrp = ifelse(date_wjd %in% c("lundi", "mardi"),"at","pt"),
         testgrp2 = case_when(sum(date_j %within% intpr)>~ "postpr", #Le sum ici est possible parce qu'il ne peut y avoir qu'un seul TRUE dans le %within%
                              sum(date_j %within% intpu)>~ "postpu",
                              TRUE ~ "at"),
         .groups = "drop")
         
 
#Ancienne méthode qui fonctionne proprement 
for (i in 1:nrow(mlickstestPRPun)){
  dat <-mlickstestPRPun[i, "date_j"]
  if (sum(dat %within% intpr)>0){#On teste si y a une valeur true dans le vecteur FALSE=0 TRUE=1
    mlickstestPRPun[i,"testgrp3"] <- "postpr"
  }else if(sum(dat %within% intpu)>0){
    mlickstestPRPun[i,"testgrp3"] <- "postpu"
  }else{
    mlickstestPRPun[i,"testgrp3"] <- "at"
  }
}
Jean-Emmanuel
Ingénieur d'étude LNEC

Logez Maxime
Messages : 3138
Enregistré le : 26 Sep 2006, 11:35

Re: Help pour réécrire une boucle for

Messagepar Logez Maxime » 16 Mar 2023, 12:05

Re,

Dans ce cas là il te faut comparer à chaque intervalle de tes vecteurs et de joindre les résultats en les comparant avec un "ou" :

Code : Tout sélectionner

x <- seq(ymd("2000-01-01"), ymd("2002-12-01"), by = "month")
x <- as.character(x)
licksdf <- data.frame(date_j = x)

auxi <- rep("at", nrow(licksdf))
dat <- ymd(licksdf$date_j)
auxi[do.call("|", lapply(intpu, function(u) dat %within% u))] <- "postpu"
auxi[do.call("|", lapply(intpr, function(u) dat %within% u))] <- "postpr"
auxi
Pour ton case_when il y a deux soucis. D'une part telle que tu l'as écrit dans le mutate le sum va s'opérer sur l'ensemble de tes lignes et non pas ligne par ligne, donc dès qu'une valeur de ta colonne sera TRUE, la condition sera systématiquement vérifiée pour toutes les autres valeurs de ta colonne. C'est pour ça que tu te retrouves avec "postpr" tout le temps. D'autre part, chaque date ne va pas être comparée à chaque valeur de ton intervalle. Comme ton vecteur d'intervalle est plus petit que le vecteur de date, alors il va être recyclé. Ce qui veut dire que la première date va être comparée au premier intervalle de ton vecteur, que la deuxième date va être comparée au deuxième intervalle, et ainsi de suite. Donc toutes les dates sur des lignes impaires sont comparées au 1er intervalle et toutes les dates sur des lignes paires sont comparées au 2ème intervalle. Tu n'as pas le choix que de forcer la comparaison des dates à chacune des valeurs de ton intervalle.

Dans la boucle ça fonctionne parce que comme tu prends chaque date séparément, c'est elle qui est recyclée pour pouvoir faire la comparaison avec les deux intervalles de tes vecteurs.

Cordialement,
Maxime

Jean-Emmanuel Longueville
Messages : 310
Enregistré le : 23 Fév 2011, 08:03

Re: Help pour réécrire une boucle for

Messagepar Jean-Emmanuel Longueville » 16 Mar 2023, 14:55

Oki je pensais qu'il fonctionnait ligne par ligne c'est parfois ce qui me perd dans le fonctionnement des fonctions plyr/dplyr.
Du coup le case_when est pas utilisable ici ?
Jean-Emmanuel
Ingénieur d'étude LNEC

Logez Maxime
Messages : 3138
Enregistré le : 26 Sep 2006, 11:35

Re: Help pour réécrire une boucle for

Messagepar Logez Maxime » 16 Mar 2023, 20:38

Quand tu fais appel a mutate de dplyr, alors sur ce quoi tu vas travailler c'est sur le vecteur de données contenu dans la colonne à laquelle tu fais appel. Quand tu fais tab %>% mutate(fun(date_j)) tu appliques bien la fonction fun sur l'ensemble des données contenues dans la colonne date_j. Ca revient à faire fun(tab$date_j). Tu travailles en vectoriel, tu ne fais fun(tab$date_j[1]) puis fun(tab$date_j[2]), fun(tab$date_j[3]), ...
Il faut raisonner en vectoriel. Quand tu as deux vecteurs de longueurs différentes que tu utilises ensemble dans la même commande, alors le plus petit est recyclé pour que toutes les opérations puissent avoir lieu sur le vecteur le plus long.
Par exemple :

Code : Tout sélectionner

x <- 1:10
y <- c(0, 10, 100)
Si tu multiplies x par y et bien y est trop court par rapport à x, donc les valeurs de y vont être recyclées.

Code : Tout sélectionner

x * y
# revient à calculer
x * rep_len(y, length(x))
Si tu n'avais pas ce recyclage, alors seules les 3 premières valeurs de x seraient multipliées par les valeurs de y.

C'est ton problème ici. Tu as les valeurs d'un vecteur (ta colonne) que tu compares à un autre vecteur de longueur 2. Donc ce dernier est recyclé, et au lieu de comparer chaque valeur de ta colonne avec tes deux intervalles, tu les compares qu'avec un seul des 2.
Le case_when peut fonctionner mais il faut expliciter les comparaisons aux deux intervalles de chaque vecteur.

Code : Tout sélectionner

licksdf %>% mutate(date_j = ymd(date_j),
  testgrp2 = case_when(date_j %within% intpr[1] ~ "postpr",
   date_j %within% intpr[2] ~ "postpr",
    date_j %within% intpu[1] ~ "postpu",
    date_j %within% intpu[2] ~ "postpu",
    TRUE ~ "at"))
# equivalent a
licksdf %>% mutate(date_j = ymd(date_j),
  testgrp2 = case_when(date_j %within% intpr[1] | date_j %within% intpr[2] ~ "postpr",
    date_j %within% intpu[1] | date_j %within% intpu[2]~ "postpu",
    TRUE ~ "at"))

Avec dplyr tu peux travailler en 'ligne' avec rowwise, mais je ne suis pas sur qu'ici ça soit très efficace si jamais tu venais à avoir beaucoup de lignes. Il vaut mieux travailler en vectoriel.

Code : Tout sélectionner

licksdf %>%
mutate(date_j = ymd(date_j)) %>%
rowwise() %>%
mutate(testgrp2 = case_when(any(date_j %within% intpr) ~ "postpr",
any(date_j %within% intpu) ~ "postpu",
TRUE ~ "at"
))

Cordialement,
Maxime


Retourner vers « Questions en cours »

Qui est en ligne

Utilisateurs parcourant ce forum : Google [Bot] et 1 invité