Series و DataFrame — المفهومان الأساسيان في بانداس
ما هو العمود وما هو الجدول، ولماذا يجب أن تتشارك كل قيم العمود نوعاً واحداً. مع توضيح الفرق الجوهري بين df["col"] و df[["col"]].
- 1المشكلة
- 2الفهم
- 3أمثلة محلولة
- 4التوقع
- 5التطبيق
- 6التحدي
المشكلة التي نقوم بحلها
في الفصل السابق كتبنا سطراً واحداً، وقد أدى مهمته بنجاح:
print(sales["quantity"].sum())19قراءة هذا السطر سهلة ومباشرة، ولكن هناك سؤالاً مهماً يختبئ في داخله: ما الذي أعاده التعبير البرمجي sales["quantity"] تحديداً؟ هل أعاد قائمة (list)؟ القوائم العادية في بايثون لا تحتوي على دالة .sum() — فكتابة [12, 5, 2].sum() في بايثون تتسبب في حدوث خطأ فوري.
من الضروري جداً معرفة الإجابة، لأنك في مكتبة بانداس تتعامل دائماً مع واحد من شيئين رئيسيين، ومعظم حالات اللبس تبدأ من هذا السؤال بعينه: ما الذي أحمله بين يديّ الآن تحديداً؟ هل هو عمود واحد أم جدول كامل؟
يتناول هذا الفصل هذين الشيئين: تسمياتهما، وأبعادهما، والفرق الجوهري بينهما.
بنهاية هذا الفصل ستكون قادراً على
- التمييز بجملة واحدة بين
SeriesوDataFrame - إنشاء كائن
Seriesوفحص فهرسه وقيمه ونوعه بشكل مستقل - شرح سبب إلزام جميع القيم داخل العمود الواحد بنفس النوع
- استخدام
df["col"]وdf[["col"]]مع إدراك الفرق بينهما - قراءة خطأ
KeyErrorوتحديد سبب المشكلة بدقة في أسماء الأعمدة
المتطلبات المسبقة: تثبيت بانداس وجدولك الأول.
Series — عمود واحد
لنبدأ بالوحدة الأصغر. كائن Series هو سلسلة متتالية من القيم، ترتبط كل قيمة منها باسم محدد:
import pandas as pd
quantity = pd.Series([12, 5, 2])
print(quantity)0 12
1 5
2 2
dtype: int64لقد مررنا ثلاث قيم، وحصلنا في المخرجات على ثلاثة أزواج. الأرقام 0 و 1 و 2 على اليسار تمثل الفهرس (index) — وهو بمثابة تسمية أو معرّف لكل قيمة. أنت لم تمرر هذه الأرقام؛ بل قام بانداس بالعد تلقائياً بدءاً من الصفر.
وفي الأسفل يظهر dtype: int64 — وهو نوع البيانات (data type) للسلسلة بأكملها. نوع واحد مشترك، وليس نوعاً مستقلاً لكل قيمة. هذا التوحيد هو ما يمنح بانداس سرعته الفائقة، وهو في الوقت نفسه قيده الأساسي.
يمكن فحص هذه الأجزاء الثلاثة بشكل منفصل:
import pandas as pd
quantity = pd.Series([12, 5, 2])
print(quantity.index)
print(quantity.values)
print(quantity.dtype)RangeIndex(start=0, stop=3, step=1)
[12 5 2]
int64يعني RangeIndex أن الفهرس هو مجرد عد تسلسلي — يبدأ من الصفر، ويزداد بمقدار واحد. وتمثل values القيم المجردة بدون أسمائها، بينما تمثل dtype نوع البيانات للعمود بأكمله.
ولا يشترط أن يكون الفهرس أرقاماً فقط:
import pandas as pd
quantity = pd.Series([12, 5, 2], index=["pen", "notebook", "bag"])
print(quantity)
print(quantity["notebook"])pen 12
notebook 5
bag 2
dtype: int64
5أصبحت الصفوف الآن مسماة بأسماء المنتجات، ويمكنك جلب أي قيمة بالاعتماد على ذلك الاسم. يشبه هذا سلوك القواميس (dictionaries) في بايثون، وهذا الشبه حقيقي بالفعل — لكن مع فارق جوهري هائل:
import pandas as pd
quantity = pd.Series([12, 5, 2], index=["pen", "notebook", "bag"])
print(quantity * 2)pen 24
notebook 10
bag 4
dtype: int64تمت مضاعفة العمود بأكمله في خطوة واحدة، ودون استخدام أي حلقات تكرارية (loops). لفعل ذلك مع القاموس العادي، يتعين عليك المرور على كل عنصر بنفسك. عند تطبيق أي عملية على كائن Series، فإن بانداس يطبقها على كل قيمة بداخله فوراً — وكل الفصول القادمة تعتمد على هذا السلوك المحوري.
DataFrame — عدة أعمدة بفهرس مشترك
كائن DataFrame هو عبارة عن عدة كائنات Series مصفوفة جنباً إلى جنب وتتشارك فهرساً واحداً.
import pandas as pd
sales = pd.DataFrame({
"product": ["pen", "notebook", "bag"],
"quantity": [12, 5, 2],
"price": [15.0, 60.0, 850.0],
})
print(sales)
print(sales.shape)
print(sales.columns)product quantity price
0 pen 12 15.0
1 notebook 5 60.0
2 bag 2 850.0
(3, 3)
Index(['product', 'quantity', 'price'], dtype='object')عبارة "تتشارك فهرساً واحداً" هي جوهر المسألة. فالقيم المقابلة للمنتج والكمية والسعر عند الفهرس 0 كلها تعود إلى نفس الشيء، وهو القلم الأول. الفهرس هو الرابط الذي يجمع الأعمدة الثلاثة في صفوف مترابطة.
جدول واحد وأنواع متعددة — ولكن عمود واحد بنوع واحد
import pandas as pd
sales = pd.DataFrame({
"product": ["pen", "notebook", "bag"],
"quantity": [12, 5, 2],
"price": [15.0, 60.0, 850.0],
})
print(sales.dtypes)product object
quantity int64
price float64
dtype: objectثلاثة أعمدة، بثلاثة أنواع مختلفة. int64 للأعداد الصحيحة، و float64 للأعداد العشرية، و object — والذي يعني عادةً النصوص.
لاحظ غياب النوع str. أنواع البيانات في بانداس مأخوذة من مكتبة NumPy، حيث تشغل كل قيمة حجماً ثابتاً من الذاكرة — وهو أمر ممكن مع الأرقام، لكنه مستحيل مع النصوص المتفاوتة في أطوالها. لذلك، للاحتفاظ بالنصوص، يخزن بانداس مؤشرات لعناصر بايثون، ويُطلق على هذا الترتيب اسم object.
والآن إلى القاعدة الذهبية: كل قيمة داخل العمود الواحد تتشارك نوعاً واحداً. فما الذي يحدث إذا خلطنا الأرقام والنصوص معاً في عمود واحد؟
import pandas as pd
mixed = pd.Series([1, "two", 3.0])
print(mixed)
print(mixed.dtype)0 1
1 two
2 3.0
dtype: objectلا يُظهر بانداس أي رسالة خطأ — بل يحول العمود بأكمله إلى نوع object، متراجعاً إلى النوع الأكثر مرونة واستيعاباً. عاقبة ذلك ليست خطأ صريحاً ومباشراً، بل مشكلة صامتة: لم يعد العمود رقمياً، لذا ستفشل دالة .mean() في حسابه، وستبدأ دالة .sum() بدمج النصوص بدلاً من جمع الأرقام. وجود خلية نصية واحدة مثل N/A في صف واحد من ملف CSV يؤدي إلى هذا السلوك تحديداً، وهو موضوع الفصل السادس.
أيهما بين يديّ الآن — Series أم DataFrame؟
import pandas as pd
sales = pd.DataFrame({
"product": ["pen", "notebook", "bag"],
"quantity": [12, 5, 2],
"price": [15.0, 60.0, 850.0],
})
column = sales["quantity"]
print(type(column))
print(type(sales))
print(column.mean())<class 'pandas.core.series.Series'>
<class 'pandas.core.frame.DataFrame'>
6.333333333333333وها هي الإجابة عن السؤال الذي افتتحنا به هذا الفصل. التعبير sales["quantity"] ليس قائمة، بل هو كائن Series — ويمتلك Series دوال مثل .sum() و .mean() و .max() وغيرها.
وهناك أيضاً تمييز دقيق يربك المبتدئين في البداية:
import pandas as pd
sales = pd.DataFrame({
"product": ["pen", "notebook", "bag"],
"quantity": [12, 5, 2],
"price": [15.0, 60.0, 850.0],
})
print(sales["quantity"])
print("---")
print(sales[["quantity"]])0 12
1 5
2 2
Name: quantity, dtype: int64
---
quantity
0 12
1 5
2 2قوس معقوف واحد إضافي كفيل بتغيير طبيعة الناتج بالكامل. الأول هو Series — عمود واحد، مع طباعة اسمه ونوعه في الأسفل. أما الثاني فهو DataFrame — جدول تصادف أنه يحتوي على عمود واحد فقط، ولذلك يظهر اسم العمود في الأعلى كعنوان جدول.
القاعدة هي أن الأقواس المعقوفة المزدوجة تعني قائمة من الأعمدة، وطلب قائمة من الأعمدة يعيد دائماً جدولاً كاملاً. بناءً على ذلك، فإن sales[["product", "price"]] يعطيك جدولاً من عمودين.
لماذا يجب استحضار هذا الفارق دائماً؟ لأن رسائل الخطأ تختلف تماماً باختلافهما. فكائنSeriesيمتلك دالة مثل.mean()ولكنه لا يمتلك أسماء أعمدة، بينما يتيح لكDataFrameاختيار الأعمدة ولكنه لا يعطيك رقماً مفرداً مباشرة. عندما تخبرك رسالة خطأ بأن الكائن لا يحتوي على خاصية معينة، فإن أول سؤال تطرحه على نفسك هو: أي الكائنين أحمله بين يديّ الآن؟
مثال تطبيقي متكامل
inventory.py:
import pandas as pd
# Product names become the index, so a row can be reached by name.
stock = pd.DataFrame(
{
"quantity": [12, 5, 2, 30],
"price": [15.0, 60.0, 850.0, 8.0],
},
index=["pen", "notebook", "bag", "eraser"],
)
print(stock)
print()
print("Shape :", stock.shape)
print("Columns :", list(stock.columns))
print("Index :", list(stock.index))
print()
# A column is a Series, so an operation applies to every value at once.
value = stock["quantity"] * stock["price"]
print("Value per product:")
print(value)
print()
print("Total value:", value.sum())quantity price
pen 12 15.0
notebook 5 60.0
bag 2 850.0
eraser 30 8.0
Shape : (4, 2)
Columns : ['quantity', 'price']
Index : ['pen', 'notebook', 'bag', 'eraser']
Value per product:
pen 180.0
notebook 300.0
bag 1700.0
eraser 240.0
dtype: float64
Total value: 2420.0هناك أمران جديران بالملاحظة هنا:
أولاً، العملية stock["quantity"] * stock["price"] قامت بضرب كائني Series وأنتجت كائن Series ثالثاً. طابق بانداس القيم بينهما حسب الفهرس — فكمية القلم ضُربت في سعر القلم، وكمية الحقيبة في سعر الحقيبة؛ أي بالاسم وليس بمجرد ترتيب الصفوف. هذا السلوك يمثل حجر الأساس لدمج الجداول لاحقاً.
ثانياً، نوع العمود الناتج هو float64، على الرغم من أن عمود quantity كان من نوع int64. فضرب عدد صحيح في عدد عشري ينتج عدداً عشرياً — وهي نفس القاعدة المتبعة في بايثون الأساسي، دون أي تغيير من جانب بانداس.
الأخطاء الشائعة وحلولها
خطأ KeyError: 'Quantity' لا يوجد عمود بهذا الاسم. أسماء الأعمدة حساسة لحالة الأحرف، وغالباً ما تحتوي الأسماء المقروءة من ملفات CSV على مسافات زائدة في البداية أو النهاية. شغّل print(list(df.columns)) لرؤية الأسماء بدقة — فطريقة عرضها كقائمة تضع علامات تنصيص حول كل اسم، مما يوضح أي مسافة خفية.
خطأ AttributeError: 'DataFrame' object has no attribute 'mean_price' يمكن لكتابة df.column_name جلب العمود أيضاً، ولكن فقط في حال كانت الأسماء بسيطة — وليس عندما يحتوي الاسم على مسافات، أو يبدأ برقم، أو يتعارض مع دالة مدمجة في بانداس مثل count. أما الصيغة df["column_name"] فهي تعمل دائماً وفي جميع الحالات، فاجعلها خيارك الأساسي والمعتمد دائماً.
خطأ TypeError: unsupported operand type(s) أثناء إجراء حسابات على عمود رقمي العمود في الغالب ليس رقمياً. افحص df.dtypes؛ فظهور object هناك يعني اختلاط نصوص بالبيانات في مكان ما.
دالة .sum() تقوم بدمج النصوص بدلاً من جمع الأرقام السبب نفسه — نوع العمود هو object. وجود خلية واحدة بقيمة N/A أو فارغة في صف واحد يكفي لتغيير نوع العمود بأكمله.
خطأ ValueError: All arrays must be of the same length القوائم الممررة إلى pd.DataFrame({...}) ليست متساوية في الطول. يحتاج كل عمود إلى نفس عدد القيم، لأن الصفوف يجب أن تصطف بدقة على فهرس مشترك واحد.
Step 4 of 6 — Predict
Check your understanding
تم تمرير ثلاث قيم. ما الذي يُطبع بالتحديد؟
import pandas as pd
quantity = pd.Series([12, 5, 2])
print(quantity)- A0 12 1 5 2 2 dtype: int64
- B12 5 2
- C[12, 5, 2]
- D12 5 2 dtype: int64
يختلف السطران فقط في زوج إضافي من الأقواس المربعة. ما الذي تعيده type()؟
import pandas as pd
sales = pd.DataFrame({
"product": ["pen", "notebook", "bag"],
"quantity": [12, 5, 2],
"price": [15.0, 60.0, 850.0],
})
print(type(sales["quantity"]))
print(type(sales[["quantity"]]))- Aالأول Series، والثاني DataFrame
- Bكلاهما Series
- Cكلاهما DataFrame
- Dالأول DataFrame، والثاني Series
تم خلط الأرقام والنصوص في عمود واحد. ما الذي يفعله بانداس؟
import pandas as pd
mixed = pd.Series([1, "two", 3.0])
print(mixed.dtype)- Aلا يصدر أي خطأ — بل يجعل نوع العمود بأكمله `object`
- Bخطأ `TypeError` — لا يمكن لنوعين مختلفين التواجد في عمود واحد
- Cيحذف النص ويحتفظ بالعمود كـ `float64`
- Dيحتفظ بنوع مستقل لكل قيمة
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.
دورك الآن
اكتب ملفاً باسم library.py يحتوي على DataFrame لخمسة كتب على الأقل — بحيث تكون عناوين الكتب هي الفهرس (index)، وتتضمن الأعمدة: عدد النسخ (أعداد صحيحة)، والسعر (أعداد عشرية)، واسم المؤلف (نص).
ثم قم بطباعة ما يلي:
- الجدول كاملاً، وأبعاده
shape، وأنواع بياناتهdtypes - عمود السعر بمفرده — وأثبت باستخدام
type()أنه كائنSeries - عمود السعر بأقواس معقوفة مزدوجة — وأثبت أنه كائن
DataFrame - القيمة الإجمالية لكل كتاب (عدد النسخ × السعر)، وإجمالي القيمة لجميع الكتب مجتمعة
بعد ذلك، قم بتجربة عملية واحدة: استبدل عدد نسخ أحد الكتب بالنص "unknown" وشغّل الملف مرة أخرى. ماذا يظهر في dtypes الآن، وماذا حدث لمجموع القيم؟
هذه التجربة هي أثمن خمس دقائق في هذا الفصل. فكلمة واحدة في خلية وحيدة قادرة على تغيير نوع عمود بأكمله وتعطيل العمليات الحسابية دون إطلاق أي استثناء صريح — وفي مجموعات البيانات الحقيقية يتكرر هذا الأمر باستمرار.
Step 6 of 6
التحدي — the chapter quiz
عشرة أسئلة متدرجة من السهل إلى الصعب. الأسئلة الأخيرة صعبة عن قصد.
Sign in to take the quiz