Spaces:
Sleeping
Sleeping
| """OULAD ingestion and scope filtering.""" | |
| from __future__ import annotations | |
| from pathlib import Path | |
| from typing import Dict | |
| import pandas as pd | |
| from .config import ( | |
| MIN_ENGAGEMENT_DAYS, | |
| PRESENTATION, | |
| PRESENTATION_LENGTH, | |
| raw_data_dir, | |
| ) | |
| Tables = Dict[str, pd.DataFrame] | |
| def _read_csv(path: Path, **kwargs) -> pd.DataFrame: | |
| return pd.read_csv(path, **kwargs) | |
| def _filter_scope(df: pd.DataFrame, code_module: str, code_presentation: str) -> pd.DataFrame: | |
| if "code_module" not in df.columns or "code_presentation" not in df.columns: | |
| return df.copy() | |
| return df[ | |
| (df["code_module"].eq(code_module)) | |
| & (df["code_presentation"].eq(code_presentation)) | |
| ].copy() | |
| def _read_student_vle_scoped( | |
| path: Path, | |
| code_module: str, | |
| code_presentation: str, | |
| chunksize: int = 1_000_000, | |
| ) -> pd.DataFrame: | |
| chunks: list[pd.DataFrame] = [] | |
| for chunk in pd.read_csv(path, chunksize=chunksize): | |
| filt = chunk[ | |
| (chunk["code_module"].eq(code_module)) | |
| & (chunk["code_presentation"].eq(code_presentation)) | |
| ].copy() | |
| if not filt.empty: | |
| chunks.append(filt) | |
| if not chunks: | |
| return pd.DataFrame( | |
| columns=["code_module", "code_presentation", "id_student", "id_site", "date", "sum_click"] | |
| ) | |
| return pd.concat(chunks, ignore_index=True) | |
| def load_oulad( | |
| code_module: str | None = None, | |
| code_presentation: str | None = None, | |
| source_dir: Path | None = None, | |
| ) -> Tables: | |
| """Load OULAD CSVs, filtering large tables to the requested presentation.""" | |
| code_module = code_module or PRESENTATION[0] | |
| code_presentation = code_presentation or PRESENTATION[1] | |
| base = source_dir or raw_data_dir() | |
| info = _filter_scope(_read_csv(base / "studentInfo.csv"), code_module, code_presentation) | |
| registration = _filter_scope( | |
| _read_csv(base / "studentRegistration.csv"), | |
| code_module, | |
| code_presentation, | |
| ) | |
| assessments = _filter_scope( | |
| _read_csv(base / "assessments.csv"), | |
| code_module, | |
| code_presentation, | |
| ) | |
| assessment = _read_csv(base / "studentAssessment.csv") | |
| if not assessments.empty: | |
| assessment = assessment[assessment["id_assessment"].isin(assessments["id_assessment"])].copy() | |
| vle = _read_student_vle_scoped(base / "studentVle.csv", code_module, code_presentation) | |
| vle_meta = _filter_scope(_read_csv(base / "vle.csv"), code_module, code_presentation) | |
| tables: Tables = { | |
| "info": info, | |
| "registration": registration, | |
| "assessment": assessment, | |
| "assessments": assessments, | |
| "vle": vle, | |
| "vle_meta": vle_meta, | |
| } | |
| if (base / "courses.csv").exists(): | |
| tables["courses"] = _filter_scope(_read_csv(base / "courses.csv"), code_module, code_presentation) | |
| return tables | |
| def exclude_early_withdrawals( | |
| tables: Tables, | |
| min_days: int = MIN_ENGAGEMENT_DAYS, | |
| ) -> Tables: | |
| """Drop learners who withdrew before the minimum engagement window.""" | |
| reg = tables["registration"].copy() | |
| reg["date_unregistration"] = pd.to_numeric(reg["date_unregistration"], errors="coerce") | |
| early = set(reg.loc[reg["date_unregistration"].fillna(999999) < min_days, "id_student"]) | |
| if not early: | |
| return tables | |
| for key in ["info", "registration", "assessment", "vle"]: | |
| tables[key] = tables[key][~tables[key]["id_student"].isin(early)].copy() | |
| return tables | |
| def apply_study_window( | |
| tables: Tables, | |
| presentation_length: int = PRESENTATION_LENGTH, | |
| ) -> Tables: | |
| """Keep registrations before course start and VLE clicks in the study window.""" | |
| reg = tables["registration"].copy() | |
| reg["date_registration"] = pd.to_numeric(reg["date_registration"], errors="coerce") | |
| eligible = set(reg.loc[reg["date_registration"].fillna(0) <= 0, "id_student"]) | |
| for key in ["info", "registration", "assessment", "vle"]: | |
| tables[key] = tables[key][tables[key]["id_student"].isin(eligible)].copy() | |
| vle = tables["vle"].copy() | |
| vle["date"] = pd.to_numeric(vle["date"], errors="coerce") | |
| tables["vle"] = vle[(vle["date"] >= 0) & (vle["date"] <= presentation_length)].copy() | |
| return tables | |
| def run( | |
| code_module: str | None = None, | |
| code_presentation: str | None = None, | |
| source_dir: Path | None = None, | |
| ) -> Tables: | |
| tables = load_oulad(code_module, code_presentation, source_dir) | |
| tables = apply_study_window(tables) | |
| tables = exclude_early_withdrawals(tables) | |
| return tables | |