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

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

تنظيف الأعمدة النصية

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

الدرس في لمحة

نظّف الأعمدة النصية باستخدام الـ accessor المسمّى ‎.str - احذف المسافات، ووحّد حالة الأحرف، واستبدل الحروف، وقسّم الأسماء، وابحث عن الأنماط - ثم تحقّق من النتيجة مقابل القيم التي تتوقّعها.

  1. 1الـ accessor المسمّى ‎.str
  2. 2المسافات غير المرئية
  3. 3الاستبدال والتقسيم والبحث
  4. 4أثبت أن العمود نظيف

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

اسأل الملف الفوضوي: إلى كم دولة تبيع Cartwheel؟ فيقول 22. الإجابة الحقيقية ثماني دول. والأربع عشرة الأخرى هي الدول نفسها مكتوبة بالشكل UNITED KINGDOM وunited kingdom وغيرهما. جمّع الإيراد بحسب الدولة الآن، فتتوزّع مبيعات المملكة المتحدة على ثلاثة صفوف، كلٌّ منها أصغر من حقيقته. تنظيف النصوص عمل غير لامع، لكنه الفرق بين رسم بياني صحيح وآخر خاطئ.

الـ accessor المسمّى ‎.str

للـ Series النصية خاصية .str تكشف methods النصوص في Python، وتطبّقها على كل القيم دفعة واحدة:

Python
import pandas as pd

messy = pd.read_csv("orders_messy.csv")
print(messy["country"].nunique(), "spellings")
print(messy["country"].value_counts().tail(6))

clean = messy["country"].str.strip().str.title()
print(clean.nunique(), "countries")
print(sorted(clean.unique()))

كل استدعاء لـ .str يعيد Series جديدة، فتسلسلها: احذف المسافات، ثم وحّد حالة الأحرف. تصبح الكتابات الـ 22 ثماني، وهذا يطابق الدول الثماني التي تبيع لها Cartwheel فعلًا. والقيم المفقودة تمرّ مفقودة بدلًا من أن تسبّب خطأً، وهذه هي الميزة الأساسية مقارنةً بكتابة حلقة تستدعي .strip() على كل قيمة بنفسك. وعدّ القيم المختلفة قبل التنظيف وبعده، كما يفعل أمرا الطباعة الأول والثالث، هو أسرع دليل على أن التنظيف فعل ما قصدته.

تعمل title() هنا لأن كل دول Cartwheel أسماء مكوّنة من كلمات عادية. لكنها ستُفسد قيمًا مثل USA (فتصبح Usa) أو McDonald (فتصبح Mcdonald). حين يكون في العمود قيم كهذه، استخدم str.lower() للمقارنة، أو قاموس تحويل مع map() كما في الدرس 2.5.

المسافات غير المرئية

ثلاثة عشر اسم عميل في الملف المُصدَّر فيها مسافات قبلها أو بعدها. تبدو متطابقة في الجدول، فالطريقة الوحيدة لرؤيتها هي القياس:

Python
import pandas as pd

messy = pd.read_csv("orders_messy.csv")
padded = messy["customer"] != messy["customer"].str.strip()
print(padded.sum(), "padded names")
print(messy.loc[padded, "customer"].head(3).map(repr).tolist())
messy["customer"] = messy["customer"].str.strip()

تعرض repr() النص مع علامات تنصيصه، فتجعل المسافات مرئية: ' Hassan Hughes '. الأسماء التي لم تُحذف مسافاتها تكسر بالضبط العمليات التي ستعتمد عليها لاحقًا: التجميع، والمطابقة مع جدول آخر، وحذف المكرّرات. احذف المسافات من كل عمود نصي ستجمّع أو تربط عليه، حتى لو بدا نظيفًا. وتحذف str.strip() أكثر من المسافات العادية: علامات الجدولة (tabs)، وفواصل الأسطر، والمسافات غير القابلة للكسر التي يحملها كثيرًا النص المنسوخ من صفحات الويب وملفات PDF، كلها مسافات بيضاء بالنسبة إلى Python، فيتعامل معها استدعاء واحد.

الاستبدال والتقسيم والبحث

يكتب الملف المُصدَّر القناة mobile app، بينما تستخدم جداول Cartwheel النظيفة mobile_app. مطابقة الأعراف الآن توفّر عليك ربطًا فاشلًا في القسم 4:

Python
import pandas as pd

messy = pd.read_csv("orders_messy.csv")
messy["channel"] = messy["channel"].str.replace(" ", "_")
print(messy["channel"].value_counts(dropna=False))

names = messy["customer"].str.strip().str.split(" ", expand=True)
names.columns = ["first_name", "last_name"]
print(names.head(3))

coffee = messy["product"].str.contains("coffee", case=False)
print(coffee.sum(), "coffee lines")

تعيد str.split(" ", expand=True) جدولًا (DataFrame) فيه عمود لكل جزء؛ وبدون expand تحصل على Series من القوائم، و.str[0] تختار الجزء الأول من كل منها. وتعيد str.contains() قناعًا منطقيًا جاهزًا للتصفية كما في الدرس 2.4، وcase=False تتجاهل حالة الأحرف.

