IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Mathématiques et Python : déterminer la position de valeurs dans une liste triée en fonction de leur répartition,
Un billet blog de Denis Hulo

Le , par User

0PARTAGES

I. Introduction

Dans une liste ordonnée de valeurs, si on a une idée de leur répartition (distribution uniforme, de Gauss, etc.) et si on connaît leur nombre total, alors, on peut estimer la position d'une valeur quelconque dans la liste.

On souhaite dans notre cas créer une fonction Python qui puisse déterminer la position d'un élément dans une liste en utilisant ce principe.

On proposera également à la fin un exemple de mise en œuvre avec la recherche d'une commande dans une liste ordonnée à partir de son numéro de référence.


II. Principe de la recherche

Prenons pour simplifier une liste de n valeurs qui se répartissent suivant une distribution de probabilité ayant comme paramètre de position loc, représentant généralement la moyenne des valeurs, et comme paramètre de dispersion scale, correspondant le plus souvent à l'écart-type :

642309

Alors la valeur de la fonction de répartition en x, notée F(x), ayant comme paramètres loc et scale, donne une estimation de la proportion de valeurs inférieures ou égales à x :

642262

Cette estimation fournit donc aussi une indication sur la position de x dans la liste.

Si on considère maintenant l'intervalle contenant les n valeurs [x0, xn-1], auquel on peut faire correspondre l'intervalle contenant les n indices [0, n-1] de la liste Python.

La position approximative de x dans la liste, c'est à dire sur l'intervalle [0, n-1] de longueur n-1 est donné par la formule :

indice_x= F(x)*(n-1)

Comme on souhaite avoir une valeur entière, on va arrondir le résultat à l'entier le plus proche :

indice_x = round(F(x)*(n-1))

Ensuite, on effectuera un parcours séquentiel à partir de cette valeur d'indice jusqu'à l'élément recherché.

Dans la pratique, on peut imaginer une liste de commandes classées suivant leur numéro. Si on connaît la fonction de répartition des numéros de commande (loi uniforme, etc.), on peut alors en déduire leur rang ou leur position dans la liste.



III. Distributions en Python

La librairie SciPy contient un grand nombre de distributions de probabilités (distribution uniforme, de Gauss, etc.), mais on peut aussi bien sûr créer sa propre fonction de répartition.

III-A. Loi uniforme

Un objet uniform du module SciPy est utilisé pour une variable aléatoire continue suivant une loi uniforme :

642256

Dans sa forme standard, la distribution est uniforme sur [0, 1]. En utilisant les paramètres loc=a et scale=b-a, on obtient une distribution uniforme sur [loc, loc + scale].

from scipy import stats

# fonction de répartition de la loi uniforme continue sur l'intervalle [0, 1]
p1 = stats.uniform.cdf(0.5) # Fonction de répartition cdf - calcul de P(X ≤ 0.5) = 0.5

# fonction de répartition de la loi uniforme sur l'intervalle [1, 10]
p2 = stats.uniform.cdf(x=5, loc=1,scale=10-1) # Fonction de répartition cdf - calcul de P(X ≤ 5)

Si vous ne souhaitez pas utiliser de librairie, alors le code de la fonction de répartition de la loi uniforme devrait ressembler à cela :

def uniform_cdf(x,loc,scale):
# fonction de répartition de la loi uniforme de paramètres loc=a et scale=(b-a) : F(x) = (x-a)/(b-a) pour a≤x≤b

if x return 0
else: # sinon
if x < (loc+scale): # si x return (x-loc)/scale
else: # sinon
return 1

III-B. Loi normale

Un objet norm est utilisé pour une variable aléatoire continue suivant une loi normale.

642262

Le paramètre loc désigne la moyenne et l'argument scale l'écart type.

La fonction de répartition de la loi normale centrée réduite en x est appelée ainsi :

p1 = stats.norm.cdf(x)

