تنظيف البيانات باستخدام Pandas: تقنيات متقدمة

تنظيف البيانات باستخدام Pandas: تقنيات متقدمة

تنظيف البيانات Pandas خطوة أساسية قبل أي تحليل أو بناء نموذج تعلم آلي. مهما كانت البيانات قوية أو حجمها كبير، إذا لم تكن نظيفة ومنسقة فلن تحصل على نتائج موثوقة. في هذا المقال سنركز على تقنيات متقدمة لتنظيف البيانات باستخدام Pandas في بايثون، مع أمثلة عملية يمكن تطبيقها مباشرة في مشاريعك.

إذا كنت جديداً على مكتبة Pandas يمكنك أولاً الاطلاع على مقالنا Pandas في Python: أداة أساسية لتحليل البيانات والتعامل معها، ثم العودة لهذا الدليل المتقدم.

لماذا تنظيف البيانات مهم في مشاريع التحليل والذكاء الاصطناعي؟

أغلب الوقت في مشاريع تحليل البيانات لا يُصرف على النمذجة، بل على تجهيز وتنظيف البيانات. بعض الأسباب الرئيسية:

  • القيم المفقودة تؤدي إلى تحيز في النتائج أو أخطاء في التدريب.
  • الأنواع غير الصحيحة (مثل تواريخ محفوظة كنصوص) تمنع استخدام دوال قوية في Pandas.
  • القيم الشاذة يمكن أن تدمر أداء النماذج الحساسة مثل الانحدار الخطي.
  • التكرار والأخطاء النصية تعقّد التحليل وتزيد حجم البيانات بلا فائدة.
  • البيانات الكبيرة تحتاج لطرق تنظيف فعالة لتحسين الأداء واستهلاك الذاكرة.

فيما يلي سنمر على أهم تقنيات تنظيف البيانات Pandas بشكل متقدم مع نصائح لتحسين الأداء.

1. قراءة البيانات وتحضيرها بشكل صحيح

1.1 تحديد أنواع البيانات من البداية

أحد أهم خطوات تنظيف البيانات Pandas هي اختيار النوع المناسب للأعمدة عند القراءة. هذا يقلل استخدام الذاكرة ويمنع مشاكل لاحقة.


import pandas as pd

dtypes = {
    "user_id": "int32",
    "age": "float32",
    "gender": "category",
    "city": "category",
    "is_active": "bool"
}

df = pd.read_csv(
    "users.csv",
    dtype=dtypes,
    parse_dates=["created_at", "last_login"]
)
  • استخدم category للأعمدة النصية المتكررة مثل المدن، النوع (ذكر/أنثى)، الحالة…
  • استخدم parse_dates لتحويل الأعمدة إلى datetime مباشرة.
  • يمكنك تقليل حجم البيانات بشكل واضح باستخدام أنواع أصغر مثل int32 و float32.

1.2 التعامل مع الترميزات (Encoding) والقيم الخاصة

أحياناً تحتوي الملفات على ترميزات مختلفة أو رموز خاصة تمثل القيم المفقودة:


df = pd.read_csv(
    "data.csv",
    encoding="utf-8",
    na_values=["NA", "N/A", "-", "?", ""]
)

بهذا الشكل يتم تحويل تلك الرموز مباشرة إلى NaN، مما يسهل اكتشافها ومعالجتها لاحقاً.

2. التعامل المتقدم مع القيم المفقودة NaN

2.1 تحليل نمط القيم المفقودة

قبل البدء في التعويض أو الحذف، يجب فهم نمط فقدان البيانات:


# عدد القيم المفقودة في كل عمود
df.isna().sum()

# نسبة القيم المفقودة
(df.isna().sum() / len(df)).sort_values(ascending=False)

للبيانات الكبيرة يمكن أخذ عينة:


df.sample(10000, random_state=42).isna().mean()

2.2 حذف أو تعويض القيم المفقودة حسب السياق

هناك عدة استراتيجيات:

  • حذف الصفوف التي تحتوي على قيم مفقودة إذا كانت قليلة نسبياً:

df_clean = df.dropna(subset=["age", "salary"])
  • تعويض بالقيمة المتوسطة أو الوسيط للأعمدة العددية:

df["age"] = df["age"].fillna(df["age"].median())
df["salary"] = df["salary"].fillna(df["salary"].mean())
  • تعويض بالقيمة الشائعة للأعمدة الفئوية:

mode_city = df["city"].mode()[0]
df["city"] = df["city"].fillna(mode_city)

2.3 تعويض متقدم باستخدام groupby

تقنية قوية في تنظيف البيانات Pandas هي التعويض داخل كل مجموعة وليس على كل البيانات:


# تعويض القيم المفقودة في الراتب بمتوسط الراتب لكل وظيفة
df["salary"] = df.groupby("job_title")["salary"] \
                 .transform(lambda s: s.fillna(s.median()))

