Feature Engineering στην Python: Οδηγός με Pandas, Scikit-Learn και Feature-engine

Μάθετε τις βασικές τεχνικές feature engineering στην Python με Pandas, Scikit-Learn 1.8 και Feature-engine 1.9. Πρακτικός οδηγός με κώδικα για encoding, scaling, pipelines και αυτοματοποιημένη μηχανική χαρακτηριστικών.

Feature Engineering Python: Scikit-Learn 2026

Τι Είναι η Feature Engineering;

Η Feature Engineering (μηχανική χαρακτηριστικών) είναι η διαδικασία μετασχηματισμού ακατέργαστων δεδομένων σε χαρακτηριστικά (features) που βελτιώνουν την απόδοση των μοντέλων μηχανικής μάθησης. Και ειλικρινά, αποτελεί ένα από τα πιο κρίσιμα βήματα στον κύκλο ζωής ενός ML project — συχνά πιο σημαντικό κι από την επιλογή αλγορίθμου.

Λένε ότι οι επιστήμονες δεδομένων αφιερώνουν περίπου το 80% του χρόνου τους στην προετοιμασία και μηχανική χαρακτηριστικών. Δεν ξέρω αν φτάνει πάντα στο 80%, αλλά σίγουρα είναι πολύ περισσότερο από ό,τι φανταζόμαστε αρχικά. Η σωστή feature engineering μπορεί να μετατρέψει ένα μέτριο μοντέλο σε εξαιρετικό, ενώ ακόμα και ο πιο εξελιγμένος αλγόριθμος θα αποτύχει με φτωχά χαρακτηριστικά εισόδου.

Λοιπόν, σε αυτόν τον οδηγό θα δούμε τις βασικές τεχνικές feature engineering χρησιμοποιώντας Pandas, Scikit-Learn 1.8 και Feature-engine 1.9, με πρακτικά παραδείγματα κώδικα που μπορείτε να εφαρμόσετε αμέσως στα δικά σας projects.

Προαπαιτούμενα και Εγκατάσταση

Πριν ξεκινήσετε, εγκαταστήστε τις απαραίτητες βιβλιοθήκες:

pip install pandas scikit-learn feature-engine numpy

Θα δουλέψουμε με ένα παράδειγμα dataset καθ' όλη τη διάρκεια του οδηγού. Τίποτα τρελό — απλά κάτι αρκετά ρεαλιστικό για να δούμε πώς λειτουργούν οι τεχνικές στην πράξη:

import pandas as pd
import numpy as np

df = pd.DataFrame({
    "ηλικία": [25, 32, np.nan, 45, 28, 55, 38],
    "μισθός": [30000, 45000, 52000, 80000, 35000, 95000, 60000],
    "πόλη": ["Αθήνα", "Θεσσαλονίκη", "Αθήνα", "Πάτρα", "Αθήνα", "Θεσσαλονίκη", "Πάτρα"],
    "εκπαίδευση": ["Λύκειο", "Πτυχίο", "Μεταπτυχιακό", "Πτυχίο", "Λύκειο", "Διδακτορικό", "Μεταπτυχιακό"],
    "ημερομηνία_πρόσληψης": pd.to_datetime(["2020-03-15", "2018-07-22", "2021-01-10",
                                              "2015-11-05", "2023-06-18", "2010-09-01", "2019-04-30"]),
    "αγόρασε": [0, 1, 1, 1, 0, 1, 1]
})

Κωδικοποίηση Κατηγορικών Μεταβλητών

Τα μοντέλα μηχανικής μάθησης θέλουν αριθμούς. Τελεία και παύλα. Οπότε η κωδικοποίηση κατηγορικών μεταβλητών είναι ίσως η πιο συχνή τεχνική feature engineering που θα συναντήσετε.

One-Hot Encoding

Ιδανικό για nominal μεταβλητές (δηλαδή χωρίς ιεράρχηση) με μικρό αριθμό κατηγοριών:

from sklearn.preprocessing import OneHotEncoder

