Simplification de code avec boucle For pour créer une liste finale unique

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

Samuel Pereira Dias
Messages : 62
Enregistré le : 07 Mar 2014, 11:09

Simplification de code avec boucle For pour créer une liste finale unique

Messagepar Samuel Pereira Dias » 29 Sep 2020, 11:41

Alerte !! reprise du code sur R, et volonté de reprendre depuis le début ... !!

Bonjour,

Je dispose d'un jeux de données de 58 colonnes, où les manipulations que je cherche à faire sont sur les colonnes c(30,31,35,36,40,41,45,46,50,51).
Ci dessous un extrait

Code : Tout sélectionner

COR_HAB1   REC_HAB1   HAB2   COR_HAB2   HAB3   COR_HAB3   HAB4   COR_HAB4   HAB5   COR_HAB5
62.12   0,6   festucion scopariae   36.434   rhododendro ferruginei _ vaccinion myrtilli   31.42            
41.12   1                        
36.434   0,75   saxifragion mediae   62.12   sedion pyrenaici   36.2            
36.434   0,75   saxifragion mediae   62.12   sedion pyrenaici   36.2            



Il s'agit donc d'habitat (festucion scopariae) avec un code Corine (62.12) spécifique à chacun des habitats. Cette association se répète plusieurs fois au sein des colonnes et entre les colonnes
J'aimerai obtenir un DF finale à deux colonnes "Hab", (le nom de l'habitat) et "Cori" (pour son code corine) me présentant toutes les mentions UNIQUE présents au sein du jeux de données.

J'ai effectué le code ci-dessous qui fonctionne, mais j'aimerais vraiment le simplifier en utilisant des boucles for. Mon objectif réel est de m'améliorer en indexation et utilisation des boucles.


