{"word_count": 1345, "character_count": 17649}
Pour continuer sur les manipulation de données publiques, je voulais m'inspirer du projet de Cédric, de la formation Data Science pour l’Actuariat sur les résultats au baccalauréat. Les données nécessaires à cette étude sont disponibles sur plusieurs sites, Ministère de l’éducation nationale Indicateurs de résultat des lycées d’enseignement général et technologique Géolocalisation des établissements d’enseignement Bases INSEE Emploi et population active - Recensement de la population par commune Structure et distribution des revenus, inégalité des niveaux de vie NosDonnees.fr Géolocalisation des communesIl ne s'agit aucunement d'une analyse poussée des résultats, juste un peu de visualisation, sans aucune autre prétention ! Ah oui, même si on ne va pas faire de carte (je les trouve peu lisibles) on va quand même utiliser les données spatiales : les établissements scolaires sont géolocalises, et on peut obtenir des informations locales, sur le taux de chômage, ou le revenu médian. Et faire des graphiques.Ce préambule passé, on peut commencer.library(dplyr)library(readxl)library(sp)library(ggmap)library(raster)library(leaflet)library(DT)library(cowplot)library(gstat)library(tmap)On va commencer par récupérer par établissement, les résultats au bac.url_resultat_etab = "https://data.education.gouv.fr/explore/dataset/fr-en-indicateurs-de-resultat-des-lycees-denseignement-general-et-technologique/download/?format=csv&#38;timezone=Europe/Berlin&#38;use_labels_for_header=true"download.file(url_resultat_etab,destfile = paste0(librairie,"import_resultat_etab.csv"), method="curl")df_resultat_etab = read.csv("import_resultat_etab.csv",header=TRUE, sep= ";", encoding="UTF-8")Comme bien souvent avec les données des administrations françaises, on a souvent des soucis de typographie. Pour simplifier, on va supprimer les accents, et uniformiser un peu les nomsMiseEnForme_Colonnes = function(text) {  text &#60;- gsub("è", "e", text)    text &#60;- gsub("é", "e", text)           text &#60;- gsub("_", ".", text)  text &#60;- gsub("serie.", "", text)  text &#60;- gsub("Effectif.Presents.", "Effectif.", text)  text &#60;- gsub("Taux.","Tx.",text)  text &#60;- gsub("Brut.de.Reussite.", "Admis.Etab.", text)  text &#60;- gsub("Reussite.Attendu.", "Admis.", text)  text &#60;- gsub("brut", "Etab", text)  text &#60;- gsub("attendu", "Academie", text)  text &#60;- gsub("toutes.", "TOTAL", text)  text &#60;- gsub("Total.", "TOTAL", text)  text &#60;- gsub("..Etablissement", ".Etab", text)  text &#60;- gsub("Pourcentage", "Tx", text)  return(text)}for(i in 1:ncol(df_resultat_etab)){  colnames(df_resultat_etab)[i] &#60;- MiseEnForme_Colonnes(names(df_resultat_etab)[i])}On va ensuite supprimer les départements et régions d'outre-mer,df_resultat_etab = df_resultat_etab[-which(toupper(df_resultat_etab$Departement) %in% c("GUADELOUPE","MAYOTTE","MARTINIQUE","REUNION","GUYANE")),]récupérer les noms des colonnesColonnes = colnames(df_resultat_etab)et comme on s’intéresse aux premières variablesColonnes_Generiques = Colonnes[1:8]on les recupere, pour construire quelques statistiques pour colonnes relatives aux séries L, ES et SColonnes_Series = Colonnes[grepl("([a-zA-Z]*?.)*\\.S$|([a-zA-Z]*?.)*\\.ES$|([a-zA-Z]*?.)*\\.L$|([a-zA-Z]*?.)*\\.TOTAL$",Colonnes)]Et on finit avec les autresColonnes_Autres = Colonnes[grepl("(Tx.Bacheliers.*)|(Tx.acces.*)|(Effectif.de.*)|(libelle.region)|(code.region)|(element)",Colonnes)] df_resultat_etab = cbind(df_resultat_etab[Colonnes_Generiques],df_resultat_etab[Colonnes_Series],df_resultat_etab[Colonnes_Autres])On peut aussi localiser les établissementsurl_carto_etab &#60;- "https://www.data.gouv.fr/s/resources/adresse-et-geolocalisation-des-etablissements-denseignement-du-premier-et-second-degres/20160526-143453/DEPP-etab-1D2D.csv"download.file(url_carto_etab,destfile=paste0(librairie,"import_carto_etab.csv"))df_carto_etab = read.csv2("import_carto_etab.csv",header=TRUEOn récupère ici la géolocalisation de 66556 établissements nationaux ! On peut croiser avec des données socio-économiques des communesnom_base_emploi = "base-cc-emploi-pop-act-2014"url_baseemploi_popactive = paste0("https://www.insee.fr/fr/statistiques/fichier/2862207/",nom_base_emploi,".zip")download.file(url_baseemploi_popactive,destfile=paste0(librairie,nom_base_emploi,".zip"))unzip(paste0(nom_base_emploi,".zip"),overwrite = TRUE) df_base_emploi_source = read_excel(paste0(nom_base_emploi,".xls"),sheet="COM_2014",skip=5)On va exclure les territoires d'outre-mer icidf_base_emploi_source &#60;- df_base_emploi_source[-which(df_base_emploi_source$DEP %in% c("971","972","973","974","975")),]df_base_emploi_colonnes = c("CODGEO","P14_POP1564","P14_H1564","P14_F1564","P14_ACT1564","P14_ACTOCC1564","P14_CHOM1564","P14_INACT1564", "P14_ETUD1564", "P14_RETR1564", "P14_AINACT1564", "P14_HCHOM1524", "P14_FCHOM1524", "C14_ACT1564","C14_ACT1564_CS1","C14_ACT1564_CS2","C14_ACT1564_CS3","C14_ACT1564_CS4","C14_ACT1564_CS5","C14_ACT1564_CS6","P14_POP15P")df_base_emploi = df_base_emploi_source[,names(df_base_emploi_source) %in% df_base_emploi_colonnes]et corriger les soucis classiques de la Corse,MiseEnForme_CodeGeo = function(text) {  text &#60;- gsub("2A", "20", text)    text &#60;- gsub("2B", "20", text)  }df_base_emploi$CODGEO = MiseEnForme_CodeGeo(df_base_emploi$CODGEO)On peut aussi utiliser des données de revenus, par communesnom_base_revenus = "indic-struct-distrib-revenu-2014-COMMUNES"url_baserevenus = paste0("https://www.insee.fr/fr/statistiques/fichier/3126151/",nom_base_revenus,".zip")download.file(url_baserevenus,destfile=paste0(librairie,nom_base_revenus,".zip"))unzip(paste0(nom_base_revenus,".zip"),overwrite = TRUE)df_base_revenus = read_excel("FILO_DISP_COM.xls",sheet="ENSEMBLE",skip=5)[,c(1,4,7)]df_base_revenus$CODGEO = MiseEnForme_CodeGeo(df_base_revenus$CODGEO)On recupere des donnees spatiales relatives aux communesurl_geoloc_communes = "http://www.nosdonnees.fr/wiki/images/b/b5/EUCircos_Regions_departements_circonscriptions_communes_gps.csv.gz"download.file(url_geoloc_communes,destfile=paste0(librairie,"geoloc_communes.csv.gz"))df_geoloc_communes = read.csv2(gzfile("geoloc_communes.csv.gz"),header=TRUE, stringsAsFactors = FALSE,encoding="UTF-8")et comme toujours, un peu de corrections s'imposentdf_geoloc_communes = df_geoloc_communes[-which(df_geoloc_communes$numéro_département %in% c("971","972","973","974","975")),]df_geoloc_communes = df_geoloc_communes[,names(df_geoloc_communes) %in% c("code_insee","latitude","longitude","codes_postaux")]df_geoloc_communes_nb &#60;- nrow(df_geoloc_communes)On va ensuite creer une fonction de remplacement des valeurs manquantes, et de correction des séparateurs décimauxMiseEnForme_CoordonneesGeo = function(valeur){pretraitement = ifelse(as.character(valeur)=="-","0",as.character(valeur))traitement = as.numeric(ifelse(pretraitement==".","0",gsub(pattern=",",replacement=".",pretraitement)))  return(traitement)}df_geoloc_communes$latitude = MiseEnForme_CoordonneesGeo(df_geoloc_communes$latitude)df_geoloc_communes$longitude = MiseEnForme_CoordonneesGeo(df_geoloc_communes$longitude)On passe ensuite a l’élimination des lignes en doubledf_geoloc_communes = unique(df_geoloc_communes)On va ensuite changer les noms des colonnes pour harmoniser avec les autres basesnames(df_geoloc_communes) = c("Codes_Postaux","CODGEO","coordonnee_y","coordonnee_x")On peut ensuite rechercher les lignes en double sur les codes inseeliste_CODGEO2 = aggregate(x=df_geoloc_communes$Codes_Postaux,by=list(df_geoloc_communes$CODGEO),FUN="length")list_geoloc_communes_CODGEO2 = liste_CODGEO2[liste_CODGEO2$x&#62;1,1]df_geoloc_communes_CODGEO2 = df_geoloc_communes[df_geoloc_communes$CODGEO %in% list_geoloc_communes_CODGEO2,1:2]Ici, un correction manuelle s'impose pour 4 configurations : les données propres à Lyon, Paris et Marseille ne sont pas géolocalisées, les données propres à la ville de Laguépie sont géolocalisées en doublesdf_geoloc_communes_propre = df_geoloc_communes[!df_geoloc_communes$CODGEO %in% list_geoloc_communes_CODGEO2,]df_geoloc_communes_corrige = data.frame(Codes_Postaux=c("13001","69001","75001","82250"),                        CODGEO=c("13055","69123","75056","82088"),coordonnee_y=c(43.3,45.75,48.85,44.15),coordonnee_x=c(5.4,4.85,2.31,1.97))df_geoloc_communes = rbind(df_geoloc_communes_propre,df_geoloc_communes_corrige)On peut enfin fusionner les basesdf_etab = merge(df_resultat_etab,df_carto_etab,by="Cod.Etab")Certains établissements ne peuvent être géolocalisés pour certaines annéesdf_etab_total_nongeolocalises &#60;- df_resultat_etab[!df_resultat_etab$Cod.Etab %in% df_carto_etab$Cod.Etab,]Comme l’étude ne porte que sur les seuls lycées d’enseignement général et technologique, le dataframe est réduit aux observations relatives d’une part aux lycées, d’autre part aux établissements d’enseignement polyvalent, général ou général et technologique.df_etab = df_etab[grep("LYCÉE",toupper(df_etab$nature_uai_libe)),]df_etab = df_etab[grep("GÉNÉRAL|POLYVALENT",toupper(df_etab$nature_uai_libe)),]df_etab_nongeolocalises = df_etab[df_etab$Cod.Etab %in% df_etab_total_nongeolocalises$Cod.Etab,]df_etab_geolocalise = df_etab[!is.na(df_etab$coordonnee_x),]df_etab_geolocalise = df_etab_geolocalise[!is.na(df_etab_geolocalise$coordonnee_y),]Enfin, on va convertir les code géographiques (ici reconnus comme facteur) en chaines de caractères (de 5 caractères) pour pouvoir fusionner les tablesConvertCODGEO = function(code) {  if(is.character(code)) {    code_character = ifelse(nchar(code)&#60;5, paste0("0",code), code)    return(code_character)  }  else if(is.factor(code)){    code_character = ifelse(code&#60;10000, paste0("0",as.numeric(as.character(code))), as.numeric(as.character(code)))    return(code_character)  }  else if(is.numeric(code)){    code_character = ifelse(code&#60;10000, paste0("0",code), as.character(code))    return(code_character)  } }df_etab_geolocalise$Code.commune = ConvertCODGEO(df_etab_geolocalise$Code.commune) df_etab_geolocalise$Secteur.Public.Prive = sapply(df_etab_geolocalise$Secteur.Public.Prive,function(nature) {ifelse(nature=="PU","Lycées Publics","Lycées Privés")})On conservation alors les établissements dont la commune n'est pas manquantedf_etab_geolocalise = df_etab_geolocalise[!is.na(df_etab_geolocalise$Code.commune),]Pour finir, on va creer une base, pour ensuite faire une graphiquetbl_etab_nature_res_source = df_etab_geolocalise[,c(3,8,9,10,11,13,14,15)]for(i in c(6,7,8)){  temp = tbl_etab_nature_res_source[!is.na(tbl_etab_nature_res_source[i]),c(1,2,i-3,i)]  temp$Serie = ifelse(i==6,"L",ifelse(i==7,"ES","S"))  names(temp)[2:4] = c("Nature","Effectif","Tx.Admis")  if(i==6){    tbl_etab_nature_result = temp  }  else{    tbl_etab_nature_result = rbind(tbl_etab_nature_result,temp)  }}graph = ggplot(tbl_etab_nature_result,aes(x=Effectif,y=Tx.Admis,colour=factor(Annee))) graph = graph + geom_point(alpha=0.45)graph = graph + facet_grid(Serie~Nature)graph = graph + xlab("Effectifs de l'établissement en terminale (par série)") + ylab("Taux d'admission (%)") graph = graph + scale_color_discrete(name="Année des\nrésultats")graph = graph + theme(legend.title = element_text(size=9,face="bold"),       legend.text = element_text(size=9),       strip.background = element_rect(colour="black", fill="gray95"),       panel.border = element_rect(linetype = "solid"),       panel.grid.major = element_line(colour = "gray75",linetype = "dashed"),       panel.grid.minor = element_line(colour = "gray95",linetype = "dashed"),       axis.title.x = element_text(size=9, face="bold"),       axis.text.x  = element_text(size=8),       axis.title.y = element_text(size=9, face="bold"),       axis.text.y  = element_text(size=8))graphOn a ici l'evolution des resultats en fonction de la taille des etablissements.df_communes_CorrNaN = df_communes_Corr[which(!df_communes_Corr$TxChomage == "NaN" &#38; !df_communes_Corr$TxCadres == "NaN" &#38; !df_communes_Corr$TxOuvriers == "NaN" &#38; !df_communes_Corr$NbPopulation == "NaN" &#38; !df_communes_Corr$TxSenior == "NaN" &#38; !df_communes_Corr$RevenusMedians == "NaN"),]df_communes_sp = SpatialPointsDataFrame(coords = df_communes_CorrNaN[, c("coordonnee_x", "coordonnee_y")], data = df_communes_CorrNaN) Grille              = as.data.frame(makegrid(df_communes_sp, nsig=2, cellsize = 0.1))names(Grille)       = c("X", "Y")coordinates(Grille) = c("X", "Y")gridded(Grille)     = TRUE  fullgrid(Grille)    = TRUE  proj4string(Grille) = proj4string(df_communes_sp)On peut ensuite faire du krigeage, histoire de lisser un peu nos donnees de chomage et de revenudf_communes_sp.TxChomage = krige(TxChomage ~ 1, df_communes_sp, Grille, nmax=1)df_communes_sp.RevenusMedians = krige(RevenusMedians ~ 1, df_communes_sp, Grille, nmax=1)sp_lycee_WGS84@data$TxChomage      = extract(R.TxChomage,sp_lycee_WGS84)sp_lycee_WGS84@data$TxCadres       = extract(R.TxCadres,sp_lycee_WGS84)sp_lycee_WGS84@data$TxOuvriers     = extract(R.TxOuvriers,sp_lycee_WGS84)sp_lycee_WGS84@data$NbPopulation   = extract(R.NbPopulation,sp_lycee_WGS84)sp_lycee_WGS84@data$TxSenior       = extract(R.TxSenior,sp_lycee_WGS84)sp_lycee_WGS84@data$RevenusMedians = extract(R.RevenusMedians,sp_lycee_WGS84)On peut enfin conclure, en faisant une fonction generique de visualisationCreation_Graphique = function(df, AnneeObs_Ouv, AnneeObs_Clo, Effectifs, Abscisses, Ordonnees, TitreAbs, TitreOrd, CouleurGraph, CouleurLiss, Serie) {  df_temp = df[which(df$Annee&#62;=AnneeObs_Ouv &#38; df$Annee&#60;=AnneeObs_Clo),]  df_temp = df_temp[which(!is.na(df_temp[,Effectifs]) &#38; !is.na(df_temp[,Abscisses]) &#38; !is.na(df_temp[,Ordonnees])),]  df_temp = df_temp[!df_temp[,Effectifs]==0,]  df_temp = df_temp[,c(Abscisses,Ordonnees)]  graphique = ggplot(df_temp,aes(x = df_temp[,Abscisses],y = df_temp[,Ordonnees]))   graphique = graphique + geom_point(data = df_temp, aes(x = df_temp[,Abscisses],y = df_temp[,Ordonnees]),size=1, color=CouleurGraph,alpha=0.25)   graphique = graphique + geom_density2d(aes(colour=..level..),show.legend=F) + scale_colour_gradient(low="gray55",high="gray25")   graphique = graphique + scale_y_continuous(breaks= seq(80,100,by=2), limits = c(80,100))  graphique = graphique + xlab(TitreAbs) + ylab(TitreOrd)   graphique = graphique + ggtitle(Serie)   graphique = graphique + theme(plot.title   = element_text(size=13,color=CouleurLiss, face="bold", hjust=0),       axis.title.x = element_text(size=8, face="bold"),       axis.text.x  = element_text(size=8),       axis.title.y = element_text(size=8, face="bold"),       axis.text.y  = element_text(size=8),       panel.border = element_rect(linetype = "solid"),       panel.grid.major = element_line(colour = "gray55",linetype = "dashed"),       panel.grid.minor = element_line(colour = "gray75",linetype = "dashed")) graphique = graphique + stat_smooth(method = "loess",fill=CouleurLiss,color=CouleurLiss)  return(graphique)}Production_Graphique_VI_1 = function(df, Titre_General, Axe_Abscisses, Titre_Abscisses, Annee_Observee_Ouv, Annee_Observee_Clo){  Graph_S = Creation_Graphique(df, Annee_Observee_Ouv, Annee_Observee_Clo, "Effectif.S", Axe_Abscisses, "Tx.Admis.Etab.S", Titre_Abscisses, "Taux d'admission (%)", "dodgerblue3","dodgerblue4","Série S")  Graph_ES = Creation_Graphique(df, Annee_Observee_Ouv, Annee_Observee_Clo, "Effectif.ES", Axe_Abscisses, "Tx.Admis.Etab.ES", Titre_Abscisses, "Taux d'admission (%)","darkorange2","darkorange3","Série ES")  Graph_L = Creation_Graphique(df, Annee_Observee_Ouv, Annee_Observee_Clo, "Effectif.L", Axe_Abscisses, "Tx.Admis.Etab.L", Titre_Abscisses, "Taux d'admission (%)", "chartreuse4","darkgreen","Série L")  Graph_TS = Creation_Graphique(df, Annee_Observee_Ouv, Annee_Observee_Clo, "Effectif.Etab", Axe_Abscisses, "Tx.Admis.Etab", Titre_Abscisses, "Taux d'admission (%)", "indianred1","red4","Toutes séries")  p = plot_grid(Graph_S, Graph_ES, Graph_L, Graph_TS, ncol = 2, nrow = 2,align = 'hv',  scale = c(0.95, 0.95, 0.95, 0.95),vjust = 0.9, hjust=-0.5)  titre &#60;- ggdraw() + draw_label(Titre_General,fontface="bold", size=10)  plot_grid(titre, p, ncol = 1, rel_heights=c(.25,5))}On note icidf_lycee &#60;- sp_lycee_WGS84@dataet on peut faire un premier graphique, avec le taux de chomageProduction_Graphique_VI_1(df              = df_lycee,                          Titre_General   = "Taux d'admission par série en fonction du taux de chômage \n dans la population active - Tous lycées confondus",                          Axe_Abscisses   = "TxChomage",                          Titre_Abscisses = "Taux de chômage dans la population active (%)",                          Annee_Observee_Ouv     = "2013",                          Annee_Observee_Clo     = "2015")On ne va pas enfoncer les portes ouvertes de l'inference ecologique, en affirmant des choses aussi stupides que "on a moins de chances d'avoir le bac quand on est au chômage". Mais on peut noter que dans les zones avec un fort taux de chômage, les résultats au bac sont moins bons.On peut ensuite regarder en fonction du revenu de la commune du lycéeProduction_Graphique_VI_1(df                     = df_lycee,                          Titre_General          = "Taux d'admission par série en fonction du niveau des revenus disponibles médians - Tous lycées confondus",                          Axe_Abscisses          = "RevenusMedians",                          Titre_Abscisses        = "Quantile du niveau des revenus disponibles médians (%)",                          Annee_Observee_Ouv     = "2013",                          Annee_Observee_Clo     = "2015")Fascinant, non ? Mais c'est clairement juste une première approche... il faudrait aller plus loin ensuite !