بهذا الشكل تكون التعويضات أكثر منطقية ودقة، خاصة في بيانات المستخدمين أو المنتجات.

2.4 تعويض متقدم للقيم الزمنية

في السلاسل الزمنية، غالباً نستخدم الاستيفاء (interpolate):


df = df.sort_values("timestamp")
df["value"] = df["value"].interpolate(method="time")

أو استخدام ffill و bfill:


df["status"] = df["status"].ffill().bfill()

3. تحويل الأنواع وتنظيف التواريخ والنصوص

3.1 تصحيح أنواع البيانات بعد القراءة

حتى بعد تحديد الأنواع قد نحتاج لتعديلات إضافية:


# تحويل نصوص رقمية إلى أرقام والتعامل مع القيم غير القابلة للتحويل
df["amount"] = pd.to_numeric(df["amount"], errors="coerce")

# ضمان أن حقل التاريخ من نوع datetime
df["order_date"] = pd.to_datetime(df["order_date"], errors="coerce", dayfirst=True)

errors="coerce" يحول القيم غير الصحيحة إلى NaT / NaN ليستمر تنظيفها لاحقاً.

3.2 تنظيف النصوص الموحدة (Normalizing Text)

البيانات النصية غالباً تأتي بمشاكل: مسافات زائدة، اختلاف في الحروف، حساسية حالة الأحرف، الخ.


# إزالة المسافات في البداية والنهاية
df["product_name"] = df["product_name"].str.strip()

# توحيد لحروف صغيرة
df["email"] = df["email"].str.lower()

# استبدال عدة مسافات بمسافة واحدة
df["comment"] = df["comment"].str.replace(r"\s+", " ", regex=True)

في العربية يمكن أيضاً توحيد أشكال الألف والياء:


def normalize_arabic(text):
    if pd.isna(text):
        return text
    text = text.replace("أ", "ا").replace("إ", "ا").replace("آ", "ا")
    text = text.replace("ى", "ي")
    return text

df["city"] = df["city"].apply(normalize_arabic)

4. اكتشاف وعلاج القيم الشاذة (Outliers)

4.1 استخدام الإحصاءات الوصفية لاكتشاف القيم الشاذة

أول خطوة: فهم التوزيع باستخدام describe:


df["salary"].describe()

للاكتشاف الآلي للقيم الشاذة يمكن استخدام قاعدة IQR:


Q1 = df["salary"].quantile(0.25)
Q3 = df["salary"].quantile(0.75)
IQR = Q3 - Q1

lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR

outliers = df[(df["salary"] < lower) | (df["salary"] > upper)]

4.2 معالجة القيم الشاذة

الخيارات الأساسية:

  • حذف الصفوف الشاذة إذا كانت قليلة وواضحة.
  • تقييد القيم (Capping) أي قصّ القيم إلى حدود معقولة:

df["salary"] = df["salary"].clip(lower=lower, upper=upper)
  • استخدام التحويلات اللوغاريتمية لتقليل تأثير القيم الكبيرة جداً:

import numpy as np
df["salary_log"] = np.log1p(df["salary"])  # log(1 + x)

اختيار الأسلوب يعتمد على سياق البيانات وطبيعة الاستخدام (تحليل وصفي، تعلم آلي، تقارير…).

5. إزالة التكرار وتوحيد القيم

5.1 كشف وإزالة الصفوف المكررة

الصفوف المكررة مشكلة شائعة في البيانات المجمّعة من مصادر مختلفة:


# جميع الصفوف المكررة بالكامل
duplicates = df[df.duplicated()]

# إزالة الصفوف المكررة مع الاحتفاظ بأول ظهور
df_unique = df.drop_duplicates()

# التكرار حسب أعمدة محددة مثل user_id
df_unique_users = df.drop_duplicates(subset=["user_id"])

5.2 توحيد القيم الفئوية المتقاربة

في الأعمدة النصية قد تظهر نفس القيمة بأشكال متعددة:


mapping = {
    "ksa": "Saudi Arabia",
    "saudi arabia": "Saudi Arabia",
    "sa": "Saudi Arabia",
    "المملكة العربية السعودية": "Saudi Arabia"
}
df["country"] = df["country"].str.lower().replace(mapping)

في البيانات العربية، يمكن استخدام التطبيع (normalization) وإزالة التشكيل لتقليل الاختلافات غير المهمة.

6. تحسين الأداء مع المجموعات الكبيرة من البيانات

6.1 استخدام أنواع بيانات مدمجة لتقليل الذاكرة

للتعامل مع بيانات ضخمة، استهلاك الذاكرة مهم جداً. بعض التقنيات:


# تحويل أعمدة نصية عالية التكرار إلى category
for col in ["city", "country", "job_title"]:
    df[col] = df[col].astype("category")

# تقليل حجم الأعداد الصحيحة
df["user_id"] = df["user_id"].astype("int32")
df["age"] = df["age"].astype("float32")

يمكنك قياس استهلاك الذاكرة:


df.info(memory_usage="deep")

