Strukturering af transformationstrin som funktioner
Opdel Deres notebook i extract-, transform- og load-funktioner, hvor hver funktion modtager og returnerer en DataFrame, så de er nemme at teste.
Strukturering af transformationstrin som funktioner er en gratis Pandas & NumPy Academy-lektion på CoddyKit. Dette er lektion 1 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i Pandas & NumPy Academy, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Pandas & NumPy Academy-kurset indeholder 4 lektioner i alt.
Videre end notebooks
Jupyter-notebooks er gode til udforskning, men dårlige til databehandlingsforløb i produktion. Kode spredt over 50 celler med global tilstand og uden test er skrøbelig: Hvis du ændrer én celle, kan du ubevidst ødelægge en anden. Det professionelle alternativ er at udtrække hver transformation til en navngiven funktion, der modtager et DataFrame-objekt og returnerer et DataFrame-objekt. Denne adskillelse af ansvar er grundlaget for kode til vedligeholdelig datateknik.
# Notebook-style (fragile)
df = pd.read_csv('orders.csv')
df = df.dropna(subset=['revenue'])
df = df[df['quantity'] > 0]
df['revenue_per_unit'] = df['revenue'] / df['quantity']
# Function-style (robust)
def extract(path):
return pd.read_csv(path)
def transform(df):
return (
df.dropna(subset=['revenue'])
.query('quantity > 0')
.assign(revenue_per_unit=lambda d: d['revenue'] / d['quantity'])
)
df = transform(extract('orders.csv'))ETL-mønsteret: Extract, Transform, Load
ETL-mønsteret opdeler et databehandlingsforløb i tre faser: Extract (læs fra kilden), Transform (rens og berig) og Load (skriv til destinationen). Hver fase er en separat funktion. Denne opdeling gør det nemt at skifte datakilder (CSV eller database), ændre rense-logikken eller ændre outputformatet uden at berøre de to andre faser. Alle databehandlingsforløb i produktion bør følge denne struktur.
def extract(config):
return pd.read_csv(config['input_path'], parse_dates=['order_date'])
def transform(df, config):
return (
df
.dropna(subset=config['required_cols'])
.query('quantity > 0')
.assign(revenue=lambda d: d['quantity'] * d['unit_price'])
)
def load(df, config):
df.to_parquet(config['output_path'], index=False)
print(f'Saved {len(df)} rows.')Princippet om ét ansvar
Hver transformationsfunktion bør gøre præcis én ting. En funktion kaldet clean_data(), der fjerner nuller, begrænser outliers, fortolker datoer og koder kategorier, er vanskelig at teste og fejlfinde. Skriv i stedet drop_nulls(), cap_outliers(), parse_dates() og encode_categories() som separate funktioner. Denne granularitet gør det nemt at springe et enkelt trin over, udskifte det eller ændre rækkefølgen.
def drop_null_rows(df, required_cols):
return df.dropna(subset=required_cols)
def remove_returns(df):
return df[df['quantity'] > 0]
def compute_revenue(df):
return df.assign(revenue=lambda d: d['quantity'] * d['unit_price'])
def add_date_features(df):
return df.assign(
year=lambda d: d['order_date'].dt.year,
month=lambda d: d['order_date'].dt.month
)Sammenkædning af trin med pipe()
Forbind funktioner med ét ansvar ved hjælp af pipe() for at opbygge hele transformationsfasen som en læsbar kæde. Kæden læses som en opskrift: hver linje er et trin, og dataene bevæger sig fra øverst til nederst. Du kan kommentere ethvert trin ud eller ændre rækkefølgen uden at omdøbe variable. Det endelige resultat er det rene, berigede DataFrame-objekt, der er klar til indlæsningsfasen.
import pandas as pd
REQUIRED = ['order_id', 'revenue', 'order_date']
def transform(raw_df):
return (
raw_df
.pipe(drop_null_rows, required_cols=REQUIRED)
.pipe(remove_returns)
.pipe(compute_revenue)
.pipe(add_date_features)
)
df_clean = transform(pd.read_csv('orders.csv', parse_dates=['order_date']))
print(df_clean.shape)Returnering af rækkeantal til kontrol
Hver transformationsfunktion bør valgfrit logge, hvor mange rækker den modtog, og hvor mange den returnerede. Hvis du indbygger logning af rækkeantallet før og efter i funktionen, får du et enkelt revisionsspor, der hurtigt viser, hvor rækker fjernes under en kørsel af databehandlingsforløbet. Gem disse antal i en liste, og udskriv dem som en rapport ved slutningen af hver kørsel.
audit_log = []
def audited(func):
def wrapper(df, *args, **kwargs):
before = len(df)
result = func(df, *args, **kwargs)
after = len(result)
audit_log.append({'step': func.__name__, 'in': before, 'out': after, 'dropped': before - after})
return result
return wrapper
@audited
def drop_null_rows(df, required_cols):
return df.dropna(subset=required_cols)Testbare funktioner med små DataFrame-objekter
Den største fordel ved navngivne funktioner er, at de kan testes. Skriv et lille test-DataFrame-objekt, der repræsenterer et realistisk kanttilfælde, og kontrollér funktionens output. Test funktionen drop_null_rows med én række, der indeholder en null-værdi, og kontrollér, at den fjernes. Test derefter en række uden en null-værdi, og kontrollér, at den bevares. Enhedstest af transformationsfunktioner opdager regressioner, når koden til databehandlingsforløbet ændres.
import pandas as pd
def test_drop_null_rows():
test_df = pd.DataFrame({
'order_id': [1, 2, 3],
'revenue': [100.0, None, 200.0]
})
result = drop_null_rows(test_df, required_cols=['revenue'])
assert len(result) == 2, 'Should have 2 non-null rows'
assert result['revenue'].isna().sum() == 0, 'No nulls in revenue'
print('test_drop_null_rows PASSED')
test_drop_null_rows()Organisering af funktioner i moduler
Efterhånden som databehandlingsforløbet vokser, kan du opdele funktionerne i Python-modulfiler: extract.py, transform.py, load.py og validate.py. Hovedscriptet pipeline.py importerer modulerne og orkestrerer dem. Denne filstruktur er overskuelig, kan testes med pytest og kan installeres som en Python-pakke. Den afspejler den standardstruktur, som datateknikteams bruger med værktøjer som dbt eller Airflow.
# pipeline.py
# from extract import extract_orders
# from transform import transform
# from load import load_to_parquet
# from validate import validate_sales_df
# def run_pipeline(config):
# raw = extract_orders(config)
# clean = transform(raw, config)
# validate_sales_df(clean)
# load_to_parquet(clean, config)
print('Module-based pipeline structure shown above (imports commented for demo)')Idempotens: Kør sikkert flere gange
En veldesignet forløbsfunktion er idempotent: Hvis du kører den to gange med de samme inputdata, giver det samme output og ingen bivirkninger. Undgå ændringer direkte i det eksisterende objekt (df.drop(..., inplace=True)), og brug altid df.copy() i begyndelsen af funktioner, der ændrer kolonner. Idempotente funktioner kan køres igen efter fejl uden at ødelægge outputdataene.
def add_revenue_flag(df, threshold=500):
# Use copy to avoid mutating the input
df = df.copy()
df['is_large_order'] = df['revenue'] >= threshold
return df
# Running twice gives the same result
df1 = add_revenue_flag(df_clean)
df2 = add_revenue_flag(df_clean)
assert df1.equals(df2), 'Function is not idempotent!'
print('Idempotency check passed.')Typeangivelser som selvdokumentation
Tilføj Python-typeangivelser til transformationsfunktionernes signaturer for at gøre kontrakten tydelig: def transform(df: pd.DataFrame) -> pd.DataFrame. Typeangivelser dokumenterer sig selv — enhver udvikler, der læser funktionen, ved præcis, hvad den forventer og returnerer, uden at skulle læse funktionskroppen. De gør det også muligt for værktøjer til statisk analyse som mypy og IDE'ers automatiske fuldførelse at opdage typefejl før kørsel.
from typing import List
def drop_null_rows(df: pd.DataFrame, required_cols: List[str]) -> pd.DataFrame:
return df.dropna(subset=required_cols)
def compute_revenue(df: pd.DataFrame) -> pd.DataFrame:
return df.assign(revenue=lambda d: d['quantity'] * d['unit_price'])
print('Type-annotated functions ready for production use.')Dokumentation af hvert trin med docstrings
Hver transformationsfunktion bør have en docstring på én linje, der angiver, hvad den gør, hvilke kolonner den kræver, og hvilke kolonner den tilføjer eller fjerner. Gode docstrings gør funktionen nem at finde via help() og i IDE-værktøjstip. De fungerer også som specifikationsdokumenter: En fejlslagen kontrol, der er i modstrid med docstringen, er en fejl; en docstring, der ikke stemmer overens med koden, er en dokumentationsfejl.
def compute_revenue(df: pd.DataFrame) -> pd.DataFrame:
"""Add 'revenue' column as quantity * unit_price.
Requires: 'quantity' (numeric), 'unit_price' (numeric) columns.
Returns: df with new 'revenue' float column appended.
"""
return df.assign(revenue=lambda d: d['quantity'] * d['unit_price'])
help(compute_revenue)Kørsel af hele databehandlingsforløbet
Orkestrer den komplette ETL ved at kalde de tre faser i rækkefølge: extract, transform og load. Tilføj tidsmåling omkring hver fase for at se, hvor tiden bruges. Fang undtagelser fra hver fase separat, så fejlmeddelelserne identificerer, hvilken fase der mislykkedes. Log start- og sluttidspunktet for hele kørslen samt om den lykkedes eller mislykkedes, så den kan overvåges.
import time
CONFIG = {
'input_path': 'orders.csv',
'output_path': 'orders_clean.parquet',
'required_cols': ['order_id', 'revenue']
}
t0 = time.time()
raw = extract(CONFIG)
clean = transform(raw, CONFIG)
load(clean, CONFIG)
print(f'Pipeline completed in {time.time()-t0:.1f}s')
print(f'Audit log: {audit_log}')Hurtigt tjek
Test din forståelse af begreberne inden for dataanalyse fra denne lektion.
Opsummering af lektionen
I denne lektion har du lært: at strukturere databehandlingsforløb som ETL-funktioner med ét ansvar, at gøre funktioner testbare, idempotente og selvdokumenterende med typeangivelser og docstrings og at orkestrere hele databehandlingsforløbet med tidsmåling og revisionslogning. Dernæst undersøger vi, hvordan databehandlingsforløb parameteriseres med konfigurationsordbøger, så de kan genbruges på forskellige datasæt.
Lær Python med en AI-underviser — gratis
Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.
- Kurser
- 30
- Lektioner
- 120
Ofte stillede spørgsmål
Er lektionen “Strukturering af transformationstrin som funktioner” gratis?
Ja — hele teksten til “Strukturering af transformationstrin som funktioner” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af Pandas & NumPy Academy-kurset, skal du opgradere til CoddyKit PRO. Pandas & NumPy Academy-kurset indeholder 4 lektioner i alt.
Hvad lærer jeg i “Strukturering af transformationstrin som funktioner”?
Opdel Deres notebook i extract-, transform- og load-funktioner, hvor hver funktion modtager og returnerer en DataFrame, så de er nemme at teste. Du øver dig i Pandas & NumPy Academy med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.
Skal jeg have erfaring for at begynde på Pandas & NumPy Academy?
Der kræves ingen tidligere erfaring. Pandas & NumPy Academy på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 1 af 4.
Hvor lang tid tager lektionen “Strukturering af transformationstrin som funktioner”?
De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.
Kan jeg skrive og køre kode i denne Pandas & NumPy Academy-lektion?
Ja. Alle Pandas & NumPy Academy-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.
Alle lektioner i dette kursus
- Strukturering af transformationstrin som funktioner
- Parametrisering af pipelines med config-dicts
- Test af pipelinetrin med assertions
- Planlægning og logning af pipelinekørsler