Créer un carnet de notes avec Pandas – Real Python

By | juillet 15, 2020

Python pas cher

L'un des emplois que tous les enseignants ont en commun est évaluer les étudiants. Que vous utilisiez des examens, des devoirs, des quiz ou des projets, vous devez généralement transformer les notes des élèves en classement par lettre à la fin du mandat. Cela implique souvent un tas de calculs que vous pourriez faire dans une feuille de calcul. Au lieu de cela, vous pouvez envisager d'utiliser Python et pandas.

Un problème avec l'utilisation d'une feuille de calcul est qu'il peut être difficile de voir quand vous faites une erreur dans une formule. Peut-être que vous avez sélectionné la mauvaise colonne et mis des questionnaires où les examens devraient aller. Vous avez peut-être trouvé au maximum deux valeurs incorrectes. Pour résoudre ce problème, vous pouvez utiliser Python et pandas pour faire tous vos calculs et trouver et corriger ces erreurs beaucoup plus rapidement.

Dans ce didacticiel, vous apprendrez à:

  • Charge et fusionner des données provenant de plusieurs sources avec des pandas
  • Filtre et groupe les données dans un pandas DataFrame
  • Calculer et terrain notes dans un DataFrame pandas

Cliquez sur le lien ci-dessous pour télécharger le code de ce projet pandas et suivez-le pendant que vous créez votre script de carnet de notes:

Démo: ce que vous allez construire

Dans ce projet pandas, vous allez créer un script Python qui charge vos données de notes et calcule les notes des lettres pour vos élèves. Découvrez cette vidéo pour une démonstration du script en action:

Votre script s'exécutera à partir de la ligne de commande ou de votre IDE et produira des fichiers de sortie CSV afin que vous puissiez coller les notes dans le système de notation de votre école. Vous allez également produire quelques graphiques pour voir comment vos notes sont distribuées.

Aperçu du projet

Ce projet pandas comprend quatre étapes principales:

  1. Explorez les données vous utiliserez dans le projet pour déterminer le format et les données dont vous aurez besoin pour calculer vos notes finales.
  2. Charger les données dans les pandas DataFrames, en veillant à connecter les notes du même étudiant à toutes vos sources de données.
  3. Calculez les notes finales et enregistrez-les sous forme de fichiers CSV.
  4. Tracer la distribution des notes et découvrez comment les notes varient selon vos élèves.

Une fois ces étapes terminées, vous disposerez d'un script Python fonctionnel qui pourra calculer vos notes. Vos notes seront dans un format que vous devriez pouvoir télécharger dans le système de gestion des élèves de votre école.

Lecture de fond

Vous tirerez le meilleur parti de ce projet pandas si vous avez un peu d'expérience de travail avec les pandas. Si vous avez besoin d'une remise à niveau, ces tutoriels et cours vous permettront de vous familiariser avec ce projet:

Ne vous inquiétez pas trop de la mémorisation de tous les détails de ces didacticiels. Vous verrez une application pratique des sujets de ce projet pandas. Voyons maintenant les données que vous utiliserez dans ce projet!

Explorer les données de ce projet Pandas

Comme la plupart des enseignants, vous avez probablement utilisé une variété de services pour gérer votre classe ce trimestre, notamment:

  • Le système de gestion des élèves de l'école
  • Un service pour gérer l'attribution et la notation des devoirs et des examens
  • Un service pour gérer l'attribution et la notation des quiz

Aux fins de ce projet, vous utiliserez des exemples de données qui représentent ce que vous pourriez retirer de ces systèmes. Les données sont dans des fichiers de valeurs séparées par des virgules (CSV). Quelques exemples de données sont présentés ici. Tout d'abord, il existe un fichier contenant les informations de la liste pour la classe. Cela proviendrait de votre système d'administration des étudiants:

Identifiant Nom NetID Adresse électronique Section
1234567 «Barrera Jr., Woody» WXB12345 WOODY.BARRERA_JR@UNIV.EDU 1
2345678 «Lambert, Malaika» MXL12345 MALAIKA.LAMBERT@UNIV.EDU 2
3456789 "Joyce, Traci" TXJ12345 TRACI.JOYCE@UNIV.EDU 1
4567890 «Fleur, John Gregg» JGF12345 JOHN.G.2.FLOWER@UNIV.EDU 3

Ce tableau indique le numéro d'identification, le nom, le NetID et l'adresse e-mail de chaque élève ainsi que la section de la classe à laquelle ils appartiennent. Dans ce terme, vous avez enseigné une classe qui s'est réunie à des moments différents, et chaque heure de classe a un numéro de section différent.

Ensuite, vous avez un fichier qui contient les devoirs et les résultats des examens. Celui-ci provient du service de devoirs et de notation des examens et a une disposition des colonnes légèrement différente de la liste:

SID Prénom Nom de famille Devoirs 1 Devoirs 1 – Max Points Devoirs 1 – Heure de soumission
jgf12345 Gregg Fleur 69 80 2019-08-29 08: 56: 02-07: 00
mxl12345 Malaika Lambert 63 80 2019-08-29 08: 56: 02-07: 00
txj12345 Traci Joyce 80 2019-08-29 08: 56: 02-07: 00
wxb12345 Boisé Barrera 55 80 2019-08-29 08: 56: 02-07: 00

Dans ce tableau, chaque élève a un SID, un prénom et un nom. De plus, trois valeurs sont rapportées pour chaque devoir et examen que vous avez donné:

  1. le But l'élève a reçu
  2. le score maximum pour cette affectation
  3. le temps l'étudiant a soumis le devoir

Enfin, vous avez des fichiers qui contiennent des informations sur les notes du quiz. Ces fichiers sont séparés de sorte qu'un quiz est stocké dans chaque fichier de données, et les informations dans ces fichiers sont différentes de la liste et des fichiers de devoirs:

Nom de famille Prénom Email Classe
Barrera Boisé woody.barrera_jr@univ.edu 4
Fleur John john.g.2.flower@univ.edu 8
Joyce Traci traci.joyce@univ.edu 8
Lambert Malaika malaika.lambert@univ.edu 8

Dans le tableau du quiz, chaque élève a un nom, un prénom, un e-mail et une note de quiz. Notez que le score de quiz maximal possible n'est pas enregistré dans ce tableau. Vous verrez comment fournir ces informations ultérieurement.

En inspectant ces données, vous remarquerez peut-être plusieurs fonctionnalités:

  • Chaque tableau a différentes représentations des noms des élèves. Par exemple, dans le tableau de la liste, les noms sont sous la forme "Nom Prénom" avec des guillemets afin qu'un analyseur CSV n'interprète pas la virgule comme une nouvelle colonne. Cependant, dans le tableau des devoirs, les prénoms et les noms de famille obtiennent chacun leur propre colonne.

  • Chaque élève peut utiliser un nom différent dans différentes sources de données. Par exemple, les tableaux de quiz n'incluent pas le suffixe Jr. au nom de Woody Barrera. Un autre exemple est que John Flower préfère être appelé par son deuxième prénom, Gregg, alors il a ajusté l'affichage dans le tableau des devoirs.

  • L'adresse e-mail de chaque étudiant n'a pas les mêmes éléments. L'adresse e-mail de base d'un étudiant est first.last@univ.edu. Cependant, si un e-mail de ce formulaire appartient déjà à un autre étudiant, l'adresse e-mail est modifiée pour être unique. Cela signifie que vous ne pouvez pas prédire l'adresse e-mail d'un étudiant uniquement à partir de son nom.

  • Chaque colonne peut utiliser un nom unique même si elle contient les mêmes données. Par exemple, tous les étudiants ont un identifiant du formulaire abc12345. La table de liste appelle cela leur NetID, tandis que la table des devoirs appelle cela leur SID. Les tables de quiz ne contiennent pas du tout ces informations. De même, certaines tables utilisent l'en-tête de colonne Adresse e-mail, tandis que d'autres utilisent simplement Email.

  • Chaque table trie les données différemment. Dans le tableau de liste, les données sont triées par Identifiant colonne. Dans le tableau des devoirs, les données sont triées par la première lettre du prénom. Dans les tableaux de quiz, les données sont triées dans un ordre aléatoire.

  • Chacune des lignes ou colonnes des tableaux peut contenir des données manquantes. Par exemple, Traci Joyce n'a pas soumis son travail pour les devoirs 1, donc sa ligne est vide dans le tableau des devoirs.

Toutes ces fonctionnalités et bien plus sont présentes dans des données que vous verrez dans le monde réel. Dans le reste de ce projet pandas, vous verrez comment vous pouvez aborder chacune de ces fonctionnalités et vous assurer qu'elles ne perturbent pas votre analyse.

Décider du format final des données

Maintenant que vous avez vu les formats de données brutes, vous pouvez penser au format final des données. En fin de compte, vous devrez calculer une note pour chaque étudiant à partir de leurs notes brutes. Chaque ligne de votre tableau de données final contiendra toutes les données pour un seul élève. Le nombre de lignes sera alors égal au nombre d'élèves de votre classe.

Les colonnes représenteront chaque score de devoirs, score de quiz et score d'examen. Vous stockerez également des informations sur chaque élève, notamment son nom et son identifiant unique. Enfin, vous stockerez chacun de vos calculs et la note finale dans des colonnes distinctes.

Voici un échantillon de votre table finale:

Identifiant Nom Devoirs Quiz Examens Score final Note finale
Étudiant 1 Derniers seront les premiers # # # # UN F
Étudiant 2 Derniers seront les premiers # # # # UN F

Chaque ligne du tableau stocke toutes les données d'un seul élève. La première colonne contient l'identifiant unique de l'élève et la deuxième colonne le nom de l'élève. Ensuite, une série de colonnes stocke les devoirs, le quiz, l'examen et les notes finales. Le dernier est une colonne pour la note finale.

Maintenant que vous avez vu quelle sera la forme finale des données, vous pouvez commencer à travailler avec les données. La première étape consiste à charger les données!

Chargement des données avec Pandas

Les pandas sont l'un des meilleurs packages pour travailler avec des données tabulaires en Python! Vous allez profiter de nombreuses fonctionnalités des pandas, en particulier pour fusionner des ensembles de données et effectuer des opérations mathématiques avec les données.

Les exemples de code présentés dans cette section sont collectés dans le 01-loading-the-data.py fichier. Vous pouvez télécharger le code source en cliquant sur le lien ci-dessous:

Créez un script Python appelé gradebook.py. Vous devrez également créer un dossier appelé Les données qui stockera les fichiers de données d'entrée pour votre script de carnet de notes.

Puis dans gradebook.py, commencez par ajouter une docstring au niveau du module qui explique le but du fichier. Vous pouvez également importer quelques bibliothèques dès maintenant:

"" "Calculez les notes des élèves en combinant des données provenant de nombreuses sources.

À l'aide de pandas, ce script combine les données de:

* Liste
* Devoirs et notes d'examen
* Grades de quiz

pour calculer les notes finales d'une classe.
"" "
de pathlib importer Chemin
importer pandas comme pd

Dans ce code, vous incluez une docstring qui décrit l'objectif du script. Ensuite, vous importez pathlib.Path et pandas.

Chargement du fichier de liste

Vous êtes maintenant prêt à charger les données, en commençant par la liste:

ICI = Chemin(__fichier__).parent
DATA_FOLDER = ICI / "Les données"

