Meilleur coefficient pour la normalisation des notes de parcourSup

Exécuter la dernière case en premier pour charger les fonctionnalités nécessaire

Dans parcourSup, pour un étudiant et pour une matière, on dispose de sa moyenne $x$, de la moyenne $m$ de la classe, de la note la plus haute et la plus basse de la classe, respectivement $x_{max}$ et $x_{min}$. Une normalisation classique correspond à calculer $$x_{norm}=\dfrac{x-m}{x_{max}-x_{min}}$$
On considère le nombre $POS$ tel que $x\in [m+POS\sigma ; m+(POS+1)\sigma]$ où $\sigma$ est l'écart-type des notes de la classe qui est inconnue. Cette position permet de situer le niveau de l'étudiant dans sa classe.
On cherche à observer un lien entre $x_{norm}$ (connue) et $POS$ (inconnue).
La simulation suivante permet d'observer qu'il existe une modélisation linéaire fiable (de corrélation très forte, ie supérieur à $95\%$) : $$POS = a x_{norm}+b$$
A cette fin :

  • On estime la taille d'une classe à un nombre entier entre $20$ et $30$. On va donc simuler la taille d'une classe taille_classe=randint(20, 30).
  • On estime que les moyennes des élèves suit une loi normale. L'expérience montre que c'est un bon choix assez proche de la réalité. On prend des moyennes et écartype variable. On rappel qu'une bonne interprétation est :
    1. Des classes hétérogènes : l'écart-type des notes est grand ($>7$)
    2. Des classes homogènes faibles : l'écart-type des notes est petit ($<5$) et la moyenne est mauvaise ($<10$)
    3. Des classes homogènes fortes : l'écart-type des notes est petit ($<5$) et la moyenne est bonne ($>10$)
    Ainsi on choisira :
    • pour moyenne m=Normale(10, 5) (un nombre réel au hasard autour de $10$ à plus ou moins $5$ points près).
    • pour ecartype s=Normale(7, 5) (un nombre réel au hasard autour de $7$ à plus ou moins $5$ points près).
  • On simule taille_classe notes suivants ces paramètres. On calcule moyenne, note la plus haute et la plus plus basse, la position et la note normalisée
  • On réalise cette simulation pour nb_classe (grand, ie $10000$).
  • On représente la position en fonction de la note normalisée. On s'attend à ce que les points soient alignées
  • On calcul la régression linéaire et on espère une corrélation très forte

In [2]:
nb_classe=10000
NORM=[]
POS=[]
for k in range(nb_classe) : 
    #Pour patienter
    print("\rCalcul en cours : "+str(round((k+1)/nb_classe*100, 3))+"%", end='   ')
    
    #La taille de cette classe
    taille_classe=randint(20,30)
    
    #Les notes de ce groupe
    m=Normale(10, 5)#Moyenne de cette classe
    while(m<0) : m=Normale(10, 5)
    s=Normale(7, 5)#Ecartype de cette classe
    while(s<0) : s=Normale(7, 5)
    N=[min(20, max(0, Normale(m, s))) for k in range(taille_classe)]#Simulation
    
    #Donnée de ce groupe
    Nmax=max(N)
    Nmin=min(N)
    Nmoy=moyenne(N)#Moyenne réelle
    Nect=ecartype(N)#Ecartype réelle

    #Tirage d'un étudiant
    etu=int(taille_classe*random())
    
    #Note de l'étudiant
    note=N[etu]
    
    #Position de l'étudiant
    pos=-499
    while(Nmoy+pos/100*Nect<note and pos<500) : pos+=1
    POS.append((pos+1)/100)
    
    #Notes normalisée
    if(Nmax!=Nmin) : NORM.append((note-Nmoy)/(Nmax-Nmin))
    else : NORM.append(0)

a=achapeau(NORM, POS)
b=bchapeau(NORM, POS)
r2=RCarre(NORM, POS)
plot(NORM, POS, 'b.')
plot([-5, 5], [-5*a+b, 5*a+b], 'r')
xlim(min(NORM), max(NORM))
xlabel("Notes normalisées")
ylim(min(POS), max(POS))
ylabel("Positions")
show()