Comment simplifier le code suivant à l'aide boucle for SVP ?
(J'ai déjà largement essayé sans succès, promis!)

Si vous avez compris mon besoin, je sais qu'il s'agira d'une formalité pour vous.
Si jamais vous avez le temps de commenter vos lignes de codes, ce serait super!

Code : Tout sélectionner

data<-read.delim("data.csv", h=T,sep=";") # Le jeu de donnée

A<-data[,c(30,31)] # j'extrait les deux première colonnes
A$Hab_COR<-paste(A[,1],A[,2], sep="_") # Je concatene dans une nouvelle colonne
colnames(A)<-c("hab","cori","hab_cor") # je renomme les colonnes pour le rbind finale

# je répète l'opération pour toutes mes colonnes.

B<-data[,c(35,36)]
B$Hab_COR<-paste(B[,1],B[,2], sep="_")
colnames(B)<-c("hab","cori","hab_cor")

C<-data[,c(40,41)]
C$Hab_COR<-paste(C[,1],C[,2], sep="_")
colnames(C)<-c("hab","cori","hab_cor")

D<-data[,c(45,46)]
D$Hab_COR<-paste(D[,1],D[,2], sep="_")
colnames(D)<-c("hab","cori","hab_cor")

E<-data[,c(50,51)]
E$Hab_COR<-paste(E[,1],E[,2], sep="_")
colnames(E)<-c("hab","cori","hab_cor")

GG<-rbind(A,B,C,D,E) # Je constitue un seul DF

GG<-GG[!duplicated(GG$hab_cor),]  # Je supprime tous les doublons



Je peux vous charger un jeux de données plus conséquent si nécessaire, il faudra me dire par quel moyen.

Merci!!
S.
Samuel Pereira Dias

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

Re: Simplification de code avec boucle For pour créer une liste finale unique

Messagepar Pierre-Yves Berrard » 29 Sep 2020, 12:13

Bonjour,

Une suggestion (il y a certainement moyen de faire autrement, mais je respecte votre souhait initial de faire une boucle for) :

Code : Tout sélectionner

# resultat initialisé vide
res <- data.frame(hab = character(0), cori = character(0))

#  boucle
for (i in seq(30, 50, by = 5)) {
  res <- rbind(
    res,
    setNames(data[ , c(i, i + 1)], c("hab", "cori"))
  )
}

# dédoublonne
res <- res[!duplicated(res), 

Code non testé, votre exemple n'étant pas reproductible.
PY

Mickael Canouil
Messages : 1315
Enregistré le : 04 Avr 2011, 08:53
Contact :

Re: Simplification de code avec boucle For pour créer une liste finale unique

Messagepar Mickael Canouil » 29 Sep 2020, 14:11

Bonjour,

en me basant sur le code de Pierre-Yves, mais avec un faux jeux de données et en initialisant à la bonne taille "res".

Code : Tout sélectionner

dta <- do.call("cbind", replicate(5, data.frame(hab = letters, cori = LETTERS), simplify = FALSE))
names(dta) <- paste0(names(dta), rep(1:5, each = 2))

head(dta)
#>   hab1 cori1 hab2 cori2 hab3 cori3 hab4 cori4 hab5 cori5
#> 1    a     A    a     A    a     A    a     A    a     A
#> 2    b     B    b     B    b     B    b     B    b     B
#> 3    c     C    c     C    c     C    c     C    c     C
#> 4    d     D    d     D    d     D    d     D    d     D
#> 5    e     E    e     E    e     E    e     E    e     E
#> 6    f     F    f     F    f     F    f     F    f     F

# resultat initialisé vide
res <- data.frame(
  hab = character(nrow(dta) * 5), 
  cori 
= character(nrow(dta) * 5)
)

#  boucle
pos_cols <- seq(1, ncol(dta), by = 2)
for (i in seq_along(pos_cols)) {
  res[(+ nrow(dta) * (- 1)):(nrow(dta) * i), ] <- dta[, c(pos_cols[i], pos_cols[i] + 1)]
}

head(res)
#>   hab cori
#> 1   a    A
#> 2   b    B
#> 3   c    C
#> 4   d    D
#> 5   e    E
#> 6   f    F

# dédoublonne
res <- res[!duplicated(res), ]



A noter, que ce type d'opération peut-être faite via reshape2::melt ou encore tidyr::pivot_longer

Code : Tout sélectionner

library(tidyr)
dta <- do.call("cbind", replicate(5, data.frame(hab = letters, cori = LETTERS), simplify = FALSE))
names(dta) <- paste0(names(dta), rep(1:5, each = 2))

pivot_longer(
  data = dta, 
  cols 
= everything(),
  names_to = c(".value", NA), # replace "NA" with "id" to have a column named "id" with unique row identifier here.
  names_pattern = "([:alpha:]*)([:digit:]*)", 
)
#> # A tibble: 130 x 2
#>    hab   cori 
#>    <chr> <chr>
#>  1 a     A    
#>  2 a     A    
#>  3 a     A    
#>  4 a     A    
#>  5 a     A    
#>  6 b     B    
#>  7 b     B    
#>  8 b     B    
#>  9 b     B    
#> 10 b     B    
#> # … with 120 more rows         


PS : un extrait "complet" des lignes aurait été bien.

EDIT : un benchmark
Avec 26 lignes:
Image
Avec 1 000 fois plus de lignes (26 000):
Image

Cordialement,
Mickaël
mickael.canouil.fr | rlille.fr

Samuel Pereira Dias
Messages : 62
Enregistré le : 07 Mar 2014, 11:09

Re: Simplification de code avec boucle For pour créer une liste finale unique

Messagepar Samuel Pereira Dias » 29 Sep 2020, 17:27

Pierre-Yves Berrard a écrit :Bonjour,

Une suggestion (il y a certainement moyen de faire autrement, mais je respecte votre souhait initial de faire une boucle for) :

Code : Tout sélectionner

# resultat initialisé vide
res <- data.frame(hab = character(0), cori = character(0))

#  boucle
for (i in seq(30, 50, by = 5)) {
  res <- rbind(
    res,
    setNames(data[ , c(i, i + 1)], c("hab", "cori"))
  )
}

# dédoublonne
res <- res[!duplicated(res), ]

Code non testé, votre exemple n'étant pas reproductible.




Bonjour Pierre-Yves,
Votre code marche à merveille directement sur mes données.
Le principe du i+1 est bien ce que j'essayai de faire sans y avoir réussi.
La création, et l'incrémentation des résultats de la boucle dans un objet était également un casse tête. Je ne connaissais pas setNames.
Pourriez-vous m'expliquer quelle est sa contribution réelle au sein de la boucle ? Elle affecte c("hab", "cori") à chaque tournée de boucle, ce qui permet à la fonction rbind de fonctionner (comme il exige des df avec les mêmes colnames) ?

Aussi, je comprends qu'une grande partie de la réussite d'une boucle commence par une définition "maline" de son i.

Internet regorge d'informations sur le codage sur R. Si toutefois vous auriez un site ou un document en particulier à me conseiller, je suis preneur.
J'ai pris connaissance du principe du code reproductible, et ferai mieux la prochaine fois. Toutefois mon df initial a plus de 15000 lignes. J'utiliserai la fonction dput en limitant à quelques lignes de données.

Encore merci,
Bien cordialement,
Samuel
Samuel Pereira Dias

Samuel Pereira Dias
Messages : 62
Enregistré le : 07 Mar 2014, 11:09

Re: Simplification de code avec boucle For pour créer une liste finale unique

Messagepar Samuel Pereira Dias » 29 Sep 2020, 17:32

Mickael Canouil a écrit :Bonjour,

en me basant sur le code de Pierre-Yves, mais avec un faux jeux de données et en initialisant à la bonne taille "res".

Code : Tout sélectionner

dta <- do.call("cbind", replicate(5, data.frame(hab = letters, cori = LETTERS), simplify = FALSE))
names(dta) <- paste0(names(dta), rep(1:5, each = 2))

head(dta)
#>   hab1 cori1 hab2 cori2 hab3 cori3 hab4 cori4 hab5 cori5
#> 1    a     A    a     A    a     A    a     A    a     A
#> 2    b     B    b     B    b     B    b     B    b     B
#> 3    c     C    c     C    c     C    c     C    c     C
#> 4    d     D    d     D    d     D    d     D    d     D
#> 5    e     E    e     E    e     E    e     E    e     E
#> 6    f     F    f     F    f     F    f     F    f     F

# resultat initialisé vide
res <- data.frame(
  hab = character(nrow(dta) * 5), 
  cori 
= character(nrow(dta) * 5)
)

#  boucle
pos_cols <- seq(1, ncol(dta), by = 2)
for (i in seq_along(pos_cols)) {
  res[(+ nrow(dta) * (- 1)):(nrow(dta) * i), ] <- dta[, c(pos_cols[i], pos_cols[i] + 1)]
}

head(res)
#>   hab cori
#> 1   a    A
#> 2   b    B
#> 3   c    C
#> 4   d    D
#> 5   e    E
#> 6   f    F

# dédoublonne
res <- res[!duplicated(res), 



A noter, que ce type d'opération peut-être faite via reshape2::melt ou encore tidyr::pivot_longer

Code : Tout sélectionner

library(tidyr)
dta <- do.call("cbind", replicate(5, data.frame(hab = letters, cori = LETTERS), simplify = FALSE))
names(dta) <- paste0(names(dta), rep(1:5, each = 2))

pivot_longer(
  data = dta, 
  cols 
= everything(),
  names_to = c(".value", NA), # replace "NA" with "id" to have a column named "id" with unique row identifier here.
  names_pattern = "([:alpha:]*)([:digit:]*)", 
)
#> # A tibble: 130 x 2
#>    hab   cori 
#>    <chr> <chr>
#>  1 a     A    
#>  2 a     A    
#>  3 a     A    
#>  4 a     A    
#>  5 a     A    
#>  6 b     B    
#>  7 b     B    
#>  8 b     B    
#>  9 b     B    
#> 10 b     B    
#> # … with 120 more rows          


PS : un extrait "complet" des lignes aurait été bien.

EDIT : un benchmark
Avec 26 lignes:
Image
Avec 1 000 fois plus de lignes (26 000):
Image

Cordialement,



Bonjour Mickaël,

J'ai bien apprécié votre façon de créer un jeu de donnée théorique. Je prendrai un peu plus de temps pour comprendre le fonctionnement de la boucle elle même.
Je note donc la disponibilité de package plus performant pour ce type de manipulation.
Je vais néanmoins me concentrer sur l'écriture de mes codes avec les fonctions de base avant d'entreprendre plus.

Merci beaucoup pour votre aide,
Bien cordialement,
Samuel
Samuel Pereira Dias

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

Re: Simplification de code avec boucle For pour créer une liste finale unique

Messagepar Pierre-Yves Berrard » 30 Sep 2020, 08:17

Samuel Pereira Dias a écrit :Je ne connaissais pas setNames.
Pourriez-vous m'expliquer quelle est sa contribution réelle au sein de la boucle ? Elle affecte c("hab", "cori") à chaque tournée de boucle, ce qui permet à la fonction rbind de fonctionner (comme il exige des df avec les mêmes colnames) ?

C'est bien pour rbind. setNames affecte des noms à un objet. Cela évite ici de créer une table intermédiaire et de modifier via colnames(tmp) <- c(...), comme dans votre code initial.
PY

Mickael Canouil
Messages : 1315
Enregistré le : 04 Avr 2011, 08:53
Contact :

Re: Simplification de code avec boucle For pour créer une liste finale unique

Messagepar Mickael Canouil » 30 Sep 2020, 11:05

Samuel Pereira Dias a écrit :La création, et l'incrémentation des résultats de la boucle dans un objet était également un casse tête.

C'est typiquement ce qu'il ne faut pas faire avec une boucle for dans R (et même de façon général).

En version simple, il ne faut pas faire une incrémentation d'un objet dont la taille est connue, en cause le temps de calcul et l'allocation mémoire en augmentation à chaque itération :

Code : Tout sélectionner

res <- numeric(0)
for (i in 1:10) res <- c(res, i


Avec la taille connue, on rempli les éléments, temps de calcul et allocation mémoire constant à chaque itération :

Code : Tout sélectionner

res <- numeric(10)
for (i in 1:10) res[i] <- i
Mickaël
mickael.canouil.fr | rlille.fr


Retourner vers « Questions en cours »

Qui est en ligne

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