الوحدة 3 · تنظيف البيانات الحقيقية
تحويل المبالغ النصية إلى أرقام
16 دقراءة 4 دتمرين Python
الدرس في لمحة
حوّل المبالغ المالية المخزّنة كنص، مثل “$1,234.50”، إلى عمود رقمي بحذف الرموز، والتحويل بـ astype أو to_numeric، ثم فحص كل قيمة فشل تحويلها.
- 1اكتشفها: النوع object في عمود أرقام
- 2أصلحها: احذف الرموز ثم حوّل
- 3فخ علامة الدولار
- 4حين لا تتحوّل بعض القيم
- 5اجعلها دالة
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
اسأل الملف الفوضوي عن إجمالي مبيعاته، فتحصل على شيء غريب. لا خطأ، ولا رقم: نص طويل يبدأ بـ '$65.0018.0033.00...'. يبدو عمود total رقميًا في جدول البيانات، لكن نحو نصف قيمه يبدأ بعلامة دولار، فخزّنت pandas العمود كله كنص، وجمع النصوص يعني لصقها. وصول المبالغ المالية كنصوص من أشيع المشكلات في البيانات المُصدَّرة، وتحتاج إصلاحًا مقصودًا ومفحوصًا.
اكتشفها: النوع object في عمود أرقام
import pandas as pd
messy = pd.read_csv("orders_messy.csv")
print(messy["total"].dtype)
print(messy["total"].head(3).tolist())
print(repr(messy["total"].sum()[:40]))
print(messy["total"].str.startswith("$").sum(), "values start with $")يلتقط روتين الفحص من الدرس 2.2 هذا فورًا: كانت info() ستعرض total كـ object حيث توقّعت float64. كلما كان عمود يُفترض أن يحمل أرقامًا من النوع object، فهناك قيمة واحدة على الأقل فيها شيء ليس رقمًا. هنا، 76 قيمة من 168 تبدأ بـ $.
أصلحها: احذف الرموز ثم حوّل
أزِل كل حرف ليس جزءًا من الرقم، ثم حوّل النص النظيف:
import pandas as pd
messy = pd.read_csv("orders_messy.csv")
total = (
messy["total"]
.str.strip()
.str.replace("$", "", regex=False)
.str.replace(",", "", regex=False)
.astype(float)
)
print(total.dtype, round(total.sum(), 2))
print(total.describe().round(2))تغليف السلسلة بقوسين يتيح لك وضع خطوة في كل سطر، وهذا أسهل في القراءة وفي تعطيل خطوة بتعليق أثناء تتبّع الأخطاء. وفاصل الآلاف يُزال أيضًا: هذا الملف المُصدَّر تصادف أنه لا يحتوي مجاميع فوق 1,000 دولار، لكن الملف التالي سيحتوي، و"1,234.50" ستكسر astype(float).
describe() هي فحص السلامة. تتراوح المجاميع بين 18 و942.90 دولارًا بوسيط 72، وهذا يطابق كتالوج Cartwheel: أرخص منتج حبل قفز بـ 18 دولارًا، وأكبر بند مكتبان واقفان. أما حد أدنى قدره 0 أو حد أقصى قدره 94,290 فسيخبرك بأن فاصلة عشرية ضاعت.
مجموع 23,500.67 ليس رقم إيراد بعد: الملف المُصدَّر ما زال يحتوي تسعة صفوف مكرّرة. ستزيلها في الدرس 3.5.
فخ علامة الدولار
تستطيع str.replace أيضًا العمل مع التعابير النمطية (regular expressions)، وهي لغة أنماط لبعض الحروف فيها معانٍ خاصة. و$ واحدة منها: تعني “نهاية النص”. انظر ماذا يحدث حين تطلب نمطًا بالخطأ:
import pandas as pd
messy = pd.read_csv("orders_messy.csv")
as_pattern = messy["total"].str.replace("$", "", regex=True)
print(as_pattern.head(1).tolist()) # still '$65.00'
try:
pd.to_numeric(as_pattern)
except ValueError as error:
print("ValueError:", error)طابق النمط $ الموضع الفارغ في نهاية كل نص واستبدله بلا شيء، فنجت كل علامات الدولار. في pandas 2 تكون القيمة الافتراضية لـ str.replace هي regex=False، لكن الدروس التعليمية القديمة وإصدارات pandas الأقدم كانت تفترض الأنماط، وستقابل كودًا مكتوبًا بالطريقتين. كتابة regex=False صراحةً توضّح القصد لكل قارئ ولكل إصدار. وإذا أردت نمطًا فعلًا، فاهرب من الحرف الخاص (escape) أو استخدم فئة حروف: str.replace(r"[$,]", "", regex=True) تزيل الرمزين في خطوة واحدة.
حين لا تتحوّل بعض القيم
astype(float) كل شيء أو لا شيء: قيمة سيئة واحدة ويفشل التحويل كله. أما pd.to_numeric مع errors="coerce" فتحوّل ما تستطيع وتحوّل البقية إلى NaN. هذا مفيد وخطير بالقدر نفسه، لذا يأتي دائمًا مع فحص لاحق:
import pandas as pd
raw = pd.Series(["$1,234.50", "18.00", "N/A", "€89.00", " $65.00 "])
cleaned = raw.str.strip().str.replace("$", "", regex=False).str.replace(",", "", regex=False)
amount = pd.to_numeric(cleaned, errors="coerce")
print(amount)
failed = amount.isna() & raw.notna()
print("Could not convert:", raw[failed].tolist())يسرد الفحص ما فشل بالضبط: N/A، وهي قيمة مفقودة فعلًا، و€89.00، وهي عملية بيع حقيقية بعملة لم تتوقّعها أداة التنظيف. وهاتان تحتاجان معاملة مختلفة. الأولى تبقى مفقودة؛ والثانية تحتاج قاعدة تحويل، أو على الأقل حديثًا مع من أنتج الملف. الإجبار على التحويل دون نظر كان سيُسقط بهدوء 89 يورو من المبيعات من كل مجموع.
اجعلها دالة
ستنظّف أعمدة مبالغ مالية مرة أخرى، في هذه الدورة وفي عملك. ضع المنطق حيث يمكن إعادة استخدامه واختباره:
import pandas as pd
def parse_money(values):
"""Convert text like ' $1,234.50' to floats; unparseable values become NaN."""
cleaned = (
values.astype("string")
.str.strip()
.str.replace("$", "", regex=False)
.str.replace(",", "", regex=False)
)
return pd.to_numeric(cleaned, errors="coerce")
messy = pd.read_csv("orders_messy.csv")
messy["total"] = parse_money(messy["total"])
print(messy["total"].dtype, messy["total"].isna().sum(), "unparsed")تجعل astype("string") في البداية الدالة آمنة مع الأعمدة التي قرأتها pandas أصلًا كأرقام (حين يتصادف ألّا يحتوي ملف على $ إطلاقًا)، لأن methods الـ .str لا تعمل إلا على النصوص. ولأن المُدخل عمود من النوع string، تعيد to_numeric النوع Float64 القابل للقيم المفقودة الذي رأيته في الدرس 3.1، مع <NA> حيث فشل التحويل. وتعيد الدالة Series جديدة، ويقرّر المستدعي أين يخزّنها، وهو المبدأ نفسه في الإعادة بدلًا من الطباعة من الدرس 1.5.
في التمرين تبني أداة التحويل وتطبّقها على الملف المُصدَّر. وبعدها يأتي أصعب عمود في الملف: تواريخ مكتوبة بثلاث طرق مختلفة.
أهم النقاط
- العمود الذي يبدو رقميًا ونوعه
objectهو نص، وsum()عليه تلصق النصوص ببعضها بدلًا من جمع الأرقام. - أزِل رموز العملة وفواصل الآلاف بـ
str.replace(..., regex=False)، ثم حوّل بـastype(float)أوpd.to_numeric. - في التعابير النمطية تعني
$“نهاية النص”، فلا تزيلstr.replace("quot;, "", regex=True)شيئًا. - تحوّل
pd.to_numeric(errors="coerce")القيم التي لا يمكن تحليلها إلىNaN؛ فاسرد دائمًا القيم التي فشلت قبل أن تتابع. - غلّف التنظيف في دالة، وافحص سلامة النتيجة بـ
describe()والحد الأدنى والحد الأقصى.