print("Corrélation du modèle =", round(sqrt(r2)*100, 3), "%")
print("Estimation de l'erreur (écart moyen entre rouge et bleue) =", round(etErreur(NORM, POS), 5))
print("a =", a)
print("b =", b)
Calcul en cours : 100.0%                                                                                               
Corrélation du modèle = 97.524 %
Estimation de l'erreur (écart moyen entre rouge et bleue) = 0.22343
a = 3.109480698669043
b = 0.01096076245843295

Conclusion

Pous estimer la position réel de l'élève dans sa classe, connaissant $m$, $x_{min}$ et $x_{max}$, on fait $$\hat{POS}=3.11\dfrac{x-m}{x_{max}-x_{min}}+0.01$$ De plus, si on ne dispose pas d'une valeur, il parait raisonnable de prendre $\hat{POS}=0$.
Dans notre modèle la position est un nombre entre $-5$ et $5$. Pour revenir à des notes (entre $0$ et $20$), on fait $$\hat{N}=10+2\hat{POS}$$ Pour s'en convaincre, refaisons encore la même simlation

In [13]:
nb_classe=10000
NORM=[]
POS=[]
for k in range(nb_classe) : 
    #Pour patienter
    print("\rCalcul en cours : "+str(round((k+1)/nb_classe*100, 3))+"%", end='   ')
    
    #La taille de cette classe
    taille_classe=randint(20,30)
    
    #Les notes de ce groupe
    m=Normale(10, 5)#Moyenne de cette classe
    while(m<0) : m=Normale(10, 5)
    s=Normale(7, 5)#Ecartype de cette classe
    while(s<0) : s=Normale(7, 5)
    N=[min(20, max(0, Normale(m, s))) for k in range(taille_classe)]#Simulation
    
    #Donnée de ce groupe
    Nmax=max(N)
    Nmin=min(N)
    Nmoy=moyenne(N)#Moyenne réelle
    Nect=ecartype(N)#Ecartype réelle

    #Tirage d'un étudiant
    etu=int(taille_classe*random())
    
    #Note de l'étudiant
    note=N[etu]
    
    #Position de l'étudiant
    pos=-499
    while(Nmoy+pos/100*Nect<note and pos<500) : pos+=1
    POS.append(10+2*(pos+1)/100) #<-------------------------------------------- ICI on modifie
    
    #Notes normalisée
    if(Nmax!=Nmin) : NORM.append((note-Nmoy)/(Nmax-Nmin))
    else : NORM.append(0)

a=achapeau(NORM, POS)
b=bchapeau(NORM, POS)
r2=RCarre(NORM, POS)
plot(NORM, POS, 'b.')
plot([-5, 5], [-5*a+b, 5*a+b], 'r')
xlim(min(NORM), max(NORM))
xlabel("Positions réelles")
ylim(min(POS), max(POS))
ylabel("Notes ajustées")
show()

print("Corrélation du modèle =", round(sqrt(r2)*100, 3), "%")
print("Estimation de l'erreur (écart moyen entre rouge et bleue) =", round(etErreur(NORM, POS), 5))
print("a =", a)
print("b =", b)
Calcul en cours : 100.0%                                                                                                                                                                 
Corrélation du modèle = 97.288 %
Estimation de l'erreur (écart moyen entre rouge et bleue) = 0.46487
a = 6.2209591787924365
b = 10.019371452903984
In [ ]:
 
In [1]:
from random import random, randint 
from math import *
from matplotlib.pyplot import *

###########################################
#        MES OUTILS DE PROBA-STATS        #
###########################################

def Normale(m, s) :
    """
    Tire un nombre réel au hasard suivant une loi normale de paramètre m (moyenne) et s (ecartype)
    """
    if(s<=0) : s=1
    if(m==0 and s==1) : return sqrt(-2*log(random()))*cos(2*pi*random())
    return s*Normale(0, 1)+m

def moyenne(x) :
    """
    Moyenne des éléments de la liste passée en paramètre
    Cas d'erreur : None
    """
    try : 
        n=len(x)
        test=True
        for i in range(n) : 
            test=(test and isinstance(x[i], (int, float)))
    except : return None
    if(n==0 or not(test)) : return None
    
    res=0
    for i in range(n) : res+=x[i]
        
    return res/n

