حول المحتوى:
طرق متقدمة لتنظيف وتجهيز البيانات باستخدام Pandas: التعامل مع القيم المفقودة، تحويل الأنواع، اكتشاف القيم الشاذة، وتحسين الأداء للمجموعات الكبيرة مع أمثلة عملية.
تنظيف البيانات Pandas خطوة أساسية قبل أي تحليل أو بناء نموذج تعلم آلي. مهما كانت البيانات قوية أو حجمها كبير، إذا لم تكن نظيفة ومنسقة فلن تحصل على نتائج موثوقة. في هذا المقال سنركز على تقنيات متقدمة لتنظيف البيانات باستخدام Pandas في بايثون، مع أمثلة عملية يمكن تطبيقها مباشرة في مشاريعك.
إذا كنت جديداً على مكتبة Pandas يمكنك أولاً الاطلاع على مقالنا Pandas في Python: أداة أساسية لتحليل البيانات والتعامل معها، ثم العودة لهذا الدليل المتقدم.
أغلب الوقت في مشاريع تحليل البيانات لا يُصرف على النمذجة، بل على تجهيز وتنظيف البيانات. بعض الأسباب الرئيسية:
فيما يلي سنمر على أهم تقنيات تنظيف البيانات Pandas بشكل متقدم مع نصائح لتحسين الأداء.
أحد أهم خطوات تنظيف البيانات 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"]
)
datetime مباشرة.int32 و float32.أحياناً تحتوي الملفات على ترميزات مختلفة أو رموز خاصة تمثل القيم المفقودة:
df = pd.read_csv(
"data.csv",
encoding="utf-8",
na_values=["NA", "N/A", "-", "?", ""]
)
بهذا الشكل يتم تحويل تلك الرموز مباشرة إلى NaN، مما يسهل اكتشافها ومعالجتها لاحقاً.
قبل البدء في التعويض أو الحذف، يجب فهم نمط فقدان البيانات:
# عدد القيم المفقودة في كل عمود
df.isna().sum()
# نسبة القيم المفقودة
(df.isna().sum() / len(df)).sort_values(ascending=False)
للبيانات الكبيرة يمكن أخذ عينة:
df.sample(10000, random_state=42).isna().mean()
هناك عدة استراتيجيات:
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)
تقنية قوية في تنظيف البيانات Pandas هي التعويض داخل كل مجموعة وليس على كل البيانات:
# تعويض القيم المفقودة في الراتب بمتوسط الراتب لكل وظيفة
df["salary"] = df.groupby("job_title")["salary"] \
.transform(lambda s: s.fillna(s.median()))
بهذا الشكل تكون التعويضات أكثر منطقية ودقة، خاصة في بيانات المستخدمين أو المنتجات.
في السلاسل الزمنية، غالباً نستخدم الاستيفاء (interpolate):
df = df.sort_values("timestamp")
df["value"] = df["value"].interpolate(method="time")
أو استخدام ffill و bfill:
df["status"] = df["status"].ffill().bfill()
حتى بعد تحديد الأنواع قد نحتاج لتعديلات إضافية:
# تحويل نصوص رقمية إلى أرقام والتعامل مع القيم غير القابلة للتحويل
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 ليستمر تنظيفها لاحقاً.
البيانات النصية غالباً تأتي بمشاكل: مسافات زائدة، اختلاف في الحروف، حساسية حالة الأحرف، الخ.
# إزالة المسافات في البداية والنهاية
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)
أول خطوة: فهم التوزيع باستخدام 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)]
الخيارات الأساسية:
df["salary"] = df["salary"].clip(lower=lower, upper=upper)
import numpy as np
df["salary_log"] = np.log1p(df["salary"]) # log(1 + x)
اختيار الأسلوب يعتمد على سياق البيانات وطبيعة الاستخدام (تحليل وصفي، تعلم آلي، تقارير…).
الصفوف المكررة مشكلة شائعة في البيانات المجمّعة من مصادر مختلفة:
# جميع الصفوف المكررة بالكامل
duplicates = df[df.duplicated()]
# إزالة الصفوف المكررة مع الاحتفاظ بأول ظهور
df_unique = df.drop_duplicates()
# التكرار حسب أعمدة محددة مثل user_id
df_unique_users = df.drop_duplicates(subset=["user_id"])
في الأعمدة النصية قد تظهر نفس القيمة بأشكال متعددة:
mapping = {
"ksa": "Saudi Arabia",
"saudi arabia": "Saudi Arabia",
"sa": "Saudi Arabia",
"المملكة العربية السعودية": "Saudi Arabia"
}
df["country"] = df["country"].str.lower().replace(mapping)
في البيانات العربية، يمكن استخدام التطبيع (normalization) وإزالة التشكيل لتقليل الاختلافات غير المهمة.
للتعامل مع بيانات ضخمة، استهلاك الذاكرة مهم جداً. بعض التقنيات:
# تحويل أعمدة نصية عالية التكرار إلى 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")
إذا كان الملف أكبر من الذاكرة المتاحة، استخدم 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)
يفضل الاقتصار على العمليات الضرورية داخل الحلقة لتقليل الوقت.
تجنّب الحلقات 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) كان الأداء أفضل.
البيانات الزمنية لها طبيعة خاصة وتتطلب تقنيات تنظيف مخصصة.
df["timestamp"] = pd.to_datetime(df["timestamp"])
df = df.set_index("timestamp").sort_index()
# إعادة أخذ العينة كل ساعة مع حساب المتوسط
hourly = df.resample("H").mean()
يمكنك بعد ذلك:
ffill أو interpolate.
df["timestamp"] = pd.to_datetime(df["timestamp"], utc=True)
df["timestamp_local"] = df["timestamp"].dt.tz_convert("Asia/Riyadh")
التعامل الصحيح مع المناطق الزمنية جزء من تنظيف البيانات Pandas خاصة في أنظمة التسجيل (Logging) والمنصات العالمية.
بدلاً من تكرار نفس خطوات التنظيف في كل مرة، من الأفضل بناء دالة تنظيف أو بايبلاين موحد:
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 ليس مجرد حذف قيم مفقودة، بل عملية متكاملة تشمل:
كلما كانت خطوة تنظيف البيانات Pandas منظمة وأكثر تقدماً، كانت المخرجات النهائية من تحليلك أو نموذجك أقرب للواقع وأدق في التنبؤ. إذا كنت مهتماً بمتابعة العمل على البيانات في بايثون يمكنك أيضاً قراءة أفضل مكاتب بايثون للتعامل مع البيانات لتوسيع أدواتك بجانب Pandas.
طرق متقدمة لتنظيف وتجهيز البيانات باستخدام Pandas: التعامل مع القيم المفقودة، تحويل الأنواع، اكتشاف القيم الشاذة، وتحسين الأداء للمجموعات الكبيرة مع أمثلة عملية.
مساحة اعلانية