Scikit-learn Pipeline og ColumnTransformer i Python: Komplet guide til ML-preprocessing (2026)

Lær at bygge pålidelige machine learning-pipelines med scikit-learn Pipeline og ColumnTransformer. Praktisk Python-guide til preprocessing, data-leakage, GridSearchCV og deployment i 2026.

Scikit-learn Pipeline-guide 2026

Opdateret: 30. juni 2026

En scikit-learn Pipeline kombineret med ColumnTransformer lader dig samle hele preprocessing- og modeltræningsforløbet i ét objekt, der kan fittes, krydsvalideres og gemmes uden data-leakage. I praksis betyder det, at numeriske kolonner kan skaleres, kategoriske kolonner kan one-hot-encodes, og modellen kan trænes i én og samme linje kode, der opfører sig korrekt både ved fit() og predict(). Ærligt talt er det nok den vigtigste enkeltforbedring, du kan lave i en Python-ML-workflow i 2026.

  • Pipeline kæder preprocessing-trin og en estimator sammen, så de fittes i den korrekte rækkefølge uden data-leakage.
  • ColumnTransformer anvender forskellige transformationer på forskellige kolonner (fx StandardScaler på numeriske og OneHotEncoder på kategoriske).
  • Brug handle_unknown='ignore' i OneHotEncoder, så ukendte kategorier ved inference ikke crasher modellen.
  • make_column_selector vælger kolonner automatisk efter dtype og fjerner manuel vedligeholdelse, når skemaet ændrer sig.
  • GridSearchCV kan tune både preprocessing- og modelparametre samtidigt via navn__parameter-syntaksen.
  • Scikit-learn 1.9 (2026) understøtter pandas-output via set_output(transform='pandas'), hvilket bevarer kolonnenavne hele vejen igennem.

Hvad er en scikit-learn Pipeline?

En Pipeline i scikit-learn er et komposit-estimator-objekt, der lænker en sekvens af transformer-trin sammen med en slutestimator (klassifikator eller regressor). Når du kalder pipeline.fit(X_train, y_train), kører biblioteket først fit_transform på hvert preprocessing-trin og til sidst fit på modellen. Ved pipeline.predict(X_test) kører kun transform på trinnene, så statistikker fra testdata aldrig lækker ind i træningen.

Denne enkapsulering har tre store fordele i produktion. For det første reducerer den koden til ét linjekald, hvilket gør krydsvalidering korrekt by-default. For det andet kan hele pipelinen serialiseres med joblib og deployes som én monolitisk artefakt, så der er ingen risiko for at glemme at skalere features i inference-laget. For det tredje integrerer Pipeline direkte med GridSearchCV og RandomizedSearchCV, så hyperparametre i alle trin kan tunes samtidig.

Pipeline accepterer en liste af tupler på formatet (navn, transformer). Det sidste trin skal være en estimator (have en fit-metode), mens alle øvrige trin skal være transformere (have både fit og transform). Hvis du tidligere har bygget en ML-model fra bunden, kender du måske workflowet fra vores guide til at bygge din første machine learning-model med scikit-learn. Pipeline er det næste naturlige skridt.

Hvorfor du har brug for ColumnTransformer

Næsten alle realistiske datasæt indeholder en blanding af numeriske, kategoriske og potentielt tekstuelle eller datostemplede features. Hver type kræver sin egen preprocessing-strategi: numeriske kolonner skal typisk imputeres med median og skaleres, kategoriske kolonner skal imputeres med modalværdi og one-hot-encodes, og tekstkolonner skal måske gennem en TfidfVectorizer. Hvis du forsøger at presse alt gennem en enkelt Pipeline, ender du med at preprocesse hele matricen ens, og det giver enten meningsløse resultater eller direkte fejl.

ColumnTransformer løser præcis dette problem. Den tager en liste af tre-elements-tupler (navn, transformer, kolonner) og anvender hver transformer på sin egen kolonneundermængde. Outputtet stables vandret tilbage til én matrix, som derefter sendes videre til næste pipeline-trin eller direkte til modellen. Resultatet er ren, vedligeholdelsesvenlig kode, hvor preprocessing-logikken sidder ét sted og kan versioneres sammen med modellen.

Et yderligere argument er data-leakage. Hvis du manuelt skalerer X med StandardScaler().fit_transform(X) før train_test_split, kender din scaler allerede testdataens gennemsnit og standardafvigelse, og dine krydsvalideringsscorer bliver optimistiske. ColumnTransformer indlejret i en Pipeline genfittes for hvert fold, så hver split får sin egen scaler, der kun kender træningsdataen.

Sådan bygger du en Pipeline med ColumnTransformer fra bunden

Her er et komplet, kørselsklart eksempel, der træner en klassifikator på et blandet datasæt med både numeriske og kategoriske kolonner. Jeg bruger Titanic-lignende data, men strukturen virker for ethvert tabulært datasæt:

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.linear_model import LogisticRegression

# 1. Indlaes data (eksempel: passagerdata)
df = pd.read_csv("passengers.csv")
y = df["survived"]
X = df.drop(columns=["survived"])

# 2. Definer kolonner efter type
numeriske_kolonner = ["age", "fare", "sibsp"]
kategoriske_kolonner = ["sex", "embarked", "pclass"]

# 3. Sub-pipeline for numeriske features
num_pipeline = Pipeline([
    ("imputer", SimpleImputer(strategy="median")),
    ("scaler", StandardScaler()),
])

# 4. Sub-pipeline for kategoriske features
cat_pipeline = Pipeline([
    ("imputer", SimpleImputer(strategy="most_frequent")),
    ("encoder", OneHotEncoder(handle_unknown="ignore")),
])

# 5. Saml dem i en ColumnTransformer
preprocessor = ColumnTransformer([
    ("num", num_pipeline, numeriske_kolonner),
    ("cat", cat_pipeline, kategoriske_kolonner),
])

# 6. Endelig pipeline med model
model = Pipeline([
    ("preprocessor", preprocessor),
    ("classifier", LogisticRegression(max_iter=1000)),
])

# 7. Traen og evaluer
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)
model.fit(X_train, y_train)
print(f"Test accuracy: {model.score(X_test, y_test):.3f}")

Bemærk fire detaljer. Først bruger jeg strategy="median" til numerisk imputation, fordi det er mere modstandsdygtigt over for outliers end "mean". For det andet sætter jeg handle_unknown="ignore" på OneHotEncoder, så en ny kategori i produktion ikke kaster en exception. For det tredje bruger jeg stratify=y ved split for at bevare klassefordelingen. Og endelig: hele preprocessing-logikken er nu del af model-objektet, så jeg kan kalde model.predict() på rå pandas-data uden at huske at skalere først.

Hvordan undgår jeg data-leakage med scikit-learn Pipeline?

Data-leakage opstår, når information fra testdata utilsigtet siver ind i træningsprocessen. Den klassiske fælde er at fitte en StandardScaler eller SimpleImputer på hele datasættet, før du splitter. Så lærer din scaler testdataens statistikker, og krydsvalideringsresultatet bliver kunstigt højt. I produktion falder ydelsen så, fordi modellen aldrig så "rene" out-of-sample data under træning.

Pipelines fjerner denne risiko helt. Når du bruger cross_val_score(pipeline, X, y, cv=5), refitter scikit-learn hele pipelinen (inklusive imputers og scalers) for hvert af de fem fold. Hvert fold ser kun sin egen træningsdel, ligesom det vil være i produktion. Det er en af grundene til, at jeg altid bygger preprocessing ind i Pipeline frem for at gøre det i forvejen på en pandas DataFrame. I mit sidste projekt så jeg en kollegas AUC falde fra 0,94 i CV til 0,71 i produktion, præcis fordi scaleren var fittet før split.

En subtil variant af leakage er target leakage, altså når en feature implicit indeholder information om målvariablen. Det fanger Pipeline ikke; du skal selv inspicere features. Brug gerne permutation_importance fra sklearn.inspection til at se, hvilke features modellen reelt læner sig op ad. Hvis du arbejder med tabulære data, kan vores guide til datarensning med pandas 3.0 hjælpe med at finde og fjerne lækkende features før modellering.

Tuning af hyperparametre med GridSearchCV

Den måske største gevinst ved Pipeline + ColumnTransformer er, at du kan tune alt samtidig: både imputer-strategier, encoder-indstillinger og modelhyperparametre. Syntaksen er trin__parameter med dobbelt underscore, og den kan kædes flere niveauer dybt, som i preprocessor__num__imputer__strategy.

from sklearn.model_selection import GridSearchCV

param_grid = {
    "preprocessor__num__imputer__strategy": ["mean", "median"],
    "preprocessor__cat__encoder__min_frequency": [None, 0.01, 0.05],
    "classifier__C": [0.01, 0.1, 1.0, 10.0],
    "classifier__penalty": ["l2"],
}