def covariance(x, y) :
    """
    Covaraiance des éléments des listes passées en paramètre
    Cas d'erreur : None
    """
    try :
        n=len(x)
        m=len(y)
        test=True
        for i in range(min(n, m)) : 
            test=(test and isinstance(x[i], (int, float)) and  isinstance(y[i], (int, float)))
    except : return None
    if(n==0 or not(test) or n!=m) : return None
    
    xy=[]
    for i in range(n) : xy.append(x[i]*y[i])
    mx=moyenne(x)
    my=moyenne(y)
    mxy=moyenne(xy)
    if(mx==None or my==None or mxy==None) : return None
    return mxy-mx*my

def variance(x) : 
    """
    Variance des éléments de la liste passée en paramètre
    Cas d'erreur : None
    """
    return covariance(x, x)

def ecartype(x) : 
    """
    Ecartype des éléments de la liste passée en paramètre
    Cas d'erreur : None
    """
    v=variance(x)
    if(v==None or v<0) : return None
    return sqrt(v)

def achapeau(x, y) :
    """
    Renvoie l'estimation de a dans le modèle linéaire y=ax+b
    Cas d'erreur : None
    """
    c=covariance(x, y)
    v=variance(x)
    if(c==None or v==None or v==0) : return None
    return c/v

def bchapeau(x, y) : 
    """
    Renvoie l'estimation de b dans le modèle linéaire y=ax+b
    Cas d'erreur : None
    """
    a=achapeau(x, y)
    mx=moyenne(x)
    my=moyenne(y)
    if(a==None or mx==None or my==None) : return None
    return my-a*mx

def ychapeau(x, y) :
    """
    Liste des estimés par le modèle linéaire y=ax+b
    Cas d'erreur : None
    """
    try : 
        n=len(x)
        m=len(y)
        test=True
        for i in range(n) : 
            test=(test and isinstance(x[i], (int, float)))
    except : return None
    if(not(test)) : return None
    
    a=achapeau(x, y)
    b=bchapeau(x, y)
    
    if(m!=n or n==0 or a==None or b==None) : return None
    
    res=[]
    for i in range(n) : res.append(a*x[i]+b)
        
    return res

def residu(x, y) : 
    """
    Liste des résidus (y-ychapeau) par le modèle linéaire y=ax+b
    Cas d'erreur : None
    """
    try : 
        n=len(x)
        m=len(y)
        test=True
        for i in range(n) : 
            test=(test and isinstance(x[i], (int, float)))
    except : return None
    if(not(test)) : return None
    
    yc=ychapeau(x, y)
    
    if(m!=n or n==0 or yc==None) : return None
    
    res=[]
    for i in range(n) : res.append(y[i]-yc[i])
    return res

def RCarre(x, y) :
    """
    R² (carré de la corrélation linéaire multiple) du modèle y=ax+b
    Cas d'erreur None
    """
    try : 
        n=len(x)
        m=len(y)
        test=True
        for i in range(n) : 
            test=(test and isinstance(x[i], (int, float)))
    except : return None
    if(not(test)) : return None
    
    yc=ychapeau(x, y)
    my=moyenne(y)
    
    if(m!=n or n==0 or yc==None or my==None) : return None
    
    num=0
    for i in range(n) : num+=((yc[i]-my)**2)
    den=0
    for i in range(n) : den+=((y[i]-my)**2)
    
    if(den==0) : return None
    return num/den    

def etErreur(x, y) : 
    """
    Estimation de l'erreur y=ax+b
    Précisément pour le modèle y=ax+b+e ou e~N(0, s), la fonction renvoie une estimation de s
    Cas d'erreur : None
    """
    try : 
        n=len(x)
        m=len(y)
    except : return None
    
    r=residu(x, y)
    
    if(m!=n or n<2 or r==None) : return None
    
    res=0
    for i in range(n) : res+=(r[i]**2)
    
    return sqrt(res/(n-2))
In [ ]: