EduFlow
Python لتحليل البيانات
⁦0%⁩

الوحدة 3 · تنظيف البيانات الحقيقية

تحليل التواريخ الفوضوية

20 دقراءة 4 دتمرين Python

الدرس في لمحة

حلّل عمود تواريخ يخلط بين صيغة ISO وصيغة اليوم أولًا وصيغة الشهر أولًا باستخدام صيغ to_datetime صريحة، وضع علامة على القيم الملتبسة، وتحقّق من النتيجة مقابل مصدر موثوق.

  1. 1ترك pandas تخمّن
  2. 2حلّل كل صيغة صراحةً
  3. 3ضع علامة على الالتباس
  4. 4تحقّق مقابل مصدر الحقيقة
  5. 5ما يعطيك إياه عمود تواريخ حقيقي

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

يكتب الملف الفوضوي التواريخ بثلاث طرق: 2024-03-05 و05/03/2024 و03/05/2024. الأولى لا لبس فيها. والأخريان اليوم نفسه مكتوبًا بجدول بيانات أوروبي وآخر أمريكي، والمشكلة أن 03/05/2024 قد تكون أيضًا الطريقة الأوروبية لكتابة 3 مايو. التواريخ هي العمود الذي تتباعد فيه عبارة “تم التحليل دون أخطاء” عن عبارة “النتيجة صحيحة” أكثر من أي عمود آخر.

ترك pandas تخمّن

Python
import pandas as pd

messy = pd.read_csv("orders_messy.csv")
print(messy["order_date"].sample(8, random_state=1).tolist())
try:
    pd.to_datetime(messy["order_date"])
except ValueError as error:
    print(str(error).splitlines()[0])

تستنتج pandas 2 صيغة واحدة من القيمة الأولى، 2024-01-02، وتطبّقها على العمود كله. الصف 6، 08/04/2024، لا يناسبها، فيتوقّف التحويل. هذه الصرامة ميزة: الإصدارات الأقدم كانت تخمّن صفًا بصف، وقد تخلط بين الأيام والأشهر بصمت.

حلّل كل صيغة صراحةً

أنت تعرف أيّ الصيغ موجودة، فقل ذلك. رموز الصيغة تصف الترتيب: %Y سنة من أربعة أرقام، و%m شهر من رقمين، و%d يوم من رقمين. حلّل العمود مرة لكل صيغة مع errors="coerce"، التي تحوّل الصفوف غير المطابقة إلى NaT (“ليس وقتًا” في pandas)، ثم املأ الفجوات بحسب ترتيب الأفضلية:

Python
import pandas as pd

messy = pd.read_csv("orders_messy.csv")
raw = messy["order_date"]
iso = pd.to_datetime(raw, format="%Y-%m-%d", errors="coerce")
dmy = pd.to_datetime(raw, format="%d/%m/%Y", errors="coerce")
mdy = pd.to_datetime(raw, format="%m/%d/%Y", errors="coerce")
print(iso.notna().sum(), "ISO |", dmy.notna().sum(), "fit day-first |", mdy.notna().sum(), "fit month-first")

parsed = iso.fillna(dmy).fillna(mdy)
print(parsed.isna().sum(), "unparsed")
print(parsed.min(), parsed.max())

حُلّل كل صف، والنطاق صحيح: من يناير 2024 إلى ديسمبر 2025. يأتي اليوم أولًا قبل الشهر أولًا في السلسلة لأن معظم دول Cartwheel تكتب التواريخ بهذه الطريقة. وتستطيع pandas فعل الشيء نفسه في استدعاء واحد بـ pd.to_datetime(raw, format="mixed", dayfirst=True)، التي تحلّل كل قيمة وحدها؛ النسخة الصريحة أطول، لكنها تبيّن لك بالضبط أيّ قاعدة حسمت كل صف.

لكن انظر إلى الأعداد. 57 قيمة تناسب اليوم أولًا و44 تناسب الشهر أولًا، من 68 قيمة فقط فيها شرطات مائلة. إذن 33 منها تناسب الاثنتين.

