🧐Cliquer ici pour les diapos de cours

Pour cette partie vous tiendrez le rôle d’un gérant de zoo, qui s’interroge sur la comptabilité de son établissement… Vous serez ainsi amenés à manipuler différents tableaux de données, de faire des petits calculs, quelques graphiques de synthèse, etc.

1. Table nutritionnelle par espèce

Le dossier fourni ici sous forme de zip ou ici contient un sous-dossier “fiches_especes”.

Ouvrez quelques-unes de ces fiches. Elles sont toutes organisées selon un modèle commun. Nous souhaiterions mettre toutes les informations qu’elles contiennent sous forme d’un seul et même tableau, dont les premières lignes ressembleraient à ceci:

1.1 Vérifier l’existence d’une fiche pour une espèce

Compléter le code pour vérifier l’existence d’un fichier (dans le répertoire du serveur “data/fiches_especes/”) pour une espèce donnée. Un message sera envoyé en cas de non-existence du fichier. Vous pourrez utiliser la fonction file.exists() qui teste l’existence d’un fichier au chemin indiqué en argument.

espece="renard blanc"
chemin=paste0("data/fiches_especes/fiche_",____,".csv")

if(___){
  print("La fiche pour cette espèce n'existe pas")
}
espece="renard blanc"
chemin=paste0("data/fiches_especes/fiche_",espece,".csv")

if(!file.exists(chemin)){
  print("La fiche pour cette espèce n'existe pas")
}

Vous pouvez également tester ce qui se passe quand il y a effectivement une fiche pour l’espèce considérée (par exemple, “antilope”).

1.2 Lire une fiche pour une espèce

Voici l’allure d’une fiche brute:

Complétez la fonction suivante pour qu’à partir d’un nom d’espèce (par exemple, “antilope”), la fonction lise la fiche correspondante et renvoie son contenu brut:

lit_fiche=function(______){
  fiche=readr::read_csv2(paste0("data/fiches_especes/fiche_",
                         espece,
                         ".csv"), col_names=FALSE)
  return(fiche)
}
lit_fiche("antilope")
lit_fiche=function(espece){
  fiche=readr::read_csv2(paste0("data/fiches_especes/fiche_",
                         espece,
                         ".csv"), col_names=FALSE)
  return(fiche)
}
fiche=lit_fiche("antilope")

1.3 Lire et nettoyer une fiche: compréhension des opérations

Examinez ces transformations pour la fiche :

fiche_nettoyee=fiche %>% 
  tidyr::separate(X1,":", into=c("categorie","quantite")) %>% 
  mutate(quantite=str_replace_all(quantite,"[\\s%kg]","")) %>% 
  mutate(quantite=as.numeric(quantite))
fiche_nettoyee

1.4 Lire, nettoyer et reformater une fiche : mise en fonction

Chaque fiche comprend dans une même colonne le poids total d’aliment (quand categorie== “quantite”) et les proportions des différents types d’aliments (toutes les autres lignes). C’est en fait un peu mélanger les torchons et les serviettes!!

On va plutôt essayer de revenir dans une logique “tidy” (où une colonne=une variable) et d’indiquer pour chaque catégorie d’aliment la proportion d’une part et le poids d’autre part…

C’est ce que permettent de réaliser les lignes suivantes, ici par exemple pour la fiche “antilope”:

# lecture de la fiche brute
fiche=readr::read_csv2(paste0("data/fiches_especes/fiche_",
                              "antilope",
                              ".csv"), col_names=FALSE)
fiche
# nettoyage
fiche_nettoyee=fiche %>% 
  tidyr::separate(X1,":", into=c("categorie","quantite")) %>% 
  mutate(quantite=str_replace_all(quantite,"[\\s%kg]","")) %>% 
  mutate(quantite=as.numeric(quantite))
fiche_nettoyee
# recup quantite totale
quantite_totale=fiche_nettoyee %>%
  filter(categorie=="quantite") %>% 
  pull(quantite)
quantite_totale
## [1] 3
# calcul du proportion et du poids pour chaque catégorie d'aliment
fiche_formatee=fiche_nettoyee %>% 
  filter(categorie!="quantite") %>%
  mutate(proportion=quantite/100) %>% 
  mutate(poids=quantite_totale*proportion) %>% 
  select(-quantite)
fiche_formatee

Pour l’instant, la fonction lit_fiche() lit la fiche et la renvoie dans sa forme brute: il faut donc ajouter quelques lignes pour que cette fonction renvoie la fiche formatée (comme on l’attend vu son nom…). La librairie tidyverse est déjà chargée dans l’environnement. Je vous laisse compléter la fonction en vous inspirant des lignes de code ci-dessus:

# Définir la fonction:
formate_fiche=function(espece){
  fiche=lit_fiche(espece)
  fiche_formatee=_____
  ________
  ________
  ________

  return(fiche_formatee)
}

# Utiliser la fonction:
formate_fiche("lion")
# Définir la fonction:
formate_fiche=function(espece){
  fiche=lit_fiche(espece)
  fiche_nettoyee=fiche %>% 
  tidyr::separate(X1,":", into=c("categorie","quantite")) %>% 
  mutate(quantite=str_replace_all(quantite,"[\\s%kg]","")) %>% 
  mutate(quantite=as.numeric(quantite))
  
  # recup quantite totale
  quantite_totale=fiche_nettoyee %>%
    filter(categorie=="quantite") %>% 
    pull(quantite)
  
  # calcul du proportion et du poids pour chaque catégorie d'aliment
  fiche_formatee=fiche_nettoyee %>% 
    filter(categorie!="quantite") %>%
    mutate(proportion=quantite/100) %>% 
    mutate(poids=quantite_totale*proportion) %>% 
    select(-quantite)
  
  return(fiche_formatee)
}

# Utiliser la fonction:
formate_fiche("lion")

1.5 Rajouter une option “sommer” pour l’utilisateur

Nous souhaiterions maintenant avoir à notre disposition une fonction import_fiche(), basée sur formate_fiche(), qui permettrait à l’utilisateur de préciser s’il souhaite les résultats par catégorie (par exemple en fixant un argument sommer=FALSE) ou si au contraire il souhaite obtenir les résultats sommés (dans ce cas la valeur d’argument serait sommer=TRUE).

On obtiendrait ainsi, pour un appel

import_fiche("singe",sommer=FALSE)

Et pour l’appel

import_fiche("singe",sommer=TRUE)

Complétez le code de cette fonction import_fiche() et testez-la (vous pouvez faire varier la valeur par défaut de l’argument sommer pour en voir l’effet):

import_fiche=function(fiche,______){
  fiche=formate_fiche(fiche)
  ______
  ______
}
import_fiche("suricate",sommer=TRUE)
import_fiche("suricate",sommer=FALSE)
import_fiche=function(fiche,sommer=FALSE){
  fiche=formate_fiche(fiche)
  if(sommer){
    fiche=fiche %>% 
      summarise(proportion=sum(proportion),
                poids=sum(poids))
  }
  return(fiche)
}
import_fiche("suricate",sommer=TRUE)
import_fiche("suricate",sommer=FALSE)

2. Table nutritionnelle pour l’ensemble des espèces

2.1 Itérer une fonction sur l’ensemble des espèces

Voici les espèces pour lesquelles vous disposez d’une fiche d’alimentation:

alim_init=tibble::tibble(espece=c("antilope","autruche","chameau","lion","elephant","fennec",
                              "flamand rose","girafes","guepard","hyene","iguane","lion",
                              "loup","lynx","otarie","ours polaire","ours","panthere","perroquets",
                              "phacochere","rhinoceros","serpent","singe","suricate","tigre","tortue"))

Utilisez purrr pour itérer la fonction formate_fiche() sur l’ensemble du vecteur alim_init$espece de manière à obtenir une tibble unique et “plate” en sortie.

La table alim_init et la fonction formate_fiche font déjà partie de l’environnement.

quanti=________(alim_init$espece, .f=___)
quanti
quanti=map_df(alim_init$espece,.f=formate_fiche)
quanti

2.2 Itérer une fonction DANS une table

Maintenant, nous allons réaliser la même opération en conservant le nom de l’espèce c’est-à-dire que nous allons compléter la table alim elle-même en utilisant la fonction mutate() de dplyr :

La table alim_init et la fonction formate_fiche font déjà partie de l’environnement.

alim = alim_init %>% 
  mutate(quanti=map(.x=___,.f=___))
head(alim)
alim = alim_init %>% 
  mutate(quanti=map(.x=espece,.f=formate_fiche))
head(alim)

2.3 Aplatir une nested tibble

Observez la structure de la table alim. La colonne quanti est une colonne-liste (et non une simple colonne-vecteur), c’est-à-dire qu’elle contient des éléments -ici des tables- imbriquées (nested) dans la colonne. Remettez cette table “à plat”.

alim = alim_init %>% 
  mutate(quanti=map(.x=espece,.f=formate_fiche)) %>% 
  _____
head(alim)

3 Budget alimentaire “grossier”

  • Cette partie (et les deux prochains exercices) ne relèvent pas purement de la programmation fonctionnelle mais vous permettront d’apprécier le fruit de votre travail des parties 1 et 2 en l’intégrant dans un projet (un tout petit peu) réaliste. *