encoder = OneHotEncoder(sparse_output=False, drop="first")
encoded = encoder.fit_transform(df[["πόλη"]])
encoded_df = pd.DataFrame(encoded, columns=encoder.get_feature_names_out(["πόλη"]))
print(encoded_df)

Η παράμετρος drop="first" αφαιρεί μία κατηγορία για να αποφύγουμε τη multicollinearity (τη λεγόμενη παγίδα πλασματικών μεταβλητών). Αν δεν τη βάλετε, ίσως να μη δείτε πρόβλημα αμέσως, αλλά θα πονέσετε αργότερα σε γραμμικά μοντέλα.

Ordinal Encoding

Κατάλληλο για μεταβλητές με φυσική ιεράρχηση — π.χ. το επίπεδο εκπαίδευσης:

from sklearn.preprocessing import OrdinalEncoder

categories = [["Λύκειο", "Πτυχίο", "Μεταπτυχιακό", "Διδακτορικό"]]
ord_encoder = OrdinalEncoder(categories=categories)
df["εκπαίδευση_κωδ"] = ord_encoder.fit_transform(df[["εκπαίδευση"]])
print(df[["εκπαίδευση", "εκπαίδευση_κωδ"]])

Target Encoding

Αυτή είναι η τεχνική που χρησιμοποιώ πιο συχνά για κατηγορικές μεταβλητές υψηλής πληθικότητας (high-cardinality). Αντικαθιστά κάθε κατηγορία με τη μέση τιμή του target:

from sklearn.preprocessing import TargetEncoder

target_enc = TargetEncoder(smooth="auto")
df["πόλη_target"] = target_enc.fit_transform(df[["πόλη"]], df["αγόρασε"])
print(df[["πόλη", "πόλη_target"]])

Προσοχή: Χρησιμοποιείτε πάντα target encoding μέσα σε pipeline με cross-validation για να αποφύγετε data leakage. Αυτό δεν είναι προαιρετικό — είναι υποχρεωτικό.

Μετασχηματισμοί Αριθμητικών Μεταβλητών

Κλιμάκωση (Scaling)

Αλγόριθμοι που βασίζονται σε αποστάσεις (KNN, SVM, νευρωνικά δίκτυα) χρειάζονται οπωσδήποτε κλιμάκωση. Χωρίς αυτή, μία μεταβλητή με τιμές στις χιλιάδες θα "καταπιεί" μία με τιμές 0-1:

from sklearn.preprocessing import StandardScaler, MinMaxScaler

# Τυποποίηση (μέσος=0, τυπική απόκλιση=1)
scaler_std = StandardScaler()
df["μισθός_std"] = scaler_std.fit_transform(df[["μισθός"]])

# Κανονικοποίηση στο [0, 1]
scaler_mm = MinMaxScaler()
df["μισθός_norm"] = scaler_mm.fit_transform(df[["μισθός"]])

Λογαριθμικός Μετασχηματισμός

Αν τα δεδομένα σας έχουν ασύμμετρη κατανομή (σκεφτείτε εισοδήματα ή τιμές ακινήτων — πάντα υπάρχει μια «ουρά» με ακραίες τιμές), ο λογαριθμικός μετασχηματισμός κάνει θαύματα:

df["μισθός_log"] = np.log1p(df["μισθός"])
print(df[["μισθός", "μισθός_log"]])

Η np.log1p υπολογίζει log(1 + x), αποφεύγοντας προβλήματα με μηδενικές τιμές. Μικρή λεπτομέρεια, μεγάλη σημασία.

Power Transforms (Box-Cox / Yeo-Johnson)

Ο Scikit-Learn προσφέρει τον PowerTransformer για αυτοματοποιημένη κανονικοποίηση κατανομών:

from sklearn.preprocessing import PowerTransformer

pt = PowerTransformer(method="yeo-johnson")  # Δουλεύει και με αρνητικές τιμές
df["μισθός_power"] = pt.fit_transform(df[["μισθός"]])

Πολυωνυμικά Χαρακτηριστικά

