# Spine dataset — data dictionary

**Source:** synthetic. Generated by `src/make_dataset.py`, seed 20260901.
**Licence:** CC0. Free to copy, modify, and redistribute.
**Contains no real student records.** This matters: the course argues that a
technically possible analysis of student data is not automatically a legitimate
one, and it would be inconsistent to make that argument on top of real records.

The tables are deliberately *linked but not pre-joined*. Joining them is part of
the work, and joining them carelessly will duplicate rows. That is intended.

---

## `students.csv` — 1,200 rows
**Unit of observation: one student.** `student_id` is unique.

| Column | Type | Meaning | Notes |
|---|---|---|---|
| `student_id` | integer | Unique identifier | Nominal despite being stored as a number. Never average it. |
| `programme` | string | Degree programme | 4 levels. Nominal. |
| `entry_cohort` | integer | Year of first registration | 2024–2026 |
| `entry_score` | float | Entry qualification score, 0–100 | Continuous |
| `study_mode` | string | Full-time or Part-time | Nominal |
| `age_at_entry` | integer | Age in whole years at registration | Discrete |
| `first_generation` | boolean | First in family to attend university | **Sensitive.** See note below. |

## `enrolments.csv` — 4,665 rows
**Unit of observation: one student-course registration.** A student appears
three or four times. Counting rows here counts registrations, not people.

| Column | Type | Meaning |
|---|---|---|
| `student_id` | integer | Foreign key to `students` |
| `course_code` | string | Course identifier (12 courses) |
| `term` | string | Academic term, e.g. `2025-T3` |
| `final_grade` | float | Final mark 0–100 |
| `passed` | boolean | `final_grade >= 50` |

## `activity.csv` — 16,165 rows
**Unit of observation: one student-week.** Up to 14 weeks per student: rows after a student withdraws are absent, which is what makes full-term activity a leaky feature.

| Column | Type | Meaning |
|---|---|---|
| `student_id` | integer | Foreign key to `students` |
| `iso_week` | integer | Teaching week, 1–14 |
| `logins` | integer | Distinct platform logins that week |
| `submissions` | integer | Assessment submissions, 0–2 |
| `forum_posts` | integer | Discussion posts |
| `minutes_on_platform` | integer | Total minutes |

## `outcomes.csv` — 1,200 rows
**Unit of observation: one student.** Weeks 1–4 features plus the target.

| Column | Type | Meaning |
|---|---|---|
| `student_id` | integer | Foreign key to `students` |
| `early_logins` | integer | Logins summed over weeks 1–4 |
| `early_submissions` | integer | Submissions summed over weeks 1–4 |
| `early_minutes` | integer | Minutes summed over weeks 1–4 |
| `midterm_satisfaction` | float | 1–5 survey response. **23.7% missing, and not at random.** |
| `withdrew` | boolean | **Target.** Withdrew before completing the term. 11.5% positive. |
| `withdrew_week` | float | Teaching week of withdrawal, 5–13. Missing for students who stayed. **Do not use as a feature** — see Chapter 5. |

## `feedback.csv` — 732 rows
Free-text course comments for the text-analysis week.

| Column | Type | Meaning |
|---|---|---|
| `student_id` | integer | Foreign key |
| `course_code` | string | Always `DS100` |
| `comment` | string | Free text |
| `sentiment` | string | `positive` / `negative`. Hand-label proxy. |

## `platform_daily.csv` — 364 rows
Daily platform sessions for the time-series week. Has trend, weekly
seasonality, a weekend effect, and a term-break level shift.

| Column | Type | Meaning |
|---|---|---|
| `date` | date (ISO) | One row per day, 2026-01-05 onward |
| `sessions` | integer | Total platform sessions |

## `students_dirty.csv` — 1,205 rows
`students.csv` with realistic damage injected, for the cleaning week. Contains:
mixed case and stray whitespace in `programme` (16 variants for 4 programmes);
nine impossible values in `age_at_entry`; 44 `entry_score` values stored as text
with a `%` suffix; missing
values in three columns; a `registered_on` column in three different date
formats; two exact duplicate rows and three duplicate-with-update pairs.

---

## Note on sensitive attributes

`first_generation`, `age_at_entry`, and `study_mode` are all attributes that
could be used to disadvantage a student. The course position, stated in
Chapter 1 and enforced in the marking rubric, is that these are legitimate
**for auditing whether outcomes differ across groups** and illegitimate **as
features in a model that allocates or withholds something**. If your model uses
one of them as an input, you must say so explicitly and justify it.

## Known structure

Instructors: the generator plants a Simpson's paradox (study mode × course
difficulty), an MNAR missingness pattern in `midterm_satisfaction`, and a real
but modest predictive signal for `withdrew`. Details are in the docstring of
`src/make_dataset.py`. Do not distribute that file before Week 6.
