Mathew K Analytics

Lesson 9 · Pandas Projects

Student Grades Analysis with Pandas: Stats, Trends & Outliers

A complete, standalone tutorial: build a synthetic gradebook and attendance log, then analyze performance with pandas. No prior pandas experience needed.…

⬇ Download notebookOpen in Colab ↗

What you'll learn

Data

No separate download needed — the notebook creates or downloads everything it uses.

📓 Full notebook

Download .ipynb

Pandas for Education: Student Grades and Attendance#

  • A complete, standalone tutorial: build a synthetic gradebook and attendance log, then analyze performance with pandas.
  • No prior pandas experience needed. Let's jump straight in.

Before You Start#

  • Open a new Jupyter Notebook in VS Code and select your Python interpreter as the kernel.
  • If pandas isn't installed yet, open a terminal in VS Code and run: pip install pandas

Part 1: Building the Dataset#

import pandas as pd
import numpy as np
print(pd.__version__)
2.3.0

Building the Students and Attendance Tables#

rng = np.random.default_rng(seed=111)

first_names = ['Aiko', 'Ben', 'Camila', 'Dev', 'Ella', 'Finn', 'Greta', 'Hassan', 'Ivy', 'Jamal', 'Kira', 'Leo', 'Mira', 'Noah', 'Priya', 'Quinn', 'Rosa', 'Sam', 'Tara', 'Umar']
students = pd.DataFrame({
    'student_id': range(1, len(first_names) + 1),
    'name': first_names,
    'attendance_pct': np.clip(rng.normal(90, 8, size=len(first_names)), 55, 100).round(1)
})
students.head()
student_id name attendance_pct
0 1 Aiko 87.5
1 2 Ben 83.3
2 3 Camila 91.0
3 4 Dev 84.7
4 5 Ella 91.3

Generating Scores Across Subjects#

subjects = ['Math', 'Science', 'English', 'History']
score_rows = []

for _, student in students.iterrows():
    base_ability = rng.normal(75, 10)
    for subject in subjects:
        attendance_bonus = (student['attendance_pct'] - 90) * 0.3
        score = base_ability + attendance_bonus + rng.normal(0, 8)
        score = float(np.clip(score, 30, 100).round(1))
        score_rows.append([student['student_id'], subject, score])

scores = pd.DataFrame(score_rows, columns=['student_id', 'subject', 'score'])
print(scores.shape)
(80, 3)
students.to_csv('students.csv', index=False)
scores.to_csv('student_scores.csv', index=False)
students = pd.read_csv('students.csv')
scores = pd.read_csv('student_scores.csv')
scores.head()
student_id subject score
0 1 Math 62.2
1 1 Science 48.7
2 1 English 68.8
3 1 History 52.8
4 2 Math 82.3

Part 2: First Look at the Data#

print('students:', students.shape)
print('scores:', scores.shape)
scores['subject'].value_counts()
students: (20, 3)
scores: (80, 3)
subject
Math       20
Science    20
English    20
History    20
Name: count, dtype: int64
scores['score'].describe().round(1)
count     80.0
mean      77.8
std       12.2
min       47.3
25%       69.6
50%       79.7
75%       85.8
max      100.0
Name: score, dtype: float64

Part 3: Pivoting to a Wide Gradebook#

gradebook = scores.pivot_table(values='score', index='student_id', columns='subject')
gradebook = gradebook.merge(students[['student_id', 'name']], on='student_id').set_index('name')
gradebook.round(1).head()
student_id English History Math Science
name
Aiko 1 68.8 52.8 62.2 48.7
Ben 2 91.7 77.9 82.3 90.9
Camila 3 87.1 87.2 82.7 78.0
Dev 4 81.6 68.2 56.1 70.3
Ella 5 82.2 55.7 68.0 75.6

Part 4: Overall Averages and Letter Grades#