La fonction de répartition de la loi normale pour une variable aléatoire de moyenne m et d'écart type s :

p2 = stats.norm.cdf(x, loc=m, scale=s)

from scipy import stats

# fonction de répartition en x=0.2 de la loi normale centrée et réduite N(0,1)
p1 = stats.norm.cdf(0.2) # calcul de la probabilité P(X ≤ 0.2)

# fonction de répartition en x=3.5 de la loi normale N(2.5, 4)
p2 = stats.norm.cdf(3.5, loc=2.5, scale=4) # calcul de la probabilité P(X ≤ 3.5)



IV. Implémentation en Python

On présente maintenant la fonction Python qui permet de déterminer la position d'un élément dans une liste en utilisant le principe décrit précédemment. Puis, on propose un exemple de mise en œuvre avec la recherche d'une commande dans une liste ordonnée à partir de son numéro de référence.


IV-A. Fonctions de recherche d'un élément

Elle permet de rechercher un élément dans une liste de dictionnaires à partir d'une valeur de clé et de la fonction de répartition des valeurs associées à cette clé.

Arguments de la fonction rechercher_element :


  • liste_elements: liste d'éléments dans laquelle on effectue la recherche ;
  • nom_cle: nom de la clé associée à la valeur recherchée (ex.: "num_commande") ;
  • valeur_cle: valeur de clé recherchée (ex.: 10) ;
  • cdf: fonction de répartition de paramètres loc et scale (distribution uniforme : stats.uniform.cdf(x, loc, scale), etc.) ;
  • loc: paramètre de position de la distribution (ex. : moyenne pour la distribution de Gauss) ;
  • scale: paramètre de dispersion de la distribution (ex. : écart-type pour la distribution de Gauss).

    Elle prend donc comme argument une fonction de répartition comme celles disponibles dans le module scipy.stats (stats.uniform.cdf(x, loc, scale), etc.) :

    def rechercher_element(liste_elements, nom_cle, valeur_cle, cdf, loc, scale):
    # nombre d'éléments de la liste
    n = len(liste_elements)

    # estimation de la position de la valeur de clé en fonction de la répartition des valeurs
    indice_est = round(cdf(x=valeur_cle,loc=loc,scale=scale)*(n-1))
    indice_val = indice_est

    # tant que la valeur de position indice_val est inférieure à la valeur de clé recherchée
    while liste_elements indice_val+=1 # on incrémente l'indice

    # tant que la valeur de position indice_val est supérieure à la valeur de clé recherchée
    while liste_elements>valeur_cle:
    indice_val-=1 # on décrémente l'indice


    # si la valeur a été trouvée
    if liste_elements==valeur_cle:
    # affiche l'écart entre la position réelle de la valeur recherchée et sa position estimée
    print("Écart entre la position réelle et la position estimée : {0}\n".format(abs(indice_val-indice_est)))

    # renvoie l'élément recherché accompagné de sa position dans la liste
    return {'position': indice_val, 'élément': liste_elements}


    Note : En Python, un dictionnaire est un ensemble de paires clé: valeur, les clés devant être uniques au sein du dictionnaire.


    IV-B. Test de la fonction de recherche

    On dispose d'une liste de commandes (sans leur détail) ordonnées suivant leur numéro auto-incrémenté. Sachant qu'il peut y avoir quelques trous dans la numérotation de ces commandes dus à certaines suppressions, on admettra que ces identifiants se répartissent de façon approximativement uniforme dans la liste.

    On souhaite dans cette situation retrouver la position d'une commande à partir de son identifiant :

    # liste des commandes clients
    liste_commandes = [{'num_commande': 1, 'date_commande': '12/02/2023', 'nom_client': 'Bonnaventure Sébastien', 'montant': 125.70},
    {'num_commande': 2, 'date_commande': '14/02/2023', 'nom_client': 'Amat Laurent', 'montant': 223.55},
    {'num_commande': 4, 'date_commande': '15/02/2023', 'nom_client': 'Le Breton Tiphaine', 'montant': 326.20},
    {'num_commande': 5, 'date_commande': '17/02/2023', 'nom_client': 'Cardin Anthony', 'montant': 141.45},
    {'num_commande': 6, 'date_commande': '17/02/2023', 'nom_client': 'Soudain Cyril', 'montant': 544.40},
    {'num_commande': 8, 'date_commande': '18/02/2023', 'nom_client': 'Valdes Lucien', 'montant': 342.25},
    {'num_commande': 9, 'date_commande': '18/02/2023', 'nom_client': 'Harry John', 'montant': 347.25},
    {'num_commande': 10, 'date_commande': '18/02/2023', 'nom_client': 'Thiel Xavier', 'montant': 316.25},
    {'num_commande': 11, 'date_commande': '19/02/2023', 'nom_client': 'Gillet Mélanie', 'montant': 716.30},
    {'num_commande': 13, 'date_commande': '20/02/2023', 'nom_client': 'Richard Océane', 'montant': 613.20},
    {'num_commande': 14, 'date_commande': '20/02/2023', 'nom_client': 'Dias Pauline', 'montant': 113.20},
    {'num_commande': 15, 'date_commande': '21/02/2023', 'nom_client': 'Moulin Olivier', 'montant': 512.50}]


    # premier et dernier élément de la liste : bornes de l'intervalle [a, b] de la loi uniforme
    a = liste_commandes['num_commande']; b = liste_commandes[-1]['num_commande']

    # recherche de la commande à partir de son numéro et de la fonction de répartition de la loi uniforme
    commande = rechercher_element(liste_commandes, 'num_commande', 10, stats.uniform.cdf, loc=a, scale=b-a)

    if commande: # si la commande a été trouvée
    # affiche la commande trouvée et sa position dans la liste
    print("Commande trouvée :")
    print(commande)
    else: # sinon
    print("Le numéro de commande n'a pas été trouvé !")


    Le code affiche :

    Écart entre la position réelle et la position estimée : 0

    Commande trouvée :
    {'position': 7, 'élément': {'num_commande': 10, 'date_commande': '18/02/2023', 'nom_client': 'Thiel Xavier', 'montant': 316.25}}


    On constate que l'écart entre la position réelle de la commande recherchée et son indice évalué à l'aide de la fonction de répartition vaut 0. Par conséquent, on accède directement à l'élément recherché en utilisant notre formule de calcul de position.



    V. Module complet

    On donne pour finir le code du module pour effectuer les tests :

    from scipy import stats # librairie contenant les distributions de probabilité

    def uniform_cdf(x,loc,scale):
    # fonction de répartition de la loi uniforme de paramètres loc=a et scale=(b-a) : F(x) = (x-a)/(b-a) pour a≤x≤b

    if x return 0
    else: # sinon
    if x < (loc+scale): # si x return (x-loc)/scale
    else: # sinon
    return 1

    def rechercher_element(liste_elements, nom_cle, valeur_cle, cdf, loc, scale):
    try:
    # nombre d'éléments de la liste
    n = len(liste_elements)

    # estimation de la position de la valeur de clé en fonction de la répartition des valeurs
    indice_est = round(cdf(x=valeur_cle,loc=loc,scale=scale)*(n-1))
    indice_val = indice_est

    # tant que la valeur de position indice_val est inférieure à la valeur de clé recherchée
    while liste_elements indice_val+=1 # on incrémente l'indice

    # tant que la valeur de position indice_val est supérieure à la valeur de clé recherchée
    while liste_elements>valeur_cle:
    indice_val-=1 # on décrémente l'indice

    # si la valeur a été trouvée
    if liste_elements==valeur_cle:
    # affiche l'écart entre la position réelle de la valeur recherchée et sa position estimée
    print("Écart entre la position réelle et la position estimée : {0}\n".format(abs(indice_val-indice_est)))

    # renvoie l'élément recherché accompagné de sa position dans la liste
    return {'position': indice_val, 'élément': liste_elements}

    # gestion d'erreur
    except IndexError: # si l'indice est en dehors des limites
    return None # on renvoie None : élément non trouvé


    # recherche d'une commande dans une liste : répartition uniforme des numéros de commandes
    print("========================================================================================")
    print(" Recherche d'une commande dans une liste : répartition uniforme des numéros de commande ")
    print("========================================================================================\n")

    # liste des commandes clients
    liste_commandes = [{'num_commande': 1, 'date_commande': '12/02/2023', 'nom_client': 'Bonnaventure Sébastien', 'montant': 125.70},
    {'num_commande': 2, 'date_commande': '14/02/2023', 'nom_client': 'Amat Laurent', 'montant': 223.55},
    {'num_commande': 4, 'date_commande': '15/02/2023', 'nom_client': 'Le Breton Tiphaine', 'montant': 326.20},
    {'num_commande': 5, 'date_commande': '17/02/2023', 'nom_client': 'Cardin Anthony', 'montant': 141.45},
    {'num_commande': 6, 'date_commande': '17/02/2023', 'nom_client': 'Soudain Cyril', 'montant': 544.40},
    {'num_commande': 8, 'date_commande': '18/02/2023', 'nom_client': 'Valdes Lucien', 'montant': 342.25},
    {'num_commande': 9, 'date_commande': '18/02/2023', 'nom_client': 'Harry John', 'montant': 347.25},
    {'num_commande': 10, 'date_commande': '18/02/2023', 'nom_client': 'Thiel Xavier', 'montant': 316.25},
    {'num_commande': 11, 'date_commande': '19/02/2023', 'nom_client': 'Gillet Mélanie', 'montant': 716.30},
    {'num_commande': 13, 'date_commande': '20/02/2023', 'nom_client': 'Richard Océane', 'montant': 613.20},
    {'num_commande': 14, 'date_commande': '20/02/2023', 'nom_client': 'Dias Pauline', 'montant': 113.20},
    {'num_commande': 15, 'date_commande': '21/02/2023', 'nom_client': 'Moulin Olivier', 'montant': 512.50}]


    # premier et dernier élément de la liste : bornes de l'intervalle [a, b] de la loi uniforme
    a = liste_commandes['num_commande']; b = liste_commandes[-1]['num_commande']

    # recherche de la commande à partir de son numéro et de la fonction de répartition de la loi uniforme
    commande = rechercher_element(liste_commandes, 'num_commande', 10, uniform_cdf, loc=a, scale=b-a)

    if commande: # si la commande a été trouvée
    # affiche la commande trouvée et sa position dans la liste
    print("Commande trouvée :")
    print(commande)
    else: # sinon
    print("Le numéro de commande n'a pas été trouvé !")



    VI. Conclusion

    Comme on l'a vu, si on a une idée de la répartition des valeurs dans la liste ordonnée, et si on connait la taille de la liste, on peut accéder plus rapidement à l'élément recherché en utilisant la fonction de répartition des valeurs.

    L'important est donc d'identifier la distribution de probabilité qui correspond le mieux à cette répartition avec les bons paramètres de position et de dispersion.


    Sources :

    https://fr.wikipedia.org/wiki/Loi_de_probabilit%C3%A9
    https://fr.wikipedia.org/wiki/Fonction_de_r%C3%A9partition
    https://fr.wikipedia.org/wiki/Loi_uniforme_continue
    https://fr.wikipedia.org/wiki/Loi_normale
    https://docs.scipy.org/doc/scipy/reference/stats.html
  • Vous avez lu gratuitement 2 912 articles depuis plus d'un an.
    Soutenez le club developpez.com en souscrivant un abonnement pour que nous puissions continuer à vous proposer des publications.

    Une erreur dans cette actualité ? Signalez-nous-la !