Décomposition, Conception et Réalisation d'Applications
T.D. 3 : Comptages, moyennes, tracés et statistiques
gilles.hunault "at" univ-angers.fr
Table des matières cliquable
1. Comptages élémentaires pour une variable qualitative binaire
3. Comptages élémentaires pour une variable qualitative éventuellement ordinale
4. Moyennes d'une variable quantitative
5. Tracés adaptés aux comptages et moyennes
6. Statistiques descriptives, statistiques inférentielles et programmation
Il est possible d'afficher toutes les solutions via ?solutions=1 et de les masquer via ?solutions=0.
1. Comptages élémentaires pour une variable qualitative binaire
On s'intéresse ici au comptage d'une variable qualitative binaire, disons le code-sexe d'un individu dans un échantillon donné. comme pour le fichier cs.txt ci-dessous, où le code-sexe est défini par 1 pour Homme et 2 pour Femme. Quels calculs peut-on faire ? Quels calculs doit-on faire ? Comment présenter les résultats ? Quelles sont les formules ? Peut-on se contenter de juste calculer et afficher les pourcentages ?
Fichier cs.txt :
IDEN AGE PROF ETUD SEXE REGI USAG M001 62 1 2 2 2 3 M002 60 9 3 1 4 1 M003 31 9 4 2 4 1 M004 27 8 4 2 1 1 M005 22 8 4 1 1 2 M006 70 4 1 2 1 1 M007 19 8 4 2 4 2 M008 53 6 2 2 2 3 M009 62 16 4 1 2 2 M010 63 16 0 2 1 0 M011 65 16 0 1 1 0 M012 11 12 2 2 4 2 M013 78 16 3 1 4 1 M014 20 8 4 1 4 2 M015 48 1 2 2 3 1 M016 50 1 1 2 4 2 M017 49 7 4 1 4 1 M018 44 1 1 2 2 1 M019 21 8 4 2 2 2 M020 23 13 3 2 1 2 M021 28 6 4 1 3 1 M022 47 6 2 1 1 1 M023 64 2 2 1 1 2 M024 26 8 4 1 4 1 M025 14 12 2 2 1 2 M026 43 1 1 1 1 2 M027 61 16 1 2 1 0 M028 15 12 3 2 1 2 M029 29 0 2 2 1 1 M030 24 1 2 2 1 2 M031 41 9 4 2 1 1 M032 37 9 4 2 2 1 M033 73 16 1 2 1 1 M034 12 12 2 2 4 1 M035 40 13 2 2 4 0 M036 42 7 3 1 3 0Ecrire un programme AWK pour fournir le nombre et le pourcentage d'hommes et de femmes (numéro de la colonne SEXE à déterminer par programme) pour le fichier cs.csv -- le séparateur est le point-virgule -- dont voici un extrait :
Fichier cs.csv (extrait) :
IDEN;AGE;PROF;ETUD;SEXE;REGI;USAG M001;62;1;2;2;2;3 M002;60;9;3;1;4;1 M003;31;9;4;2;4;1 M004;27;8;4;2;1;1Modèle d'affichage :
Résultats pour la colonne SEXE, colonne numéro 5 (36 valeurs) moda 1 efectif 13 pourcentage 36.11 moda 2 efectif 23 pourcentage 63.89Donner enfin le code R et Python qui effectue les mêmes calculs à l'aide de fonctions déjà existantes.
En admettant que les valeurs du code sexe correspondent au champ SEXE d'une table CS dans une base de données RECENSE, quel serait le code MySQL équivalent, sachant qu'on désirerait un affichage (sans doute incomplet) comme ci-dessous :
+------+----------+-------+---+ | sexe | Effectif | pct | % | +------+----------+-------+---+ | 2 | 23 | 63.89 | % | | 1 | 13 | 36.11 | % | +------+----------+-------+---+On pourra suivre la méthode ci-dessous pour réaliser le tri à plat :
## méthode pour calculer le tri à plat d'une variable qualitative # 1. lire les données # 2. calculer les effectifs # 3. calculer les pourcentages # 4. créer une structure d'accueil des modalités, des effectifs et des pourcentages # 5. trier éventuellement cette structure par pourcentage décroissant # 6. afficher la structureSolution : afficher la solution
Pour une variable qualitative, binaire, on doit dénombrer les effectifs absolus $e_i$ (notés aussi $n_i$), les effectifs relatifs $f_i$ et les pourcentages $p_i$ associés, relativement aux modalités $m_i$, c'est-à-dire ici compter le nombre d'hommes, de femmes avec les fréquences et pourcentages associés. Il est d'usage de présenter les résultats par pourcentages décroissants et d'afficher à la fois les comptages et les pourcentages, sans oublier l'effectif total, comme ci-dessous (qui ne correspond pas aux données proposées) :
TRI A PLAT DE : code SEXE (ordre des modalités) pour 416 valeurs Homme Femme Total Effectif 187 229 416 Cumul Effectif 187 416 416 Frequence (en %) 45 55 100 Cumul fréquences 45 100 100 VARIABLE : code SEXE (par fréquence décroissante sur 416 codes SEXE) Femme Homme Total Effectif 229 187 416 Cumul Effectif 187 416 416 Frequence (en %) 55 45 100 Cumul fréquences 55 100 100Formules :
Pour $n$ valeurs et $p$ modalités $m_i$ avec $i$ de 1 à $p$, étant donnés les comptages $n_i$ de chaque modalité, on doit vérifier que la somme des $n_i$ vaut $n$ avant de calculer $f_i=n_i/n$ et $p_i=100\times f_i$.
Les cumuls $c_i$ avec $i$ de 1 à $p$ sont définis par $c_i=\displaystyle \displaystyle\sum_{j=1}^i n_j$
On ne peut pas se contenter des pourcentages $p_i$ si on ne dispose pas du nombre total $n$ de valeurs parce que les effectifs $e_i$ se calculent par $e_i=p_i\times n$.
Voici le code AWK demandé :
### (gH) -_- cs.awk ## comptages pour la variable qualitative binaire SEXE dans le fichier cs.cv ## s'utilise par : gawk --file=cs.awk --field-separator=";" cs.csv BEGIN { colSexe = -1 ; tot = 0 } # inutile si on est pressé(e) # détermination de la colonne pour SEXE (FNR==1) { for (idc=1;idc<=NF;idc++) { if ($idc=="SEXE") { colSexe = idc } } # fin pour idc } # fin traitement ligne 1 # effectifs (comptages) à la volée (FNR>1) { ntot++ eff[ $colSexe ]++ } # fin traitement autres lignes END { print "Résultats pour la colonne SEXE, colonne numéro " colSexe " (" ntot " valeurs)" for (moda in eff) { pct[moda] = 100*eff[moda]/ntot print " moda " moda " effectif " eff[moda] " pourcentage " sprintf("%6.2f",pct[moda]) } # fin pour moda } # fin de partie ENDVoici le code R demandé, on notera qu'il est vectoriel (on ne voit aucune boucle dans le code) :
### (gH) -_- cs.r ## comptages pour la variable qualitative binaire SEXE dans le fichier cs.cv ## s'utilise par : R --vanilla --quiet --slave --encoding=latin1 --file=cs.r ## ou par : Rscript cs.r allData <- read.csv2("cs.csv",header=TRUE) # lecture des données tap <- table(allData[,"SEXE"]) # calcul des effectifs pct <- 100*prop.table(tap) # calcul des pourcentages mdr <- data.frame(cbind(names(tap),tap,sprintf("%6.2f",pct))) # structure d'accueil names(mdr) <- c("Moda","Effectif","Pct") print(mdr,quote=FALSE,row.names=FALSE) # affichageEt son affichage :
$gh> R --vanilla --quiet --slave --encoding=latin1 --file=cs.r Moda Effectif Pct 1 13 36.11 2 23 63.89Voici le code Python correspondant, on notera qu'il est vectoriel aussi :
# -*- coding:latin1 -*- ### (gH) -_- cs.py ## comptages pour la variable qualitative binaire SEXE dans le fichier cs.cv ## s'utilise par : python cs.py import pandas as pd allData = pd.read_table("cs.csv",sep=";") # lecture des données tap = allData["SEXE"].value_counts() # calcul des effectifs pct = 100*tap/allData["SEXE"].size # calcul des pourcentages mdr = pd.concat([tap,pct],axis=1,ignore_index=True) # structure d'accueil mdr.columns = ["Effectif","Pourcentage"] # noms des colonnes mdr["Pourcentage"] = mdr["Pourcentage"].map('{:,.2f}'.format) # formatage des données print(mdr) # affichageSon affichage est légèrement différent de celui de R :
Effectif Pourcentage 2 23 63.89 1 13 36.11Pour MySQL, il est facile de calculer les effectifs, les pourcentages et de les afficher en une seule requête grâce au mot clé GROUP BY :
SET @ntot = (SELECT COUNT(sexe) FROM recense) ; SELECT sexe, COUNT(sexe) AS Effectif, ROUND(COUNT(sexe)*100/@ntot,2) AS pct, "%" FROM recense GROUP BY sexe ORDER BY pct DESC ; # fin de selectA cause des numéros de modalités pour le code-sexe, un affichage plus correct serait :
SET @ntot = (SELECT COUNT(sexe) FROM recense) ; SELECT IF(sexe=1,"hommes","femmes") AS genre , COUNT(sexe) AS Effectif, ROUND(COUNT(sexe)*100/@ntot,2) AS pct, "%" FROM recense GROUP BY sexe ORDER BY pct DESC ; # fin de selectComme détaillé en cours, chaque action élémentaire classique a sa solution classique sous forme d'une fonction en R ou en Python. Il ne faut donc pas réécrire du code pour ces actions.
De l'intérêt de bien relire son code et les solutions :
L'affichage des résultats du script cs.awk ne correspond pas rigoureusement au code fourni car il manque un f dans le mot efectif.
Il ne faut pas laisser de telles fautes d'orthographe simples à corriger car un utilisateur, une utilisatrice verra toujours ce genre de fautes et pensera, sans doute à juste titre, que vous ne faites pas assez attention aux résultats. S'il y a une faute d'orthographe, il y a peut-être aussi une faute de calcul...
Dans ce même code AWK, il y a une variable nommée tot qui est initialisée dans la partie BEGIN qui n'est jamais utilisée ailleurs (c'est la variable ntot qui calcule l'effectif total). Les langages interprétés n'obligent pas à déclarer les variables, certains langages comme AWK n'obligent pas à les initialiser et donc cela peut mener à des erreurs. Avec le mode strict le langage Perl signalerait que la variables tot n'est utilisée qu'une seule fois...
Le tri décroissant sur les pourcentages arrondis n'est rigoureusement égal dans tous les cas au tri décroissant sur les effectifs par ce que justement les pourcentages sont arrondis. Il serait prudent dans la requête MySQL de trier sur Effectif plutôt que sur pct.
Relire, c'est aussi l'occasion de peaufiner les commentaires, d'ajuster le formatage et la lisibilité du code, de penser aux tests à effectuer, selon les trois règles d'or :
d'abord on écrit les commentaires ;
ensuite on écrit le code ;
enfin on teste tout ce qu'il est raisonnable de tester...
2. Comptages avancés
On admet maintenant qu'il s'agit d'informations qualitatives (sexe : homme/femme ; polio : malade/sain ; état civil : célibataire, marié, veuf-veuve...) issues de grands fichiers de données, disons, plusieurs milliers ou millions de lignes, comme pour le fichier polio.dar qui contient presque 402 000 lignes. On suppose également que chaque fichier est utilisé une seule fois par an, en fin d'année. Peut-on définir, pour une année donnée, un fichier de taille minimale correspondant aux mêmes informations résumées ? Que doit-on alors stocker comme informations si on veut pouvoir comparer les comptages par année, si on veut les cumuler ?
Si on nomme $m_{i}$ les modalités de la variable, $n_{i,a}$ les effectifs associés pour l'année $a$ et $f_{i,a}$ les effectifs relatifs pour l'année $a$, donner la formule qui permet d'obtenir les effectifs cumulés $C_{i,a+1}$ et celle des effectifs relatifs cumulés $F_{i,a+1}$ à partir des valeurs de $n_{i,a}$, de $c_{i,a}$ et de $n_{i,a+1}$ si on suppose qu'on est rendu à l'année $a+1$ et qu'on vient de calculer les $n_{i,a+1}$.
Au passage, comment produit-on des formules de mathématiques dans une page Web ?
Solution : afficher la solution
Comme on s'intéresse ici au cumul d'informations résumées et puisqu'une variable qualitative est définie par ses modalités, on doit pouvoir se contenter en fin d'année, de ne conserver que les effectifs absolus, c'est-à-dire les comptages $n_{i,a}$ où $i$ correspond à la modalité et $a$ à l'année.
Comme ni l'énoncé ni ces explications ne sont sans doute pas très claires, voici un exemple :
Année Hommes Femmes cumulHommes cumulFemmes [...] 2017 50 30 250 300 2018 80 90 330 390 2019 100 350 ??? ???$n_{1,2019}$ représente le nombre d'hommes recensés en 2019. Le cumul $C_{1,2019}$ correspond au nombre d'hommes en tout pour 2019. On l'obtient en ajoutant l'effectif de 2019, soit 100, au cumul $C_{1,2018}$ des années précédentes qui est 330. On doit donc trouver ici 430.
Comme il est d'usage de calculer les effectifs relatifs (ou les pourcentages), il faut, en plus des $C_{i,a+1}$, calculer les effectifs relatifs $f_{i,a+1}$ à partir des effectifs relatifs $n_{i,a+1}$, ce qui impose de calculer auparavant la somme $n_{a+1}$ des effectifs absolus pour l'année $a+1$. Il faut enfin calculer la somme $N_{a+1}$ des effectifs cumulés $C_{i,a+1}$ et finir par le calcul des effectifs cumulés relatifs $F_{i,a+1}$ à partir des effectifs cumulés $C_{i,a+1}$ et de la somme $N_{a+1}$.
Voici les formules :
Nom Notation Formule Effectif total pour l'année $a+1$ $n_{a+1}$ $\displaystyle \displaystyle\sum_{i=1}^n n_{i,a+1}$ Effectifs relatifs pour l'année $a+1$ $f_{i,a+1}$ $100 \times n_{i,a+1} / n_{a+1}$ Effectifs cumulés pour l'année $a+1$ $C_{i,a+1}$ $C_{i,a} + n_{i,a+1}$ Effectif total cumulé pour l'année $a+1$ $N_{a+1}$ $\displaystyle \displaystyle\sum_{i=1}^n C_{i,a+1}$ Effectifs cumulés relatifs pour l'année $a+1$ $F_{i,a+1}$ $100 \times C_{i,a+1} / N_{a+1}$ Voici à quoi pourrait ressembler le tableau final demandé (nom complété) :
Année Hommes Femmes cumulHommes cumulFemmes totalAn totalCumul pctHommes pctFemmes pctCumulHommes pctCumulFemmes [...] 2017 50 30 250 300 2018 80 90 330 390 2019 100 350 ??? ???Si on admet qu'on dispose de la toute première année de comptages, alors on peut réduire le tableau d'informations initiales aux seuls effectifs par modalité, et n'avoir que ces effectifs à rajouter chaque année, soit par exemple ici
Année Hommes Femmes 2016 200 270 2017 50 30 2018 80 90 2019 100 350A titre d'exemple, nous vous laissons écrire, disons en AWK (ou en Python, ou en R...) le calcul des cumuls, des pourcentages d'effectifs et de cumuls tels que demandés. La seule question ici est sans doute : faut-il écrire une ou plusieurs fonctions (et lesquelles ?) pour simplifier le code ?
Pour écrire des formules mathématiques dans une page Web, le plus simple est sans doute de passer par MathJax avec une syntaxe $\LaTeX$. Un test en ligne est possible ici.
3. Comptages élémentaires pour une variable qualitative éventuellement ordinale
On travaille maintenant avec une variable ordinale, par exemple le degré de sévérité d'une maladie, comme dans le fichier malades.txt où le code 0 signifie non malade, le code 1 signifie un peu malade, le code 2 signifie malade, le code 3 signifie très malade et le code 4 signifie très gravement malade. Un exemple de code réel est par exemple le score METAVIR F.
Fichier malades.txt :
iden Score I0001 1 I0002 4 I0003 4 I0004 0 I0005 1 [...] I9805 2 I9806 3Reprendre la discussion sur les calculs et le stockage.
Solution : afficher la solution
Pour une variable qualitative autre que binaire, les mêmes calculs s'appliquent : on doit aussi calculer les pourcentages. Si la variable est nominale, comme par exemple l'état-civil simplifié (marié ou en couple, célibataire, veuf), il est d'usage d'afficher par pourcentage décroissant.
ETAT CIVIL (2067 valeurs) Modalité Effectif Pourcentage cumulEffectif cumulPourcentage Marié 1141 55 % 1141 55 % Célibataire 527 25 % 1668 80 % Veuve-Veuf 285 14 % 1953 94 % Autre 114 6 % 2067 100 %Pour une variable qualitative ordinale, il faut impérativement garder l'ordre des modalités dans les tableaux de résultats et afficher le cumul des pourcentages, comme ci-dessous :
Distribution des 5 modalités F0 F1 F2 F3 F4 (1012 valeurs vues) --------------------------------------------------------------- Num Modalité Effectifs Eff. cumulés Pct Cumul_Pct 1 F0 44 44 4.35 4.35 2 F1 439 483 43.38 47.73 3 F2 273 756 26.98 74.71 4 F3 141 897 13.93 88.64 5 F4 115 1012 11.36 100.00Au niveau des calculs annuels, les mêmes formules s'appliquent aussi. On aura juste plus de colonnes à calculer.
4. Moyennes d'une variable quantitative
On s'intéresse ici au résumé nommé moyenne arithmétique d'une variable quantitative, comme par exemple l'âge d'une personne exprimé en années. Si on dispose d'un fichier comme ages.txt où sur chaque ligne on a un identifiant et l'âge, comment calculer rapidement cette moyenne ?
Fichier ages.txt :
Patient Age I001 35 I012 18 I103 12 I017 20 I088 14On vient de calculer la moyenne arithmétique $m$ de $n$ valeurs. Si on rajoute une $n+1$-ème valeur notée $x$, quelle est la nouvelle valeur de la moyenne ?
On vient de calculer la moyenne arithmétique $m_1$ de $n_1$ valeurs et la moyenne arithmétique $m_2$ de $n_2$ valeurs. Quelle est la valeur de la moyenne des $n_1+n_2$ valeurs ?
On admet maintenant qu'il s'agit d'informations quantitatives (ages, salaires...) issues de grands fichiers de données, disons, plusieurs milliers ou millions de lignes, comme les fichiers de recensement nationaux. On suppose également que chaque fichier est utilisé une seule fois par an, en fin d'année pour des informations résumées. Peut-on définir, pour une année donnée, un fichier de taille minimale correspondant aux mêmes informations résumées si on s'intéresse juste à la moyenne ? Que doit-on alors stocker comme informations si on veut pouvoir comparer les moyennes par année, si on veut les cumuler ?
Si,pour la variable $i$, on note $n_{i,a}$ le nombre de valeurs pour l'an $a$ et $s_{i,a}$ la somme de ces valeurs pour l'an $a$, donner la formule qui permet d'obtenir pour l'année suivante la moyenne $M_{i,a+1}$ globale cumulée à partir des valeurs de $n_{j,a}$, de $s_{j,a}$ et $m_{j,a}$ pour $i\leqslant j$.
Est-ce qu'il existe d'autre types de moyennes ? Ou d'autres résumés des données ?
Que signifie l'expression « la moyenne est invariante par permutation » ? A quoi cela peut-il servir de le savoir si on programme un calcul de moyenne ?
Solution : afficher la solution
La moyenne arithmétique est définie comme le quotient de la somme des valeurs divisée par le nombre de valeurs. Pour la calculer ici, un programme AWK en mode one-liner comme ci-dessous serait suffisant :
awk -e ' (FNR>1) { sumAges += $2 } ; END { print " moyenne " sumAges/(FNR-1) } ' ages.txtIl c'est clair que c'est du code rapide et jetable car non robuste. La valeur $2 n'est valable que pour ce fichier. Et il a fallu diviser par FNR-1 à cause de l'entête. Utiliser R ou Excel aurait été possible ici aussi, mais au prix d'un temps d'écriture ou de réalisation des calculs plus longs.
Puis que la moyenne arithmétique est la somme des valeurs divisée par le nombre de valeurs, la formule est $m=s/n$. Si on ajoute la valeur $x$ alors la somme devient $s+x$ et la nouvelle moyenne est donc $(s+x)/(n+1)$. On peut se passer de la variable intermédiaire $s$ puisque $s=n\times m$. La formule de calcul de la nouvelle moyenne est donc $m =(n\times m+x)/(n+1)$.
Si on calculé la moyenne arithmétique $m_1$ de $n_1$ valeurs et la moyenne arithmétique $m_2$ de $n_2$ valeurs, alors la somme $s_1$ des $n_1$ valeurs est $s_1=n_1\times m_1$. De même, la somme $s_2$ des $n_2$ valeurs est $s_2=n_2\times m_2$. La somme globale des $n_1+n_2$ valeurs est donc $s_1+s_2$ et leur moyenne est alors $(s_1+s_2)/(n_1+n_2)$. Si on se passe des variables intermédiaires $s_i$, la formule de calcul de la nouvelle moyenne est donc celle d'une moyenne pondérée : $(n_1\times m_1 + n_2\times m_2)/(n_1+n_2)$.
Au niveau des formules, si on prend en compte les années, cela donne $m_a=s_a/n_a$. Si maintenant il s'agit de la variable numéro $i$, alors la formule est $m_{i,a}=s_{i,a}/n_{i,a}$. Vouloir stocker en fin d'année juste la moyenne de l'année et la moyenne cumulée jusque là n'est pas correct par ce qu'il n'est pas possible de calculer la moyenne générale cumulée $M_{i,a+1}$ à partir seulement des valeurs de $M_{i,a}$ et de $m_{i,a+1}$. Il faut au minimum conserver le nombre de valeurs par année et leur cumul pour pouvoir calculer la moyenne générale cumulée $M_{i,a+1}$.
Le plus astucieux est certainement de garder, année par année, le cumul $N_{i,a}$ du nombre de valeurs et de leur somme $S_{i,a}$. La moyenne de l'année se calcule directement par $m_{i,a+1}=s_{i,a+1}/n_{i,a+1}$. Puisqu'on met à jour le cumul du nombre de valeurs par la formule $N_{i,a+1}=N_{i,a} + n_{i,a+1}$ et leur somme cumulée par $S_{i,a+1}=S_{i,a} + s_{i,a+1}$, la nouvelle moyenne générale s'obtient directement par $M_{i,a+1}=S_{i,a+1}/N_{i,a+1}$.
Il existe plusieurs autres types de moyennes que la moyenne arithmétique. Il y a la moyenne géométrique, la moyenne harmonique, la moyenne quadratique... Voir l'article Wikipedia correspondant pour leur définition et le lien hubaut pour des exemples de calcul.
L'expression « la moyenne est invariante par permutation » signifie que l'ordre des valeurs n'a pas d'importance pour le calcul de la moyenne. Ainsi les notes trimestrielles aux examens 18, 5, 12 fournissent la même moyenne que les notes 18, 12, 5 et que les notes 5, 12, 18. Comme il s'agit de notes trimestrielles, l'ordre des notes apporte une information sur la progression ou la diminution des notes que le résumé moyenne ne traduit pas.
Savoir qu'il y a invariance par permutation n'apporte rien au programmeur quant au code informatique. Par contre savoir qu'une moyenne est un résumé non robuste peut amener à d'autres calculs comme le celui de la médiane, celui de la moyenne tronquée... et surtout à d'autres interprétations.
5. Tracés adaptés aux comptages et moyennes
Y a-t-il des représentations graphiques classiques pour les comptages, pour les valeurs, pour les moyennes ? Comment les nomme-t-on ? Est-ce que les logiciels Microsoft Excel et Libre Office Calc les implémentent ? Y a-t-il des précautions à prendre au niveau des graphiques ?
Solution : afficher la solution
Solution volontairement non détaillée ici. Venez en TD !
6. Statistiques descriptives, statistiques inférentielles et programmation
Comment décrit-on en statistiques une variable qualitative ? Et une variable quantitative ? Et les deux en même temps ?
Comment fait-on en statistiques pour comparer des comptages, des moyennes, et plus généralement des séries de valeurs ?
Que faut-il en déduire si on doit programmer des calculs statistiques ?
Solution : afficher la solution
Ceci n'est pas de l'informatique. Consulter par exemple l'introduction non élémentaire au logiciel R ou formules pour répondre en partie à ces questions.
Lorsqu'on programme ce genre de calculs, ce n'est pas tant la difficulté des calculs qui est en jeu -- car ils sont souvent déjà programmés -- que de savoir lesquels appliquer. Ainsi, pour une variable qualitative dont on connait et maitrise l'unité, disons dont les valeurs sont stockées dans le vecteur V, le calcul de la moyenne de V s'obtient sans doute par mean(V) ou V.mean() alors que le calcul de la médiane de V s'obtient sans doute par median(V) ou V.median(). Mais quel descripteur faut-il utiliser ? La moyenne ou la médiane ? Et ce n'est pas une question simple, à cause des distributions multimodales.
De même, comparer des effectifs ou des moyennes, des distributions ne s'effectue pas avec un simple calcul de différence. Pour que la différence soit considérée comme significative, il faut s'appuyer sur un test statistique qui ne s'applique parfois que lorsque certaines conditions (normalité, homoscédasticité...) sont vérifiées. Savoir quel test statistique s'applique n'est pas un problème simple. Voir par exemple la page whatstat.
7. Affichages «intelligents» de résultats
7.1 Affichages en général
Si on devait présenter les résultats de plusieurs variables qualitatives non nécessairement binaires, quel serait l'affichage le plus «intelligent» statistiquement ?
Et pour plusieurs variables quantitatives non nécessairement de même unité ?
Là encore, que faut-il en déduire si on doit programmer des affichages issus de calculs statistiques ?
7.2 Affichage des résultats de variables qualitatives
Reprendre les solutions AWK et R de la question 1 afin d'avoir un affichage avec des labels de modalité, des effectifs décroissants, des pourcentages avec un nombre choisi de décimales.
En supposant qu'on dispose en PHP du tableau suivant (obtenu comment ?), donner ensuite une solution PHP et Javascript qui permet de choisir la colonne de tri.
array (size=2) 'Homme' => int 13 'Femme' => int 23 array ( 'Homme' => 13, 'Femme' => 23, )Trouver un exemple numérique qui montre que le tri par pourcentage arrondi décroissant n'est pas équivalent au tri par effectif décroissant.
Solution : afficher la solution
7.1 Affichages en général
A nouveau, ceci n'est pas de l'informatique. On peut raisonnablement penser qu'afficher les variables statistiques qui varient le plus en premier est un bon choix, mais comment apprécier cette variation ? On pourra consulter rappels statistiques pour trouver quelques pistes de réflexion.
Si on doit programmer de tels affichages, il faut certainement coopérer avec des statisticiennes et des statisticiens pour savoir dans quel ordre afficher les résultats. Ensuite, quelques tris et indexations sur les vecteurs et tableaux de résultats doivent suffire...
7.2 Affichage des résultats de variables qualitatives
Voici le code AWK demandé, sans variable tot inutilisée.
### (gH) -_- cs-tri.awk ## comptages pour la variable qualitative binaire SEXE dans le fichier cs.cv ## s'utilise par : gawk --file=cs-tri.awk --field-separator=";" --assign=decim=1 --assign=labels="Homme Femme" cs.csv BEGIN { colSexe = -1 ntot = 0 } # fin de partie BEGIN # détermination de la colonne pour SEXE (FNR==1) { for (idc=1;idc<=NF;idc++) { if ($idc=="SEXE") { colSexe = idc } } # fin pour idc } # fin traitement ligne 1 (FNR>1) { ntot++ eff[ $colSexe ]++ } # fin traitement autres lignes END { print "Résultats pour la colonne SEXE, colonne numéro " colSexe " (" ntot " valeurs)" for (moda in eff) { pct[moda] = 100*eff[moda]/ntot print " moda " moda " effectif " eff[moda] " pourcentage " sprintf("%6.2f",pct[moda]) } # fin pour moda # ajouts : print "Résultats triés pour la colonne SEXE, colonne numéro " colSexe " (" ntot " valeurs) avec " decim " décimale(s) " split(labels,noms," ") fmt = sprintf("%s%s%s","%6.",decim,"f") n = asort(eff) for (idm=1;idm<=n;idm++) { moda = n + 1 - idm # car le tri est toujours croissant pct[moda] = 100*eff[moda]/ntot print " modalité " noms[moda] " effectif " eff[moda] " pourcentage " sprintf(fmt,pct[moda]) " %" } # fin pour moda } # fin de partie ENDUtilisation et affichage :
$gh> gawk --file=cs-tri.awk --field-separator=";" --assign=decim=1 --assign=labels="Homme Femme" cs.csv Résultats pour la colonne SEXE, colonne numéro 5 (36 valeurs) moda 1 effectif 13 pourcentage 36.11 moda 2 effectif 23 pourcentage 63.89 Résultats triés pour la colonne SEXE, colonne numéro 5 (36 valeurs) avec 1 décimale(s) modalité Femme effectif 23 pourcentage 63.9 % modalité Homme effectif 13 pourcentage 36.1 %Maintenant, si on est pressé(e), il aurait été possible, juste pour le tri, d'utiliser un pipe avec un sort décroissant sur la clé 4 :
$gh> gawk --file=cs.awk --field-separator=";" cs.csv Résultats pour la colonne SEXE, colonne numéro 5 (36 valeurs) moda 1 effectif 13 pourcentage 36.11 moda 2 effectif 23 pourcentage 63.89 $gh> gawk --file=cs.awk --field-separator=";" cs.csv | awk -e ' { print $4 } ' colonne 13 23 $gh> gawk --file=cs.awk --field-separator=";" cs.csv | sort -r -k 4,4 Résultats pour la colonne SEXE, colonne numéro 5 (36 valeurs) moda 2 effectif 23 pourcentage 63.89 moda 1 effectif 13 pourcentage 36.11Il n'y a pas grand chose à changer dans le code R précédent pour qu'il corresponde à ce qui est demandé :
### (gH) -_- cs-tri.r ## comptages pour la variable qualitative binaire SEXE dans le fichier cs.cv ## s'utilise par : Rscript --vanilla -cs-tri.r NBDEC args <- commandArgs(TRUE) ; deci <- 1 ; if (length(args)>0) { deci <- as.numeric(args[1]) } # nouveauté allData <- read.csv2("cs.csv",header=TRUE) allData[,"SEXE"] <- factor(allData[,"SEXE"],levels=1:2,labels=c("Homme","Femme")) # nouveauté tap <- table(allData[,"SEXE"]) pct <- 100*prop.table(tap) fmt <- paste("%6.",deci,"f %%",sep="") # nouveauté mdr <- data.frame(cbind(names(tap),tap,sprintf(fmt,pct))) names(mdr) <- c("Moda","Effectif","Pct") oda <- order(mdr[,2],decreasing=TRUE) # nouveauté print(mdr[oda,],quote=FALSE,row.names=FALSE)Utilisation et affichage :
$gh> Rscript --vanilla cs-tri.r 3 Moda Effectif Pct Femme 23 63.889 % Homme 13 36.111 % $gh> Rscript --vanilla cs-tri.r # même sans paramètres ! Moda Effectif Pct Femme 23 63.9 % Homme 13 36.1 %PHP dispose de nombreuses fonctions avec des tableaux en paramètre, dont print_r, array_sum et arsort.
Les affichages fournis sont obtenus via var_export et var_dump.
La solution que nous utilisons pour trier le tableau en Javascript se nomme sortable, très légèrement aménagée. Elle fait partie des 33 solutions proposées en 2009 sur le site tympanu : javascript-solutions-for-sorting-tables.
On lira donc avec attention le code source PHP et Javascript de la page cs-tri, codes sources disponibles via des liens en bas de page et dont le tableau interactif est reproduit ci-dessous.
Modalité Effectif Pourcentage Femme 23 63.89 % Homme 13 36.11 % Au passage, est-ce risqué de permettre de visualiser du code PHP sur un site ?
On trouvera ci-dessous un exemple en R qui montre que des pourcentages arrondis ne permettent pas de trier comme le feraient des effectifs :
eff <- c(5129,351,352) ; cbind(eff,printf("%6.1f",eff*100/sum(eff))) [1,] 5129 87.9 [2,] 351 6.0 [3,] 352 6.0Les pourcentages arrondis ont aussi un autre défaut : leur somme ne fait pas toujours 100 %, comme dans l'exemple précédent.
8. Search and display en Python, R et PHP
A partir d'un fichier Excel nommé elf.xls supposé exister, avec une colonne AGE de nombres entiers on veut afficher les nbVal premières lignes triées par age décroissant. Donner une solution en interactif puis faire une fonction avec le nom du fichier et le nom de la colonne passés en paramètres obligatoires et nbVal passé en paramètre optionnel, avec la valeur 5 par défaut. On fournira une solution R puis une solution Python.
Donner les grandes lignes d'un code PHP qui réalise la même chose et qui en plus produit un fichier PDF des données sélectionnées.
Solution : afficher la solution
Les actions à exécuter sont simples : ouvrir le fichier elf.xls, trier selon la colonne AGE, puis afficher les nbVal premières lignes. On peut donc écrire la méthode de résolution en trois étapes :
## searchdisp : recherche et affichage # 1. lecture du fichier Excel # 2. définition de l'ordre d'affichage ou tri # 3. affichage des nbVal premières lignesVoici le code R interactif écrit à la suite du commentaire pour chaque étape :
## searchdisp1.r : recherche et affichage # 1. lecture du fichier Excel library("gdata") fichier <- "elf.xls" elf <- read.xls(fichier) # 2. définition de l'ordre d'affichage idx <- order(elf$AGE,decreasing=TRUE) # ou order(elf[,"AGE"],decreasing=TRUE) # 3. affichage des nbVal premières lignes nbVal <- 5 print(head(elf[idx,],n=nbVal))Il est très facile d'en faire une fonction :
## searchdisp2.r : recherche et affichage # définition de la fonction afficheExcel <- function(nomFichier,colonne,nbVal=5) { suppressMessages( library("gdata") ) data <- read.xls(nomFichier) # 1. lecture du fichier Excel idx <- order(data[,colonne],decreasing=TRUE) # 2. définition de l'ordre d'affichage print(head(data[idx,],n=nbVal)) # 3. affichage des nbVal premières ligne } # fin de fonction afficheExcel # exemples d'utilisation afficheExcel("elf.xls","AGE") afficheExcel("iris.xls","largsep",3)Voici le résultat de son exécution :
Num SEXE AGE PROF ETUD REGI USAGE 13 M013 0 78 16 3 4 1 48 M048 1 76 16 3 1 0 33 M033 1 73 16 1 1 1 43 M043 1 73 16 4 4 0 85 M085 1 73 16 3 1 0 iden espece longsep largsep longpet largpet 16 I016 1 5.7 4.4 1.5 0.4 34 I034 1 5.5 4.2 1.4 0.2 33 I033 1 5.2 4.1 1.5 0.1mais c'est encore mieux de faire une fonction robuste. Il y a trois paramètres, donc trois risques d'erreurs possibles : fichier non vu, colonne non présente, valeur non numérique. Trois vérifications suffisent pour garantir le bon fonctionnement de la fonction.
## searchdisp3.r # définition de la fonction afficheExcel <- function(nomFichier,colonne,nbVal=5) { if (!file.exists(nomFichier)) { stop(paste("fichier",nomFichier,"non vu.\n")) } # nouveau if (!is.numeric(nbVal)) { stop(paste("valeur",nbVal,"non numérique.\n"))} # nouveau suppressMessages( library("gdata") ) data <- read.xls(nomFichier) # 1. lecture du fichier Excel if (!colonne %in% names(data)) { stop(paste("colonne",colonnef,"non présente.\n")) } # nouveau idx <- order(data[,colonne],decreasing=TRUE) # 2. définition de l'ordre d'affichage print(head(data[idx,],n=nbVal)) # 3. affichage des nbVal premières ligne } # fin de fonction afficheExcelVoici le fichier des tests et le résultat de l'exécution des tests :
$gh> cat searchdisp3.sh Rscript -e ' source("searchdisp3.r",encoding="latin1") ; afficheExcel("NON.xls","AGE") ' Rscript -e ' source("searchdisp3.r",encoding="latin1") ; afficheExcel("elf.xls","Age") ' Rscript -e ' source("searchdisp3.r",encoding="latin1") ; afficheExcel("iris.xls","largsep","oui") ' $gh> sh searchdisp3.sh Error in afficheExcel("NON.xls", "AGE") : fichier NON.xls non vu. Exécution arrêtée Error in afficheExcel("elf.xls", "Age") : colonne Age non présente. Exécution arrêtée Error in afficheExcel("iris.xls", "largsep", "oui") : valeur oui non numérique. Exécution arrêtéeIl resterait, pour généraliser un peu cette fonction, à fournir un paramètre facultatif pour indiquer si le tri est croissant ou décroissant (valeur par défaut). Nous vous laissons écrire le code correspondant...
La solution Python est similaire, puisque pour chaque action on dispose aussi d'une fonction ou d'une méthode :
# -*- coding: iso-8859-15 -*- ## searchdisp1.py : recherche et affichage import pandas as pd # 1. lecture du fichier Excel elf = pd.read_excel("elf.xls") # 2. tri par age décroissant elfTri = elf.sort_values(["AGE"],ascending=False) # 3. affichage des nbVal premières lignes nbVal = 5 # attention : on commence à 0 print(elfTri.head(n=nbVal))En faire une fonction est là aussi très simple :
# -*- coding: iso-8859-15 -*- ## searchdisp2.py : recherche et affichage # définition de la fonction def afficheExcel(fichierExcel, colonne, nbVal=5) : import pandas as pd # 1. lecture du fichier Excel data = pd.read_excel(fichierExcel) # 2. tri par age décroissant dataTri = data.sort_values([colonne],ascending=False) # 3. affichage des nbVal premières lignes print(dataTri.head(n=nbVal)) # fin de fonction afficheExcel # exemples d'utilisation afficheExcel("elf.xls","AGE") afficheExcel("iris.xls","largsep",3)Voici le résultat de son exécution :
Num SEXE AGE PROF ETUD REGI USAGE 12 M013 0 78 16 3 4 1 47 M048 1 76 16 3 1 0 84 M085 1 73 16 3 1 0 42 M043 1 73 16 4 4 0 32 M033 1 73 16 1 1 1 iden espece longsep largsep longpet largpet 15 I016 1 5.7 4.4 1.5 0.4 33 I034 1 5.5 4.2 1.4 0.2 32 I033 1 5.2 4.1 1.5 0.1Passons là encore à une fonction robuste :
# -*- coding: iso-8859-15 -*- ## searchdisp3.py : recherche et affichage # définition de la fonction def afficheExcel(fichierExcel, colonne, nbVal=5) : import sys import os if (not os.path.isfile(fichierExcel)) : print("le fichier "+fichierExcel+" n'existe pas. Stop.\n") sys.exit() # fin de si import re if (not re.search("^[0-9]+$",str(nbVal))) : print("la valeur "+str(nbVal)+" n'est pas un nombre entier. Stop.\n") sys.exit() # fin de si import pandas as pd # 1. lecture du fichier Excel data = pd.read_excel(fichierExcel) if (not colonne in data.columns) : print("colonne "+colonne+" non vue. Stop.\n") sys.exit() # fin de si # 2. tri par age décroissant dataTri = data.sort_values([colonne],ascending=False) # 3. affichage des nbVal premières lignes print(dataTri.head(n=nbVal)) # fin de fonction afficheExcel # exemples d'utilisation (auto test) if __name__ == "__main__" : afficheExcel("elf.xls","AGE") afficheExcel("iris.xls","largsep",3) # fin de l'auto-testVoici le fichier des tests et le résultat de l'exécution des tests :
$gh> cat searchdisp3_py.sh python3 -c 'from searchdisp3 import * ;afficheExcel("NON.xls", "AGE") ' python3 -c 'from searchdisp3 import * ;afficheExcel("elf.xls", "Age") ' python3 -c 'from searchdisp3 import * ;afficheExcel("elf.xls", "AGE","oui") ' $gh> sh searchdisp3_py.sh le fichier NON.xls n'existe pas. Stop. colonne Age non vue. Stop. la valeur oui n'est pas un nombre entier. Stop.Il faut noter ici la très grande similarité des affichages entre R et Python, la seule différence notable étant le numéro des lignes de données.
On appréciera aussi que le corollaire de l'adage classique « à problème classique, solution classique » se double de la remarque pratique « si c'est simple et classique, il y a bien une fonction R ou Python qui résoud le problème».
On lira avec attention la page comparison qui fournit un affichage synoptique des commandes Python/pandas et R pour les mêmes actions.
De même, les pages R_vs_Py et python-vs-r fournissent les mêmes calculs statistiques en R et en Python.
Si en plus de trier les données pour une certaine colonne, on devait filtrer les lignes (par exemple en n'affichant que les femmes pour notre exemple), il faudrait utiliser un filtre vectoriel en R, comme elf[ elf$SEXE==2, ] ou une méthode de filtrage en Python, comme elf[ "SEXE==2"], donc sans virgule cette fois-ci.
Pour réaliser la même chose en PHP, il y a deux difficultés : la lecture de fichiers Excel n'est pas native et la structure de données data.frame n'existe pas. Il est toutefois possible de contourner le problème via des librairies externes comme PhpSpreadsheet et la fonction multisort.
Voici le fichier principal :
<?php ### affichage trié des données d'un fichier Excel require 'searchdisp-inc.php' ; require 'vendor/autoload.php'; use PhpOffice\PhpSpreadsheet\Spreadsheet; ## exemples d'utilisation : # afficheExcel("elf.xls","AGE") ; # afficheExcel("iris.xls","largsep",3) ; ?>et son fichier inclus :
<?php require 'vendor/autoload.php'; use PhpOffice\PhpSpreadsheet\Spreadsheet ; ### affichage trié des données d'un fichier Excel ########################################################################## function printLigne($tab,$gauche=1) { ########################################################################## $icol = 0 ; foreach ($tab as $element) { $icol++ ; if ($icol==$gauche) { echo sprintf("%-6s", $element) ; } else { echo sprintf("%6s", $element) ; } # finsi } # fin pour echo "\n" ; } # fin fonction printLigne ########################################################################## function afficheExcel($nomXls,$nomCol,$nbVal=5) { ########################################################################## if (!preg_match("/^[1-9][0-9]*$/",$nbVal)) { echo " La valeur $nbVal n'est pas un entier strictement positif." ; exit() ; } # fin si # 1. lecture des données if (!file_exists($nomXls)) { echo" Fichier $nomXls non vu.\n" ; exit() ; } # fin si $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls(); $spreadsheet = $reader->load($nomXls); $dfObjet = array($spreadsheet->getActiveSheet() ->toArray(null,true,true,true)); $df = $dfObjet[0] ; # pour debug : print_r($elf) ; # recherche de la colonne désirée $headers = $df[1] ; unset($df[1]) ; # pour debug : print_r($headers) ; $numCol = -1 ; foreach ($headers as $indice=>$colonne) { # echo " indice : $indice, colonne $colonne \n" ; if ($colonne==$nomCol) { $numCol = $indice ; } ; } # fin pour chaque if ($numCol==-1) { echo" Colonne $nomCol non vue. Voici la liste des colonnes présentes :\n" ; print_r($headers) ; exit() ; } # fin si # extraction des valeurs de la colonne sélectionnée $valeurs = [] ; foreach ($df as $ligne=>$donnees) { $valeurs[$ligne] = $donnees[$numCol] ; } # fin pour chaque # pour debug : print_r($valeurs) ; # 2. tri des deux structures selon le même ordre array_multisort($valeurs,SORT_DESC,$df) ; # 3. affichage trié des nbVal lignes printLigne($headers) ; $ilig = 0 ; foreach ($df as $ligne=>$donnees) { $ilig++ ; if ($ilig<=$nbVal) { printLigne($donnees) ; } # fin si } # finpour chaque } # fin de fonction afficheExcel ?>Le fichier des tests est là encore simple à écrire :
## tests php ligne de commande pour searchdisp # 1. tout va bien (1) nbVal par défaut echo "<?php include('searchdisp-inc.php') ; afficheExcel('elf.xls','AGE') ?>" | php # 2. tout va bien (2) nbVal=3 echo "<?php include('searchdisp-inc.php') ; afficheExcel('iris.xls','largsep',3) ?>" | php # 3. rien ne va plus : (1) fichier non vu echo "<?php include('searchdisp-inc.php') ; afficheExcel('NON.xls','largsep',3) ?>" | php # 4. rien ne va plus : (2) colonne non vue echo "<?php include('searchdisp-inc.php') ; afficheExcel('iris.xls','LARGE',3) ?>" | php # 5. rien ne va plus : (3) nbVal non numérique echo "<?php include('searchdisp-inc.php') ; afficheExcel('iris.xls','largsep','oui') ?>" | phpVoici ce qu'il produit :
Num SEXE AGE PROF ETUD REGI USAGE M013 0 78 16 3 4 1 M048 1 76 16 3 1 0 M033 1 73 16 1 1 1 M043 1 73 16 4 4 0 M085 1 73 16 3 1 0 iden especelongseplargseplongpetlargpet I016 1 5.7 4.4 1.5 0.4 I034 1 5.5 4.2 1.4 0.2 I033 1 5.2 4.1 1.5 0.1 Fichier NON.xls non vu. Colonne LARGE non vue. Voici la liste des colonnes présentes : Array ( [A] => iden [B] => espece [C] => longsep [D] => largsep [E] => longpet [F] => largpet ) La valeur oui n'est pas un entier strictement positif.En ce qui concerne la production d'un PDF pour les données sélectionnées, on pourra lire 5 Best opensource PHP pdf generation libraries.
Code-source PHP de cette page ; code Javascript associé.
Retour à la page principale de (gH)