أطلق العنان للإمكانات الكاملة لـ Pandas من خلال إتقان الدوال المخصصة. يشرح هذا الدليل المفصل الاختلافات والأداء وأفضل حالات الاستخدام لـ apply() و map() و applymap() لتحليل البيانات الاحترافي.
إتقان Pandas: نظرة متعمقة على الدوال المخصصة باستخدام apply() و map() و applymap()
في عالم علم البيانات والتحليل، تعد مكتبة Pandas الخاصة بـ Python أداة لا غنى عنها. إنها توفر هياكل بيانات قوية ومرنة وفعالة مصممة لجعل العمل مع البيانات المنظمة سهلاً وبديهيًا. في حين أن Pandas تأتي مع مجموعة غنية من الدوال المضمنة للتجميع والترشيح والتحويل، إلا أنه يأتي وقت في رحلة كل متخصص في البيانات عندما لا تكون هذه الدوال كافية. تحتاج إلى تطبيق المنطق المخصص الخاص بك، أو قاعدة عمل فريدة، أو تحويل معقد غير متاح بسهولة.
هنا تصبح القدرة على تطبيق الدوال المخصصة قوة خارقة. ومع ذلك، تقدم Pandas عدة طرق لتحقيق ذلك، بشكل أساسي من خلال طرق apply() و map() و applymap(). بالنسبة للقادم الجديد، قد تبدو هذه الدوال متشابهة بشكل مربك. أي واحدة يجب أن تستخدمها؟ متى؟ وما هي الآثار المترتبة على الأداء لاختيارك؟
سيزيل هذا الدليل الشامل الغموض عن هذه الطرق القوية. سوف نستكشف كل واحدة بالتفصيل، ونفهم حالات الاستخدام المحددة الخاصة بها، والأهم من ذلك، نتعلم كيفية اختيار الأداة المناسبة للوظيفة لكتابة كود Pandas نظيف وفعال وقابل للقراءة. سنغطي ما يلي:
- طريقة
map(): مثالية للتحويل التدريجي للعناصر في سلسلة واحدة. - طريقة
apply(): أداة العمل متعددة الاستخدامات للعمليات صفًا بصف أو عمودًا بعمود على DataFrame. - طريقة
applymap(): المتخصص في العمليات التدريجية للعناصر عبر DataFrame بأكمله. - اعتبارات الأداء: الفرق الحاسم بين هذه الطرق والتوجيه الآلي الحقيقي.
- أفضل الممارسات: إطار عمل لاتخاذ القرارات لمساعدتك على اختيار الطريقة الأكثر فعالية في كل مرة.
تهيئة المسرح: مجموعة البيانات النموذجية الخاصة بنا
لجعل أمثلتنا عملية وواضحة، دعنا نعمل مع مجموعة بيانات متسقة ذات صلة عالميًا. سنقوم بإنشاء DataFrame نموذجية تمثل بيانات المبيعات عبر الإنترنت من شركة تجارة إلكترونية دولية وهمية.
import pandas as pd
import numpy as np
data = {
'OrderID': [1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008],
'Product': ['Laptop', 'Mouse', 'Keyboard', 'Monitor', 'Webcam', 'Headphones', 'Docking Station', 'Mouse'],
'Category': ['Electronics', 'Accessories', 'Accessories', 'Electronics', 'Accessories', 'Audio', 'Electronics', 'Accessories'],
'Price_USD': [1200, 25, 75, 300, 50, 150, 250, 30],
'Quantity': [1, 2, 1, 2, 1, 1, 1, 3],
'Country': ['USA', 'Canada', 'USA', 'Germany', 'Japan', 'Canada', 'Germany', np.nan]
}
df = pd.DataFrame(data)
print(df)
تمنحنا DataFrame هذه مزيجًا رائعًا من أنواع البيانات (رقمية وسلسلة وحتى قيمة مفقودة) لإثبات القدرات الكاملة للدوال المستهدفة.
طريقة `map()`: التحويل التدريجي للعناصر لسلسلة
ما هي `map()`؟
طريقة map() هي أداتك المتخصصة لتعديل القيم داخل عمود واحد (Series Pandas). تعمل على أساس عنصر بعنصر. فكر في الأمر على أنه يقول: "لكل عنصر في هذا العمود، ابحث عنه في قاموس أو مرره من خلال هذه الدالة واستبدله بالنتيجة."
يتم استخدامه بشكل أساسي لمهمتين:
- استبدال القيم بناءً على قاموس (تعيين).
- تطبيق دالة بسيطة على كل عنصر.
حالة الاستخدام 1: تعيين القيم باستخدام قاموس
هذا هو الاستخدام الأكثر شيوعًا وفعالية لـ map(). تخيل أننا نريد إنشاء عمود "Department" أوسع بناءً على عمود "Category" الخاص بنا. يمكننا تحديد تعيين في قاموس Python واستخدام map() لتطبيقه.
category_to_department = {
'Electronics': 'Technology',
'Accessories': 'Peripherals',
'Audio': 'Technology'
}
df['Department'] = df['Category'].map(category_to_department)
print(df[['Category', 'Department']])
الإخراج:
Category Department
0 Electronics Technology
1 Accessories Peripherals
2 Accessories Peripherals
3 Electronics Technology
4 Accessories Peripherals
5 Audio Technology
6 Electronics Technology
7 Accessories Peripherals
لاحظ مدى أناقة عمل هذا. يتم البحث عن كل قيمة في سلسلة "Category" في قاموس `category_to_department`، وتستخدم القيمة المقابلة لملء عمود "Department" الجديد. إذا لم يتم العثور على مفتاح في القاموس، فستنتج map() قيمة NaN (ليست رقمًا)، وهو غالبًا السلوك المطلوب للفئات غير المعينة.
حالة الاستخدام 2: تطبيق دالة مع `map()`
يمكنك أيضًا تمرير دالة (بما في ذلك دالة lambda) إلى map(). سيتم تنفيذ الدالة لكل عنصر في السلسلة. لنقم بإنشاء عمود جديد يعطينا تصنيفًا وصفيًا للسعر.
def price_label(price):
if price > 200:
return 'High-Value'
elif price > 50:
return 'Mid-Value'
else:
return 'Low-Value'
df['Price_Label'] = df['Price_USD'].map(price_label)
# Using a lambda function for a simpler task:
# df['Product_Length'] = df['Product'].map(lambda x: len(x))
print(df[['Product', 'Price_USD', 'Price_Label']])
الإخراج:
Product Price_USD Price_Label
0 Laptop 1200 High-Value
1 Mouse 25 Low-Value
2 Keyboard 75 Mid-Value
3 Monitor 300 High-Value
4 Webcam 50 Low-Value
5 Headphones 150 Mid-Value
6 Docking Station 250 High-Value
7 Mouse 30 Low-Value
متى تستخدم `map()`: ملخص سريع
- أنت تعمل على عمود واحد (سلسلة).
- تحتاج إلى استبدال القيم بناءً على قاموس أو سلسلة أخرى. هذه هي قوتها الأساسية.
- تحتاج إلى تطبيق دالة بسيطة للعناصر على عمود واحد.
طريقة `apply()`: أداة العمل متعددة الاستخدامات
ما هي `apply()`؟
إذا كانت map() متخصصًا، فإن apply() هي الأداة متعددة الأغراض. إنها أكثر مرونة لأنها يمكن أن تعمل على كل من السلاسل و DataFrames. المفتاح لفهم apply() هو معلمة axis، التي توجه عملها:
- على سلسلة: تعمل على أساس عنصر بعنصر، تمامًا مثل
map(). - على DataFrame مع
axis=0(الإعداد الافتراضي): تطبق دالة على كل عمود. تتلقى الدالة كل عمود كسلسلة. - على DataFrame مع
axis=1: تطبق دالة على كل صف. تتلقى الدالة كل صف كسلسلة.
`apply()` على سلسلة
عند استخدامها على سلسلة، تتصرف apply() بشكل مشابه جدًا لـ map(). إنها تطبق دالة على كل عنصر. على سبيل المثال، يمكننا تكرار مثال تصنيف السعر الخاص بنا.
df['Price_Label_apply'] = df['Price_USD'].apply(price_label)
print(df['Price_Label_apply'].equals(df['Price_Label'])) # Output: True
في حين أنها تبدو قابلة للتبديل هنا، غالبًا ما تكون map() أسرع قليلاً لعمليات استبدال القاموس البسيطة والعمليات التدريجية للعناصر على سلسلة لأن لديها مسارًا أكثر تحسينًا لتلك المهام المحددة.
`apply()` على DataFrame (عموديًا، `axis=0`)
هذا هو الوضع الافتراضي لـ DataFrame. يتم استدعاء الدالة التي تقدمها مرة واحدة لكل عمود. هذا مفيد لتجميعات أو تحويلات العمود.
لنوجد الفرق بين الحد الأقصى والحد الأدنى للقيمة (النطاق) لكل عمود من أعمدتنا الرقمية.
numeric_cols = df[['Price_USD', 'Quantity']]
def get_range(column_series):
return column_series.max() - column_series.min()
column_ranges = numeric_cols.apply(get_range, axis=0)
print(column_ranges)
الإخراج:
Price_USD 1175.0
Quantity 2.0
dtype: float64
هنا، تلقت الدالة get_range أولاً سلسلة "Price_USD"، وحسبت نطاقها، ثم تلقت سلسلة "Quantity" وفعلت الشيء نفسه، وأعادت سلسلة جديدة بالنتائج.
`apply()` على DataFrame (صفًا، `axis=1`)
يمكن القول إن هذا هو حالة الاستخدام الأقوى والأكثر شيوعًا لـ apply(). عندما تحتاج إلى حساب قيمة جديدة بناءً على أعمدة متعددة في نفس الصف، فإن apply() مع axis=1 هو الحل الأمثل لك.
ستتلقى الدالة التي تمررها كل صف كسلسلة، حيث الفهرس هو أسماء الأعمدة. لنحسب التكلفة الإجمالية لكل طلب.
def calculate_total_cost(row):
# 'row' is a Series representing a single row
price = row['Price_USD']
quantity = row['Quantity']
return price * quantity
df['Total_Cost'] = df.apply(calculate_total_cost, axis=1)
print(df[['Product', 'Price_USD', 'Quantity', 'Total_Cost']])
الإخراج:
Product Price_USD Quantity Total_Cost
0 Laptop 1200 1 1200
1 Mouse 25 2 50
2 Keyboard 75 1 75
3 Monitor 300 2 600
4 Webcam 50 1 50
5 Headphones 150 1 150
6 Docking Station 250 1 250
7 Mouse 30 3 90
هذا شيء لا تستطيع map() القيام به ببساطة، لأنه يقتصر على عمود واحد. لنرَ مثالًا أكثر تعقيدًا. نريد تصنيف أولوية شحن كل طلب بناءً على فئته وبلده.
def assign_shipping_priority(row):
if row['Category'] == 'Electronics' and row['Country'] == 'USA':
return 'High Priority'
elif row['Total_Cost'] > 500:
return 'High Priority'
elif row['Country'] == 'Japan':
return 'Medium Priority'
else:
return 'Standard'
df['Shipping_Priority'] = df.apply(assign_shipping_priority, axis=1)
print(df[['Category', 'Country', 'Total_Cost', 'Shipping_Priority']])
متى تستخدم `apply()`: ملخص سريع
- عندما يعتمد المنطق الخاص بك على أعمدة متعددة في صف (استخدم
axis=1). هذه هي ميزتها القاتلة. - عندما تحتاج إلى تطبيق دالة تجميع أسفل الأعمدة أو عبر الصفوف.
- كأداة لتطبيق الدوال للأغراض العامة عندما لا تتناسب
map().
إشارة خاصة: طريقة `applymap()`
ما هي `applymap()`؟
طريقة applymap() هي متخصص آخر، لكن مجاله هو DataFrame بأكمله. إنها تطبق دالة على كل عنصر واحد في DataFrame. لا تعمل على سلسلة—إنها طريقة DataFrame فقط.
فكر في الأمر على أنه تشغيل map() على كل عمود في وقت واحد. إنه مفيد للتحولات الواسعة والاجتياحية، مثل التنسيق أو تحويل النوع، عبر جميع الخلايا.
DataFrame.applymap(). الطريقة الجديدة الموصى بها هي استخدام DataFrame.map(). الوظيفة هي نفسها. سنستخدم applymap() هنا للتوافق، ولكن كن على علم بهذا التغيير للكود المستقبلي.
مثال عملي
لنفترض أن لدينا DataFrame فرعية تحتوي فقط على أعمدتنا الرقمية ونريد تنسيقها جميعًا كسلاسل عملة لتقرير.
numeric_df = df[['Price_USD', 'Quantity', 'Total_Cost']]
# Using a lambda function to format each number
formatted_df = numeric_df.applymap(lambda x: f'${x:,.2f}')
print(formatted_df)
الإخراج:
Price_USD Quantity Total_Cost
0 $1,200.00 $1.00 $1,200.00
1 $25.00 $2.00 $50.00
2 $75.00 $1.00 $75.00
3 $300.00 $2.00 $600.00
4 $50.00 $1.00 $50.00
5 $150.00 $1.00 $150.00
6 $250.00 $1.00 $250.00
7 $30.00 $3.00 $90.00
الاستخدام الشائع الآخر هو تنظيف DataFrame لبيانات السلسلة عن طريق، على سبيل المثال، تحويل كل شيء إلى أحرف صغيرة.
string_df = df[['Product', 'Category', 'Country']].copy() # Create a copy to avoid SettingWithCopyWarning
# Ensure all values are strings to prevent errors
string_df = string_df.astype(str)
lower_df = string_df.applymap(str.lower)
print(lower_df)
متى تستخدم `applymap()`: ملخص سريع
- عندما تحتاج إلى تطبيق دالة واحدة بسيطة على كل عنصر في DataFrame.
- لمهام مثل تحويل نوع البيانات أو تنسيق السلسلة أو تحويلات رياضية بسيطة عبر DataFrame بأكمله.
- تذكر إهمالها لصالح
DataFrame.map()في إصدارات Pandas الحديثة.
نظرة متعمقة على الأداء: التوجيه الآلي مقابل التكرار
الحلقة "المخفية"
هذا هو المفهوم الأكثر أهمية لفهمه لكتابة كود Pandas عالي الأداء. في حين أن apply() و map() و applymap() ملائمة، إلا أنها في الأساس مجرد أغلفة فاخرة حول حلقة Python. عند استخدام df.apply(..., axis=1)، تكرر Pandas DataFrame صفًا صفًا، وتمرر كل واحد إلى الدالة الخاصة بك. هذه العملية لها نفقات عامة كبيرة وهي أبطأ بكثير من العمليات المحسنة في C أو Cython.
قوة التوجيه الآلي
التوجيه الآلي هو ممارسة إجراء العمليات على المصفوفات بأكملها (أو السلاسل) مرة واحدة، بدلاً من العناصر الفردية. تم تصميم Pandas ومكتبتها الأساسية، NumPy، خصيصًا لتكون سريعة بشكل لا يصدق في عمليات التوجيه الآلي.
لنراجع حساب "Total_Cost" الخاص بنا. استخدمنا apply()، ولكن هل هناك طريقة موجهة آليًا؟
# Method 1: Using apply() (Iteration)
df['Total_Cost'] = df.apply(lambda row: row['Price_USD'] * row['Quantity'], axis=1)
# Method 2: Vectorized Operation
df['Total_Cost_Vect'] = df['Price_USD'] * df['Quantity']
# Check if the results are the same
print(df['Total_Cost'].equals(df['Total_Cost_Vect'])) # Output: True
الطريقة الثانية موجهة آليًا. إنها تأخذ سلسلة "Price_USD" بأكملها وتضربها في سلسلة "Quantity" بأكملها في عملية واحدة محسنة للغاية. إذا كنت ستوقت هاتين الطريقتين على DataFrame كبيرة (بملايين الصفوف)، فلن يكون النهج الموجه آليًا أسرع فحسب—بل سيكون أسرع بترتيبات من حيث الحجم. نحن نتحدث عن ثوانٍ مقابل دقائق، أو دقائق مقابل ساعات.
متى تكون `apply()` لا مفر منها؟
إذا كان التوجيه الآلي أسرع بكثير، فلماذا توجد هذه الطرق الأخرى؟ لأنه في بعض الأحيان، يكون المنطق الخاص بك معقدًا للغاية بحيث لا يمكن توجيهه آليًا. apply() هي الأداة الضرورية والصحيحة عندما:
- منطق شرطي معقد: يتضمن المنطق الخاص بك عبارات `if/elif/else` المعقدة التي تعتمد على أعمدة متعددة، مثل مثال `assign_shipping_priority` الخاص بنا. في حين أن بعض هذا يمكن تحقيقه باستخدام `np.select()`، إلا أنه يمكن أن يصبح غير قابل للقراءة.
- دوال المكتبة الخارجية: تحتاج إلى تطبيق دالة من مكتبة خارجية على بياناتك. على سبيل المثال، تطبيق دالة من مكتبة جغرافية مكانية لحساب المسافة بناءً على أعمدة خطوط الطول والعرض، أو دالة من مكتبة معالجة اللغة الطبيعية (مثل NLTK) لإجراء تحليل المشاعر على عمود نصي.
- العمليات التكرارية: يعتمد حساب صف معين على قيمة محسوبة في صف سابق (على الرغم من أن هذا نادرًا وغالبًا ما يكون علامة على أن هناك حاجة إلى هيكل بيانات مختلف).
أفضل الممارسات: التوجيه الآلي أولاً، `apply()` ثانيًا
يقودنا هذا إلى القاعدة الذهبية لأداء Pandas:
ابحث دائمًا عن حل موجه آليًا أولاً. استخدم apply() كحل احتياطي قوي ومرن عندما لا يكون الحل الموجه آليًا عمليًا أو ممكنًا.
ملخص والنتائج الرئيسية: اختيار الأداة المناسبة
دعنا نجمع معرفتنا في إطار عمل واضح لاتخاذ القرارات. عند مواجهة مهمة تحويل مخصصة، اطرح على نفسك هذه الأسئلة:
جدول المقارنة
| الطريقة | يعمل على | نطاق التشغيل | تتلقى الدالة | حالة الاستخدام الأساسية |
|---|---|---|---|---|
| التوجيه الآلي | سلسلة، DataFrame | المصفوفة بأكملها في وقت واحد | غير متاح (العملية مباشرة) | العمليات الحسابية والمنطقية. أعلى أداء. |
.map() |
سلسلة فقط | عنصرًا تلو الآخر | عنصر واحد | استبدال القيم من قاموس. |
.apply() |
سلسلة، DataFrame | صفًا تلو الآخر أو عمودًا تلو الآخر | سلسلة (صف أو عمود) | منطق معقد باستخدام أعمدة متعددة لكل صف. |
.applymap() |
DataFrame فقط | عنصرًا تلو الآخر | عنصر واحد | تنسيق أو تحويل كل خلية في DataFrame. |
مخطط انسيابي للقرار
- هل يمكن التعبير عن عمليتي باستخدام العمليات الحسابية الأساسية (+، -، *، /) أو عوامل التشغيل المنطقية (&، |، ~) على الأعمدة بأكملها؟
→ نعم؟ استخدم نهجًا موجهًا آليًا. هذا هو الأسرع. (على سبيل المثال، `df['col1'] * df['col2']`) - هل أعمل فقط على عمود واحد، وهل هدفي الرئيسي هو استبدال القيم بناءً على قاموس؟
→ نعم؟ استخدمSeries.map(). إنه مُحسَّن لهذا الغرض. - هل أحتاج إلى تطبيق دالة على كل عنصر واحد في DataFrame بأكمله؟
→ نعم؟ استخدمDataFrame.applymap()(أوDataFrame.map()في Pandas الأحدث). - هل المنطق الخاص بي معقد ويتطلب قيمًا من أعمدة متعددة في كل صف لحساب نتيجة واحدة؟
→ نعم؟ استخدمDataFrame.apply(..., axis=1). هذه هي أداتك للمنطق المعقد صفًا بصف.
خاتمة
يعد التنقل بين الخيارات الخاصة بتطبيق الدوال المخصصة في Pandas طقوس عبور لأي ممارس بيانات. في حين أنها قد تبدو قابلة للتبديل للوهلة الأولى، إلا أن map() و apply() و applymap() هي أدوات متميزة، لكل منها نقاط قوتها وحالات الاستخدام المثالية. من خلال فهم اختلافاتهم، يمكنك كتابة تعليمات برمجية ليست صحيحة فحسب، بل أيضًا أكثر قابلية للقراءة والصيانة وأكثر فعالية بشكل كبير.
تذكر التسلسل الهرمي: تفضل التوجيه الآلي لسرعته الخام، واستخدم map() لاستبدال السلسلة الفعال، واختر applymap() للتحويلات على مستوى DataFrame، واستفد من قوة ومرونة apply() للمنطق المعقد صفًا بصف أو عمودًا بعمود الذي لا يمكن توجيهه آليًا. مسلحًا بهذه المعرفة، أنت الآن مجهز بشكل أفضل لمواجهة أي تحدٍ لمعالجة البيانات يأتي في طريقك، وتحويل البيانات الأولية إلى رؤى قوية بمهارة وكفاءة.