collablearn-int396 / src /ingest.py
Cyril-36's picture
Deploy CollabLearn Streamlit demo via Docker
d81f51d verified
Raw
History Blame Contribute Delete
4.56 kB
"""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