نص عادي أم نمط؟

تستطيع str.replace وstr.contains أيضًا أن تأخذا تعابير نمطية (regular expressions)، وهي لغة مصغّرة لأنماط النصوص. في pandas 2 تعامل str.replace الوسيط الأول كنص عادي ما لم تمرّر regex=True، وتعامله str.contains كنمط ما لم تمرّر regex=False. ويوقعك هذا الفرق في المشكلات مع الرموز التي لها معنى خاص في الأنماط، مثل $ و. و(. ويعرض الدرس 3.3 حالة $، التي تمسّ كل عمود مبالغ مالية ستنظّفه في حياتك.

أثبت أن العمود نظيف

كود التنظيف الذي نجح مع ملف هذا الشهر قد يقابل كتابة جديدة الشهر القادم. اختم بفحص يحدّد معنى “نظيف”:

Python
import pandas as pd

EXPECTED = {"Egypt", "United Arab Emirates", "Saudi Arabia", "Jordan",
            "United Kingdom", "Germany", "United States", "Canada"}

messy = pd.read_csv("orders_messy.csv")
messy["country"] = messy["country"].str.strip().str.title()
unexpected = set(messy["country"]) - EXPECTED
assert not unexpected, f"Unknown countries: {unexpected}"
print("country column OK:", messy["country"].nunique(), "values")

لا تفعل assert condition, message شيئًا حين يكون الشرط صحيحًا، وتوقف الـ script برسالتك حين يكون خاطئًا. طرح المجموعات يسرد أيّ قيمة منظّفة ليست في القائمة المتوقّعة، وتضعها الـ f-string في رسالة الخطأ لتعرف بالضبط ما الذي تصلحه.

في التمرين تنظّف الأعمدة النصية الثلاثة في الملف المُصدَّر. وبعدها تأتي المجاميع: أرقام محبوسة داخل نصوص مثل $1,234.50.

أهم النقاط

  • الـ accessor المسمّى .str يطبّق method نصية على كل قيمة في العمود، وتبقى القيم المفقودة مفقودة بدلًا من أن تسبّب أخطاء.
  • str.strip() وحالة أحرف موحّدة (str.title() أو str.lower()) تصلح معظم الكتابات المختلفة؛ تحقّق منها بـ nunique() قبل وبعد.
  • str.replace(old, new) تعامل old كنص عادي افتراضيًا في pandas 2؛ مرّر regex=True فقط حين تقصد نمطًا.
  • str.split(" ", expand=True) تحوّل عمودًا نصيًا واحدًا إلى عدة أعمدة، وstr.contains() تبني قناعًا منطقيًا من نمط.
  • اختم كل تنظيف نصي بفحص مقابل قائمة القيم التي تتوقّعها، كي تفشل كتابة جديدة الشهر القادم بصوت عالٍ.

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

تحقّق من فهمك

3 أسئلة

السؤال 1 من 3

في messy["country"] 22 قيمة مختلفة لثماني دول حقيقية. أيّ سطر هو الإصلاح الذي يجب أن تكتبه؟

السؤال 2 من 3

لماذا يسبّب messy.product.str.contains("Coffee") الخطأ AttributeError: 'function' object has no attribute 'str'؟

السؤال 3 من 3

نظّفت عمود الدولة ثم شغّلت assert set(clean) <= set(EXPECTED). ما فائدة هذا الـ assertion؟

تدريب

تمرين Python

نظّف الأعمدة النصية في الملف المُصدَّر

في messy: احذف المسافات من customer؛ واحذف المسافات من country وحوّله إلى حالة العنوان (title case)؛ وفي channel، استبدل المسافة بشرطة سفلية (mobile app ← mobile_app) واملأ القيم المفقودة بـ "unknown". أضِف عمودًا first_name يحمل الكلمة الأولى من اسم العميل المنظّف. وأخيرًا اجعل desk_lines عدد الصفوف التي يحتوي فيها product على “desk” بأيّ حالة أحرف.

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

import pandas as pd

messy = pd.read_csv("orders_messy.csv")

# 1. customer: remove leading/trailing spaces

# 2. country: remove spaces and use title case ("UNITED KINGDOM" -> "United Kingdom")

# 3. channel: "mobile app" -> "mobile_app", and missing -> "unknown"

# 4. New column first_name: the first word of the cleaned customer name

# 5. Number of rows whose product name mentions "desk" (any capitalisation)
desk_lines = None
جارٍ تحميل المحرّر…

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

(لا مخرجات)

الفحوص

  • أسماء العملاء ليست فيها مسافات في البداية أو النهاية
  • country يحمل الدول الحقيقية الثماني بالضبط
  • channel يستخدم web وmobile_app وmarketplace وunknown
  • first_name يحمل كلمة واحدة لكل صف، أولها Daniel
  • desk_lines يساوي 15