6.2 القراءة على دفعات (Chunks) للملفات الكبيرة

إذا كان الملف أكبر من الذاكرة المتاحة، استخدم chunksize:


chunks = pd.read_csv("big_data.csv", chunksize=100_000)

cleaned_chunks = []
for chunk in chunks:
    # تطبيق خطوات تنظيف مختصرة على كل chunk
    chunk["age"] = chunk["age"].fillna(chunk["age"].median())
    chunk["amount"] = pd.to_numeric(chunk["amount"], errors="coerce")
    cleaned_chunks.append(chunk)

df_clean = pd.concat(cleaned_chunks, ignore_index=True)

يفضل الاقتصار على العمليات الضرورية داخل الحلقة لتقليل الوقت.

6.3 الاستفادة من العمليات المتجهة (Vectorized Operations)

تجنّب الحلقات for على مستوى الصفوف قدر الإمكان، واستخدم الدوال المتجهة:


# سيّئ: حلقة على الصفوف
for i, row in df.iterrows():
    df.loc[i, "full_name"] = row["first_name"] + " " + row["last_name"]

# أفضل: عملية متجهة
df["full_name"] = df["first_name"].str.cat(df["last_name"], sep=" ")

كلما كانت عملياتك متجهة (باستخدام str و apply على الأعمدة أو where و mask) كان الأداء أفضل.

7. تنظيف متقدم للبيانات الزمنية (Time Series)

البيانات الزمنية لها طبيعة خاصة وتتطلب تقنيات تنظيف مخصصة.

7.1 ضبط الفهرس الزمني والتكرار


df["timestamp"] = pd.to_datetime(df["timestamp"])
df = df.set_index("timestamp").sort_index()

# إعادة أخذ العينة كل ساعة مع حساب المتوسط
hourly = df.resample("H").mean()

يمكنك بعد ذلك:

  • ملء الفترات المفقودة بـ ffill أو interpolate.
  • اكتشاف الفترات التي لا تحتوي بيانات كافية.

7.2 التعامل مع المناطق الزمنية (Time Zones)


df["timestamp"] = pd.to_datetime(df["timestamp"], utc=True)
df["timestamp_local"] = df["timestamp"].dt.tz_convert("Asia/Riyadh")

التعامل الصحيح مع المناطق الزمنية جزء من تنظيف البيانات Pandas خاصة في أنظمة التسجيل (Logging) والمنصات العالمية.

8. بناء دالة أو بايبلاين لتنظيف البيانات

بدلاً من تكرار نفس خطوات التنظيف في كل مرة، من الأفضل بناء دالة تنظيف أو بايبلاين موحد:


def clean_users(df):
    df = df.copy()

    # أنواع البيانات
    df["user_id"] = df["user_id"].astype("int32")
    df["gender"] = df["gender"].astype("category")

    # القيم المفقودة
    df["age"] = df["age"].fillna(df["age"].median())
    df["city"] = df["city"].fillna("Unknown")

    # النصوص
    df["email"] = df["email"].str.strip().str.lower()

    # التكرار
    df = df.drop_duplicates(subset=["user_id"])

    return df

df_clean = clean_users(df_raw)

هذا الأسلوب يجعل عملية تنظيف البيانات Pandas قابلة لإعادة الاستخدام، أسهل في الاختبار، وأكثر وضوحاً عند العمل ضمن فريق.

خلاصة

تنظيف البيانات Pandas ليس مجرد حذف قيم مفقودة، بل عملية متكاملة تشمل:

  • اختيار أنواع البيانات المناسبة من البداية وتوفير الذاكرة.
  • تحليل أنماط القيم المفقودة وتعويضها بطرق منطقية (تعويض عالمي أو حسب المجموعة أو زمني).
  • تحويل الأنواع، تنظيف النصوص، وتوحيد القيم الفئوية.
  • اكتشاف القيم الشاذة والتعامل معها بطرق إحصائية.
  • إزالة التكرار وبناء بيانات موحدة وموثوقة.
  • تحسين الأداء مع مجموعات البيانات الكبيرة باستخدام chunks والعمليات المتجهة وأنواع البيانات المدمجة.

كلما كانت خطوة تنظيف البيانات Pandas منظمة وأكثر تقدماً، كانت المخرجات النهائية من تحليلك أو نموذجك أقرب للواقع وأدق في التنبؤ. إذا كنت مهتماً بمتابعة العمل على البيانات في بايثون يمكنك أيضاً قراءة أفضل مكاتب بايثون للتعامل مع البيانات لتوسيع أدواتك بجانب Pandas.

حول المحتوى:

طرق متقدمة لتنظيف وتجهيز البيانات باستخدام Pandas: التعامل مع القيم المفقودة، تحويل الأنواع، اكتشاف القيم الشاذة، وتحسين الأداء للمجموعات الكبيرة مع أمثلة عملية.

هل كان هذا مفيدًا لك؟

أضف تعليقك