search = GridSearchCV(
    model,
    param_grid,
    cv=5,
    scoring="roc_auc",
    n_jobs=-1,
    verbose=1,
)
search.fit(X_train, y_train)

print(f"Bedste CV-score: {search.best_score_:.3f}")
print(f"Bedste parametre: {search.best_params_}")

Grid'et ovenfor afsøger 2 × 3 × 4 × 1 = 24 kombinationer i 5-fold krydsvalidering, altså 120 fits i alt. Det lyder af meget, men med n_jobs=-1 og en moderne 8-kerne-CPU er det færdigt på sekunder for små datasæt. min_frequency-parameteren på OneHotEncoder grupperer sjældne kategorier til "infrequent" og er ofte en stille gevinst på datasæt med lange haler.

Automatisk kolonnevalg med make_column_selector

I praksis ændrer skemaet på dine data sig over tid: nye kolonner kommer til, gamle forsvinder. Hvis du har hardcodet ["age", "fare", "sibsp"] som numeriske kolonner, skal du opdatere koden hver gang. make_column_selector løser dette ved at vælge kolonner dynamisk baseret på dtype eller regex-mønstre.

from sklearn.compose import make_column_selector as selector

preprocessor = ColumnTransformer([
    ("num", num_pipeline, selector(dtype_include="number")),
    ("cat", cat_pipeline, selector(dtype_include=["object", "category"])),
    ("dato", dato_pipeline, selector(pattern=".*_date$")),
])

Selectoren evalueres ved fit()-tidspunktet, så hvis dine træningsdata indeholder en ny customer_age-kolonne, fanges den automatisk af det numeriske trin. Det er især nyttigt, når flere teams arbejder på samme feature-tabel, og kolonner kommer og går. Kombineret med pandas.api.types.CategoricalDtype kan du tilmed styre, hvilke object-kolonner der skal behandles som ægte kategorier.

Bemærk dog én ting: selector ser kun de kolonner, du sender ind ved fit(). Hvis inference-data har en ekstra kolonne, der ikke fandtes ved træning, vil scikit-learn 1.6+ kaste en ValueError som standard. Håndter det enten ved at sætte remainder='drop' eksplicit, eller ved at aligne kolonnerne i et separat preprocessing-trin.

Bevar kolonnenavne med set_output i scikit-learn 1.9

Et af de længstvarende kritikpunkter mod scikit-learn var, at transformere returnerede NumPy-arrays uden kolonnenavne. Efter en ColumnTransformer.fit_transform() mistede du al kolonneidentitet og kunne kun debugge ved indeks. Det blev rettet i scikit-learn 1.2 og er nu i 2026 med scikit-learn 1.9 fuldt understøttet via set_output:

# Globalt for hele scikit-learn
from sklearn import set_config
set_config(transform_output="pandas")

# Eller per pipeline
preprocessor.set_output(transform="pandas")

X_transformed = preprocessor.fit_transform(X_train)
print(type(X_transformed))  # <class 'pandas.DataFrame'>
print(X_transformed.columns.tolist())
# ['num__age', 'num__fare', 'num__sibsp', 'cat__sex_female', ...]

Kolonnenavnene præfikses med transformer-navnet, så du nemt kan se, hvilket trin der har produceret hvilken feature. Det gør feature importance-plots langt mere brugbare, og det letter debugging af modeller, der opfører sig mærkeligt. Jeg ramte præcis den bug, da jeg shippede en kreditscoringsmodel sidste år, hvor feature 17 viste sig at være en encoded ZIP-kode i stedet for indkomst. Hvis du senere skal lave datavisualisering af de transformerede features, kan du nu plotte direkte med pandas. Se vores guide til datavisualisering med matplotlib og seaborn for konkrete plot-opskrifter.

Gem og deploy din pipeline med joblib

En af de største praktiske gevinster ved Pipeline er, at hele preprocessing-til-prediction-flowet kan serialiseres som ét objekt. Det betyder, at dit deployment-script ikke skal genimplementere encodings og scaling. Det indlæser bare pipelinen og kalder predict():

import joblib

# Gem den faerdigtraenede pipeline
joblib.dump(search.best_estimator_, "model.joblib", compress=3)

# I produktion (maaske et FastAPI-endpoint)
loaded_model = joblib.load("model.joblib")

