تحضير البيانات — تعلم الآلة
تحضير البيانات
"قمامة تدخل، قمامة تخرج". أفضل خوارزمية لن تنقذ بيانات سيئة. هذا الدرس يغطي الخطوات التي تسبق أي تدريب.
1. الاستكشاف أولاً
import pandas as pd
df = pd.DataFrame({
"age": [25, 32, None, 41, 29, 35],
"city": ["بغداد", "البصرة", "بغداد", "أربيل", None, "البصرة"],
"income": [700, 1200, 900, 1500, 800, 1100],
"purchased": [0, 1, 0, 1, 0, 1],
})
print(df.head())
print(df.isna().sum()) # كم قيمة ناقصة في كل عمود
print(df.describe()) # إحصائيات الأعمدة الرقمية
print(df["purchased"].value_counts()) # هل الفئات متوازنة؟ابحث عن: قيم ناقصة، قيم شاذة (عمر 250؟)، أعمدة نصية، وعدم توازن الفئات.
2. القيم الناقصة
| الطريقة | متى |
|---|---|
| حذف الصفوف dropna() | النقص قليل جداً وعشوائي |
| التعويض بالمتوسط/الوسيط | أعمدة رقمية (الوسيط أفضل مع القيم الشاذة) |
| التعويض بالأكثر تكراراً | أعمدة فئوية |
| عمود "كان ناقصاً" | عندما يكون النقص نفسه معلومة |
import pandas as pd
df = pd.DataFrame({"age": [25, None, 41], "city": ["بغداد", None, "أربيل"]})
df["age"] = df["age"].fillna(df["age"].median()) # أسند النتيجة صراحةً
df["city"] = df["city"].fillna("غير معروف")
print(df)⚠️ df["age"].fillna(..., inplace=True) لا يعدّل df في pandas الحديثة (Copy-on-Write) — أسند النتيجة دائماً كما أعلاه.
3. النصوص إلى أرقام
النماذج تفهم الأرقام فقط. لعمود مثل المدينة:
- One-Hot Encoding: عمود لكل مدينة بقيمة 0 أو 1. الصحيح للفئات بلا ترتيب.
- ترميز ترتيبي: صغير=0، متوسط=1، كبير=2 — فقط عندما يوجد ترتيب حقيقي.
import pandas as pd
df = pd.DataFrame({"city": ["بغداد", "البصرة", "أربيل", "بغداد"]})
print(pd.get_dummies(df, columns=["city"], dtype=int))⚠️ تحويل المدن إلى 0 و 1 و 2 (LabelEncoder) يوهم النموذج أن "أربيل > البصرة > بغداد" — علاقة غير موجودة. LabelEncoder مخصص للهدف y لا للميزات.
4. التقييس (Scaling)
الدخل بالآلاف والعمر بالعشرات: النماذج التي تعتمد على المسافات (KNN، SVM) أو على الانحدار التدريجي ستهتم بالدخل فقط لأن أرقامه أكبر.
| الأداة | الناتج |
|---|---|
| StandardScaler | متوسط 0 وانحراف معياري 1 (الأكثر استخداماً) |
| MinMaxScaler | بين 0 و 1 |
الأشجار (Decision Tree، Random Forest) لا تحتاج تقييساً.
5. التقسيم ومنع تسرب البيانات
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
import numpy as np
X = np.array([[25, 700], [32, 1200], [41, 1500], [29, 800], [35, 1100], [50, 2000], [23, 600], [38, 1300]])
y = np.array([0, 1, 1, 0, 1, 1, 0, 1])
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train) # fit على التدريب فقط
X_test_s = scaler.transform(X_test) # transform فقط على الاختبار
print(X_train_s.mean(axis=0).round(2))- قسّم أولاً، ثم تعلّم أي شيء (المتوسط، المقياس) من التدريب فقط. حساب المتوسط على كل البيانات ثم التقسيم يسرّب معلومات من الاختبار إلى التدريب، فتبدو النتائج أفضل مما ستكون في الواقع. هذا اسمه تسرب البيانات (data leakage)، وهو من أشهر أخطاء المبتدئين والمحترفين.
- random_state: يجعل التقسيم قابلاً للتكرار.
- stratify=y: يحافظ على نسبة الفئات نفسها في التدريب والاختبار.
في درس Pipelines ستتعلم طريقة تجعل منع التسرب تلقائياً.
تمرين
أضف للبيانات أعلاه عمود "المستوى التعليمي" (ثانوي، بكالوريوس، ماجستير). أي ترميز يناسبه؟ عوّض القيم الناقصة، وحوّل المدينة بـ One-Hot، وقسّم ثم قيّس بالترتيب الصحيح.
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# بيانات صغيرة مكتوبة هنا (في مشروع حقيقي: pd.read_csv("data.csv"))
df = pd.DataFrame({
"age": [25, 32, None, 41, 29, 35, 52, 23, 38, None, 45, 30],
"city": ["بغداد", "البصرة", "بغداد", "أربيل", None, "البصرة", "بغداد", "أربيل", "البصرة", "بغداد", "أربيل", "بغداد"],
"income": [700, 1200, 900, 1500, 800, 1100, 2100, 600, 1300, 950, 1700, 850],
"purchased": [0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0],
})
print("القيم الناقصة:\n", df.isna().sum(), "\n")
# 1) القيم الناقصة — نسند النتيجة (inplace على عمود لا يعمل في pandas الحديثة)
df["age"] = df["age"].fillna(df["age"].median())
df["city"] = df["city"].fillna("غير معروف")
# 2) One-Hot للمدينة (فئات بلا ترتيب)
df = pd.get_dummies(df, columns=["city"], dtype=int)
X = df.drop(columns="purchased")
y = df["purchased"]
# 3) التقسيم أولاً — مع الحفاظ على نسبة الفئات
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)
# 4) التقييس: fit على التدريب فقط
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
print("الأعمدة:", list(X.columns))
print(f"التدريب: {len(X_train)} عينة، الاختبار: {len(X_test)} عينة")
print("متوسط ميزات التدريب بعد التقييس:", X_train_s.mean(axis=0).round(2))اختبر فهمك