Δημιουργούν αλληλεπιδράσεις (interactions) και μη-γραμμικούς όρους. Πολύ χρήσιμο σε γραμμικά μοντέλα που δεν μπορούν να "δουν" μη γραμμικές σχέσεις από μόνα τους:

from sklearn.preprocessing import PolynomialFeatures

poly = PolynomialFeatures(degree=2, interaction_only=True, include_bias=False)
num_features = df[["ηλικία", "μισθός"]].dropna()
poly_features = poly.fit_transform(num_features)
print(f"Αρχικά χαρακτηριστικά: {num_features.shape[1]}")
print(f"Μετά πολυωνυμικά: {poly_features.shape[1]}")

Με interaction_only=True δημιουργούνται μόνο cross-terms (π.χ. ηλικία × μισθός), χωρίς τετράγωνα. Έτσι κρατάμε τον αριθμό χαρακτηριστικών σε λογικά επίπεδα.

Εξαγωγή Χαρακτηριστικών από Ημερομηνίες

Εδώ κρύβεται κρυμμένος θησαυρός. Οι στήλες ημερομηνιών περιέχουν πολύτιμη πληροφορία που πρέπει να εξαχθεί ρητά — αλλιώς, το μοντέλο δεν μπορεί να τη χρησιμοποιήσει:

# Βασικά χαρακτηριστικά χρόνου
df["έτος_πρόσληψης"] = df["ημερομηνία_πρόσληψης"].dt.year
df["μήνας_πρόσληψης"] = df["ημερομηνία_πρόσληψης"].dt.month
df["ημέρα_εβδομάδας"] = df["ημερομηνία_πρόσληψης"].dt.dayofweek

# Παλαιότητα σε ημέρες
df["ημέρες_εργασίας"] = (pd.Timestamp.now() - df["ημερομηνία_πρόσληψης"]).dt.days

# Κυκλική κωδικοποίηση μήνα (για εποχικότητα)
df["μήνας_sin"] = np.sin(2 * np.pi * df["μήνας_πρόσληψης"] / 12)
df["μήνας_cos"] = np.cos(2 * np.pi * df["μήνας_πρόσληψης"] / 12)

Η κυκλική κωδικοποίηση (sin/cos) είναι κάτι που πολλοί αγνοούν στην αρχή. Σκεφτείτε το: χωρίς αυτήν, ο Δεκέμβριος (12) φαίνεται πολύ μακριά από τον Ιανουάριο (1) στον χώρο χαρακτηριστικών — ενώ στην πραγματικότητα είναι γείτονες. Η sin/cos κωδικοποίηση διορθώνει ακριβώς αυτό.

Χειρισμός Ελλειπουσών Τιμών

Οι ελλείπουσες τιμές είναι αναπόφευκτες σε πραγματικά δεδομένα. Δεν έχω δει ποτέ production dataset χωρίς NaN κάπου. Η στρατηγική αντιμετώπισης εξαρτάται από τη φύση των δεδομένων σας:

from sklearn.impute import SimpleImputer, KNNImputer

# Αντικατάσταση με τη διάμεσο
imputer_median = SimpleImputer(strategy="median")
df["ηλικία_imputed"] = imputer_median.fit_transform(df[["ηλικία"]])

# Αντικατάσταση βάσει KNN — χρησιμοποιεί γειτονικές εγγραφές
knn_imputer = KNNImputer(n_neighbors=3)
df[["ηλικία_knn"]] = knn_imputer.fit_transform(df[["ηλικία", "μισθός"]])[:, 0:1]

Ο KNNImputer είναι πιο ακριβής από τον SimpleImputer γιατί λαμβάνει υπόψη τις σχέσεις μεταξύ μεταβλητών. Βέβαια, είναι και πιο αργός — οπότε σε μεγάλα datasets μπορεί να χρειαστεί να κάνετε trade-off.

Pipelines και ColumnTransformer

