hippotese / DataGraph

Ce programme permet la lecture des données du capteur Datafficheur (http://hippotese.free.fr/blog/index.php/?q=datafficheur).
1 stars 2 forks source link

Autre type de graphiques #10

Open Anonymous-computer opened 1 year ago

Anonymous-computer commented 1 year ago

Bonjour.

En discutant avec C.H. Il a évoqué plusieurs fois la possibilité d'avoir la distribution des efforts.

Les graphiques qui suivent sont obtenus à partir des données "23-05-31-Dalton-buttoir"

Voici un premier essai :

Notes :

  1. Le dataframe utilisé est le même que pour le graphique effort / temps.
  2. il y a un filtrage supplémentaire (uniquement les data strictement supérieures a zéro)
  3. l'axe X est identique pour tous les graphiques. 2023-05-23_Histogrammes2_ttt

Deuxième essai :

Notes :

  1. Le dataframe utilisé est le même que pour le graphique effort / temps.
  2. il y a un filtrage supplémentaire (uniquement les data strictement supérieures a neuf)
  3. l'axe X n'est pas identique pour tous les graphiques. 2023-05-23_Histogrammes_ttt

Voici le code pour réaliser ce genre de graphique:

def create_histograms(df, outputfile, exclure=9, verbose=False):
    """
    Crée un histogramme et une courbe KDE pour chaque colonne du DataFrame, 
    excluant les valeurs inférieures à un certain seuil. 
    Chaque colonne est présentée dans un sous-graphique séparé.

    Args:
        df (pandas.DataFrame): Le DataFrame dont les données seront utilisées pour les histogrammes.
        outputfile (str): Le nom du fichier où sauvegarder le graphique.
        exclure (float, optional): Le seuil en dessous duquel les valeurs seront exclues de l'histogramme. Par défaut : 9.
        verbose (bool, optional): Contrôle l'affichage des messages détaillés pendant l'exécution de la fonction. Par défaut: False.

    Returns:
        None

    Raises:
        ValueError: Si df n'est pas un DataFrame pandas.
                    Si outputfile n'est pas une chaîne de caractères.
                    Si exclure n'est pas un nombre.
    """
    if not isinstance(df, pd.DataFrame):
        raise ValueError("df doit être un DataFrame pandas.")
    if not isinstance(outputfile, str):
        raise ValueError("outputfile doit être une chaîne de caractères.")
    if not isinstance(exclure, (int, float)):
        raise ValueError("exclure doit être un nombre.")

    num_cols = len(df.columns)
    fig, axs = plt.subplots(num_cols, figsize=(10, 6*num_cols))

    # Pour chaque colonne du DataFrame
    for i, column_name in enumerate(df.columns):
        data = df[column_name]
        data = data[data > exclure]  # Exclure les valeurs inférieures

        # Créer l'histogramme avec des espaces entre les barres (c.f. Note density)
        axs[i].hist(data, bins=30, rwidth=0.9, density=True, alpha=0.3, label=column_name)

        # Ajouter une courbe de distribution de noyau (c.f. Note KDE)
        sns.kdeplot(data, bw_adjust=0.5, ax=axs[i])

        # Ajouter une courbe de distribution normale
        mu, std = data.mean(), data.std()
        x = np.linspace(data.min(), data.max(), 100)
        p = norm.pdf(x, mu, std)
        axs[i].plot(x, p, 'k', linestyle='--', label='Normal Distribution')

        axs[i].set_title(f"Histogramme et KDE de {column_name}")
        axs[i].legend()
        axs[i].set_xlabel("kgf (kilogramme force - équivalant daN)")

    # Ajuster l'espacement entre les sous-graphiques
    plt.tight_layout()

    if outputfile:
        if verbose:
            print(f"Export du graphique dans {outputfile}.")
        plt.savefig(outputfile)
        plt.close()  # Ferme la figure pour liberer de la memoire
    else:
        plt.show()

Notes sur le choix du code :


# Note density :
#   L'option density dans la fonction hist de matplotlib change l'axe des y de l'histogramme 
#   pour afficher une estimation de la densité de probabilité au lieu du nombre de données dans chaque bin.
#
#   Lorsque density=True, les valeurs de l'histogramme sont normalisées de telle manière que 
#   l'aire sous l'histogramme (c'est-à-dire l'intégrale de la densité de probabilité sur toute la 
#   plage de données) est égale à 1. Cela signifie que chaque barre de l'histogramme n'affiche plus
#   le nombre d'observations dans chaque bin, mais plutôt l'estimation de la densité de 
#   probabilité que la valeur aléatoire tombe dans ce bin.
#
#   Cela permet de comparer des histogrammes de différents ensembles de données qui peuvent avoir des 
#   nombres d'échantillons différents. Il est également utile pour comparer avec une distribution de 
#   probabilité théorique ou pour ajuster une courbe à l'histogramme.
#                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

# Note KDE :
#   KDE signifie Kernel Density Estimation (Estimation de la densité par noyaux). C'est une technique qui 
#   permet d'estimer la fonction de densité de probabilité (PDF) d'une variable aléatoire. 
#   En termes simples, elle permet de lisser un histogramme.
#
#   Lorsque vous créez un histogramme pour représenter la distribution de vos données, 
#   le nombre de "bins" (c'est-à-dire les barres de l'histogramme) et leur largeur peuvent 
#   avoir un impact important sur l'apparence de l'histogramme. Deux personnes peuvent interpréter 
#   différemment les données en fonction de la façon dont elles choisissent de "biner" ces données. 
#   C'est un des problèmes majeurs des histogrammes.
#
#   L'estimation de densité par noyaux est une technique qui permet de "lisser" un histogramme. 
#   Au lieu de "biner" les données, elle utilise une "fonction de noyau" (d'où le nom "Kernel Density Estimation") 
#   pour créer une courbe lisse qui s'adapte aux données. Cette courbe peut alors être utilisée 
#   pour estimer la densité de probabilité à n'importe quel point.

Vos retours et vos avis sont les bienvenus, car étant extérieur au projet, je ne suis pas sûr de la pertinence de mes propositions.

Cordialement

Hippotese-Deny-Fady commented 1 year ago

Bonjour, Super travail... Merci.

Précision sur la demande (parfaitement remplie ici) : L'idée des courbes de fréquence de valeurs d'effort est de caractériser un travail régulier (donc labour, binage... et pas débardage). L'objectif est de faire un focus sur la courbe des fréquence, en excluant les arrets ou les faibles efforts qui ne correspondent pas à un travail (demi-tour par exemple). D'où l'intérêt de pouvoir éliminer les efforts faibles (ici inférieurs à 9).

Proposition 1 : S'il était possible de tracer la valeur moyenne de la courbe de gauss et les écart-types (-1 E-T et + 1 E-T) sous forme de droites verticales (et de valeurs arrondies dans la légende), on aurait des infos sur l'effort moyen demandé par un travail donné et la régularité des efforts demandés.

Proposition 2 : Peut-on changer les subdivisions de X dans les options pour avoir plus de lisibilité ?

Remarque (sans doute idiote) : Je n'ai pas bien compris où on récupère le "pandas.DataFrame", ce programme sera t'il un module de l'ensemble des programmes "Datafficheur" et si "oui", comment l'appelle t'on depuis le programme principal ?

Anonymous-computer commented 1 year ago

Bonjour.

Merci pour le retour.

Concernant les deux propositions :

Proposition 1 : S'il était possible de tracer la valeur moyenne de la courbe de gauss et les écart-types (-1 E-T et + 1 E-T) sous forme de droites verticales (et de valeurs arrondies dans la légende), on aurait des infos sur l'effort moyen demandé par un travail donné et la régularité des efforts demandés.

Proposition 2 : Peut-on changer les subdivisions de X dans les options pour avoir plus de lisibilité ?

Voici le graphique modifié en ce sens : 2023-05-23_Histogrammes_27

Et le code :

def create_histograms(df, outputfile, exclure=9, verbose=False):
    """
    Crée un histogramme et une courbe KDE pour chaque colonne du DataFrame,
    excluant les valeurs inférieures à un certain seuil.
    Chaque colonne est présentée dans un sous-graphique séparé.

    Args:
        df (pandas.DataFrame): Le DataFrame dont les données seront utilisées pour les histogrammes.
        outputfile (str): Le nom du fichier où sauvegarder le graphique.
        exclure (float, optional): Le seuil en dessous duquel les valeurs seront exclues de l'histogramme. Par défaut : 9.

    Returns:
        None

    Raises:
        ValueError: Si df n'est pas un DataFrame pandas.
                    Si outputfile n'est pas une chaîne de caractères.
                    Si exclure n'est pas un nombre.
    """
    if not isinstance(df, pd.DataFrame):
        raise ValueError("df doit être un DataFrame pandas.")
    if not isinstance(outputfile, str):
        raise ValueError("outputfile doit être une chaîne de caractères.")
    if not isinstance(exclure, (int, float)):
        raise ValueError("exclure doit être un nombre.")

    num_cols = len(df.columns)
    fig, axs = plt.subplots(num_cols, figsize=(10, 6 * num_cols))

    # Pour chaque colonne du DataFrame
    for i, column_name in enumerate(df.columns):
        data = df[column_name]
        data = data[data > exclure]  # Exclure les valeurs inférieures

        # Créer l'histogramme avec des espaces entre les barres (c.f. Note density)
        axs[i].hist(
            data, bins=30, rwidth=0.9, density=True, alpha=0.3, label=column_name
        )

        # Ajouter une courbe de distribution de noyau (c.f. Note KDE)
        sns.kdeplot(data, bw_adjust=0.5, ax=axs[i])

        # Ajouter une courbe de distribution normale
        mu, std = data.mean(), data.std()
        x = np.linspace(data.min(), data.max(), 100)
        p = norm.pdf(x, mu, std)
        axs[i].plot(x, p, "k", linestyle="--", label="Normal Distribution")

        # Ajouter des droites verticales pour la moyenne et l'écart type
        axs[i].axvline(
            mu,
            color="red",
            linestyle="dotted",
            alpha=0.7,
            linewidth=2,
            label=f"Moyenne: {mu:.0f} kgf",
        )
        axs[i].axvline(
            mu - std,
            color="purple",
            linestyle="dotted",
            alpha=0.7,
            linewidth=2,
            label=f"-1 E-T: {mu-std:.0f} kgf",
        )
        axs[i].axvline(
            mu + std,
            color="green",
            linestyle="dotted",
            alpha=0.7,
            linewidth=2,
            label=f"+1 E-T: {mu+std:.0f} kgf",
        )

        axs[i].set_title(f"Histogramme et KDE de {column_name}")
        axs[i].legend()

        # Changer les subdivisions de X
        axs[i].xaxis.set_major_locator(plt.MaxNLocator(20))

        axs[i].set_xlabel("kgf (kilogramme force - équivalant daN)")

    # Ajuster l'espacement entre les sous-graphiques
    plt.tight_layout()

    if outputfile:
        if verbose:
            print(f"Export du graphique dans {outputfile}.")
        plt.savefig(outputfile)
        plt.close()  # Ferme la figure pour liberer de la memoire
    else:
        plt.show()

Par rapport à la remarque :

Remarque : Je n'ai pas bien compris où on récupère le "pandas.DataFrame", ce programme sera t'il un module de l'ensemble des programmes "Datafficheur" et si "oui", comment l'appelle t'on depuis le programme principal ?

Si vous validez l'ajout de ce graphique, je l'ajouterai à la prochaine "pull request" que je ferai. J'attends que le responsable du dépôt valide celle-ci avant.

Pour utiliser la fonction create_histograms():

  1. Ajouter la fonction au fichier plotting.py. Attention, il faut également importer les deux modules suivant en début de fichier :
    import seaborn as sns
    from scipy.stats import norm
  2. Importer la fonction create_histograms dans le fichier datafficheur.py :
    from plotting import create_plot, create_histograms
  3. Puis appeler create_histograms dans la fonction main() de datafficheur.py. Par exemple à la suite du premier graphique :
        # creation des fichier
        if plot:
            if verbose:
                print("Creation du graphique.")
            create_plot(
                df,
                os.path.join(dir, date_str + "_" + outputplot) if outputplot else None,
                verbose,
                args.plothticks,
                date_str,
            )
            create_histograms(
                df,
                os.path.join(dir, date_str + "_Histogrammes_" + outputplot)
                if outputplot
                else None,
            )
Hippotese-Deny-Fady commented 1 year ago

Super ! Exactement ce à quoi je rêvais... Bravo ! Deny

Nb : et pour les graduation des X modifiables ?

Le jeu. 27 juil. 2023 10:03, Anonymous-computer @.***> a écrit :

Bonjour.

Merci pour le retour.

Concernant les deux propositions :

Proposition 1 : S'il était possible de tracer la valeur moyenne de la courbe de gauss et les écart-types (-1 E-T et + 1 E-T) sous forme de droites verticales (et de valeurs arrondies dans la légende), on aurait des infos sur l'effort moyen demandé par un travail donné et la régularité des efforts demandés.

Proposition 2 : Peut-on changer les subdivisions de X dans les options pour avoir plus de lisibilité ?

Voici le graphique modifié en ce sens : [image: 2023-05-23_Histogrammes_27] https://user-images.githubusercontent.com/134064414/256465273-4f065f3a-3ee8-4282-b8c3-a95d4804339d.png

Et le code :

def create_histograms(df, outputfile, exclure=9, verbose=False): """ Crée un histogramme et une courbe KDE pour chaque colonne du DataFrame, excluant les valeurs inférieures à un certain seuil. Chaque colonne est présentée dans un sous-graphique séparé. Args: df (pandas.DataFrame): Le DataFrame dont les données seront utilisées pour les histogrammes. outputfile (str): Le nom du fichier où sauvegarder le graphique. exclure (float, optional): Le seuil en dessous duquel les valeurs seront exclues de l'histogramme. Par défaut : 9. Returns: None Raises: ValueError: Si df n'est pas un DataFrame pandas. Si outputfile n'est pas une chaîne de caractères. Si exclure n'est pas un nombre. """ if not isinstance(df, pd.DataFrame): raise ValueError("df doit être un DataFrame pandas.") if not isinstance(outputfile, str): raise ValueError("outputfile doit être une chaîne de caractères.") if not isinstance(exclure, (int, float)): raise ValueError("exclure doit être un nombre.")

num_cols = len(df.columns)
fig, axs = plt.subplots(num_cols, figsize=(10, 6 * num_cols))

# Pour chaque colonne du DataFrame
for i, column_name in enumerate(df.columns):
    data = df[column_name]
    data = data[data > exclure]  # Exclure les valeurs inférieures

    # Créer l'histogramme avec des espaces entre les barres (c.f. Note density)
    axs[i].hist(
        data, bins=30, rwidth=0.9, density=True, alpha=0.3, label=column_name
    )

    # Ajouter une courbe de distribution de noyau (c.f. Note KDE)
    sns.kdeplot(data, bw_adjust=0.5, ax=axs[i])

    # Ajouter une courbe de distribution normale
    mu, std = data.mean(), data.std()
    x = np.linspace(data.min(), data.max(), 100)
    p = norm.pdf(x, mu, std)
    axs[i].plot(x, p, "k", linestyle="--", label="Normal Distribution")

    # Ajouter des droites verticales pour la moyenne et l'écart type
    axs[i].axvline(
        mu,
        color="red",
        linestyle="dotted",
        alpha=0.7,
        linewidth=2,
        label=f"Moyenne: {mu:.0f} kgf",
    )
    axs[i].axvline(
        mu - std,
        color="purple",
        linestyle="dotted",
        alpha=0.7,
        linewidth=2,
        label=f"-1 E-T: {mu-std:.0f} kgf",
    )
    axs[i].axvline(
        mu + std,
        color="green",
        linestyle="dotted",
        alpha=0.7,
        linewidth=2,
        label=f"+1 E-T: {mu+std:.0f} kgf",
    )

    axs[i].set_title(f"Histogramme et KDE de {column_name}")
    axs[i].legend()

    # Changer les subdivisions de X
    axs[i].xaxis.set_major_locator(plt.MaxNLocator(20))

    axs[i].set_xlabel("kgf (kilogramme force - équivalant daN)")

# Ajuster l'espacement entre les sous-graphiques
plt.tight_layout()

if outputfile:
    if verbose:
        print(f"Export du graphique dans {outputfile}.")
    plt.savefig(outputfile)
    plt.close()  # Ferme la figure pour liberer de la memoire
else:
    plt.show()

Par rapport à la remarque :

Remarque : Je n'ai pas bien compris où on récupère le "pandas.DataFrame", ce programme sera t'il un module de l'ensemble des programmes "Datafficheur" et si "oui", comment l'appelle t'on depuis le programme principal ?

Si vous validez l'ajout de ce graphique, je l'ajouterai à la prochaine "pull request" que je ferai. J'attends que le responsable du dépôt valide celle-ci https://github.com/hippotese/datafficheur/pull/9 avant.

Pour utiliser la fonction create_histograms():

  1. Ajouter la fonction au fichier plotting.py. Attention, il faut également importer les deux modules suivant en début de fichier :

import seaborn as snsfrom scipy.stats import norm

  1. Importer la fonction create_histograms dans le fichier datafficheur.py :

from plotting import create_plot, create_histograms

  1. Puis appeler create_histograms dans la fonction main() de datafficheur.py. Par exemple à la suite du premier graphique :

    # creation des fichier
    if plot:
        if verbose:
            print("Creation du graphique.")
        create_plot(
            df,
            os.path.join(dir, date_str + "_" + outputplot) if outputplot else None,
            verbose,
            args.plothticks,
            date_str,
        )
        create_histograms(
            df,
            os.path.join(dir, date_str + "_Histogrammes_" + outputplot)
            if outputplot
            else None,
        )

— Reply to this email directly, view it on GitHub https://github.com/hippotese/datafficheur/issues/10#issuecomment-1653100915, or unsubscribe https://github.com/notifications/unsubscribe-auth/BANS4HMASWEQPX5HSE6QGVDXSIOFDANCNFSM6AAAAAA2PUFY34 . You are receiving this because you commented.Message ID: @.***>

Anonymous-computer commented 1 year ago

J'ai modifié la fonction pour définir les subdivisions X.

        # Changer les subdivisions de X
        axs[i].xaxis.set_major_locator(plt.MaxNLocator(20))

A l'intégration de cette nouvelle fonction dans le futur, j'ajouterai une variable dans le fichier constants.py à l'instar de GRAPH_ALPHA.

Hippotese-Deny-Fady commented 1 year ago

Bonjour, Nous utilisons de manière intensive la dernière version du programme (du 7 août), tout est ok sauf détail sur l'axe des X précisé dans les commentaires. Pourrions-nous avoir une intégration du traçage des courbes de densité dans le programme principal, nous n'arrivons pas à l'utiliser par bidouillage (manque de compétences de notre part). Le résultat attendu semble vraiment prometteur. Merci d'avance.

Anonymous-computer commented 1 year ago

Bonjour.

Ce que vous demandez a été posté dans la pull request #11 mi-aout 2023 !

Hippotese-Deny-Fady commented 1 year ago

Bonjour,

Navré, je ne comprend pas encore tout au fonctionnement de Ghitub... Aujourd'hui, j'ai donc testé la nouvelle version en particulier pour obtenir les histogramme...

Si je lance le "nouveau" programme avec des paramètres déjà validés précédemment, mais sans en ajouter, le graphique s'affiche bien, mais quand je ferme le graphique pour avoir la suite, ça plante et j'ai le message : "Une erreur s'est produite : outputfile doit être une chaîne de caractères."

Je suis allé dans plotting.py et j'ai testé plein de trucs comme ajouter un nom par défaut à "outputfile" en inscrivant à la ligne 70 : def create_histograms(df, outputfile=Histo, exclure=9, verbose=False): ou def create_histograms(df, outputfile="Histo", exclure=9, verbose=False): ou def create_histograms(df, outputfile='Histo', exclure=9, verbose=False): ou en ajoutant plein de trucs sur la ligne de commande... Mais toujours la même erreur...

Je vois pas comment faire... Pourrait-on avoir l'histogramme (les histogrammes) par défaut à l'affichage juste après le graphique. Et éventuellement la possibilité de désactiver si on veut pas...

Ne pas oublier que les futurs utilisateur du programme sont des quiches en informatique, les options par défaut doivent donner une maximum de résultats... Merci d'avance...

Hippotese-Deny-Fady commented 1 year ago

Je me réponds à moi-même... Pour avoir le graphique des efforts et l'histogramme des fréquences par défaut, j'ai modifié 2 lignes du fichiers plotting.py. (Évidemment, j'ai pas trouvé ça tout seul mais avec l'aide de Mathan). Les lignes modifiées sont les lignes 70 et 90

def create_histograms(df, outputfile, exclure=9, verbose=False):

def create_histograms(df, outputfile=None, exclure=100, verbose=False):

#if not isinstance(outputfile, str):
if outputfile is not None and not isinstance(outputfile, str):

Et ça marche bien... Je suis bien incapable de le modifier dans le github...

Pour ne pas avoir d'affichage mais seulement un enregistrement des fichiers graphiques, on doit ajouter l'option "-op nomfichier.png" ou "-op nomfichier.pdf" à la ligne de commande Les 2 fichiers créés s’appelleront : "2023-08-27_Histogrammes_nomfichier.pdf" et "2023-08-27_nomfichier.pdf" (ou .png)

D'autre part, dans la ligne 70, il y a le paramètre "exclure=9", avec des mesures d'efforts important (je joins 2 fichiers exemple), pour avoir une moyenne correcte, on doit exclure les valeurs à partir au moins de 100. Ne devrait-on pas ajouter la valeur "exclure" à notre fichier "constants.py" pour pouvoir l'adapter au besoin.

Merci, en tout cas aux programmeurs, le résultat commence à être vraiment super...

Les 2 fichiers exemple avec de gros efforts (moyenne plus de 700 kgf pour 3 chevaux de front) 08271014.TXT 08271015.TXT

Hippotese-Deny-Fady commented 1 year ago

Juste une dernière petite remarque (orthographe). Sur les graphiques, il est écrit "kgf (kilogramme force - équivalant daN)", je suis pas spécialiste, mais j'aurai écrit "kgf (kilogramme force - équivalent daN)" mais ça se discute... Soit c'est le verbe "équivaloir" au participe présent (équivalant) soit c'est l'adjectif (équivalent)... Perso, je penche plutôt pour l'adjectif.