Au cours de cette séance, nous allons continuer à nous familiariser avec les packages du tidyverse et ggplot2 pour explorer des données spatiales à l’échelle d’un site. Nous utiliserons le même jeu de données que dans la séance 1 et 2.
Nous entrons à présent dans la dernière partie de ce TP, et peut-être la plus utile… l’automatisation d’un code ! Ce processus signifie que l’on va demander au code de réaliser plusieurs fois la même opération mais sur des données différentes. Pour cela, il existe deux fonctions permettant de répéter un bout de code tant qu’une certaine condition ne sera pas atteinte : for() et while(). Nous allons les découvrir ensemble sur des exemples très simples, puis nous verrons un exemple avec le jeux de données archéologique.
for()Une boucle for() permet de répéter un bout de code tant qu’une certaine valeur (par convention, on l’appelle i) se situe dans un certain intervalle.
Créons un objet x contenant la valeur “0”.
x <- 0
Nous allons y ajouter des valeurs à l’aide d’une boucle :
i <- 35 # tout d'abord, on initialise la valeur i
for(i in 35:100) # conditions de la boucle : tant que i se trouve dans ("in") l'intervalle 1 à 5 (défini par ":"), répéter le code suivant.
{ # on ouvre la boucle
x <- c(x, i) # on place la valeur i dans l'objet x.
i <- i + 1 # on incrémente i de 1
}
Affichons le résultat :
x
## [1] 0 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
## [20] 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
## [39] 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
## [58] 91 92 93 94 95 96 97 98 99 100
Nous avons bien réussi à créer une suite de nombres ! Cet exemple est certes trivial, mais nous verrons un peu plus tard comment l’appliquer sur des données et avec des opérations plus complexes.
Petite note à ce stade : la dernière ligne de notre boucle, i <- i + 1, est facultative dans R. La fonction for() permet en effet d’incrémenter automatique la valeur i de 1 à chaque fin de boucle.
Voici la preuve :
y <- 0
for(i in 1:5) {
y <- c(y, i)
}
y # même résultat !
## [1] 0 1 2 3 4 5
while()L’autre type de boucle est moins fréquemment utilisée, mais peut toujours être utile. Il s’agit d’une boucle while() : c’est à dire que l’on va répéter le code tant qu’une certaine condition est remplie. Une fois qu’elle ne l’est plus, la boucle est terminée.
Essayons avec un autre exemple simple : nous allons créer un code qui va afficher une valeur tant que celle-ci est inférieure à 5.
i <- 1 # initialisation de la valeur
while(i <= 5) # cette ligne de code se lit : tant que i est inférieur ou égal à 5, répéter le code suivant
{ # début de la boucle
print(i) # afficher "i" dans la console
i <- i + 1 # incrémenter i de 1
# ATTENTION: l'incrémentation ne se fait pas automatiquement avec while() !!!
} # fin de la boucle
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
Si vous consultez la console, vous remarquez que l’on a affiché la valeur i tant que celle-ci est inférieure à 5 !
Dans la mesure du possible, nous vous conseillons d’utiliser plutôt les boucles for(), car ce sont elles qui sont plus utilisée dans la communauté. while() est également très bien et vous pouvez arriver au même résultat qu’avec une boucle for(), mais l’utilisation de for() fait vraiment partie de la “culture R” en tant que culture de programmation !
Reprenons ici l’un des exercices proposés dans le premier TP du workshop. Pour rappel, nous travaillons avec le jeu de données DartPoints. Commençons donc par ouvrir le jeu de données.
library(archdata)
data("DartPoints")
Maintenant, nous allons créer un tableau résumant la médiane et l’écart-type des différentes mesures prises sur les pointes de chaque type. Commençons par créer un tableau vide, dont chaque ligne correspond à un type de pointe, et chaque colonne correspond à une valeur statistique.
col <- c("Length_Median", "Length_Range", "Width_Median", "Width_Range", "Thickness_Median", "Thickness_Range")
row <- levels(DartPoints$Name)
df <- data.frame(matrix(nrow = length(row), ncol = length(col)))
colnames(df) <- col
rownames(df) <- row
df
## Length_Median Length_Range Width_Median Width_Range Thickness_Median
## Darl NA NA NA NA NA
## Ensor NA NA NA NA NA
## Pedernales NA NA NA NA NA
## Travis NA NA NA NA NA
## Wells NA NA NA NA NA
## Thickness_Range
## Darl NA
## Ensor NA
## Pedernales NA
## Travis NA
## Wells NA
Notre tableau est créé et il est pour l’instant vide. Remplissons pour commencer la première colonne.
# On commence par sélectionner les lignes qui correspondent aux pointes de type "Darl" dans le jeu de données.
Darl <- DartPoints[DartPoints$Name == "Darl", ]
# Puis on créé les stats pour chaque valeur et on la stocke dans la première ligne de la colonne correspondante.
df$Length_Median[1] <- median(Darl$Length)
df$Length_Range[1] <- max(Darl$Length) - min(Darl$Length)
df$Width_Median[1] <- median(Darl$Width)
df$Width_Range[1] <- max(Darl$Width) - min(Darl$Width)
df$Thickness_Median[1] <- median(Darl$Thickness)
df$Thickness_Range[1] <- max(Darl$Thickness) - min(Darl$Thickness)
df
## Length_Median Length_Range Width_Median Width_Range Thickness_Median
## Darl 40.15 23.6 16.95 8.8 5.85
## Ensor NA NA NA NA NA
## Pedernales NA NA NA NA NA
## Travis NA NA NA NA NA
## Wells NA NA NA NA NA
## Thickness_Range
## Darl 4.1
## Ensor NA
## Pedernales NA
## Travis NA
## Wells NA
Faire cela pour tous les types serait très long et verbeux (ie., trop de lignes de code!), ce qui rend le code moins lisible et moins facilement adaptable ! Or, nous allons exécuter les mêmes opérations en changeant uniquement le type de pointe. C’est un cas idéal pour créer un loop. / Voyons donc comment faire :
for(i in 1:nrow(df)){ # on veut que le code se répète pour chaque type de pointe ; nous demandons donc à la machine de répéter le code suivant tant que i se trouve dans l'intervalle [1 ; nombre de types (=nombre de lignes dans le tableau, d'où la fonction nrow())]
type <- DartPoints[DartPoints$Name == levels(DartPoints$Name)[i], ] # on sélectionne les lignes correspondant au ième type
# on calcule les statistiques et on les range dans la ième ligne pour chaque colonne
df$Length_Median[i] <- median(type$Length)
df$Length_Range[i] <- max(type$Length) - min(type$Length)
df$Width_Median[i] <- median(type$Width)
df$Width_Range[i] <- max(type$Width) - min(type$Width)
df$Thickness_Median[i] <- median(type$Thickness)
df$Thickness_Range[i] <- max(type$Thickness) - min(type$Thickness)
}
Vérifions notre travail…
df
## Length_Median Length_Range Width_Median Width_Range Thickness_Median
## Darl 40.15 23.6 16.95 8.8 5.85
## Ensor 42.30 20.3 21.10 9.8 6.25
## Pedernales 55.75 74.1 26.85 30.8 8.10
## Travis 49.10 29.4 21.10 4.5 8.30
## Wells 53.65 24.2 23.50 10.7 7.05
## Thickness_Range
## Darl 4.1
## Ensor 2.8
## Pedernales 4.3
## Travis 3.2
## Wells 3.5
Notre tableau s’est rempli automatiquement avec les bonnes valeurs ! / Bravo, vous avez réalisé votre première boucle sur R !
Mettons en pratique ce concept sur le jeu de données du site de la Forêt. Nous allons créer plusieurs plans de répartition en fonction de la dimensions des pièces. /
Préparons un peu notre script en ouvrant des bibliothèques
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.5.1 ✔ tibble 3.2.1
## ✔ lubridate 1.9.3 ✔ tidyr 1.3.1
## ✔ purrr 1.0.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
A présent, ouvrons le jeu de données
jdd <- as_tibble(read.csv("data/data_exemple.csv", header = TRUE, sep = ","))
head(jdd)
## # A tibble: 6 × 8
## numero type x_cm y_cm longueur_cm largeur_cm silex raccord
## <int> <chr> <int> <int> <dbl> <dbl> <chr> <int>
## 1 1 gravette 10 60 5 1.5 type A NA
## 2 2 gravette 30 50 5.9 1.8 type C NA
## 3 3 picardie 50 80 3.2 1.2 type B 1
## 4 4 percoir 260 100 4.7 2.1 type B NA
## 5 5 burin 240 120 6 2.4 type B 2
## 6 6 burin diedre 280 150 8 3.5 type A NA
jdd$type_fac <- factor(jdd$type,
# Généralement, les levels sont dans l'ordre alphabétique ; il est possible de changer leur ordre si vous le voulez !
levels = c("nucleus a eclats laminaires", "burin du Raysse",
"eclat", "lame", "chute de burin", "burin",
"burin diedre", "percoir", "picardie", "gravette"))
levels(jdd$type_fac)
## [1] "nucleus a eclats laminaires" "burin du Raysse"
## [3] "eclat" "lame"
## [5] "chute de burin" "burin"
## [7] "burin diedre" "percoir"
## [9] "picardie" "gravette"
Tout a l’air en ordre ! Nous pouvons donc commencer :) /
Je copie-colle tout d’abord le code qui permet de créer notre fond de plan. Un petit message d’erreur s’affiche, indiquant que les levels des valeurs ne correspondent pas aux échelles de couleur manuelles. C’est tout à fait normal car nous n’avons pas encore “nourrit” notre code avec les enregistrements ! Nous verrons plus tard comment les ajouter au moyen d’une boucle et de la fonctionnalité “+” de ggplot2
carres.x <- c("A", "B", "C", "D")
carres.y <- c("1", "2", "3", "4")
shape.pal <- c("nucleus a eclats laminaires" = 7, "burin du Raysse" = 12,
"eclat" = 22, "lame" = 23, "chute de burin" = 21, "burin" = 8,
"burin diedre" = 24, "percoir" = 10, "picardie" = 14,
"gravette" = 3)
color.pal <- c("type A" = "#7AD151FF", "type B" = "#2A788EFF", "type C" = "#440154FF")
plan <- ggplot2::ggplot() +
theme_linedraw() +
theme(axis.ticks = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_blank(),
panel.grid.major = element_line(),
panel.grid.minor = element_blank()) +
scale_x_continuous(limits = c(0, 400), breaks = seq(0, 400, by = 100)) +
scale_y_continuous(limits = c(0, 400), breaks = seq(0, 400, by = 100)) +
ylab(element_blank()) +
xlab(element_blank()) +
annotate(geom = "text", x = c(seq(from = 50, to = 400, by = 100)), y = 10, label = carres.x) +
annotate(geom = "text", x = 10, y = c(seq(from = 50, to = 400, by = 100)), label = carres.y) +
scale_shape_manual(values = shape.pal) +
scale_color_manual(values = color.pal) +
scale_fill_manual(values = color.pal)
plan
## Warning: No shared levels found between `names(values)` of the manual scale and the
## data's shape values.
## Warning: No shared levels found between `names(values)` of the manual scale and the
## data's colour values.
## Warning: No shared levels found between `names(values)` of the manual scale and the
## data's fill values.
Tout d’abord, rafraîchissons nous la mémoire sur les statistiques basiques du jeu de données.
summary(jdd)
## numero type x_cm y_cm longueur_cm
## Min. : 1 Length:33 Min. : 10.0 Min. : 50.0 Min. : 1.4
## 1st Qu.: 9 Class :character 1st Qu.: 50.0 1st Qu.: 90.0 1st Qu.: 4.5
## Median :17 Mode :character Median :210.0 Median :280.0 Median : 5.9
## Mean :17 Mean :174.8 Mean :217.6 Mean : 5.8
## 3rd Qu.:25 3rd Qu.:260.0 3rd Qu.:340.0 3rd Qu.: 7.4
## Max. :33 Max. :290.0 Max. :390.0 Max. :10.2
##
## largeur_cm silex raccord type_fac
## Min. :0.500 Length:33 Min. :1.0 eclat :15
## 1st Qu.:1.900 Class :character 1st Qu.:1.0 lame : 8
## Median :2.800 Mode :character Median :1.5 percoir : 2
## Mean :2.976 Mean :1.5 gravette : 2
## 3rd Qu.:3.700 3rd Qu.:2.0 burin du Raysse: 1
## Max. :7.100 Max. :2.0 (Other) : 4
## NA's :29 NA's : 1
La longueur des pièces se situe entre 1.4 et 10.2 cm. Je vous propose donc de créer un plan pour des intervalles de longueurs tous les 2 cm. Nous allons donc devoir combiner les commandes de subsetting et le loop for().
i <- 1
pace <- 2 # ici il s'agit de la valeur d'intervalle ; dans ce cas concret, il est nécessaire de la spécifier étant donné qu'elle est différente de la valeur par défaut (1)
Je crée également un dossier afin de pouvoir sauvegarder les plots au fur et à mesure.
dir.create("figures/plans_fct_longueur")
## Warning in dir.create("figures/plans_fct_longueur"):
## 'figures/plans_fct_longueur' existe déjà
Démarrons la boucle !
for(i in 1:10) { # Cette boucle va s'exécuter tant que i sera compris entre 1 et 10
## Je commence par créer les bornes de l'intervalle i
borne.min <- i # le minimum est i
borne.max <- i + pace # et le maximum est i + pace, soit i + 2
## Ensuite, je subset le jeux de données pour ne sélectionner que les pièces dont la longueur est comprise entre la borne min et la borne max de mon intervalle.
repart <- subset(jdd, longueur_cm >= borne.min & longueur_cm <= borne.max)
## maintenant, on crée le plan de répartition avec les données sélectionnées
plan +
geom_point(data = repart, mapping = aes(x = x_cm, y = y_cm, color = silex,
fill = silex, shape = type_fac, size = 1)) +
# titre et légende
labs(title = paste0("Répartition spatiale des pièces dont L est comprise entre ", borne.min, " et ", borne.max, "cm"),
### NB : ici, on va utiliser la fonction paste0() pour adapter le titre de la figure à chaque intervalle.
### Cette fonction est extrêmement utile, retenez la ! Elle permet de coller côte à côte des objets en les transformant en texte.
color = "Matière première",
shape = "Catégorie d'objet") +
guides(fill = "none", size = "none")
## Enfin, on sauvegarde la figure dans le dossier "plans_fct_longueur" en adaptant son titre avec paste() - cette fonction est un peu différente de paste0() en cela qu'elle recquiert que l'on précise un séparateur à ajouter entre les blocs de texte. Ici ce sera "_" (argument "sep").
ggsave(paste("figures/plans_fct_longueur/repart_pieces_entre ", borne.min, "et", borne.max, ".png", sep = "_"),
width = 6, height = 4)
}
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_point()`).
## Removed 1 row containing missing values or values outside the scale range
## (`geom_point()`).
FELICITATIONS ! Vous avez fait vos premières boucles sur R !!