Ας περάσουμε στο σοβαρό κομμάτι. Στην πράξη, οι μετασχηματισμοί πρέπει να εφαρμόζονται με συνέπεια σε train/test sets. Ο ColumnTransformer του Scikit-Learn 1.8 σας επιτρέπει να εφαρμόσετε διαφορετικούς μετασχηματισμούς σε διαφορετικές στήλες — και αυτό αλλάζει τα πάντα:

from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score

# Ορισμός στηλών ανά τύπο
num_cols = ["ηλικία", "μισθός"]
cat_cols = ["πόλη"]
ord_cols = ["εκπαίδευση"]

# Μετασχηματισμοί
num_transformer = Pipeline([
    ("imputer", SimpleImputer(strategy="median")),
    ("scaler", StandardScaler())
])

cat_transformer = Pipeline([
    ("encoder", OneHotEncoder(drop="first", handle_unknown="ignore"))
])

ord_transformer = Pipeline([
    ("encoder", OrdinalEncoder(categories=[["Λύκειο", "Πτυχίο", "Μεταπτυχιακό", "Διδακτορικό"]]))
])

# Συνδυασμός με ColumnTransformer
preprocessor = ColumnTransformer([
    ("num", num_transformer, num_cols),
    ("cat", cat_transformer, cat_cols),
    ("ord", ord_transformer, ord_cols)
])

# Πλήρες pipeline με ταξινομητή
pipeline = Pipeline([
    ("preprocessor", preprocessor),
    ("classifier", RandomForestClassifier(n_estimators=100, random_state=42))
])

# Αξιολόγηση με cross-validation
X = df[num_cols + cat_cols + ord_cols]
y = df["αγόρασε"]
scores = cross_val_score(pipeline, X, y, cv=3, scoring="accuracy")
print(f"Μέση ακρίβεια: {scores.mean():.2f} (+/- {scores.std():.2f})")

Αυτή η προσέγγιση εξασφαλίζει ότι δεν υπάρχει data leakage, καθώς κάθε μετασχηματισμός γίνεται fit μόνο στα training δεδομένα. Αν πάρετε μόνο ένα πράγμα από αυτόν τον οδηγό, ας είναι αυτό: χρησιμοποιήστε πάντα pipelines.

Αυτοματοποιημένη Feature Engineering με Feature-engine

Η βιβλιοθήκη Feature-engine 1.9 προσφέρει transformers συμβατούς με scikit-learn, με ένα μεγάλο πλεονέκτημα: επιστρέφουν DataFrames αντί για NumPy arrays. Αυτό ακούγεται μικρό, αλλά στην πράξη κάνει τον κώδικα πολύ πιο ευανάγνωστο και τα debugging sessions λιγότερο επώδυνα.

Αυτόματη Εξαγωγή Χαρακτηριστικών Ημερομηνίας

from feature_engine.datetime import DatetimeFeatures

dt_transformer = DatetimeFeatures(
    variables=["ημερομηνία_πρόσληψης"],
    features_to_extract=["year", "month", "day_of_week", "quarter"],
    drop_original=True
)

df_dt = dt_transformer.fit_transform(df)
print(df_dt.columns.tolist())

Κωδικοποίηση με Feature-engine

from feature_engine.encoding import OrdinalEncoder as FEOrdinalEncoder
from feature_engine.encoding import CountFrequencyEncoder

# Κωδικοποίηση βάσει συχνότητας — ιδανικό για high-cardinality
freq_encoder = CountFrequencyEncoder(
    encoding_method="frequency",
    variables=["πόλη"]
)
df_freq = freq_encoder.fit_transform(df)
print(df_freq[["πόλη"]].head())

Αυτόματη Δημιουργία Μαθηματικών Χαρακτηριστικών

from feature_engine.creation import MathFeatures

math_transformer = MathFeatures(
    variables=["ηλικία", "μισθός"],
    func=["sum", "mean", "std", "min", "max"],
    missing_values="ignore"
)
df_math = math_transformer.fit_transform(df)
print(df_math.filter(like="sum").head())