On dispose maintenant d’une table d’alimentation par individu d’une espèce. Pour gérer l’approvisionnement en nourriture du zoo, il faut savoir quelles quantités totales des différents types de nourriture sont nécessaires. On doit donc “croiser” l’information concernant les besoins d’UN individu d’UNE espèce (table alim) avec les effectifs pour chaque espèce (table effectifs) et les prix au kilo de chaque catégorie d’aliment (table prix):

head(effectifs)
prix

3.1 Réaliser la jointure des tables de nutrition, d’effectifs et de prix

Créer la table alim_tot en réalisant la jointure de alim et effectifs puis calculer la nouvelle variable poids_tot correspondant au poids total de chaque type d’aliment par espèce.

Les tables alim, effectifs, et prix font déjà partie de l’environnement.

Si vous avez besoin d’un petit (ra)fraîchissement de vos idées sur la notion de jointure, vous pouvez aller voir ici

alim_tot = alim %>% 
  full_join(___, by = "espece") %>% 
  full_join(___, by = "___")
  mutate(poids_tot = ___*___,
         prix_tot = ___*___)
alim_tot= alim %>% 
  full_join(effectifs, by = "espece") %>% 
  full_join(prix, by = "categorie") %>% 
  mutate(poids_tot = poids*effectifs,
         prix_tot = prix*poids_tot)

3.2 Représenter les résultats graphiquement

Produisez un graphique en “colonnes” correspondant aux prix totaux d’aliment par espèce (en faisant varier la couleur de remplissage par type d’aliment).

La table alim_tot fait déjà partie de l’environnement.

ggplot(alim_tot, aes(x=___, y=___,fill=___))+
  geom_col()+
  coord_flip()
ggplot(alim_tot, aes(x=espece, y=prix_tot,fill=categorie))+
  geom_col()+
  coord_flip()

4. Prix détaillés des aliments

Examinez la table prix_detailles.

Cette table fournit, pour un aliment:

  • son prix de base au kilo
  • la catégorie d’aliment auquel il correspond (viande, poisson, fruits, légumes ou fourrages)
  • la réduction (en %) en fonction du volume des achats. Par exemple, le prix du premier kilo de viande de mouton acheté est 7.46 euros. Le deuxième kilo voit son prix réduit de 1.1%, son prix est donc de (100 − 1.1)/100 ∗ 7.46 = 7.38 euros. Le troisième kilo voit son prix réduit de 1.1%, son prix est donc de (100-1.1)/100 * 7.38 = 7.30.
  • le prix minimal au kilo en dessous duquel il ne peut pas descendre quel que soit le volume acheté.

4.1 Calcul du prix du n-ième kilo d’un aliment

Ecrire une fonction prix_kilo_n() qui a pour entrée

  • nom_aliment: le nom d’un aliment
  • n un “numéro” de kilo

et qui a en sortie le prix du n-ième kilo de cet aliment.

Il s’agit en fait d’une suite géométrique telle que le n-ième kilo d’un aliment a pour prix prix_base*((100-reduc)/100)^(n-1)

La table prix_detailles fait déjà partie de l’environnement.

prix_kilo_n=function(nom_aliment,n){
  ________
  ________
  ________
  return()
}
prix_kilo_n("sardine",15) # devrait retourner 5.790952
prix_kilo_n=function(nom_aliment,nieme){
  resultat=prix_detailles %>% 
    filter(aliment==nom_aliment) %>% 
    mutate(prix=prix_base*((100-reduc)/100)^(nieme-1)) %>% 
    mutate(prix=max(prix,prix_min)) %>% 
    pull(prix)
  return(resultat)
}

prix_kilo_n("sardine",15) # devrait retourner 5.79 (si arrondi)
prix_kilo_n("sardine",150) # devrait retourner 3.00 (si arrondi)

4.2 Calcul du prix de n kilos d’un aliment

Ecrire une fonction prix() qui a pour entrée

  • nom_aliment: le nom d’un aliment
  • n un nombre total de kilos

et qui a en sortie le prix de n kilos de cet aliment. Vous pourrez pour cela réutiliser la fonction prix_kilo_n() que vous avez écrite dans l’exercice précédent.

La table prix_detailles et la fonction prix_kilo_n font déjà partie de l’environnement.

prix=function(nom_aliment,n){
  ________
  ________
  return()
}
prix("sardine",50) # devrait retourner 272.84 (si arrondi)
prix=function(nom_aliment,n){
  resultat=map_dbl(1:n,
                   ~prix_kilo_n(nom_aliment,.x)) %>% 
    sum()
  return(resultat)
}
prix("sardine",50) # devrait retourner 272.84 (si arrondi)