ثلاثة صناديق. 25/10/2025 لا يمكن أن تكون إلا اليوم أولًا لأنه لا يوجد شهر 25. و 10/25/2025 لا يمكن أن تكون إلا الشهر أولًا. أما 05/08/2025 فتناسب القراءتين، 5 أغسطس أو 8 مايو، فهي ملتبسة وتحتاج معلومات خارجية. 25/10/2025 لا يوجد شهر 25 اليوم أولًا فقط 10/25/2025 لا يوجد شهر 25 الشهر أولًا فقط 05/08/2025 5 أغسطس أم 8 مايو؟ ملتبس أداة التحليل تستطيع حسم هذه وحدها بيانات خارجية تحسم
ثلاثة أنواع من التواريخ ذات الشرطات المائلة

ضع علامة على الالتباس

القيمة التي تُحلَّل بالطريقتين إلى تاريخين مختلفين ملتبسة. ثلاث من القيم الـ 33 تواريخ مثل 05/05/2025، حيث تتّفق القراءتان، فهي غير مؤذية:

Python
import pandas as pd

messy = pd.read_csv("orders_messy.csv")
raw = messy["order_date"]
dmy = pd.to_datetime(raw, format="%d/%m/%Y", errors="coerce")
mdy = pd.to_datetime(raw, format="%m/%d/%Y", errors="coerce")
ambiguous = dmy.notna() & mdy.notna() & (dmy != mdy)
print(ambiguous.sum(), "ambiguous dates")
print(raw[ambiguous].head(6).tolist())

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

تحقّق مقابل مصدر الحقيقة

جدول orders النظيف لدى Cartwheel هو نظام السجلات الأساسي: كل معرّف طلب في الملف المُصدَّر يظهر هناك أيضًا مع وقته الحقيقي. البحث بمعرّف الطلب يتيح لك اختبار الافتراض مباشرة. تحوّل set_index("order_id") التواريخ إلى Series مسمّاة بالمعرّف، وتبحث map() عن كل صف من الملف المُصدَّر فيها:

Python
import pandas as pd

messy = pd.read_csv("orders_messy.csv")
raw = messy["order_date"]
iso = pd.to_datetime(raw, format="%Y-%m-%d", errors="coerce")
dmy = pd.to_datetime(raw, format="%d/%m/%Y", errors="coerce")
mdy = pd.to_datetime(raw, format="%m/%d/%Y", errors="coerce")
parsed = iso.fillna(dmy).fillna(mdy)

orders = pd.read_csv("orders.csv", parse_dates=["order_date"])
true_dates = orders.set_index("order_id")["order_date"].dt.normalize()
truth = messy["order_id"].map(true_dates)

wrong = parsed != truth
print(wrong.sum(), "dates disagree with the order system")
print(messy.loc[wrong, ["order_id", "order_date"]].assign(parsed=parsed[wrong].dt.date, real=truth[wrong].dt.date))

تُسقط dt.normalize() الوقت من اليوم كي تكون المقارنة تاريخًا بتاريخ. خمسة تواريخ خاطئة، وكل واحد منها قيمة ملتبسة كانت فعلًا بصيغة الشهر أولًا: 02/08/2025 هو 8 فبراير، لا 2 أغسطس. كان افتراض اليوم أولًا صحيحًا في 25 من 30 صفًا ملتبسًا، وهذا يبدو جيدًا حتى تدرك أنه ينقل خمسة طلبات إلى الشهر الخطأ، وبعضها إلى الربع الخطأ.

الأوقات والمناطق الزمنية

الملف المُصدَّر يحمل تواريخ فقط، لكن جدول orders فيه طوابع زمنية كاملة مثل 2025-12-30 21:19:03، ولا يذكر أيٌّ منها منطقته الزمنية. تسجّل Cartwheel كل شيء بساعة متجر واحدة، فتستطيع معاملتها كأوقات محلية والمضيّ قُدمًا. أما حين يحتوي ملف على فروق توقيت مثل +04:00، فمرّر utc=True إلى pd.to_datetime كي تستقر كل القيم على ساعة مشتركة واحدة قبل أن تقارنها أو تجمّعها؛ ترك المناطق الزمنية مختلطة هو ما يجعل طلبين قُدّما في اللحظة نفسها في دبي ولندن ينتهيان في تاريخين مختلفين، أو حتى في سنتين مختلفتين حول منتصف ليل 31 ديسمبر.

ما يعطيك إياه عمود تواريخ حقيقي

حين يصبح العمود datetime64، يكشف الـ accessor المسمّى .dt أجزاءه:

Python
import pandas as pd

orders = pd.read_csv("orders.csv", parse_dates=["order_date"])
d = orders["order_date"]
print(d.dt.year.value_counts().sort_index())
print(d.dt.day_name().value_counts().head(3))
print(d.dt.to_period("M").value_counts().sort_index().tail(3))

