قراءة ملف CSV وفحص البيانات بعد قراءتها
قراءة الملفات باستخدام read_csv وفحصها مباشرة عبر shape و head و info و describe — لأن الملف المقروء بشكل غير سليم يعطي نتائج خاطئة دون إظهار أي أخطاء.
- 1المشكلة
- 2الفهم
- 3أمثلة محلولة
- 4التوقع
- 5التطبيق
- 6التحدي
المشكلة التي نقوم بحلها
حتى الآن، كنا ندخل كل قيمة في الجدول يدوياً داخل الكود. قد يكون هذا مقبولاً لثلاثة صفوف فقط.
لكن البيانات الحقيقية لا تعيش داخل الكود البرمجي؛ بل توجد داخل ملفات — يرسلها إليك شخص عبر البريد الإلكتروني، أو تُصدّر من نظام داخلي، أو تُحفظ من جدول بيانات إلكتروني. وغالباً ما يحتوي هذا الملف على اثني عشر ألف صف، وليس اثني عشر فقط.
يبدو هذا الملف في العادة كنص بسيط: يضم السطر الأول أسماء الأعمدة، يليه صف واحد في كل سطر مفصولاً بفواصل:
product,quantity,price
pen,12,15.0
notebook,5,60.0هذا هو ملف CSV — أي القيم المفصولة بفواصل (Comma-Separated Values). وتحويله إلى جدول يتطلب سطراً واحداً فقط في بانداس، وهو السطر الأكثر استخداماً على الإطلاق في هذه المكتبة.
لكن هناك عادة بالغة الأهمية يجب بناؤها فور كتابة هذا السطر، وهي الموضوع الحقيقي لهذا الفصل: بمجرد قراءة الملف، افحصه فوراً. كم عدد الصفوف التي وصلت؟ هل أسماء الأعمدة مطابقة لما توقعته؟ هل دخلت الأعمدة الرقمية كأرقام فعلية؟ إهمال هذه الثواني الثلاثين يعني ظهور الأخطاء لاحقاً في النتائج دون أن يُصدر البرنامج أي رسالة تحذير أو خطأ.
بنهاية هذا الفصل ستكون قادراً على
- قراءة ملف باستخدام
pd.read_csv()والتعامل مع مشكلات مسار الملف - فحص الملف سريعاً باستخدام
head()وtail()وshapeوcolumns - قراءة مخرجات
info()لمعرفة عدد الصفوف والخلايا الفارغة ونوع كل عمود في آن واحد - تلخيص الأعمدة الرقمية إحصائياً باستخدام
describe() - تمييز وإصلاح خطأ
FileNotFoundErrorومشاكل اختلاف الفواصل (separators)
المتطلبات المسبقة: Series و DataFrame — المفهومان الأساسيان في بانداس.
أنشئ الملف أولاً
داخل مجلد مشروعك، أنشئ ملفاً باسم sales.csv يحتوي بدقة على هذه الأسطر الستة:
product,quantity,price
pen,12,15.0
notebook,5,60.0
bag,2,850.0
bottle,7,120.0
eraser,30,8.0السطر الأول ليس بيانات فعلية، بل هو أسماء الأعمدة — الترويسة (header). والأسطر الخمسة التالية هي خمسة صفوف من البيانات.
قراءة الملف في سطر واحد
import pandas as pd
sales = pd.read_csv("sales.csv")
print(sales)product quantity price
0 pen 12 15.0
1 notebook 5 60.0
2 bag 2 850.0
3 bottle 7 120.0
4 eraser 30 8.0أنجز هذا السطر الواحد ثلاثة أشياء في آن واحد، وبهدوء تام:
أولاً، اعتبر السطر الأول كترويسة للجدول — فأصبحت product و quantity و price أسماء أعمدة وليست صفاً من البيانات.
ثانياً، أنشأ الفهرس تلقائياً، وهو الأرقام من 0 إلى 4 على الجانب الأيسر، والتي لم تكن موجودة في الملف الأصلي.
ثالثاً، وهو الأهم، خمّن نوع البيانات لكل عمود. كل شيء في الملف هو في الأصل نص عادي — فالعدد 12 هو في حقيقته حرفان. نظر بانداس إلى كل عمود وقرر ما إذا كان يمثل أرقاماً أم نصوصاً.
غالباً ما يكون هذا التخمين صحيحاً. ولكن عندما يخطئ، لا يُطلق البرنامج أي خطأ برمجي — وهذا هو السبب الدقيق الذي يوجب عليك فحصه بنفسك دائماً.
أربعة أشياء يجب فحصها فور قراءة الملف
shape — كم عدد البيانات التي وصلت
print(sales.shape)(5, 3)خمسة صفوف وثلاثة أعمدة. يحتوي الملف على ستة أسطر والجدول على خمسة صفوف، لأن الترويسة لا تُحتسب كصف بيانات. إذا كان هذا الرقم أقل بكثير مما توقعته، أو زائداً بمقدار واحد بالضبط، فهذه الإشارة الأولى إلى أن الملف قُرئ بطريقة غير سليمة.
head() و tail() — البداية والنهاية
print(sales.head(3))product quantity price
0 pen 12 15.0
1 notebook 5 60.0
2 bag 2 850.0طباعة جميع الصفوف الاثني عشر ألفاً لملف حقيقي ستملأ شاشة الطرفية ولن تفيدك بشيء. تعرض head() الصفوف الأولى — خمسة صفوف إذا لم تحدد عدداً. بينما تعرض tail() الشيء نفسه لنهاية الجدول، وفحص النهاية مفيد بشكل خاص لأن الملفات المصدرة غالباً ما تحتوي على أسطر فارغة شاردة أو صفوف إجماليات في أسفلها.
info() — كل شيء في لمحة واحدة
sales.info()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5 entries, 0 to 4
Data columns (total 3 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 product 5 non-null object
1 quantity 5 non-null int64
2 price 5 non-null float64
dtypes: float64(1), int64(1), object(1)
memory usage: 252.0+ bytesلاحظ أننا كتبنا sales.info() وليس print(sales.info()) — فهذه الدالة تتولى الطباعة بنفسها ولا تُعيد قيمة، ووضعها داخل print يضيف كلمة None زائدة في النهاية.
هذا هو الأمر الأكثر فائدة بعد قراءة أي ملف، لأنه يجمع بين معلومتين جوهريتين:
Non-Null Count — عدد الخلايا الممتلئة فعلياً في كل عمود. تظهر هنا 5 non-null في الأعمدة الثلاثة، مما يعني عدم وجود أي خلية فارغة. أما إذا ظهرت 4 non-null في أي عمود، فهذا يعني وجود خلية فارغة واحدة، مما سيؤثر على حساباتك اللاحقة.
Dtype — نوع البيانات الذي استقر عليه تخمين بانداس. حيث تم تمييز quantity كـ int64، و price كـ float64، و product كـ object. وهو ما نريده تماماً.
(قد يختلف رقم memory usage في السطر الأخير على جهازك، وهو أمر غير مهم حالياً.)
describe() — ملخص إحصائي للأعمدة الرقمية
print(sales.describe())quantity price
count 5.00000 5.000000
mean 11.20000 210.600000
std 11.12205 360.217712
min 2.00000 8.000000
25% 5.00000 15.000000
50% 7.00000 60.000000
75% 12.00000 120.000000
max 30.00000 850.000000تقوم هذه الدالة بانتقاء الأعمدة الرقمية وإجراء ثماني عمليات حسابية إحصائية على كل منها. عمود product غائب هنا لأنه نصي.
هناك أمر يلفت الانتباه هنا: المتوسط الحسابي mean لعمود price هو 210.6، في حين أن الوسيط 50% هو 60 فقط. وجود فجوة بهذا الحجم يعني وجود قيمة واحدة أعلى بكثير من بقية القيم، وتكشف خانة max عن هذه القيمة: 850. هكذا تُقرأ مخرجات describe() — عندما يتباعد المتوسط عن الوسيط، ابحث عن القيم الشاذة والمتطرفة هناك.
مثال تطبيقي متكامل
report.py:
import pandas as pd
sales = pd.read_csv("sales.csv")
print("Rows, columns:", sales.shape)
print()
print(sales.head(3))
print()
print("Columns :", list(sales.columns))
print("Total qty :", sales["quantity"].sum())
print("Avg price :", round(sales["price"].mean(), 2))Rows, columns: (5, 3)
product quantity price
0 pen 12 15.0
1 notebook 5 60.0
2 bag 2 850.0
Columns : ['product', 'quantity', 'price']
Total qty : 56
Avg price : 210.6برنامج صغير، ولكن هيكله جدير بالتأمل — اقرأ الملف، ثم افحص وتأكد، ثم احسب. الدالتان shape و head() ليستا مجرد عمليات حسابية، بل هما أسئلة استكشافية: هل قرأت الملف الصحيح؟ هل عدد الصفوف مطابق لما ينبغي؟
وهناك سبب وجيه لطباعة list(sales.columns)؛ فعند طباعتها كقائمة، يظهر كل اسم محاطاً بعلامات تنصيص، مما يكشف فوراً أي مسافة زائدة مثل 'quantity '. لو طُبعت بأي شكل آخر لبقيت تلك المسافة خفية وعادت لاحقاً لتسبب خطأ KeyError.
الأخطاء الشائعة وحلولها
خطأ FileNotFoundError: [Errno 2] No such file or directory: 'sales.csv' يبحث بايثون في المجلد الحالي ولم يعثر على الملف هناك. هذه هي نفس المشكلة التي واجهناها في دورة بايثون: الطرفية مفتوحة في مكان والملف موجود في مكان آخر. شغّل pwd لمعرفة مسارك الحالي، و dir للتأكد من وجود الملف، ثم استخدم cd للانتقال إليه. في ويندوز، قد يكون الاسم الفعلي للملف هو sales.csv.txt — فعّل إظهار امتدادات أسماء الملفات في File Explorer.
وصول الملف بأكمله في عمود واحد فقط الفاصل المستخدم ليس الفاصلة العادية. الملفات المحفوظة في العديد من الدول الأوروبية تستخدم الفاصلة المنقوطة (;)، لأن الفاصلة تُستخدم هناك كعلامة عشرية. اكتب pd.read_csv("sales.csv", sep=";"). وبالنسبة للملفات المفصولة بعلامة التبويب (Tab)، استخدم sep="\t".
تحول عمود رقمي إلى نوع object تسربت نصوص إلى العمود في مكان ما — مثل N/A، أو شرطة -، أو خلية فارغة، أو فاصلة الآلاف (1,200). شغّل df["col"].unique() لمعرفة القيم الفعلية بداخل العمود؛ فالقيمة الغريبة تبرز عادة بوضوح. الحل هو pd.to_numeric(df["col"], errors="coerce")، الذي يحول أي قيمة غير رقمية إلى قيمة فارغة — وسنتناول هذا بالتفصيل في فصل البيانات المفقودة.
خطأ UnicodeDecodeError الملف غير محفوظ بترميز UTF-8، وهو ما يحدث بانتظام مع الملفات المصدرة من برامج الجداول الإلكترونية. جرّب تمرير pd.read_csv("sales.csv", encoding="utf-8-sig")، وإذا لم ينجح فاستخدم encoding="latin-1".
تحول أول صف من البيانات إلى أسماء أعمدة على الرغم من أن الملف لا يحتوي على ترويسة اكتب pd.read_csv("sales.csv", header=None) وستصبح الأعمدة مرقمة تلقائياً 0, 1, 2. ولتسميتها بأسمائك الخاصة، أضف المعامل names=["product", "quantity", "price"].
طباعة كلمة None زائدة بعد مخرجات sales.info() السبب هو كتابتها داخل print(sales.info()). دالة info() تطبع المخرجات بنفسها ولا تعيد شيئاً، لذلك تقوم print بطباعة القيمة None التي أعادتها الدالة. اكتب ببساطة sales.info().
Step 4 of 6 — Predict
Check your understanding
يحتوي الملف على ستة أسطر. ما الذي يُرجعه shape؟
from io import StringIO
import pandas as pd
# Stands in for sales.csv, so this snippet runs on its own.
RAW = """product,quantity,price
pen,12,15.0
notebook,5,60.0
bag,2,850.0
bottle,7,120.0
eraser,30,8.0
"""
sales = pd.read_csv(StringIO(RAW))
print(sales.shape)- A(5, 3)
- B(6, 3)
- C(3, 5)
- D(5, 4)
ما الذي يخبرك به عمود Non-Null Count في مخرجات info()؟
- Aعدد الخلايا المملوءة فعلياً في كل عمود — وأي رقم يقل عن إجمالي عدد الصفوف يعني وجود قيم فارغة
- Bعدد القيم المميزة والفريدة في كل عمود
- Cعدد الخلايا الفارغة في كل عمود
- Dحجم الذاكرة التي يستهلكها كل عمود
الملف مفصول بفواصل منقوطة، ولكن لم يتم تمرير sep. ما الذي يُطبع؟
from io import StringIO
import pandas as pd
# Stands in for sales.csv, so this snippet runs on its own.
RAW = """product;quantity
pen;12
notebook;5
"""
df = pd.read_csv(StringIO(RAW))
print(df.shape)
print(list(df.columns))- A(2, 1) ['product;quantity']
- B(2, 2) ['product', 'quantity']
- C(3, 1) ['product;quantity']
- Dخطأ `ParserError`
Answering needs an account
Sign in to check your answers
The questions are above, and working them out in your head is the part that matters. Sign in to see the answers, the explanations and the three-level hints.
دورك الآن
أنشئ ملفاً باسم students.csv يحتوي على ثمانية طلاب على الأقل موزعين على ثلاثة أعمدة — الاسم، والعمر، والدرجة.
ثم اكتب ملفاً باسم report.py يقوم بما يلي:
- قراءة الملف
- طباعة
shapeوlist(columns) - طباعة
head(3)وtail(2) - تشغيل
info()وتوضيح ما إذا كانت أنواع الأعمدة الثلاثة مطابقة لتوقعاتك - طباعة
describe()، وبشكل منفصل طباعة المتوسط وأعلى قيمة لعمود الدرجات
بعد ذلك، تسبّب في حدوث الخطأ عمداً — ثلاث مرات، مع ملاحظة ما يحدث في كل مرة:
- ضع
N/Aفي إحدى خلايا عمود الدرجات. ماذا يظهر في خانةDtypeداخل مخرجاتinfo()الآن؟ وماذا حدث للمتوسط الحسابي؟ - استبدل كل فاصلة في الملف بفاصلة منقوطة (
;). ماذا يظهر فيshapeالآن، ولماذا؟ - احذف السطر الأول بالكامل، أي الترويسة. كم صفاً وصل إلى الجدول، وما هي أسماء الأعمدة الآن؟
هذه الحالات الثلاث تحدث باستمرار في بيئات العمل الحقيقية، ولكل منها عَرَض واضح ومميز. مواجهتها عمداً اليوم يمنحك القدرة على تشخيصها وحلها بمجرد رؤيتها في المرات القادمة.
Step 6 of 6
التحدي — the chapter quiz
عشرة أسئلة متدرجة من السهل إلى الصعب. الأسئلة الأخيرة صعبة عن قصد.
Sign in to take the quiz