Skip to content

Completarea valorilor lipsă

În seturile de date reale, este foarte comun să lipsească anumite valori. Poate că un utilizator nu a completat un câmp într-un formular, sau un senzor nu a înregistrat o măsurătoare. Aceste valori lipsă (în engleză, missing values) pot cauza probleme serioase pentru modelele de machine learning, care de obicei nu știu cum să le gestioneze.

Vestea bună: există mai multe strategii pentru a trata valorile lipsă, de la cele simple (înlocuire cu media) până la cele avansate (estimare pe baza celorlalte date).


Identificarea valorilor lipsă

Primul pas este să înțelegi cât de multe valori lipsesc și în ce coloane. Pandas folosește None și NaN (Not a Number) pentru a reprezenta valorile lipsă.

import pandas as pd
import numpy as np

# Încarcă datele
df = pd.read_csv("date.csv")

# Câte valori lipsesc în fiecare coloană?
print(df.isnull().sum())

# Ce procent din date lipsește?
print(df.isnull().sum() / len(df) * 100)

# Total valori lipsă în întreg setul
print(df.isnull().sum().sum())

Pentru seturi mari de date, o vizualizare ajută să înțelegi pattern-ul valorilor lipsă:

import matplotlib.pyplot as plt
import seaborn as sns

# Heatmap - fiecare linie verticală reprezintă o valoare lipsă
plt.figure(figsize=(10, 6))
sns.heatmap(df.isnull(), cbar=True, yticklabels=False)
plt.title("Valori lipsă în dataset")
plt.show()

# Grafic cu procentul valorilor lipsă per coloană
missing_pct = df.isnull().sum() / len(df) * 100
missing_pct = missing_pct[missing_pct > 0].sort_values(ascending=False)

plt.figure(figsize=(10, 6))
missing_pct.plot(kind='bar')
plt.ylabel('Procent lipsă (%)')
plt.title('Valori lipsă per coloană')
plt.show()

Strategia 1: Ștergerea datelor

Cea mai simplă abordare: dacă o linie sau coloană are valori lipsă, o eliminăm complet.

Ștergerea rândurilor

# Șterge toate rândurile care au cel puțin o valoare lipsă
df_clean = df.dropna()

# Șterge rândurile doar dacă TOATE valorile lipsesc
df_clean = df.dropna(how='all')

# Șterge rândurile doar dacă lipsesc valori în anumite coloane
df_clean = df.dropna(subset=['coloana1', 'coloana2'])

# Păstrează rândurile care au cel puțin 5 valori prezente
df_clean = df.dropna(thresh=5)

Ștergerea coloanelor

# Șterge coloanele care au valori lipsă
df_clean = df.dropna(axis=1)

# Șterge doar coloanele cu mai mult de 50% valori lipsă
threshold = len(df) * 0.5
df_clean = df.dropna(axis=1, thresh=threshold)

Când e o idee bună: Când valorile lipsă sunt puține (sub 5%) și par a fi aleatorii.

Problema: Pierzi informație. Dacă ștergi prea multe rânduri, s-ar putea să rămâi cu prea puține date pentru a antrena un model bun.


Strategia 2: Înlocuirea cu valori simple (Imputare)

În loc să ștergi rândurile cu valori lipsă, le poți înlocui (imputa) cu o valoare estimată.

Media - pentru date numerice

Dacă într-o coloană numerică lipsesc valori, le poți înlocui cu media celorlalte valori:

# Varianta manuală
medie = df['coloana'].mean()
df['coloana'].fillna(medie, inplace=True)

# Cu SimpleImputer din sklearn
from sklearn.impute import SimpleImputer

imputer = SimpleImputer(strategy='mean')
df['coloana'] = imputer.fit_transform(df[['coloana']])

Mediana - când ai outlieri

Media poate fi influențată de valori extreme. De exemplu, dacă într-un grup de oameni cu salarii de 3000-5000 lei ai unul cu salariu de 100.000 lei, media va fi mult mai mare decât salariul tipic. Mediana (valoarea din mijloc când sortezi datele) este mai robustă în aceste cazuri:

mediana = df['coloana'].median()
df['coloana'].fillna(mediana, inplace=True)

# Cu SimpleImputer
imputer = SimpleImputer(strategy='median')
df['coloana'] = imputer.fit_transform(df[['coloana']])

Modul - pentru date categorice

Pentru date categorice (gen, oraș, categorie de produs), nu poți calcula media. În schimb, poți folosi modul - valoarea care apare cel mai frecvent:

mod = df['coloana'].mode()[0]
df['coloana'].fillna(mod, inplace=True)

# Cu SimpleImputer
imputer = SimpleImputer(strategy='most_frequent')
df['coloana'] = imputer.fit_transform(df[['coloana']])

Valoare constantă

Uneori vrei să înlocuiești cu o valoare specifică (0 pentru valori numerice, "Necunoscut" pentru text):

df['coloana'].fillna(0, inplace=True)
df['coloana'].fillna('Necunoscut', inplace=True)

# Cu SimpleImputer
imputer = SimpleImputer(strategy='constant', fill_value=0)

Aplicare pe mai multe coloane simultan

from sklearn.impute import SimpleImputer
import numpy as np

# Coloanele numerice - înlocuiesc cu media
numeric_cols = df.select_dtypes(include=[np.number]).columns
imputer_num = SimpleImputer(strategy='mean')
df[numeric_cols] = imputer_num.fit_transform(df[numeric_cols])

# Coloanele categorice - înlocuiesc cu modul
cat_cols = df.select_dtypes(include=['object']).columns
imputer_cat = SimpleImputer(strategy='most_frequent')
df[cat_cols] = imputer_cat.fit_transform(df[cat_cols])

