معالجة البيانات المكررة والمشوهة (Duplicates & Outliers) باستخدام بايثون
لماذا تمثل البيانات المكررة والمشوهة تحدياً حقيقياً؟
في مشاريع Data Science وBig Data، لا تأتي المشكلة غالباً من نقص البيانات، بل من رداءة جودتها. التكرار يضخم الإحصاءات، والقيم الشاذة قد تضلل النماذج أو تشوه مؤشرات الأداء. لذلك تبدأ أي عملية تحليل جادة بفحص بنية البيانات قبل بناء Pipeline موثوق.
إذا كنت قد قرأت سابقاً تنظيف البيانات (Data Cleaning): اكتشاف ومعالجة القيم المفقودة (Missing Values)، فستلاحظ أن معالجة التكرار والقيم الشاذة هي الامتداد الطبيعي لنفس المنهجية: فهم السلوك الطبيعي للبيانات ثم عزل الانحرافات غير الموثوقة.
التمييز بين التكرار والقيم الشاذة
التكرار Duplicate
البيانات المكررة هي سجلات متطابقة كلياً أو جزئياً تتكرر داخل الجدول نتيجة أخطاء إدخال، دمج مصادر متعددة، أو تكرار في ETL. الخطر هنا أن التكرار قد يرفع عدد العملاء، المبيعات، أو الأحداث بطريقة مضللة.
القيم الشاذة Outliers
القيمة الشاذة ليست بالضرورة خطأ. أحياناً تمثل حدثاً نادراً ذا قيمة تحليلية عالية، مثل عملية شراء ضخمة أو ذروة استخدام غير معتادة. المهم هو تحديد ما إذا كانت القيمة تمثل ظاهرة حقيقية أم ضجيجاً ناتجاً عن خلل في القياس أو الإدخال.
المنهج العملي في بايثون
أفضل ممارسة هي السير وفق سلسلة واضحة: استكشاف، اكتشاف، قرار، ثم توثيق. هذه السلسلة تمنع الحذف العشوائي وتدعم قابلية التتبع في البيئات المؤسسية.
- توحيد الأنواع والحقول قبل أي فحص.
- قياس التكرار على مستوى الصف أو المفتاح التجاري.
- تحليل التوزيع لاكتشاف القيم الشاذة.
- اختيار المعالجة: حذف، قص، استبدال، أو وسم.
- التحقق النهائي من أثر التنظيف على الإحصاءات.
مثال عملي باستخدام Pandas وNumPy
import pandas as pd
import numpy as np
df = pd.read_csv("sales.csv")
# 1) إزالة السجلات المكررة
df = df.drop_duplicates()
# 2) اكتشاف التكرار على مستوى عميل + تاريخ + منتج
duplicate_mask = df.duplicated(subset=["customer_id", "order_date", "product_id"], keep=False)
duplicate_rows = df.loc[duplicate_mask]
# 3) حساب القيم الشاذة بطريقة IQR
numeric_col = "order_value"
q1 = df[numeric_col].quantile(0.25)
q3 = df[numeric_col].quantile(0.75)
iqr = q3 - q1
lower_bound = q1 - 1.5 * iqr
upper_bound = q3 + 1.5 * iqr
outlier_mask = (df[numeric_col] < lower_bound) | (df[numeric_col] > upper_bound)
outliers = df.loc[outlier_mask]
# 4) معالجة القيم الشاذة عبر القص
df[numeric_col] = df[numeric_col].clip(lower_bound, upper_bound)
print("Duplicates:", len(duplicate_rows))
print("Outliers:", len(outliers))
متى نستخدم الحذف ومتى نستخدم القص؟
الحذف مناسب عندما تكون السجلات المكررة غير مفيدة وتؤثر مباشرة في التحليل. أما القص أو Winsorization فيناسب الحالات التي نرغب فيها بالاحتفاظ بالسجل مع تقليل تأثير الطرف الشاذ على المتوسط والانحراف المعياري.
في التحليلات المالية والتشغيلية، حذف القيم الشاذة دون مراجعة قد يخفي أحداثاً مهمة. لذلك يفضل أحياناً وسم السجل بدلاً من حذفه، ثم استخدامه في النمذجة كإشارة خاصة أو Feature إضافية.
عند العمل في بيئات
Data Architectureواسعة النطاق، اجعل فحص التكرار جزءاً مبكراً من خط الإدخال وليس خطوة لاحقة في التحليل. هذا يقلل الكلفة ويمنع تضخم التخزين ويثبت جودة النتائج عبر الطبقات التحليلية.
كشف القيم الشاذة بإحصاء متين
يعتمد الكثير من المحللين على الانحراف المعياري، لكنه حساس للقيم المتطرفة نفسها. لهذا يفضل في حالات عديدة استخدام الوسيط وIQR أو درجة Z-score مع الانتباه إلى شكل التوزيع.
- إذا كان التوزيع شبه طبيعي، فـ
Z-scoreخيار مناسب. - إذا كان التوزيع منحرفاً، فـ
IQRأكثر استقراراً. - في البيانات الزمنية، افحص الشذوذ داخل النافذة الزمنية نفسها.
- في البيانات متعددة الفئات، طبّق الفحص داخل كل شريحة على حدة.
معالجة متقدمة في Spark
عندما تكبر البيانات، تصبح المعالجة المتجهة داخل Pandas محدودة. هنا تظهر قيمة PySpark في توزيع الحمل عبر العقد، مع الحفاظ على منطق التنظيف نفسه.
from pyspark.sql import SparkSession
from pyspark.sql.functions import col, row_number
from pyspark.sql.window import Window
spark = SparkSession.builder.appName("clean_duplicates_outliers").getOrCreate()
df = spark.read.option("header", True).csv("sales.csv", inferSchema=True)
# إزالة التكرار
df_clean = df.dropDuplicates()
# اكتشاف التكرار المنطقي
window_spec = Window.partitionBy("customer_id", "order_date", "product_id").orderBy(col("order_value").desc())
df_ranked = df.withColumn("rn", row_number().over(window_spec))
df_unique = df_ranked.filter(col("rn") == 1).drop("rn")
# حدود IQR تقريبية عبر جمع الإحصاءات
quantiles = df.approxQuantile("order_value", [0.25, 0.75], 0.01)
q1, q3 = quantiles[0], quantiles[1]
iqr = q3 - q1
lower_bound = q1 - 1.5 * iqr
upper_bound = q3 + 1.5 * iqr
df_bounded = df_unique.withColumn(
"order_value",
when(col("order_value") < lower_bound, lower_bound)
.when(col("order_value") > upper_bound, upper_bound)
.otherwise(col("order_value"))
)
في خطوط
ETLالكبيرة، لا تكتفِ بتنظيف الملف النهائي. الأفضل بناء طبقة تحقق مبكر داخلPipelineتضع قيوداً على المفاتيح، وتحتفظ بسجل تدقيق، وتفصل السجلات المشكوك فيها في منطقة حجر بيانات مخصصة.
أفضل الممارسات المهنية
من منظور الجودة، الهدف ليس إزالة كل شذوذ، بل بناء قرار قابل للتفسير. لذلك وثّق القاعدة المستخدمة، واحتفظ بعدّادات قبل وبعد التنظيف، وراجع أثر المعالجة على التوزيع والمتوسط والوسيط.
كما أن توحيد المنهج بين فرق التحليل والـ Machine Learning يضمن أن التدريب والإنتاج لا يختلفان في فهم السجل نفسه. هذا مهم جداً لتجنب انحراف الأداء بين بيئة التجربة وبيئة التشغيل.
خلاصة عملية
البيانات المكررة والمشوهة ليست مجرد مشكلة تنسيق، بل قضية تؤثر في القرار والتحليل والتنبؤ. باستخدام Python وPandas وSpark يمكنك بناء طبقة تنظيف دقيقة تتعامل مع التكرار والقيم الشاذة بطريقة متوازنة، قابلة للتوسع، وملائمة لمتطلبات الجودة الحديثة.
ومع ربط هذه الممارسة بما تعلمته في مكتبة Pandas (2): استكشاف هيكل البيانات وفهم DataFrame و Series، تصبح معالجة الجودة خطوة منهجية وليست إجراءً طارئاً، وهذا هو الفرق بين تحليل سريع وتحليل احترافي.