การจัดโครงสร้างขั้นตอนการแปลงเป็นฟังก์ชัน
แยกสมุดบันทึกของคุณเป็นฟังก์ชันดึงข้อมูล แปลงข้อมูล และโหลดข้อมูล โดยแต่ละฟังก์ชันรับและส่งคืน DataFrame เพื่อให้ทดสอบได้ง่าย
การจัดโครงสร้างขั้นตอนการแปลงเป็นฟังก์ชัน เป็นบทเรียน Pandas & NumPy Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Pandas & NumPy Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Pandas & NumPy Academy มีบทเรียนทั้งหมด 4 บทเรียน
ก้าวต่อไปจากโน้ตบุ๊ก
โน้ตบุ๊ก Jupyter เหมาะสำหรับการสำรวจข้อมูล แต่ไม่เหมาะกับสายงานข้อมูลระดับใช้งานจริง โค้ดที่กระจัดกระจายอยู่ใน 50 เซลล์ มีสถานะส่วนกลาง และไม่มีการทดสอบนั้นเปราะบาง การเปลี่ยนเซลล์หนึ่งอาจทำให้อีกเซลล์เสียหายโดยไม่แสดงข้อผิดพลาด ทางเลือกแบบมืออาชีพคือแยกการแปลงข้อมูลแต่ละรายการออกเป็น ฟังก์ชันที่มีชื่อ ซึ่งรับ DataFrame และคืนค่า DataFrame การแยกหน้าที่ในลักษณะนี้เป็นพื้นฐานของโค้ดวิศวกรรมข้อมูลที่ดูแลรักษาได้
# 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: สกัด แปลง และโหลด
รูปแบบ ETL แบ่งสายงานข้อมูลออกเป็นสามระยะ ได้แก่ Extract (อ่านจากแหล่งข้อมูล), Transform (ทำความสะอาดและเพิ่มข้อมูล) และ Load (เขียนไปยังปลายทาง) แต่ละระยะเป็นฟังก์ชันแยกกัน การแยกเช่นนี้ทำให้เปลี่ยนแหล่งข้อมูลได้ง่าย (CSV หรือฐานข้อมูล) เปลี่ยนตรรกะการทำความสะอาด หรือเปลี่ยนรูปแบบผลลัพธ์ได้โดยไม่ต้องแก้ไขอีกสองระยะ ทุกสายงานระดับใช้งานจริงควรใช้โครงสร้างนี้
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.')หลักความรับผิดชอบเดียว
ฟังก์ชันการแปลงแต่ละฟังก์ชันควรทำ เพียงอย่างเดียว ฟังก์ชันชื่อ clean_data() ที่ลบค่าว่าง จำกัดค่าผิดปกติ แปลงวันที่ และเข้ารหัสหมวดหมู่พร้อมกัน จะทดสอบและแก้จุดบกพร่องได้ยาก ดังนั้นให้เขียน drop_nulls(), cap_outliers(), parse_dates() และ encode_categories() เป็นฟังก์ชันแยกกัน ความละเอียดระดับนี้ทำให้ข้าม แทนที่ หรือจัดลำดับขั้นตอนใดขั้นตอนหนึ่งใหม่ได้ง่าย
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
)การเชื่อมขั้นตอนด้วย pipe()
เชื่อมฟังก์ชันที่มีความรับผิดชอบเดียวเข้าด้วยกันโดยใช้ pipe() เพื่อสร้างระยะแปลงข้อมูลทั้งหมดเป็นสายคำสั่งที่อ่านเข้าใจง่าย สายคำสั่งนี้อ่านคล้ายสูตรอาหาร แต่ละบรรทัดคือหนึ่งขั้นตอน และข้อมูลจะไหลจากบนลงล่างตามลูกศร คุณสามารถใส่หมายเหตุเพื่อปิดขั้นตอนใดขั้นตอนหนึ่งหรือจัดลำดับใหม่ได้โดยไม่ต้องเปลี่ยนชื่อตัวแปร ผลลัพธ์สุดท้ายคือ DataFrame ที่สะอาดและมีข้อมูลเพิ่มเติม พร้อมเข้าสู่ระยะโหลด
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)การคืนค่าจำนวนแถวเพื่อการตรวจสอบ
ฟังก์ชันการแปลงแต่ละฟังก์ชันควรบันทึกจำนวนแถวที่ได้รับและจำนวนแถวที่คืนค่าได้ตามต้องการ การห่อส่วนเนื้อหาของฟังก์ชันด้วยการบันทึกจำนวนแถวก่อนและหลัง เป็นประวัติการตรวจสอบแบบเรียบง่ายที่ช่วยให้เห็นได้อย่างรวดเร็วว่าแถวถูกลบออกที่ใดระหว่างการทำงานของสายงาน ให้เก็บจำนวนเหล่านี้ไว้ในรายการ แล้วพิมพ์เป็นรายงานเมื่อการทำงานแต่ละครั้งสิ้นสุดลง
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)ฟังก์ชันที่ทดสอบได้ด้วย DataFrame ขนาดเล็ก
ข้อได้เปรียบสำคัญที่สุดของฟังก์ชันที่มีชื่อคือการทดสอบได้ง่าย ให้เขียน DataFrame ขนาดเล็กสำหรับทดสอบ ซึ่งแทนกรณีขอบที่เกิดขึ้นได้จริง แล้วตรวจสอบผลลัพธ์ของฟังก์ชัน ทดสอบฟังก์ชัน drop_null_rows ด้วยแถวหนึ่งที่มีค่าว่างและตรวจสอบว่าแถวนั้นถูกลบ อีกแถวหนึ่งไม่มีค่าว่างและตรวจสอบว่ายังคงอยู่ การทดสอบแบบหน่วยกับฟังก์ชันการแปลงจะตรวจจับการถดถอยเมื่อโค้ดสายงานมีการเปลี่ยนแปลง
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()การจัดฟังก์ชันเป็นมอดูล
เมื่อสายงานมีขนาดใหญ่ขึ้น ให้แยกฟังก์ชันไว้ในไฟล์มอดูล Python ได้แก่ extract.py, transform.py, load.py และ validate.py สคริปต์หลัก pipeline.py จะนำเข้าและควบคุมการทำงานของมอดูลเหล่านี้ โครงสร้างไฟล์นี้อ่านเข้าใจง่าย ทดสอบด้วย pytest ได้ และนำไปติดตั้งเป็นแพ็กเกจ Python ได้ โดยมีรูปแบบสอดคล้องกับโครงสร้างมาตรฐานที่ทีมวิศวกรรมข้อมูลซึ่งใช้เครื่องมืออย่าง dbt หรือ 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)')การทำงานแบบไอดอมโพเทนต์: เรียกใช้ซ้ำได้อย่างปลอดภัย
ฟังก์ชันสายงานที่ออกแบบมาอย่างดีควรมีคุณสมบัติ ไอดอมโพเทนต์ กล่าวคือ เมื่อเรียกใช้สองครั้งกับข้อมูลขาเข้าเดียวกัน จะได้ผลลัพธ์เหมือนเดิมและไม่ก่อให้เกิดผลข้างเคียง หลีกเลี่ยงการเปลี่ยนแปลงข้อมูลในที่เดิม (df.drop(..., inplace=True)) และใช้ df.copy() ที่จุดเริ่มต้นของฟังก์ชันที่แก้ไขคอลัมน์เสมอ ฟังก์ชันแบบไอดอมโพเทนต์สามารถเรียกใช้ซ้ำหลังเกิดข้อผิดพลาดได้โดยไม่ทำให้ข้อมูลผลลัพธ์เสียหาย
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.')คำใบ้ชนิดข้อมูลเพื่อการอธิบายตัวเอง
เพิ่มคำใบ้ชนิดข้อมูลของ Python ให้กับลายเซ็นฟังก์ชันการแปลง เพื่อระบุข้อตกลงให้ชัดเจน: def transform(df: pd.DataFrame) -> pd.DataFrame คำใบ้ชนิดข้อมูลช่วยอธิบายตัวเอง — นักพัฒนาคนใดก็ตามที่อ่านฟังก์ชันจะทราบทันทีว่าฟังก์ชันต้องการอะไรและคืนค่าอะไร โดยไม่ต้องอ่านเนื้อหาภายใน นอกจากนี้ยังช่วยให้เครื่องมือวิเคราะห์แบบสถิติเช่น mypy และการเติมโค้ดอัตโนมัติในสภาพแวดล้อมพัฒนา ตรวจจับข้อผิดพลาดของชนิดข้อมูลก่อนการทำงานจริงได้
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.')การจัดทำเอกสารแต่ละขั้นตอนด้วย Docstring
ฟังก์ชันการแปลงแต่ละฟังก์ชันควรมี docstring หนึ่งบรรทัดที่ระบุว่าฟังก์ชันทำอะไร ต้องใช้คอลัมน์ใด และเพิ่มหรือลบคอลัมน์ใด Docstring ที่ดีทำให้ค้นพบฟังก์ชันได้ผ่าน help() และคำแนะนำเครื่องมือในสภาพแวดล้อมพัฒนา นอกจากนี้ยังทำหน้าที่เป็นเอกสารข้อกำหนดด้วย กล่าวคือ การตรวจสอบที่ล้มเหลวและขัดแย้งกับ docstring คือข้อผิดพลาด ส่วน docstring ที่ไม่ตรงกับโค้ดคือข้อผิดพลาดด้านเอกสาร
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)การเรียกใช้สายงานทั้งหมด
ควบคุมการทำงานของ ETL ทั้งหมดโดยเรียกใช้สามระยะตามลำดับ ได้แก่ extract, transform และ load เพิ่มการจับเวลาแต่ละระยะเพื่อวัดว่าใช้เวลาไปกับส่วนใด ดักจับข้อยกเว้นจากแต่ละระยะแยกกัน เพื่อให้ข้อความข้อผิดพลาดระบุได้ว่าระยะใดล้มเหลว บันทึกเวลาเริ่มต้นและสิ้นสุดของการทำงานทั้งหมด รวมถึงระบุว่าสำเร็จหรือล้มเหลวเพื่อใช้ในการติดตาม
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}')ตรวจสอบความเข้าใจอย่างรวดเร็ว
ทดสอบความเข้าใจแนวคิดด้านการวิเคราะห์ข้อมูลจากบทเรียนนี้
สรุปบทเรียน
ในบทเรียนนี้ คุณได้เรียนรู้เรื่อง การจัดโครงสร้างสายงานเป็นฟังก์ชัน ETL ที่มีความรับผิดชอบเดียว การทำให้ฟังก์ชันทดสอบได้ มีคุณสมบัติไอดอมโพเทนต์ และอธิบายตัวเองได้ด้วยคำใบ้ชนิดข้อมูลและ docstring และ การควบคุมการทำงานของสายงานทั้งหมดพร้อมการจับเวลาและบันทึกการตรวจสอบ บทถัดไป เราจะสำรวจการกำหนดพารามิเตอร์ให้สายงานด้วยพจนานุกรมการตั้งค่า เพื่อให้นำกลับมาใช้กับชุดข้อมูลต่าง ๆ ได้
คำถามที่พบบ่อย
บทเรียน “การจัดโครงสร้างขั้นตอนการแปลงเป็นฟังก์ชัน” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การจัดโครงสร้างขั้นตอนการแปลงเป็นฟังก์ชัน” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Pandas & NumPy Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Pandas & NumPy Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การจัดโครงสร้างขั้นตอนการแปลงเป็นฟังก์ชัน”
แยกสมุดบันทึกของคุณเป็นฟังก์ชันดึงข้อมูล แปลงข้อมูล และโหลดข้อมูล โดยแต่ละฟังก์ชันรับและส่งคืน DataFrame เพื่อให้ทดสอบได้ง่าย คุณปฏิบัติ Pandas & NumPy Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Pandas & NumPy Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Pandas & NumPy Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน
บทเรียน “การจัดโครงสร้างขั้นตอนการแปลงเป็นฟังก์ชัน” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Pandas & NumPy Academy นี้ได้ไหม
ได้ บทเรียน Pandas & NumPy Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การจัดโครงสร้างขั้นตอนการแปลงเป็นฟังก์ชัน
- การกำหนดพารามิเตอร์ให้กระบวนการด้วยพจนานุกรมการตั้งค่า
- การทดสอบขั้นตอนกระบวนการด้วยการยืนยันเงื่อนไข
- การจัดเวลาและบันทึกการทำงานของกระบวนการ