Enne mudelit vaata andmed üle

Andmete eeltöötlus on tegelikult kaks eri tööd. Esimene on vigade parandamine: vale andmetüüp, kirjavead, duplikaadid, võimatud väärtused. Teine on andmete ettevalmistamine mudelile: puuduvate väärtuste täitmine, kodeerimine, skaleerimine. Nende vahele jääb üks otsustav samm – andmete jagamine treening- ja testosaks.

  1. 1Laadipd.read_csv, märgi peidetud puuduvad väärtused
  2. 2Vaata üle ja paranda veadtüübid, duplikaadid, kategooriad, vahemikud, leke
  3. 3Jaga train / testsellest hetkest õpib eeltöötlus ainult treeningandmetelt
  4. 4Eeltöötleimputeerimine, kodeerimine, skaleerimine Pipeline'is
  5. 5Treeni ja hindaristvalideerimine, test alles lõpus

1Üks küsimus, mis otsustab järjekorra

Iga eeltöötlussammu juures küsi: kas see samm arvutab midagi andmete põhjal? Kui arvutab (keskmine, mediaan, min ja max, kategooriate loend), siis on see samm „õppiv” ja see tohib õppida ainult treeningandmetelt. Kui ei arvuta, vaid rakendab kindlat reeglit, võib seda teha kohe pärast laadimist.

Reegel – enne jagamist

Tulemus ei sõltu sellest, millised teised read andmestikus on.

  • tekst arvuks: pd.to_numeric
  • "?", "NA" → NaN
  • kirjavigade ühtlustamine: "tallinn " → "Tallinn"
  • duplikaatide eemaldamine
  • võimatud väärtused (vanus −3) → NaN
  • kuupäevade ühtne vorming
  • lekkivate tunnuste eemaldamine

Õppimine – pärast jagamist

fit treeningandmetel, transform mõlemal.

  • puuduvate täitmine keskmise või mediaaniga
  • skaleerimine (StandardScaler jt)
  • one-hot kodeerimine (kategooriate loend)
  • erindite piirid (IQR, protsentiilid)
  • tunnuste valik, PCA
  • klasside tasakaalustamine (SMOTE)

Esimene samm on laadimine. Juba siin tasub pandasele öelda, millised tekstid tähendavad „väärtus puudub”, muidu jääb näiteks "?" tavaliseks tekstiks ja rikub terve arvulise veeru.

import pandas as pd df = pd.read_csv("kliendid.csv") # naiivne df = pd.read_csv("kliendid.csv", na_values=["?", "NA", "-"]) # parem

Järgmises osas on andmestik laaditud naiivselt – nii näed, mida iga kontroll leiab.

2Andmete ülevaatus

Allpool on väike klientide andmestik, kus ennustatakse, kas klient ostab (ostis). Selles on peidus kümme tüüpilist probleemi. Vali vasakult kontroll: näed pandase koodi, selle väljundit ja tabelis märgitakse kollasega, mida kontroll leidis. Kui parandus on reeglipõhine, saad selle kohe rakendada.

    3Jaga enne, kui eeltöötlus midagi õpib

    Testandmed mängivad „tulevikku”: andmeid, mida mudel treenimise ajal ei näe. Kui arvutad puuduvate täitmiseks keskmise kogu andmestikult, on testandmete info juba treeningusse jõudnud. Seda nimetatakse andmelekkeks (data leakage). Tulemus: testitäpsus paistab parem, kui mudel tegelikult uute andmete peal saavutab.

    Vaata, kust tuleb väärtus, millega täidetakse puuduv sissetulek. Raamitud väärtused lähevad keskmise arvutamisse.

    Vale

    imp = SimpleImputer(strategy="mean") X = imp.fit_transform(X) # näeb ka testi X_train, X_test, ... = train_test_split(X, y)

    Õige

    X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, stratify=y, random_state=42) imp = SimpleImputer(strategy="mean") X_train = imp.fit_transform(X_train) # õpib X_test = imp.transform(X_test) # ainult rakendab

    stratify=y hoiab klasside osakaalu treening- ja testosas samana – oluline, kui üks klass on haruldane. Ristvalideerimisel kehtib sama reegel iga foldi kohta; Pipeline hoolitseb selle eest automaatselt (vt samm 5).

    4Eeltöötlus mudeli jaoks

    Siin on kolm õppivat sammu. Igaühel on mitu võimalust ja õige valik sõltub tunnuse sisust ning mudelist.

    Veerg vanus pärast ülevaatust: kolm väärtust on NaN ("?", −3 ja 210 muudeti puuduvaks). Vali strateegia ja vaata, kuidas muutuvad veeru statistikud.

    Kas minu mudel seda vajab?

    Kõik mudelid ei vaja sama eeltöötlust. Vali veerupäisest mudel.

    vajalik – ilma selleta mudel ei tööta või töötab halvasti   soovitatav – mõjutab tulemust   pole vaja

    5Kõik kokku üheks Pipeline'iks

    Reeglipõhine puhastus on funktsioonina enne jagamist, õppiv eeltöötlus on Pipeline'i sees. Nii tehakse fit ristvalideerimisel iga foldi treeningosal eraldi ja lekke võimalust pole.

    import numpy as np import pandas as pd from sklearn.model_selection import train_test_split, cross_val_score from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline from sklearn.impute import SimpleImputer from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder, RobustScaler from sklearn.linear_model import LogisticRegression # 1. Laadi – peidetud puuduvad väärtused kohe NaN-iks df = pd.read_csv("kliendid.csv", na_values=["?", "NA", ""]) # 2. Reeglipõhine puhastus (ei õpi andmetest → võib teha enne jagamist) def puhasta(df): df = df.drop_duplicates().copy() df["sissetulek"] = pd.to_numeric( df["sissetulek"].astype(str).str.replace(" ", ""), errors="coerce") df["vanus"] = pd.to_numeric(df["vanus"], errors="coerce") df.loc[~df["vanus"].between(0, 110), "vanus"] = np.nan df["linn"] = df["linn"].str.strip().str.title().replace({"Tln": "Tallinn"}) df["haridus"] = df["haridus"].str.strip().str.lower() df["liitus"] = pd.to_datetime(df["liitus"], format="mixed", dayfirst=True) df["liitus_kuu"] = df["liitus"].dt.month return df.drop(columns=["tagastas"]) # leke: teada alles pärast ostu df = puhasta(df) X = df.drop(columns=["id", "ostis", "liitus"]) y = df["ostis"] # 3. Jaga ENNE õppivat eeltöötlust X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, stratify=y, random_state=42) # 4. Õppiv eeltöötlus – veerutüübi kaupa arvulised = ["vanus", "sissetulek", "liitus_kuu"] nominaalsed = ["linn"] jarjestatud = ["haridus"] eeltootlus = ColumnTransformer([ ("arv", Pipeline([ ("taida", SimpleImputer(strategy="median", add_indicator=True)), ("skaleeri", RobustScaler()), # erind sissetulekus ]), arvulised), ("nom", Pipeline([ ("taida", SimpleImputer(strategy="most_frequent")), ("onehot", OneHotEncoder(handle_unknown="ignore")), ]), nominaalsed), ("jrk", Pipeline([ ("taida", SimpleImputer(strategy="most_frequent")), ("ordinaal", OrdinalEncoder(categories=[["põhi", "kesk", "kõrg"]], handle_unknown="use_encoded_value", unknown_value=-1)), ]), jarjestatud), ]) mudel = Pipeline([ ("eeltootlus", eeltootlus), ("klassifikaator", LogisticRegression(max_iter=1000)), ]) # 5. Hinda – iga fold teeb eeltöötluse fit'i oma treeningosal print(cross_val_score(mudel, X_train, y_train, cv=5).mean()) mudel.fit(X_train, y_train) print(mudel.score(X_test, y_test)) # test ainult korra, päris lõpus

    Kui vahetad LogisticRegression välja RandomForestClassifier vastu, võid skaleerimise ära jätta – vaata tabelit eespool. Ülejäänud struktuur jääb samaks.

    Kontrolli ennast