liste = pd.read_csv(
    DATA_FOLDER / "roster.csv",
    convertisseurs="NetID": str.inférieur, "Adresse électronique": str.inférieur,
    usecols=[[[["Section", "Adresse électronique", "NetID"],
    index_col="NetID",
)

Dans ce code, vous créez deux constantes, ICI et DATA_FOLDER, pour garder une trace de l'emplacement du fichier en cours d'exécution ainsi que du dossier dans lequel les données sont stockées. Ces constantes utilisent le pathlib pour faciliter la consultation de différents dossiers.

Ensuite, vous lisez le fichier de liste en utilisant pd.read_csv (). Pour aider à traiter les données ultérieurement, vous définissez un index à l'aide de index_col et inclure uniquement les colonnes utiles avec usecols.

Pour vous assurer de pouvoir comparer les chaînes ultérieurement, vous passez également convertisseurs argument pour convertir les colonnes en minuscules. Cela simplifiera les comparaisons de chaînes que vous ferez plus tard.

Vous pouvez voir certaines des données dans le liste DataFrame ci-dessous:

NetID Adresse électronique Section
wxb12345 woody.barrera_jr@univ.edu 1
mxl12345 malaika.lambert@univ.edu 2
txj12345 traci.joyce@univ.edu 1
jgf12345 john.g.2.flower@univ.edu 3

Ce sont les quatre premières lignes de liste, et ils correspondent aux lignes de la table de liste que vous avez consultée dans la section précédente. Cependant, le NetID et Adresse électronique les colonnes ont toutes deux été converties en chaînes minuscules car vous avez réussi str.lower à convertisseurs pour ces deux colonnes. Vous avez également omis le Nom et Identifiant Colonnes.

Chargement du fichier de devoirs et d'examen

Ensuite, vous pouvez charger le fichier CSV des devoirs et des notes d'examen. N'oubliez pas que ce fichier comprend le prénom et le nom et la colonne SID en plus de toutes les notes. Vous souhaitez ignorer les colonnes avec les heures de soumission:

hw_exam_grades = pd.read_csv(
    DATA_FOLDER / "hw_exam_grades.csv",
    convertisseurs="SID": str.inférieur,
    usecols=lambda X: "Soumission" ne pas dans X,
    index_col="SID",
)

Dans ce code, vous utilisez à nouveau le convertisseurs argument pour convertir les données dans le SID et Adresse électronique colonnes en minuscules. Bien que les données de ces colonnes semblent être en minuscules lors de la première inspection, la meilleure pratique consiste à s'assurer que tout est cohérent. Vous devez également spécifier SID comme colonne d'index pour correspondre à la liste Trame de données.

Dans ce fichier CSV, il existe un certain nombre de colonnes contenant les heures de soumission des affectations que vous n'utiliserez dans aucune autre analyse. Cependant, il y a tellement d'autres colonnes que vous souhaitez conserver qu'il serait fastidieux de toutes les énumérer explicitement.

Pour contourner cela, usecols accepte également les fonctions appelées avec un seul argument, le nom de la colonne. Si la fonction retourne Vrai, la colonne est incluse. Sinon, la colonne est exclue. Avec le lambda fonction que vous passez ici, si la chaîne "Soumission" apparaît dans le nom de la colonne, la colonne sera exclue.

Voici un échantillon des hw_exam_grades DataFrame pour vous donner une idée de l'apparence des données après leur chargement:

SID Devoirs 1 Devoirs 1 – Max Points Devoirs 2
jgf12345 69 80 52
mxl12345 63 80 57
txj12345 nan 80 77
wxb12345 55 80 62

Ce sont les lignes des exemples d'élèves dans le fichier CSV des devoirs et des notes d'examen que vous avez vu dans la section précédente. Notez que les données manquantes pour Traci Joyce (SID txj12345) dans le Devoirs 1 la colonne a été lue comme nanou Pas un nombre, valeur. Vous verrez comment gérer ce type de données dans une section ultérieure. Les ellipses (...) indiquent des colonnes de données qui ne figurent pas dans l'exemple ici mais qui sont chargées à partir des données réelles.

Chargement des fichiers de quiz

Enfin, vous devez charger les données des quiz. Il y a cinq questionnaires que vous devez lire, et la forme la plus utile de ces données est un DataFrame unique plutôt que cinq DataFrames distincts. Le format de données final ressemblera à ceci:

Email Quiz 5 Quiz 2 Quiz 4 Quiz 1 Quiz 3
woody.barrera_jr@univ.edu dix dix 7 4 11
john.g.2.flower@univ.edu 5 8 13 8 8
traci.joyce@univ.edu 4 6 9 8 14
malaika.lambert@univ.edu 6 dix 13 8 dix

Ce DataFrame a le Email colonne comme index, et chaque quiz est dans une colonne distincte. Notez que les questionnaires sont hors service, mais vous verrez quand vous calculerez les notes finales que la commande n'a pas d'importance. Vous pouvez utiliser ce code pour charger les fichiers de quiz:

quiz_grades = pd.Trame de données()
pour chemin du fichier dans DATA_FOLDER.glob("quiz _ * _ grades.csv"):
    quiz_name = "".joindre(chemin du fichier.tige.Titre().Divisé("_")[:[:[:[:2])
    quiz = pd.read_csv(
        chemin du fichier,
        convertisseurs="Email": str.inférieur,
        index_col=[[[["Email"],
        usecols=[[[["Email", "Classe"],
    ).Renommer(Colonnes="Classe": quiz_name)
    quiz_grades = pd.concat([[[[quiz_grades, quiz], axe=1)

Dans ce code, vous créez un DataFrame vide appelé quiz_grades. Vous avez besoin du DataFrame vide pour la même raison que vous devez créer une liste vide avant d'utiliser list.append ().

Tu utilises Path.glob () pour trouver tous les fichiers CSV du quiz et les charger avec des pandas, en veillant à convertir les adresses e-mail en minuscules. Vous définissez également la colonne d'index pour chaque quiz sur les adresses e-mail des étudiants, qui pd.concat () utilise pour aligner les données de chaque élève.

Remarquez que vous passez axe = 1 à pd.concat (). Cela oblige les pandas à concaténer des colonnes plutôt que des lignes, en ajoutant chaque nouveau quiz dans une nouvelle colonne dans le DataFrame combiné.

Enfin, vous utilisez DataFrame.rename () pour changer le nom de la colonne de notes de Classe à quelque chose de spécifique à chaque quiz.

Fusion des cadres de données de notes

Maintenant que toutes vos données sont chargées, vous pouvez combiner les données de vos trois DataFrames, liste, hw_exam_grades, et quiz_grades. Cela vous permet d'utiliser un DataFrame pour tous vos calculs et d'enregistrer un carnet de notes complet dans un autre format à la fin.

Toutes les modifications apportées à gradebook.py faites dans cette section sont collectées dans le 02-merging-dataframes.py fichier. Vous pouvez télécharger le code source en cliquant sur le lien ci-dessous:

Vous allez fusionner les données en deux étapes:

  1. Fusionner liste et hw_exam_grades ensemble dans un nouveau DataFrame appelé final_data.
  2. Fusionner final_data et quiz_grades ensemble.

Vous utiliserez différentes colonnes dans chaque DataFrame comme clé de fusion, c'est ainsi que les pandas déterminent les lignes à conserver ensemble. Ce processus est nécessaire car chaque source de données utilise un identifiant unique différent pour chaque élève.

Fusionner la liste et les devoirs

Dans liste et hw_exam_grades, vous avez la colonne NetID ou SID comme identifiant unique pour un étudiant donné. Lorsque vous fusionnez ou rejoignez des DataFrames dans des pandas, il est très utile d'avoir un index. Vous avez déjà vu à quel point cela était utile lorsque vous chargiez les fichiers de quiz.

N'oubliez pas que vous avez réussi le index_col argument à pd.read_csv () lorsque vous avez chargé la liste et les notes de devoirs. Vous pouvez maintenant fusionner ces deux DataFrames ensemble:

final_data = pd.fusionner(
    liste, hw_exam_grades, left_index=Vrai, right_index=Vrai,
)

Dans ce code, vous utilisez pd.merge () pour combiner le liste et hw_exam_grades DataFrames.

Voici un exemple du DataFrame fusionné pour les quatre exemples d'étudiants:

NetID Adresse électronique Devoirs 1
wxb12345 woody.barrera_jr@univ.edu 55
mxl12345 malaika.lambert@univ.edu 63
txj12345 traci.joyce@univ.edu nan
jgf12345 john.g.2.flower@univ.edu 69

Comme vous l'avez vu précédemment, les ellipses indiquent des colonnes qui ne sont pas affichées dans l'exemple ici mais qui sont présentes dans le DataFrame réel. L'exemple de tableau montre que les élèves ayant le même NetID ou SID ont été fusionnés, de sorte que leurs adresses e-mail et notes de devoirs 1 correspondent aux tableaux que vous avez vus précédemment.

Fusion des notes du quiz

Lorsque vous avez chargé les données pour le quiz_grades, vous avez utilisé l'adresse e-mail comme identifiant unique pour chaque élève. C'est différent de hw_exam_grades et liste, qui utilisaient respectivement le NetID et le SID.

Fusionner quiz_grades dans final_data, vous pouvez utiliser l'index à partir de quiz_grades et le Adresse électronique colonne de final_data:

final_data = pd.fusionner(
    final_data, quiz_grades, à gauche sur="Adresse électronique", right_index=Vrai
)

Dans ce code, vous utilisez le à gauche sur argument à pd.merge () pour dire aux pandas d'utiliser le Adresse électronique colonne dans final_data dans la fusion. Vous utilisez également right_index pour dire aux pandas d'utiliser l'index de quiz_grades dans la fusion.

Voici un échantillon du DataFrame fusionné montrant les quatre exemples d'étudiants:

NetID Adresse électronique Devoirs 1 Quiz 3
wxb12345 woody.barrera_jr@univ.edu 55 11
mxl12345 malaika.lambert@univ.edu 63 dix
txj12345 traci.joyce@univ.edu nan 14
jgf12345 john.g.2.flower@univ.edu 69 8

N'oubliez pas que les ellipses signifient que des colonnes sont manquantes dans l'exemple de table ici mais seront présentes dans le DataFrame fusionné. Vous pouvez revérifier les tableaux précédents pour vérifier que les nombres sont alignés pour les bons élèves.

Remplir nan Valeurs

Maintenant, toutes vos données sont fusionnées en un seul DataFrame. Avant de pouvoir passer au calcul des notes, vous devez effectuer un nettoyage supplémentaire des données.

Vous pouvez voir dans le tableau ci-dessus que Traci Joyce a toujours un nan valeur pour son devoir de devoirs 1. Vous ne pouvez pas utiliser nan valeurs dans les calculs parce que, eh bien, ce ne sont pas un nombre! Vous pouvez utiliser DataFrame.fillna () d'attribuer un numéro à tous les nan valeurs en final_data:

final_data = final_data.fillna(0)

Dans ce code, vous utilisez DataFrame.fillna () pour tout remplir nan valeurs en final_data avec la valeur 0. Il s'agit d'une résolution appropriée car le nan dans la colonne Devoirs de Traci Joyce 1 indique que le score est manquant, ce qui signifie qu'elle n'a probablement pas remis le devoir.

Voici un échantillon du DataFrame modifié montrant les quatre exemples d'étudiants:

NetID Prénom Nom de famille Devoirs 1
wxb12345 Boisé Barrera 55
mxl12345 Malaika Lambert 63
txj12345 Traci Joyce 0
jgf12345 John Fleur 69

Comme vous pouvez le voir dans ce tableau, le score de Traci Joyce Homework 1 est désormais 0 au lieu de nan, mais les notes des autres étudiants n'ont pas changé.

Calcul des notes avec des cadres de données Pandas

Il y a trois catégories de devoirs que vous aviez dans votre classe:

  1. Examens
  2. Devoirs
  3. Quiz

Chacune de ces catégories se voit attribuer un poids vers la note finale des élèves. Pour votre classe ce terme, vous avez attribué les poids suivants:

Catégorie Pourcentage de la note finale Poids
Examen 1 Score 5 0,05
Score de l'examen 2 dix 0,10
Score de l'examen 3 15 0,15
Score du quiz 30 0,30
Score de devoirs 40 0,40

Le score final peut être calculé en multipliant le poids par le score total de chaque catégorie et en additionnant toutes ces valeurs. Le score final sera ensuite converti en une note finale.

Toutes les modifications apportées à gradebook.py faites dans cette section sont collectées dans le 03-calculating-grades.py fichier. Vous pouvez télécharger le code source en cliquant sur le lien ci-dessous:

Cela signifie que vous devez calculer le total de chaque catégorie. Le total de chaque catégorie est un nombre à virgule flottante de 0 à 1 qui représente le nombre de points qu'un élève a gagné par rapport au score maximum possible. Vous gérerez tour à tour chaque catégorie d'affectation.

Calcul du score total à l'examen

Vous calculerez d'abord les notes des examens. Étant donné que chaque examen a un poids unique, vous pouvez calculer le score total pour chaque examen individuellement. Il est plus judicieux d'utiliser un pour boucle, que vous pouvez voir dans ce code:

n_exams = 3
pour n dans gamme(1, n_exams + 1):
    final_data[[[[F"Examen n    But"] = (
        final_data[[[[F"Examen n"] / final_data[[[[F"Examen n    - Max Points "]
    )

Dans ce code, vous définissez n_exams égal à 3 parce que vous avez passé trois examens pendant le trimestre. Ensuite, vous parcourez chaque examen pour calculer le score en divisant le score brut par le nombre maximal de points pour cet examen.

Voici un échantillon des données d'examen pour les quatre exemples d'étudiants:

NetID Examen 1 Score Score de l'examen 2 Score de l'examen 3
wxb12345 0,86 0,62 0,90
mxl12345 0,60 0,91 0,93
txj12345 1,00 0,84 0,64
jgf12345 0,72 0,83 0,77

Dans ce tableau, chaque élève a obtenu entre 0,0 et 1,0 pour chacun des examens. À la fin de votre script, vous multipliez ces scores par le poids pour déterminer la proportion de la note finale.

Calcul des scores de devoirs

Ensuite, vous devez calculer les scores des devoirs. Le nombre maximal de points pour chaque devoir varie de 50 à 100. Cela signifie qu'il existe deux façons de calculer le score des devoirs:

  1. Par score total: Additionnez les scores bruts et les points maximum indépendamment, puis prenez le rapport.
  2. Par score moyen: Divisez chaque score brut par ses points maximums respectifs, puis prenez la somme de ces ratios et divisez le total par le nombre d'affectations.

La première méthode donne un score plus élevé aux élèves qui ont obtenu des résultats constants, tandis que la deuxième méthode favorise les élèves qui ont bien réussi les travaux qui valaient plus de points. Pour aider les étudiants, vous leur donnerez le maximum de ces deux scores.

Le calcul de ces scores prendra quelques étapes:

  1. Collectez les colonnes avec des données de devoirs.
  2. Calculez le score total.
  3. Calculez le score moyen.
  4. Déterminez quel score est le plus grand et sera utilisé dans le calcul du score final.

Tout d'abord, vous devez collecter toutes les colonnes avec des données de devoirs. Vous pouvez utiliser DataFrame.filter () pour faire ça:

devoirs_scores = final_data.filtre(regex=r"^ Devoirs  d  d? $", axe=1)
homework_max_points = final_data.filtre(regex=r"^ Devoirs  d  d? -", axe=1)

Dans ce code, vous utilisez une expression régulière (regex) pour filtrer final_data. Si un nom de colonne ne correspond pas à l'expression régulière, la colonne ne sera pas incluse dans le DataFrame résultant.

L'autre argument auquel vous passez DataFrame.filter () est axe. De nombreuses méthodes d'un DataFrame peuvent fonctionner en ligne ou en colonne, et vous pouvez basculer entre les deux approches à l'aide de la axe argument. Avec l'argument par défaut axe = 0, les pandas rechercheraient dans l'index des lignes correspondant à l'expression régulière que vous avez transmise. Puisque vous voulez trouver tous les Colonnes qui correspondent à l'expression rationnelle à la place, vous passez axe = 1.

Maintenant que vous avez collecté les colonnes dont vous avez besoin à partir du DataFrame, vous pouvez effectuer les calculs avec elles. Tout d'abord, vous additionnez les deux valeurs indépendamment, puis vous les divisez pour calculer le score total des devoirs:

sum_of_hw_scores = devoirs_scores.somme(axe=1)
sum_of_hw_max = homework_max_points.somme(axe=1)
final_data[[[["Total devoirs"] = sum_of_hw_scores / sum_of_hw_max

Dans ce code, vous utilisez DataFrame.sum () et passer le axe argument. Par défaut, .somme() additionnera les valeurs de toutes les lignes de chaque colonne. Cependant, vous voulez la somme de toutes les colonnes pour chaque ligne car chaque ligne représente un étudiant. le axe = 1 l'argument dit aux pandas de faire exactement cela.

Ensuite, vous affectez une nouvelle colonne dans final_data appelé Total devoirs au rapport des deux sommes.

Voici un exemple des résultats du calcul pour les quatre exemples d'élèves:

NetID Somme des scores de devoirs Somme des scores maximum Total devoirs
wxb12345 598 740 0.808108
mxl12345 612 740 0,827027
txj12345 581 740 0,785135
jgf12345 570 740 0,770270

Dans ce tableau, vous pouvez voir la somme des scores des devoirs, la somme des scores max et le score total des devoirs pour chaque élève.

L'autre méthode de calcul consiste à diviser chaque score de devoirs par son score maximum, à additionner ces valeurs et à diviser le total par le nombre de devoirs. Pour ce faire, vous pouvez utiliser un pour boucle et parcourez chaque colonne. Cependant, pandas vous permet d'être plus efficace car il correspondra aux étiquettes de colonne et d'index et effectuera des opérations mathématiques uniquement sur les étiquettes correspondantes.

Pour que cela fonctionne, vous devez modifier les noms de colonne pour homework_max_points pour faire correspondre les noms devoirs_scores. Vous pouvez le faire en utilisant DataFrame.set_axis ():

hw_max_renamed = homework_max_points.set_axis(devoirs_scores.Colonnes, axe=1)

Dans ce code, vous créez un nouveau DataFrame, hw_max_renamedet vous définissez les colonnes axe d'avoir les mêmes noms que les colonnes devoirs_scores. Vous pouvez maintenant utiliser ce DataFrame pour plus de calculs:

average_hw_scores = (devoirs_scores / hw_max_renamed).somme(axe=1)

Dans ce code, vous calculez la average_hw_scores en divisant chaque score de devoirs par ses points maximums respectifs. Ensuite, vous additionnez les ratios pour toutes les affectations de devoirs dans chaque ligne avec DataFrame.sum () et l'argument axe = 1.

Étant donné que la valeur maximale pour chaque devoir individuel est de 1,0, la valeur maximale que cette somme pourrait prendre serait égale au nombre total de devoirs. Cependant, vous avez besoin d'un nombre qui va de 0 à 1 pour prendre en compte la note finale.

Cela signifie que vous devez diviser average_hw_scores par le nombre d'affectations que vous pouvez faire avec ce code:

final_data[[[["Devoirs moyens"] = average_hw_scores / devoirs_scores.forme[[[[1]

Dans ce code, vous utilisez DataFrame.shape pour obtenir le nombre d'affectations de devoirs_scores. Comme un tableau NumPy, DataFrame.shape renvoie un tuple de (n_rows, n_columns). Prendre la deuxième valeur du tuple vous donne le nombre de colonnes dans devoirs_scores, qui est égal au nombre d'affectations.

Ensuite, vous affectez le résultat de la division à une nouvelle colonne dans final_data appelé Devoirs moyens.

Voici un exemple de résultat de calcul pour les quatre exemples d'étudiants:

NetID Somme des notes moyennes des devoirs Devoirs moyens
wxb12345 7.99405 0,799405
mxl12345 8.18944 0,818944
txj12345 7.85940 0,785940
jgf12345 7.65710 0,765710

Dans ce tableau, notez que le Somme des notes moyennes des devoirs peut varier de 0 à 10, mais le Devoirs moyens varie de 0 à 1. La deuxième colonne sera utilisée pour comparer à Total devoirs suivant.

Maintenant que vous avez calculé vos deux notes de devoirs, vous pouvez prendre la valeur maximale à utiliser dans le calcul de la note finale:

final_data[[[["Score de devoirs"] = final_data[[[[
    [[[["Total devoirs", "Devoirs moyens"]
].max(axe=1)

Dans ce code, vous sélectionnez les deux colonnes que vous venez de créer, Total devoirs et Devoirs moyenset attribuez la valeur maximale à une nouvelle colonne appelée Score de devoirs. Notez que vous prenez le maximum pour chaque élève avec axe = 1.

Voici un échantillon des résultats calculés pour les quatre exemples d'étudiants:

NetID Total devoirs Devoirs moyens Score de devoirs
wxb12345 0.808108 0,799405 0.808108
mxl12345 0,827027 0,818944 0,827027
txj12345 0,785135 0,785940 0,785940
jgf12345 0,770270 0,765710 0,770270

Dans ce tableau, vous pouvez comparer les Total devoirs, Devoirs moyenset final Score de devoirs Colonnes. Vous pouvez voir que le Score de devoirs reflète toujours le plus grand des Total devoirs ou Devoirs moyens.

Calcul du score du quiz

Ensuite, vous devez calculer le score du quiz. Les questionnaires ont également différents nombres de points maximum, vous devez donc faire la même procédure que vous avez faite pour les devoirs. La seule différence est que la note maximale de chaque questionnaire n'est pas spécifiée dans les tableaux de données du questionnaire, vous devez donc créer une série pandas pour contenir ces informations:

quiz_scores = final_data.filtre(regex=r"^ Quiz  d $", axe=1)
quiz_max_points = pd.Séries(
    "Quiz 1": 11, "Quiz 2": 15, "Quiz 3": 17, "Quiz 4": 14, "Quiz 5": 12
)

sum_of_quiz_scores = quiz_scores.somme(axe=1)
sum_of_quiz_max = quiz_max_points.somme()
final_data[[[["Total des quiz"] = sum_of_hw_scores / sum_of_hw_max

average_quiz_scores = (quiz_scores / quiz_max_points).somme(axe=1)
final_data[[[["Quiz moyens"] = average_quiz_scores / quiz_scores.forme[[[[1]

final_data[[[["Score du quiz"] = final_data[[[[
    [[[["Total des quiz", "Quiz moyens"]
].max(axe=1)

La plupart de ce code est assez similaire au code des devoirs de la dernière section. La principale différence avec le cas des devoirs est que vous avez créé une série pandas pour quiz_max_points en utilisant un dictionnaire comme entrée. Les clés du dictionnaire deviennent des étiquettes d'index et les valeurs du dictionnaire deviennent les valeurs de série.

Étant donné que les étiquettes d'index dans quiz_max_points avoir les mêmes noms que quiz_scores, vous n'avez pas besoin d'utiliser DataFrame.set_axis () pour les quiz. pandas aussi émissions la forme d'une série afin qu'elle corresponde au DataFrame.

Voici un exemple du résultat de ce calcul pour les quiz:

NetID Total des quiz Quiz moyens Score du quiz
wxb12345 0.808108 0,602139 0.808108
mxl12345 0,827027 0,682149 0,827027
txj12345 0,785135 0,585399 0,785135
jgf12345 0,770270 0,615286 0,770270

Dans ce tableau, le Score du quiz est toujours le plus grand de Total des quiz ou Quiz moyens, comme prévu.

Calcul de la note de la lettre

Vous avez maintenant terminé tous les calculs requis pour la note finale. Vous avez des scores pour les examens, les devoirs et les questionnaires qui sont tous échelonnés entre 0 et 1. Ensuite, vous devez multiplier chaque score par sa pondération pour déterminer la note finale. Ensuite, vous pouvez mapper cette valeur sur une échelle pour les notes alphabétiques, de A à F.

Comme pour les scores de quiz maximaux, vous utiliserez une série pandas pour stocker les pondérations. De cette façon, vous pouvez multiplier par les colonnes correctes de final_data automatiquement. Créez vos pondérations avec ce code:

pondérations = pd.Séries(
    
        "Score de l'examen 1": 0,05,
        "Score de l'examen 2": 0,1,
        "Score de l'examen 3": 0,15,
        "Score du quiz": 0,30,
        "Score de devoirs": 0,4,
    
)

Dans ce code, vous donnez une pondération à chaque composant de la classe. Comme vous l'avez vu plus tôt, Examen 1 vaut 5%, Examen 2 vaut 10%, Examen 3 vaut 15%, les quiz valent 30% et Devoirs vaut 40% de la note globale.

Ensuite, vous pouvez combiner ces pourcentages avec les scores que vous avez calculés précédemment pour déterminer le score final:

final_data[[[["Score final"] = (final_data[[[[pondérations.indice] * pondérations).somme(
    axe=1
)
final_data[[[["Score plafond"] = np.plafond(final_data[[[["Score final"] * 100)

Dans ce code, vous sélectionnez les colonnes de final_data qui ont les mêmes noms que l'index dans pondérations. Vous devez le faire car certaines des autres colonnes de final_data avoir un type str, donc les pandas Erreur-type si vous essayez de vous multiplier pondérations par tous final_data.

Ensuite, vous prenez la somme de ces colonnes pour chaque étudiant avec DataFrame.sum (axe = 1) et vous attribuez le résultat à une nouvelle colonne appelée Score final. La valeur de cette colonne pour chaque élève est un nombre à virgule flottante compris entre 0 et 1.

Enfin, étant le professeur vraiment sympa que vous êtes, vous allez arrondir la note de chaque élève. Vous multipliez chaque élève Score final par 100 pour le mettre sur une échelle de 0 à 100, vous utilisez numpy.ceil () pour arrondir chaque score à l'entier le plus élevé suivant. Vous affectez cette valeur à une nouvelle colonne appelée Score plafond.

Voici un exemple de résultat de calcul pour ces colonnes pour les quatre exemples d'étudiants:

NetID Score final Score plafond
wxb12345 0.805676 81
mxl12345 0,839419 84
txj12345 0,779917 78
jgf12345 0,773689 78

La dernière chose à faire est de mapper le score plafond de chaque élève sur une note de lettre. Dans votre école, vous pouvez utiliser les notes suivantes:

  • UNE: Score de 90 ou plus
  • B: Score entre 80 et 90
  • C: Score entre 70 et 80
  • : Score entre 60 et 70
  • F: Score inférieur à 60

Étant donné que chaque note de lettre doit correspondre à une plage de scores, vous ne pouvez pas facilement utiliser simplement un dictionnaire pour le mappage. Heureusement, les pandas ont Series.map (), qui vous permet d'appliquer une fonction arbitraire aux valeurs d'une série. Vous pourriez faire quelque chose de similaire si vous utilisiez une échelle de notation différente de celle des lettres.

Vous pouvez écrire une fonction appropriée de cette façon:

grades = 
    90: "UNE",
    80: "B",
    70: "C",
    60: "RÉ",
    0: "F",


def grade_mapping(valeur):
    pour clé, lettre dans grades.articles():
        si valeur > = clé:
            revenir lettre

Dans ce code, vous créez un dictionnaire qui stocke le mappage entre la limite inférieure de chaque grade de lettre et la lettre. Ensuite, vous définissez grade_mapping (), qui prend comme argument la valeur d'une ligne de la série de scores de plafond. Vous parcourez les éléments dans grades, comparant valeur à la clé from the dictionary. If value is greater than key, then the student falls in that bracket and you return the appropriate letter grade.

Avec grade_mapping() defined, you can use Series.map() to find the letter grades:

letter_grades = final_data[[[["Ceiling Score"].carte(grade_mapping)
final_data[[[["Final Grade"] = pd.Categorical(
    letter_grades, catégories=grades.valeurs(), ordered=Vrai
)

In this code, you create a new Series called letter_grades by mapping grade_mapping() onto the Ceiling Score column from final_data. Since there are five choices for a letter grade, it makes sense for this to be a categorical data type. Once you’ve mapped the scores to letters, you can create a categorical column with the pandas Categorical class.

To create the categorical column, you pass the letter grades as well as two keyword arguments:

  1. catégories is passed the values from grades. The values in grades are the possible letter grades in the class.
  2. ordered is passed Vrai to tell pandas that the categories are ordered. This will help later if you want to sort this column.

The categorical column that you create is assigned to a new column in final_data appelé Final Grade.

Here are the final grades for the four example students:

NetID Final Score Ceiling Score Final Grade
wxb12345 0.805676 81 B
mxl12345 0.839419 84 B
txj12345 0.779917 78 C
jgf12345 0.773689 78 C

Among the four example students, two people got Bs and two people got Cs, matching their ceiling scores and the letter grade mapping you created.

Grouping the Data

Now that you’ve calculated the grades for each student, you probably need to put them into the student administration system. This term, you’re teaching several sections of the same class, as indicated by the Section column in the roster table.

All of the modifications to gradebook.py made in this section are collected in the 04-grouping-the-data.py fichier. You can download the source code by clicking the link below:

To put the grades into your student administration system, you need to separate the students into each section and sort them by their last name. Fortunately, pandas has you covered here as well.

pandas has powerful abilities to group and sort data in DataFrames. You need to group your data by the students’ section number and sort the grouped result by their name. You can do that with this code:

pour section, table dans final_data.groupby("Section"):
    section_file = DATA_FOLDER / F"Section section    Grades.csv"
    num_students = table.shape[[[[0]
    impression(
        F"In Section section    there are num_students    students saved to "
        F"file section_file. "
    )
    table.sort_values(by=[[[["Last Name", "First Name"]).to_csv(section_file)

In this code, you use DataFrame.groupby() sur final_data to group by the Section column and DataFrame.sort_values() to sort the grouped results. Last, you save the sorted data to a CSV file for upload to the student administration system. With that, you’re done with your grades for the term and you can relax for the break!

Plotting Summary Statistics

Before you hang up the whiteboard marker for the summer, though, you might like to see a little bit more about how the class did overall. Using pandas and Matplotlib, you can plot some summary statistics for the class.

All of the modifications made to gradebook.py in this section are collected in the 05-plotting-summary-statistics.py fichier. You can download the source code by clicking the link below:

First, you might want to see a distribution of the letter grades in the class. You can do that with this code:

grade_counts = final_data[[[["Final Grade"].value_counts().sort_index()
grade_counts.terrain.bar()
plt.spectacle()

In this code, you use Series.value_counts() sur le Final Grade column in final_data to calculate how many of each of the letters appear. By default, the value counts are sorted from most to fewest, but it would be more useful to see them in letter-grade order. Tu utilises Series.sort_index() to sort the grades into the order that you specified when you defined the Categorical column.

You then leverage pandas’s ability to use Matplotlib and produce a bar plot of the grade counts with DataFrame.plot.bar(). Since this is a script, you need to tell Matplotlib to show you the plot with plt.show(), which opens an interactive figure window.

Your figure should look similar to the figure below:

Histogram of Letter Grades

The height of the bars in this figure represents the number of students who received each letter grade shown on the horizontal axis. The majority of your students got a B letter grade.

Next, you might want to see a histogram of the numerical scores of the students. pandas can use Matplotlib with DataFrame.plot.hist() to do that automatically:

final_data[[[["Final Score"].terrain.hist(bins=20, label="Histogram")

In this code, you use DataFrame.plot.hist() to plot a histogram of the final scores. Any keyword arguments are passed through to Matplotlib when the plotting is done.

A histogram is one way to estimate the distribution of the data, but you might be interested in more sophisticated methods as well. pandas has the ability to use the SciPy library to calculate a kernel density estimate with DataFrame.plot.density(). You can also guess that the data will be normally distributed and manually calculate a normal distribution with the mean and standard deviation from your data. You can try this code to see how it works:

final_data[[[["Final Score"].terrain.density(
    linewidth=4, label="Kernel Density Estimate"
)

final_mean = final_data[[[["Final Score"].mean()
final_std = final_data[[[["Final Score"].std()
X = np.linspace(final_mean - 5 * final_std, final_mean + 5 * final_std, 200)
normal_dist = scipy.stats.norm.pdf(X, loc=final_mean, scale=final_std)
plt.terrain(X, normal_dist, label="Normal Distribution", linewidth=4)
plt.Légende()
plt.spectacle()

In this code, you first use DataFrame.plot.density() to plot the kernel density estimate for your data. You adjust the line width and label for the plot to make it easier to see.

Next, you calculate the mean and standard deviation of your Final Score data using DataFrame.mean() et DataFrame.std(). Tu utilises np.linspace() to generate a set of x-values from -5 à +5 standard deviations away from the mean. Then you calculate the normal distribution in normal_dist by plugging into the formula for the standard normal distribution.

Finally, you plot X contre normal_dist and adjust the line width and add a label. Once you show the plot, you should get a result that looks like this:

Numerical Grade Histogram With PDF Estimates

In this figure, the vertical axis shows the density of the grades in a particular bin. The peak occurs near a grade of 0.83. Both the kernel density estimate and the normal distribution do a pretty good job of matching the data.

Conclusion

You now know how to build a gradebook script with pandas so you can stop using spreadsheet software. This will help you avoid errors and calculate your final grades more quickly in the future.

In this tutorial, you learned:

  • Comment load, clean, et merge data into pandas DataFrames
  • Comment calculate with DataFrames and Series
  • Comment carte values from one set to another
  • Comment terrain summary statistics using pandas and Matplotlib

In addition, you saw how to group data and save files to upload to your student administration system. Now you’re ready to create your pandas gradebook for next term!

Click the link below to download the code for this pandas project and learn how to build a gradebook without spreadsheets: