Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions extras/scripts/attendance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# this is a script rather than a notebook to avoid private student information being committed to the repository

import pandas as pd

NUM_CLASSES = 7
FREEBIES = 1
TOP_SCORE = NUM_CLASSES - FREEBIES

ROLL_CALL_CSV = (
"~/Downloads/attendance_reports_attendance-264e4d14-1765-4396-b311-4d927b59566d.csv"
)
# get by clicking into the Assignment and getting from the URL
ASSIGNMENT_ID = 1405957
GRADEBOOK_FILE = "attendance.csv"
STUDENT_UNIQUE_COLS = ["Student ID", "Student Name", "Section Name", "Section"]


def normalize_sections(entries: pd.DataFrame):
"""For students who switch sections, use their final section."""

student_info = entries.drop_duplicates(subset=["Student ID"], keep="last")
student_info = student_info[["Student ID", "Section Name"]]

entries = entries.drop(columns=["Section Name"])
return entries.merge(student_info, on="Student ID")


def get_entries(filename: str):
entries = pd.read_csv(
filename,
index_col=False,
usecols=[
"Section Name",
"Student Name",
"Student ID",
"Class Date",
"Attendance",
],
parse_dates=["Class Date"],
)

entries = normalize_sections(entries)
# pull the section number out
entries["Section"] = (
entries["Section Name"].str.extract(r"INAFU6504_(\d{3})_").astype(int)
)

return entries


def print_heading(text: str):
print(f"-------------------\n\n{text.upper()}:\n")


def print_students(students: pd.Series):
print(students.droplevel(["Student ID", "Section Name"]))
print()


def validate(entries: pd.DataFrame):
recording_counts = entries.groupby(STUDENT_UNIQUE_COLS).size()
print_heading("Students missing entries")
print_students(recording_counts[recording_counts < NUM_CLASSES])

total_classes = entries["Class Date"].nunique()
assert total_classes == NUM_CLASSES


def compute_scores(entries: pd.DataFrame):
attended = entries[entries["Attendance"] == "present"]
attendance_counts = attended.groupby(STUDENT_UNIQUE_COLS).size()
# print_heading("Attendance counts")
# print_students(attendance_counts)

# cap the top scores
attendance_counts[attendance_counts > TOP_SCORE] = TOP_SCORE

return attendance_counts


def write_canvas_csv(scores: pd.Series):
"""https://community.canvaslms.com/t5/Instructor-Guide/How-do-I-import-grades-in-the-Gradebook/ta-p/807"""

attendance_col = f"Attendance ({ASSIGNMENT_ID})"

gradebook = (
scores.reset_index(name=attendance_col)
.drop(columns=["Section"])
.rename(
columns={
# Roll Call has `FIRST LAST`, gradebook has `LAST, FIRST`. Shouldn't matter.
"Student Name": "Student",
"Student ID": "ID",
"Section Name": "Section",
}
)
)
gradebook.to_csv(GRADEBOOK_FILE, index=False)

print(f"Now upload {GRADEBOOK_FILE} to CourseWorks Gradebook.")


def run():
entries = get_entries(ROLL_CALL_CSV)
validate(entries)

scores = compute_scores(entries)
print_heading("Scores")
print_students(scores)

lowered_scores = scores[scores < TOP_SCORE]
print_heading(f"Scores below {TOP_SCORE}")
print_students(lowered_scores.sort_values())

write_canvas_csv(scores)


run()
11 changes: 11 additions & 0 deletions meta/assistant_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,14 @@ It isn't your responsibility to look for potential instances of cheating/plagiar
1. In the Gradebook, give points to the reviewer under the Final Project Peer Review.

[Scoring details.](../syllabus.md#final-project)

{% if id == "columbia" -%}
## Final grades

To compute the [attendance](../syllabus.md#attendance) score:

1. [Export the Roll Call attendance data.](https://community.canvaslms.com/t5/Canvas-Basics-Guide/What-is-the-Roll-Call-Attendance-Tool/ta-p/59#export_attendance_data)
1. Copy [the script](https://github.com/afeld/python-public-policy/blob/main/extras/scripts/attendance.py) into {{coding_env_name}}.
1. Adjust the constants at the top as necessary.
1. Run the code.
{%- endif %}
Loading