student_avg = scores.groupby('student_id')['score'].mean().round(1)
student_avg = student_avg.reset_index().merge(students[['student_id', 'name']], on='student_id')
student_avg = student_avg.sort_values('score', ascending=False)
student_avg.head()
student_id score name
7 8 94.2 Hassan
15 16 91.7 Quinn
13 14 88.0 Noah
16 17 87.8 Rosa
1 2 85.7 Ben
def letter_grade(score):
    if score >= 90:
        return 'A'
    elif score >= 80:
        return 'B'
    elif score >= 70:
        return 'C'
    elif score >= 60:
        return 'D'
    else:
        return 'F'

student_avg['letter_grade'] = student_avg['score'].apply(letter_grade)
student_avg[['name', 'score', 'letter_grade']].head()
name score letter_grade
7 Hassan 94.2 A
15 Quinn 91.7 A
13 Noah 88.0 B
16 Rosa 87.8 B
1 Ben 85.7 B
student_avg['letter_grade'].value_counts().reindex(['A', 'B', 'C', 'D', 'F'], fill_value=0)
letter_grade
A    2
B    7
C    7
D    2
F    2
Name: count, dtype: int64

Part 5: Pass/Fail Analysis by Subject#

scores['passed'] = scores['score'] >= 60
pass_rate_by_subject = scores.groupby('subject')['passed'].mean().mul(100).round(1).sort_values(ascending=False)
pass_rate_by_subject
subject
English    95.0
History    90.0
Math       90.0
Science    90.0
Name: passed, dtype: float64
subject_summary = scores.groupby('subject').agg(
    avg_score=('score', 'mean'),
    pass_rate_pct=('passed', lambda s: round(s.mean() * 100, 1)),
    lowest_score=('score', 'min')
).round(1).sort_values('avg_score', ascending=False)
subject_summary
avg_score pass_rate_pct lowest_score
subject
English 81.2 95.0 56.2
History 78.2 90.0 52.8
Math 76.5 90.0 47.3
Science 75.4 90.0 48.7

Part 6: Attendance and Performance#

performance = students.merge(student_avg[['student_id', 'score']], on='student_id')
performance = performance.rename(columns={'score': 'avg_score'})
correlation = performance[['attendance_pct', 'avg_score']].corr().iloc[0, 1]
print(f'Correlation between attendance and average score: {correlation:.2f}')
Correlation between attendance and average score: 0.39

Part 7: Visualizing the Results#

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(8, 5))
subject_summary['avg_score'].plot(kind='bar', ax=ax, color='seagreen', ylim=(0, 100))
ax.set_title('Average Score by Subject')
ax.set_ylabel('Average Score')
plt.xticks(rotation=0)
plt.tight_layout()
plt.savefig('avg_score_by_subject.png', dpi=150)
plt.close(fig)
print('Saved avg_score_by_subject.png')
Saved avg_score_by_subject.png
fig, ax = plt.subplots(figsize=(8, 5))
ax.scatter(performance['attendance_pct'], performance['avg_score'], alpha=0.7, color='indigo')
ax.set_title('Attendance vs. Average Score')
ax.set_xlabel('Attendance (%)')
ax.set_ylabel('Average Score')
plt.tight_layout()
plt.savefig('attendance_vs_score.png', dpi=150)
plt.close(fig)
print('Saved attendance_vs_score.png')
Saved attendance_vs_score.png

Wrap-Up: What You Learned#

  • Generating a realistic synthetic gradebook and attendance log, then saving and reloading with to_csv and read_csv.
  • Reshaping long-format scores into a wide, readable gradebook with pivot_table.
  • Per-student averages, plus letter grades from a custom function with apply.
  • Pass/fail analysis by subject, including a lambda inside agg for a custom pass-rate statistic.
  • Merging tables to check a real correlation between attendance and performance.
  • A bar chart and a scatter plot, both presentation-ready, with matplotlib.
  • That wraps up the full 10-video pandas series, from retail sales all the way to student grades. If you've made it through the whole series, subscribing means you won't miss whatever's next thanks for following along.

Found this useful?

All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.