Strategia 3: Înlocuirea bazată pe context

Uneori, valorile din jurul celei lipsă îți pot da indicii despre ce ar trebui să fie.

Forward Fill / Backward Fill

Pentru date care au o ordine naturală (serii temporale, date cronologice), poți completa cu valoarea anterioară sau următoare:

# Completează cu valoarea anterioară (forward fill)
df['coloana'].fillna(method='ffill', inplace=True)

# Completează cu valoarea următoare (backward fill)
df['coloana'].fillna(method='bfill', inplace=True)

# Combinație: întâi înainte, apoi înapoi (pentru cazurile de la margini)
df['coloana'].fillna(method='ffill', inplace=True)
df['coloana'].fillna(method='bfill', inplace=True)

Interpolarea

Pentru date numerice ordonate, interpolarea estimează valorile lipsă pe baza celor din jur:

# Interpolare liniară - trasează o linie între punctele cunoscute
df['coloana'] = df['coloana'].interpolate(method='linear')

# Interpolare polinomială - trasează o curbă
df['coloana'] = df['coloana'].interpolate(method='polynomial', order=2)

Imputare pe grupuri

Uneori, media generală nu e relevantă - media salariului per departament e mai utilă decât media pe toată compania:

# Înlocuiește salariul lipsă cu media salariului din același departament
df['salariu'] = df.groupby('departament')['salariu'].transform(
    lambda x: x.fillna(x.mean())
)

Strategia 4: Imputare avansată

KNN Imputer

Ideea: valorile lipsă sunt estimate pe baza celor mai similare K rânduri din set. Dacă un client seamănă cu alți 5 clienți în toate celelalte atribute, probabil că și valoarea lipsă e similară cu a lor.

from sklearn.impute import KNNImputer

# Creează imputer-ul care folosește 5 vecini
imputer = KNNImputer(n_neighbors=5)

# Aplică doar pe coloanele numerice
df_numeric = df.select_dtypes(include=[np.number])
df_imputed = pd.DataFrame(
    imputer.fit_transform(df_numeric),
    columns=df_numeric.columns
)

Iterative Imputer (MICE)

O metodă și mai sofisticată: fiecare coloană cu valori lipsă este modelată ca o funcție de celelalte coloane. Procesul se repetă de mai multe ori (iterativ) până când valorile se stabilizează.

from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer

imputer = IterativeImputer(max_iter=10, random_state=42)
df_imputed = pd.DataFrame(
    imputer.fit_transform(df_numeric),
    columns=df_numeric.columns
)

Strategia 5: Indicator pentru valori lipsă

Uneori, faptul că o valoare lipsește este în sine informativ. De exemplu, dacă oamenii cu venituri mari tind să nu răspundă la întrebări despre venit, absența răspunsului e un semnal.

Poți crea o coloană nouă care indică dacă valoarea era lipsă:

# Creează indicator (1 = lipsea, 0 = era prezentă)
df['coloana_lipsa'] = df['coloana'].isnull().astype(int)

# Apoi completează valoarea cu mediana
df['coloana'].fillna(df['coloana'].median(), inplace=True)

Acum modelul poate învăța atât din valoarea imputată, cât și din informația că aceasta lipsea inițial.


Ce metodă aleg?

Metodă Când o folosesc Avantaje Dezavantaje
Ștergere Puține valori lipsă, aleatorii Simplă Pierzi date
Media/Mediana Date numerice Rapidă Reduce variația
Modul Date categorice Simplă Poate introduce bias
KNN Relații între features Consideră contextul Lentă pe date mari
Iterative Date complexe Foarte precisă Complexă, lentă

Tipuri de date lipsă

Nu toate valorile lipsă sunt la fel. Înțelegerea cauzei te ajută să alegi strategia potrivită:

MCAR (Missing Completely At Random) - Lipsa e complet aleatorie, fără nicio legătură cu alte variabile. Exemplu: un senzor s-a defectat aleatoriu.

MAR (Missing At Random) - Lipsa depinde de alte variabile observate. Exemplu: tinerii completează mai rar câmpul de venit, dar lipsa nu depinde de venitul în sine.

MNAR (Missing Not At Random) - Lipsa depinde de valoarea însăși. Exemplu: oamenii cu venituri foarte mari sau foarte mici evită să răspundă la întrebări despre venit.

Pentru MCAR și MAR, majoritatea metodelor de imputare funcționează bine. Pentru MNAR, lucrurile sunt mai complicate și ar trebui să folosești metode speciale sau să interpretezi rezultatele cu grijă.


Exemplu complet

import pandas as pd
import numpy as np
from sklearn.impute import SimpleImputer, KNNImputer

# Încarcă datele
df = pd.read_csv("date.csv")

print("Valori lipsă înainte:")
print(df.isnull().sum())

# Separă coloanele numerice de cele categorice
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
cat_cols = df.select_dtypes(include=['object']).columns.tolist()

# Elimină coloanele cu mai mult de 50% valori lipsă
for col in df.columns:
    if df[col].isnull().sum() / len(df) > 0.5:
        df.drop(col, axis=1, inplace=True)
        if col in numeric_cols:
            numeric_cols.remove(col)
        if col in cat_cols:
            cat_cols.remove(col)

# Imputare coloane numerice cu KNN
if numeric_cols:
    knn_imputer = KNNImputer(n_neighbors=5)
    df[numeric_cols] = knn_imputer.fit_transform(df[numeric_cols])

# Imputare coloane categorice cu modul
if cat_cols:
    mode_imputer = SimpleImputer(strategy='most_frequent')
    df[cat_cols] = mode_imputer.fit_transform(df[cat_cols])

print("\nValori lipsă după:")
print(df.isnull().sum())