Επιλογή Χαρακτηριστικών (Feature Selection)

Μετά τη δημιουργία χαρακτηριστικών έρχεται ένα εξίσου σημαντικό βήμα: να αφαιρέσουμε όσα δεν προσθέτουν αξία. Γιατί ναι, περισσότερα features δεν σημαίνει πάντα καλύτερη απόδοση (ακόμα κι αν μας αρέσει να φτιάχνουμε καινούργια).

from feature_engine.selection import (
    DropConstantFeatures,
    DropCorrelatedFeatures,
    SmartCorrelatedSelection
)

# Αφαίρεση σταθερών χαρακτηριστικών
drop_const = DropConstantFeatures(tol=0.95)

# Αφαίρεση συσχετισμένων χαρακτηριστικών
drop_corr = DropCorrelatedFeatures(
    threshold=0.85,
    method="pearson"
)

# Έξυπνη επιλογή — κρατά αυτά με την καλύτερη απόδοση
smart_sel = SmartCorrelatedSelection(
    threshold=0.85,
    selection_method="variance"
)

Μπορείτε επίσης να χρησιμοποιήσετε τον SelectKBest του scikit-learn για στατιστικά τεστ:

from sklearn.feature_selection import SelectKBest, f_classif

selector = SelectKBest(score_func=f_classif, k=5)
X_selected = selector.fit_transform(X_transformed, y)
print(f"Επιλεγμένα χαρακτηριστικά: {selector.get_support()}")

Ολοκληρωμένο Παράδειγμα: Pipeline End-to-End

Ωραία, ας δούμε πώς δένουν όλα μαζί σε ένα πλήρες pipeline. Αυτό είναι πιο κοντά σε αυτό που θα γράφατε σε πραγματικό project:

from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, OneHotEncoder, OrdinalEncoder
from sklearn.impute import SimpleImputer
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import GridSearchCV
from feature_engine.datetime import DatetimeFeatures
from feature_engine.creation import MathFeatures

# 1. Εξαγωγή χαρακτηριστικών ημερομηνίας πρώτα
dt_pipe = Pipeline([
    ("datetime", DatetimeFeatures(
        variables=["ημερομηνία_πρόσληψης"],
        features_to_extract=["year", "month", "day_of_week"],
        drop_original=True
    ))
])

# 2. Ορισμός μετασχηματισμών ανά τύπο στήλης
num_pipe = Pipeline([
    ("imputer", SimpleImputer(strategy="median")),
    ("scaler", StandardScaler())
])

cat_pipe = Pipeline([
    ("encoder", OneHotEncoder(drop="first", handle_unknown="ignore"))
])

ord_pipe = Pipeline([
    ("encoder", OrdinalEncoder(
        categories=[["Λύκειο", "Πτυχίο", "Μεταπτυχιακό", "Διδακτορικό"]]
    ))
])

# 3. Συνδυασμός σε ColumnTransformer
preprocessor = ColumnTransformer([
    ("num", num_pipe, ["ηλικία", "μισθός"]),
    ("cat", cat_pipe, ["πόλη"]),
    ("ord", ord_pipe, ["εκπαίδευση"])
], remainder="passthrough")

# 4. Πλήρες pipeline
full_pipeline = Pipeline([
    ("dt_features", dt_pipe),
    ("preprocessor", preprocessor),
    ("classifier", GradientBoostingClassifier(random_state=42))
])

# 5. Υπερπαραμετροποίηση
param_grid = {
    "classifier__n_estimators": [50, 100, 200],
    "classifier__max_depth": [3, 5, 7],
    "classifier__learning_rate": [0.01, 0.1, 0.2]
}

grid_search = GridSearchCV(
    full_pipeline, param_grid,
    cv=3, scoring="f1", n_jobs=-1
)

# grid_search.fit(X, y)
# print(f"Καλύτερο σκορ: {grid_search.best_score_:.3f}")
# print(f"Καλύτερες παράμετροι: {grid_search.best_params_}")