السنة، والشهر، واسم يوم الأسبوع، والشهر التقويمي كفترة (period): هذه هي المفاتيح التي ستجمّع بحسبها في القسم 4، حيث يشرح الدرس 4.4 السلاسل الزمنية (time series) بالتفصيل.

في التمرين تحلّل تواريخ الملف المُصدَّر وتضع عليها العلامات وتصلحها. ثم تبقى مشكلة واحدة قبل أن يصبح الملف نظيفًا: صفوف تظهر مرتين.

أهم النقاط

  • بدون format= تخمّن pandas 2 الصيغة من القيمة الأولى، وتفشل مع أيّ صف مكتوب بشكل مختلف.
  • حلّل كل صيغة معروفة صراحةً بـ pd.to_datetime(col, format=..., errors="coerce")، ثم ادمج النتائج بـ fillna.
  • تاريخ مثل 05/08/2025 يُحلَّل وفق قاعدتي اليوم أولًا والشهر أولًا كلتيهما؛ ولا تستطيع أيّ أداة تحليل أن تخبرك أيّهما الصحيح.
  • ضع علامة على القيم الملتبسة وحلّها من مصدر موثوق (نظام السجلات الأساسي، أو عمود آخر، أو الشخص الذي أعدّ الملف)، لا بالافتراض وحده أبدًا.
  • حين يصبح العمود datetime64، يعطيك الـ accessor المسمّى .dt كلًّا من year وmonth وday_name() وto_period() للتجميع.

قراءات إضافية

تحقّق من فهمك

3 أسئلة

السؤال 1 من 3

يُطلق pd.to_datetime(messy["order_date"]) الخطأ ValueError: time data "08/04/2024" doesn't match format "%Y-%m-%d". لماذا؟

السؤال 2 من 3

أيّ هذه القيم ملتبس، أي أن قراءتي اليوم أولًا والشهر أولًا تعطيان تاريخين صالحين لكن مختلفين؟

السؤال 3 من 3

تفترض أداة التحليل لديك أن اليوم أولًا في التواريخ الملتبسة. كيف تتحقّق من صحة هذا الافتراض؟

تدريب

تمرين Python

حلّل التواريخ وضع العلامات وأصلحها

باستخدام raw = messy["order_date"]: ابنِ parsed بتحليل صيغة ISO (%Y-%m-%d)، ثم اليوم أولًا (%d/%m/%Y)، ثم الشهر أولًا (%m/%d/%Y)، ودمجها بهذا الترتيب. أضِف عمودًا منطقيًا is_ambiguous يساوي True حيث ينجح تحليلا اليوم أولًا والشهر أولًا كلاهما ويعطيان تاريخين مختلفين. اجعل truth التاريخ الحقيقي لكل صف من orders، بالبحث بـ order_id ومع حذف الوقت، وn_wrong عدد تواريخ parsed التي تختلف عنه. وأخيرًا، اجعل messy["order_date"] مساويًا لـ parsed، إلا في الصفوف الملتبسة، حيث يأخذ القيمة من truth.

ملفات البيانات: orders_messy.csv, orders.csv

import pandas as pd

messy = pd.read_csv("orders_messy.csv")
orders = pd.read_csv("orders.csv", parse_dates=["order_date"])
raw = messy["order_date"]

# 1. Parse raw three ways (ISO, day-first, month-first) and combine: ISO, then day-first, then month-first
parsed = None

# 2. Column is_ambiguous: True where raw parses both day-first and month-first to DIFFERENT dates

# 3. The real date of each export row, looked up by order_id in orders (date only, no time)
truth = None

# 4. How many parsed dates disagree with truth
n_wrong = None

# 5. messy["order_date"]: parsed dates, but use truth for the ambiguous rows
جارٍ تحميل المحرّر…

Ctrl/⌘ + Enter للتشغيل · يعمل في بيئة معزولة داخل متصفّحك. لا شيء يغادر جهازك.

(لا مخرجات)

الفحوص

  • parsed هي Series من التواريخ ليس فيها قيم غير محلَّلة
  • is_ambiguous تضع علامة على 30 صفًا
  • n_wrong يساوي 5
  • تاريخ الطلب 2401 هو 8 فبراير 2025، لا 2 أغسطس
  • كل تاريخ مُصلَح يطابق نظام الطلبات