def predict(raa_data: dict) -> float:
    X = pd.DataFrame([raa_data])
    return float(loaded_model.predict_proba(X)[0, 1])

To gode vaner: pin altid scikit-learn-versionen i din requirements.txt, fordi joblib-artefakter er bagudkompatible, men ikke nødvendigvis fremadkompatible mellem major-versioner. Gem desuden en lille schema.json ved siden af modelfilen, der beskriver de forventede inputkolonner og dtypes. Så fanger du skemafejl tidligt frem for at få kryptiske transformer-fejl ved inference.

Almindelige fejl og hvordan du retter dem

Selv erfarne ML-udviklere falder i de samme grøfter med Pipeline og ColumnTransformer. Her er de fem hyppigste fejl, jeg ser i kode-reviews i 2026:

  1. ValueError: Found unknown categories ved inference. Du har glemt handle_unknown='ignore' på OneHotEncoder. Tilføj den; det er næsten altid det rigtige valg uden for streng audit-kontekst.
  2. "Mean of empty slice" warning fra SimpleImputer. En kolonne indeholder kun NaN i et bestemt fold. Skift til strategy='constant' med fill_value=0, eller drop kolonnen før pipelinen.
  3. Feature-navne er numeriske indekser efter transformation. Du har glemt at sætte set_output(transform='pandas'). Tilføj den globalt med sklearn.set_config(transform_output='pandas').
  4. GridSearchCV er ekstremt langsom. Du tuner n_estimators i RandomForest sammen med preprocessing, og refit hele preprocessing-pipelinen for hver kombination. Brug memory='cache_dir' i Pipeline-konstruktøren til at cache transformere mellem fits.
  5. remainder='drop' dropper kolonner, du ikke vidste eksisterede. Vær eksplicit med remainder='passthrough', hvis du forventer at bevare ikke-specificerede kolonner, eller log en advarsel ved fit-tid.

Hvis du sammenligner scikit-learn med andre ML-frameworks, så kig også på vores sammenligning af Polars og pandas. I 2026 er Polars en seriøs kandidat til preprocessing-laget i højvolumen-pipelines, hvor du senere konverterer til pandas eller NumPy lige før modeltræning.

For dybere reference til alle parametre og avancerede mønstre kan du læse den officielle scikit-learn-dokumentation for Pipeline og komposit-estimatorer, det fulde ColumnTransformer API-reference, samt eksempel-galleriets Column Transformer with Mixed Types-tutorial.

Ofte stillede spørgsmål

Hvad er forskellen på Pipeline og make_pipeline i scikit-learn?

Pipeline kræver, at du eksplicit navngiver hvert trin med en tuple (navn, transformer), mens make_pipeline automatisk genererer navne ud fra klassenavnene i små bogstaver. Brug Pipeline, når du vil referere til trin i GridSearchCV eller debugging, fordi automatiske navne kolliderer let.

Kan jeg bruge ColumnTransformer uden en Pipeline?

Ja, ColumnTransformer er selv en transformer og kan kaldes direkte med fit_transform. Men kombineret med en Pipeline får du automatisk korrekt håndtering af krydsvalidering og serialisering, så i 99% af tilfældene bør du wrap'pe den i en Pipeline.

Hvordan håndterer jeg tekstkolonner i en ColumnTransformer?

Tilføj en separat tuple med en TfidfVectorizer eller CountVectorizer som transformer og angiv tekstkolonnen som en streng (ikke en liste): ("tekst", TfidfVectorizer(), "beskrivelse"). Bemærk forskellen: andre transformere kræver en liste af kolonnenavne, mens vectorizere kun accepterer ét kolonnenavn ad gangen.

Skal jeg bruge fit_transform eller fit + transform separat?

Brug fit_transform på træningsdata og kun transform på testdata. Pipeline gør dette automatisk for dig, så hvis du arbejder via pipeline.fit() og pipeline.predict(), behøver du aldrig kalde transformere manuelt. Manuel håndtering er den hyppigste kilde til data-leakage hos begyndere.

Virker scikit-learn Pipeline med deep learning-modeller som PyTorch?

Ikke direkte, fordi Pipeline forventer scikit-learn-API'et (fit, predict, score). Du kan dog wrappe PyTorch-modeller med skorch-biblioteket, som giver dem et scikit-learn-kompatibelt interface, så de kan indgå som det sidste trin i en Pipeline sammen med ColumnTransformer-preprocessing.

Editorial Team
Om Forfatteren Editorial Team

Our team of expert writers and editors.