File size: 3,472 Bytes
6cda091
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
"""
src/data/loader.py

Carga y valida el dataset de comentarios de YouTube.
Responsabilidad ΓΊnica: leer el CSV y verificar que tiene las columnas esperadas.
El preprocesamiento viene despuΓ©s (src/features/text_preprocessor.py).
"""

import pandas as pd
from pathlib import Path
from src.utils.logger import get_logger

logger = get_logger(__name__)

# Columnas obligatorias en el dataset
REQUIRED_COLUMNS = {"Text", "IsToxic"}

# Sublabels opcionales (pueden no estar presentes)
SUBLABEL_COLUMNS = {
    "IsAbusive", "IsProvocative", "IsHatespeech",
    "IsRacist", "IsObscene", "IsThreat",
}


def load_raw_data(path: str | Path) -> pd.DataFrame:
    """
    Carga el CSV crudo de comentarios de YouTube.

    Args:
        path: Ruta al archivo CSV.

    Returns:
        DataFrame validado y limpio a nivel estructural.

    Raises:
        FileNotFoundError: si el archivo no existe.
        ValueError: si faltan columnas obligatorias.
    """
    path = Path(path)
    if not path.exists():
        raise FileNotFoundError(f"Dataset no encontrado: {path}")

    logger.info(f"Cargando dataset: {path}")
    df = pd.read_csv(path)
    logger.info(f"  Shape: {df.shape}")

    _validate_columns(df)
    df = _clean_structure(df)

    logger.info(f"  Toxicos: {df['IsToxic'].sum()} ({df['IsToxic'].mean()*100:.1f}%)")
    return df


def load_preprocessed_data(path: str | Path) -> pd.DataFrame:
    """
    Carga el CSV preprocesado (con columna clean_text).
    Generado por el notebook 02 o por run_pipeline.

    Args:
        path: Ruta al CSV preprocesado.

    Returns:
        DataFrame con columna clean_text lista para vectorizar.
    """
    path = Path(path)
    if not path.exists():
        raise FileNotFoundError(
            f"Datos preprocesados no encontrados: {path}\n"
            f"Ejecuta: python -m src.pipeline.run_pipeline"
        )

    df = pd.read_csv(path)
    if "clean_text" not in df.columns:
        raise ValueError("El CSV no tiene columna 'clean_text'. Regenera el preprocesamiento.")

    logger.info(f"Datos preprocesados cargados: {df.shape}")
    return df


# ── Funciones internas ────────────────────────────────────────────────────────

def _validate_columns(df: pd.DataFrame) -> None:
    """Verifica que el dataset tenga las columnas obligatorias."""
    missing = REQUIRED_COLUMNS - set(df.columns)
    if missing:
        raise ValueError(
            f"Columnas obligatorias ausentes: {missing}\n"
            f"Columnas encontradas: {list(df.columns)}"
        )
    logger.info(f"  Columnas validadas βœ…")


def _clean_structure(df: pd.DataFrame) -> pd.DataFrame:
    """
    Limpieza estructural mΓ­nima:
    - Elimina filas con Text vacΓ­o
    - Convierte IsToxic a bool
    - Convierte sublabels a bool si existen
    """
    df = df.copy()

    # Texto
    df["Text"] = df["Text"].fillna("").astype(str).str.strip()
    df = df[df["Text"] != ""].reset_index(drop=True)

    # Target binario
    df["IsToxic"] = df["IsToxic"].astype(bool)

    # Sublabels
    for col in SUBLABEL_COLUMNS:
        if col in df.columns:
            df[col] = df[col].astype(bool)

    # Eliminar duplicados
    n_before = len(df)
    df = df.drop_duplicates(subset=["Text"]).reset_index(drop=True)
    if len(df) < n_before:
        logger.warning(f"  {n_before - len(df)} duplicados eliminados")

    return df