Βέλτιστες Πρακτικές

  • Πάντα μέσα σε pipeline: Κάθε μετασχηματισμός πρέπει να γίνεται εντός pipeline. Δεν υπάρχει εξαίρεση σε αυτόν τον κανόνα αν θέλετε να αποφύγετε data leakage.
  • Ξεκινήστε απλά: Δοκιμάστε πρώτα βασικές τεχνικές (scaling, encoding) πριν προχωρήσετε σε πιο σύνθετες (polynomial, interactions). Πολλές φορές τα βασικά αρκούν.
  • Γνώση πεδίου: Η καλύτερη feature engineering προέρχεται από κατανόηση του προβλήματος. Κανένα εργαλείο αυτοματοποίησης δεν αντικαθιστά το να ξέρεις τι σημαίνουν τα δεδομένα σου.
  • Μετρήστε τον αντίκτυπο: Κάθε νέο χαρακτηριστικό πρέπει να αξιολογείται. Αν δεν βελτιώνει το μοντέλο, αφαιρέστε το.
  • Προσέξτε το target leakage: Ποτέ μην χρησιμοποιείτε πληροφορία που δεν θα ήταν διαθέσιμη κατά την πρόβλεψη. Είναι η πιο κοινή αιτία υπερβολικά αισιόδοξων αποτελεσμάτων.

Συχνές Ερωτήσεις (FAQ)

Ποια είναι η διαφορά μεταξύ feature engineering και feature selection;

Η feature engineering δημιουργεί νέα χαρακτηριστικά από τα υπάρχοντα δεδομένα (π.χ. εξαγωγή μήνα από ημερομηνία). Η feature selection επιλέγει τα πιο χρήσιμα και αφαιρεί τα περιττά. Στην πράξη, πρώτα κάνετε engineering κι ύστερα selection.

Πότε πρέπει να χρησιμοποιώ One-Hot αντί Ordinal Encoding;

Χρησιμοποιήστε One-Hot Encoding για nominal μεταβλητές χωρίς φυσική σειρά (χρώμα, πόλη, κτλ.). Χρησιμοποιήστε Ordinal Encoding μόνο όταν υπάρχει σαφής ιεράρχηση (εκπαίδευση: Λύκειο < Πτυχίο < Μεταπτυχιακό). Λάθος επιλογή μπορεί να εισάγει ψευδείς σχέσεις στο μοντέλο — κι αυτό είναι δύσκολο να το εντοπίσετε εκ των υστέρων.

Πώς αποφεύγω το data leakage στη feature engineering;

Με δύο λέξεις: scikit-learn pipelines. Τα pipelines εξασφαλίζουν ότι κάθε μετασχηματισμός κάνει fit μόνο στα training δεδομένα. Αποφύγετε τον υπολογισμό στατιστικών (μέσος, τυπική απόκλιση) σε ολόκληρο το dataset πριν το split — είναι ο πιο κοινός τρόπος να μπει leakage χωρίς να το καταλάβετε.

Χρειάζεται scaling για tree-based αλγορίθμους;

Σύντομη απάντηση: όχι. Οι αλγόριθμοι δέντρων (Random Forest, XGBoost, LightGBM) δεν επηρεάζονται από κλιμάκωση γιατί βασίζονται σε thresholds. Αντίθετα, αλγόριθμοι βασισμένοι σε αποστάσεις (KNN, SVM) ή gradient-based (νευρωνικά δίκτυα, logistic regression) χρειάζονται πάντα scaling.

Πώς αντιμετωπίζω κατηγορικές μεταβλητές υψηλής πληθικότητας;

Αποφύγετε οπωσδήποτε One-Hot Encoding — θα καταλήξετε με εκατοντάδες ή χιλιάδες στήλες. Καλύτερες εναλλακτικές: Target Encoding (πάντα με cross-validation), Frequency Encoding μέσω Feature-engine, ή Feature Hashing μέσω sklearn.feature_extraction.FeatureHasher.

Σχετικά με τον Συγγραφέα Editorial Team

Our team of expert writers and editors.