Pandas: تحسين الأداء

آخر تحديث: 2026-08-26

استغرق سكربت Bob 10 دقائق، بينما انتقلت Alice إلى استخدام التوحيد المتجهي (vectorization) وأنهت العمل في 30 ثانية — أي تسريع بfactor 20. هذه ليست حالة معزولة: 90% من مشاكل أداء Pandas تأتي من "استخدام الحلقات بدلاً من التوحيد المتجهي." تغطي هذه القسم مجموعة أدوات الأداء الكاملة، من عادات البرمجة إلى التحسين على مستوى المحرك، لمساعدتك في كتابة كود Pandas سريع وفعال من حيث استخدام الذاكرة.

⚠️ ملاحظة: يتطلب الكود التالي بيئة Python محلية للتشغيل.

1. ما ستتعلمه


2. تقديم بوب خلال 10 دقائق

(1) المشكلة: الحلقات الصفية بطيئة جدًا

استخدم بوب iterrows لمعالجة 100,000 صف، واستغرق الأمر 10 دقائق:

PYTHON
import pandas as pd
import numpy as np

# لا تفعل هذا — بطيء جدًا!
# for i, row in df.iterrows():
#     df.loc[i, 'new_col'] = row['a'] * row['b'] + row['c']
TEXT 📖 للعرض فقط
> **الخرج:** قم بتشغيل هذا في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبته مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) والمتابعة. القيم الفعلية قد تختلف قليلاً حسب إصدار pandas.

(2) الحل: التوحيد الاتجاهي (Vectorization) في 30 ثانية

▶ مثال: التوحيد الاتجاهي مقابل الحلقات (الصعوبة ⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'a': np.random.randn(100000),
    'b': np.random.randn(100000),
    'c': np.random.randn(100000)
})

# ❌ بطيء: iterrows (~300 ثانية لـ 100,000 صف)
# for i, row in df.iterrows():
#     df.loc[i, 'result'] = row['a'] * row['b'] + row['c']

# ✅ سريع: توحيد اتجاهي (~0.03 ثانية)
df['result'] = df['a'] * df['b'] + df['c']

print(f"Result mean: {df['result'].mean():.4f}")
# ~30 ثانية مقابل ~0.03 ثانية — أسرع بمقدار 1000 مرة!
TEXT 📖 للعرض فقط
> **الخرج:** قم بتشغيل هذا في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبته مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) والمتابعة. القيم الفعلية قد تختلف قليلاً حسب إصدار pandas.

3. ترتيب الأداء: الحلقات → apply → الت vectorization (المتجهات)

(1) مقارنة 4 طرق

▶ مثال

TEXT 📖 للعرض فقط
> **المخرجات:** قم بتشغيل هذا في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبتًا مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) والمتابعة. قد تختلف القيم الفعلية قليلاً حسب إصدار pandas.

: مقارنة سرعة 4 طرق (الصعوبة ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'a': np.random.randn(50000),
    'b': np.random.randn(50000)
})

# الطريقة 1: iterrows (الأبطأ — لا تستخدمها أبدًا للحسابات)
# ~60 ثانية لـ 50,000 صف

# الطريقة 2: apply (بطيئة)
result2 = df.apply(lambda row: row['a'] + row['b'], axis=1)

# الطريقة 3: vectorized (سريعة)
result3 = df['a'] + df['b']

# الطريقة 4: numpy (الأسرع)
result4 = (df['a'].values + df['b'].values)

# التحقق من تطابق النتائج
print(np.allclose(result2.values, result3.values))  # True
print(np.allclose(result3.values, result4))          # True
TEXT 📖 للعرض فقط
> **المخرجات:** قم بتشغيل هذا في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبتًا مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) والمتابعة. قد تختلف القيم الفعلية قليلاً حسب إصدار pandas.

(2) جدول ترتيب الأداء

الترتيب الطريقة السرعة النسبية الأفضل لـ
1 عمليات NumPy الأسرع (1x) حسابات رقمية بحتة
2 ت vectorization (المتجهات) مع Pandas سريعة (1-2x) تسميات + حسابات رقمية
3 map (قاموس) متوسطة (5-10x) ربط واحد بواحد
4 apply بطيئة (50-100x) منطق معقد
5 itertuples بطيئة جداً (200x) يجب التكرار عبر الصفوف
6 iterrows الأبطأ (500x) لا تستخدمها أبدًا
🔥 القاعدة الذهبية: استخدم الت vectorization (المتجهات) في كل مرة ممكنة. إذا لم تتمكن، استخدم apply. الحلقات هي الملاذ الأخير. iterrows يجب أن تكون دائماً الخيار الأخير (أو لا خيار على الإطلاق).


4. محرك تعبير eval / query

(1) eval تُسرّع العمليات المعقدة

▶ مثال

TEXT 📖 للعرض فقط
> **الخرج:** قم بتشغيل هذا في بيئة بايثون محلية (pandas 2.x). لا يحتوي خادم Piston على pandas مثبتة مسبقاً. يرجى تثبيتها محلياً (`pip install pandas`) واتباع الشرح. قد تختلف القيم الفعليّة قليلاً حسب إصدار pandas.

: محرك تعبير eval (الصعوبة ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'a': np.random.randn(100000),
    'b': np.random.randn(100000),
    'c': np.random.randn(100000),
    'd': np.random.randn(100000)
})

# تعبير معقد — eval يتجنب البيانات الوسيطة (DataFrames)
result1 = df['a'] * df['b'] + df['c'] / df['d'] - df['a'] ** 2
result2 = df.eval('a * b + c / d - a ** 2')

print(np.allclose(result1, result2))  # True
# eval أسرع عندما:
# 1. أعمدة كثيرة في التعبير (>4)
# 2. DataFrame كبير (>10K صف)
# 3. الذاكرة محدودة (يتجنب الوسائط)

# query للتصفية
filtered = df.query('a > 0 and b < 0')
# المعادل: df[(df['a'] > 0) & (df['b'] < 0)]
TEXT 📖 للعرض فقط
> **الخرج:** قم بتشغيل هذا في بيئة بايثون محلية (pandas 2.x). لا يحتوي خادم Piston على pandas مثبتة مسبقاً. يرجى تثبيتها محلياً (`pip install pandas`) واتباع الشرح. قد تختلف القيم الفعليّة قليلاً حسب إصدار pandas.

(2) eval مقابل العمليات العادية

السيناريو ميزة eval عيب eval
تعابير معقدة متعددة الأعمدة متغيرات وسيطة أقل صيغ محدودة
DataFrame كبير يوفر الذاكرة لا فائدة للبيانات الصغيرة
العمليات البسيطة لا ميزة سوء قابلية القراءة
التصفية الشرطية (query) قابلية قراءة أفضل مرونة محدودة

5. تحسين الذاكرة من الفئة

▶ مثال

TEXT 📖 للعرض فقط
> **المخرجات:** قم بتشغيل هذا في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبّتًا مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) والمتابعة. قد تختلف القيم الفعلية قليلاً حسب إصدار pandas.

: ضغط نوع الفئة (الصعوبة ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
# Simulate: 100K rows, 10 unique cities
df = pd.DataFrame({
    'city': np.random.choice(['NYC', 'LA', 'Chicago', 'Houston', 'Phoenix',
                               'Philly', 'San Antonio', 'San Diego', 'Dallas', 'Austin'], 100000),
    'status': np.random.choice(['Active', 'Inactive', 'Pending', 'Churned'], 100000)
})

# Before: object type
mem_before = df.memory_usage(deep=True).sum()
print(f"Before (object): {mem_before / 1024:.1f} KB")

# After: category type
df['city'] = df['city'].astype('category')
df['status'] = df['status'].astype('category')
mem_after = df.memory_usage(deep=True).sum()
print(f"After (category): {mem_after / 1024:.1f} KB")
print(f"Savings: {(1 - mem_after / mem_before) * 100:.1f}%")
# Typically 80-95% memory reduction for low-cardinality strings!
TEXT 📖 للعرض فقط
> **المخرجات:** قم بتشغيل هذا في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبّتًا مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) والمتابعة. قد تختلف القيم الفعلية قليلاً حسب إصدار pandas.

(1) متى تستخدم الفئة (category)

الشرط مناسب غير مناسب
القيم الفريدة < 50% من إجمالي الصفوف
أعمدة سلاسل نصية
الترتيب ذو معنى سلاسل نصية غير مرتبة عشوائياً
الحاجة لطرق السلاسل النصية ❌ (.str محدود) ✅ (استخدم كائن)
تجميع متكرر (groupby) ✅ (أسرع)

6. تخفيض الأنواع العددية (Numeric Compression)

▶ مثال

TEXT 📖 للعرض فقط
> **الخرج:** قم بتشغيل هذا في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على مكتبة pandas مثبته مسبقاً. يرجى تثبيتها محليًا (`pip install pandas`) ثم المتابعة. قد تختلف القيم الفعليّة قليلاً حسب إصدار pandas.

: تخفيض الأنواع العددية (الصعوبة ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'id': np.arange(100000),
    'age': np.random.randint(0, 120, 100000),
    'score': np.random.randint(0, 100, 100000),
    'price': np.round(np.random.uniform(0, 1000, 100000), 2),
    'flag': np.random.choice([0, 1], 100000)
})

# التحقق من الأنواع والذاكرة الحالية
print("قبل:")
print(df.dtypes)
print(f"الذاكرة: {df.memory_usage(deep=True).sum() / 1024:.1f} كيلوبايت")

# تخفيض الأعداد الصحيحة
df['id'] = pd.to_numeric(df['id'], downcast='unsigned')   # uint32 → uint32
df['age'] = pd.to_numeric(df['age'], downcast='unsigned')  # int64 → uint8 (0-255)
df['score'] = pd.to_numeric(df['score'], downcast='unsigned')
df['flag'] = pd.to_numeric(df['flag'], downcast='unsigned')  # int64 → uint8

# تخفيض الأعداد العشرية
df['price'] = pd.to_numeric(df['price'], downcast='float')  # float64 → float32

print("\nبعد:")
print(df.dtypes)
print(f"الذاكرة: {df.memory_usage(deep=True).sum() / 1024:.1f} كيلوبايت")
# عادةً ما يكون التوفير: 50-75%
TEXT 📖 للعرض فقط
> **الخرج:** قم بتشغيل هذا في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على مكتبة pandas مثبته مسبقاً. يرجى تثبيتها محليًا (`pip install pandas`) ثم المتابعة. قد تختلف القيم الفعليّة قليلاً حسب إصدار pandas.

7. النسخ عند الكتابة واستراتيجيات البيانات الكبيرة

(1) النسخ عند الكتابة (CoW)

▶ مثال

TEXT 📖 للعرض فقط
> **الخرج:** قم بتشغيل هذا في بيئة Python محلية (pandas 2.x). خادم Piston ليس لديه pandas مثبتًا مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) واتباع التعليمات. القيم الفعلية قد تختلف قليًependent على إصدار pandas.

: سلوك النسخ عند الكتابة (الصعوبة ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

# Pandas 3.x: CoW is default
pd.options.mode.copy_on_write = True

df = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]})

# With CoW: slice returns a view, modification creates a copy
subset = df[['a']]  # no copy until modified
df.loc[0, 'a'] = 99  # modifies df, subset unchanged
print(subset)  # still [1, 2, 3] — CoW protects

# Safe patterns under CoW:
df['c'] = df['a'] + df['b']  # ✅ assign new column
df = df.drop(columns='c')     # ✅ reassign

# Avoid:
# subset['a'] = 10  # ❌ SettingWithCopyWarning (pre-CoW issue, fixed by CoW)
TEXT 📖 للعرض فقط
> **الخرج:** قم بتشغيل هذا في بيئة Python محلية (pandas 2.x). خادم Piston ليس لديه pandas مثبتًا مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) واتباع التعليمات. القيم الفعلية قد تختلف قليًependent على إصدار pandas.

(2) استراتيجيات البيانات الكبيرة

حجم البيانات الاستراتيجية
< 1GB Pandas قياسي
1-5GB تقليص الفئة + التقييم (downcast + category + eval)
5-50GB المعالجة المجزأة باستخدام chunksize
> 50GB فكر في استخدام Dask / Polars / PySpark

▶ مثال

TEXT 📖 للعرض فقط
> **الخرج:** قم بتشغيل هذا في بيئة Python محلية (pandas 2.x). خادم Piston ليس لديه pandas مثبتًا مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) واتباع التعليمات. القيم الفعلية قد تختلف قليًependent على إصدار pandas.

: التجزئة باستخدام chunksize (الصعوبة ⭐)

PYTHON
import pandas as pd
from io import StringIO

# Simulate large CSV
csv_data = "id,value\n" + "\n".join([f"{i},{i*10}" for i in range(1000)])

# Process in chunks
total = 0
for chunk in pd.read_csv(StringIO(csv_data), chunksize=200):
    total += chunk['value'].sum()

print(f"Total: {total}")
TEXT 📖 للعرض فقط
> **الخرج:** قم بتشغيل هذا في بيئة Python محلية (pandas 2.x). خادم Piston ليس لديه pandas مثبتًا مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) واتباع التعليمات. القيم الفعلية قد تختلف قليًependent على إصدار pandas.

8. مثال كامل: التحسين من النهاية إلى النهاية لمليون صف

(6) ▶ قائمة التحقق من تحسين الأداء

100%
graph TB
    A[تحسين الأداء] --> B[1. التمثيل المتجهي بدل الدوال التكرارية]
    A --> C[2. تسريع eval / query]
    A --> D[3. ضغط سلسلة الفئة]
    A --> E[4. ضغط الأرقام بتقليل الدقة]
    A --> F[5. التقسيم حسب حجم الدفعات]
    A --> G[6. النسخ عند الكتابة]
    B --> H[تسريع 100-500 مرة]
    C --> I[توفير الذاكرة، تجنب الوسائط]
    D --> J[توفير 80-95% من الذاكرة]
    E --> K[توفير 50-75% من الذاكرة]
    F --> L[معالجة بيانات تتجاوز سعة الذاكرة]
    G --> M[تقطيع بدون نسخ]
TEXT 📖 للعرض فقط
> **الإخراج:** قم بتشغيل هذا في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبتة مسبقاً. يرجى تثبيته محلياً (`pip install pandas`) واتباع التعليمات. قد تختلف القيم الفعلي قليلاً حسب إصدار pandas.

▶ المثال

TEXT 📖 للعرض فقط
> **الإخراج:** قم بتشغيل هذا في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبتة مسبقاً. يرجى تثبيته محلياً (`pip install pandas`) واتباع التعليمات. قد تختلف القيم الفعلي قليلاً حسب إصدار pandas.

: من البطء إلى السرعة، سير العمل الكامل (الصعوبة ⭐⭐⭐)

PYTHON
import pandas as pd
import numpy as np

# ============================================
# مثال شامل: تحسين مليون صف
# iterrows → apply → vectorized → eval → category
# ============================================

np.random.seed(42)
df = pd.DataFrame({
    'city': np.random.choice([f'City_{i}' for i in range(20)], 1000000),
    'category': np.random.choice(['Electronics', 'Clothing', 'Home', 'Sports'], 1000000),
    'price': np.round(np.random.uniform(10, 500, 1000000), 2),
    'quantity': np.random.randint(1, 100, 1000000),
    'discount': np.random.choice([0, 0.1, 0.2, 0.3], 1000000)
})

# الخطوة 1: الذاكرة الأساسية
mem1 = df.memory_usage(deep=True).sum() / (1024**2)
print(f"الخطوة 1 — الذاكرة الخام: {mem1:.1f} ميجابايت")

# الخطوة 2: تقليل دقة الأرقام
df['quantity'] = pd.to_numeric(df['quantity'], downcast='unsigned')
df['price'] = pd.to_numeric(df['price'], downcast='float')
df['discount'] = pd.to_numeric(df['discount'], downcast='float')
mem2 = df.memory_usage(deep=True).sum() / (1024**2)
print(f"الخطوة 2 — بعد تقليل الدقة: {mem2:.1f} ميجابايت ({(1-mem2/mem1)*100:.0f}% تم توفيرها)")

# الخطوة 3: استخدام الفئات للنصوص
df['city'] = df['city'].astype('category')
df['category'] = df['category'].astype('category')
mem3 = df.memory_usage(deep=True).sum() / (1024**2)
print(f"الخطوة 3 — بعد استخدام الفئات: {mem3:.1f} ميجابايت ({(1-mem3/mem1)*100:.0f}% تم توفيرها إجمالياً)")

# الخطوة 4: حساب بالتمثيل المتجهي (سريع)
df['revenue'] = df['price'] * df['quantity'] * (1 - df['discount'])
print(f"الخطوة 4 — تم حساب الإيرادات (بتمثيل متجهي، ~0.01 ثانية)")

# الخطوة 5: بديل eval
df['revenue2'] = df.eval('price * quantity * (1 - discount)')
print(f"الخطوة 5 — الإيرادات عبر eval")

# التحقق
print(f"النتائج متطابقة: {np.allclose(df['revenue'], df['revenue2'])}")
TEXT 📖 للعرض فقط
> **الإخراج:** قم بتشغيل هذا في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبتة مسبقاً. يرجى تثبيته محلياً (`pip install pandas`) واتباع التعليمات. قد تختلف القيم الفعلي قليلاً حسب إصدار pandas.

❓ أسئلة شائعة

س ما الفرق بين iterrows و itertuples؟
ج iterrows تُعيد أزواج (index, Series)، مما يُنشئ سلسلة جديدة في كل مرة - بطيء للغاية (~أبطأ بـ 500 مرة من التجسيد). itertuples تُعيد أزواج (index, namedtuple) دون إنشاء سلسلة - أسرع بـ 5-10 مرات من iterrows ولكنها لا تزال أبطأ بـ 50 مرة من التجسيد. استخدم itertuples عندما يجب عليك التكرار صفًا بصف، ولكن أولاً فكّر فيما إذا كان التجسيد ممكنًا.
س متى يكون eval أسرع؟
ج eval أسرع مع البيانات الكبيرة (>10 آلاف صف) والتعبيرات المعقدة متعددة الأعمدة - حيث يتجنب إنشاء أطر بيانات وسيطة ويستخدم محرك numexpr للتسريع. بالنسبة للبيانات الصغيرة أو التعبيرات البسيطة، يكون eval أبطأ فعليًا (عطل بدء تشغيل المحرك). قاعدة عامة: eval يستحق الاستخدام فقط للعمليات المركبة مع 4+ أعمدة على مجموعات بيانات كبيرة.
س هل يوفر inplace ذاكرة فعلًا؟
ج في Pandas 2.x، لا يوفر inplace عادةً ذاكرة - داخليًا قد يظل يُنشئ نسخة مؤقتة قبل التعيين. تحت وضع Copy-on-Write في Pandas 3.x، يكون لكل من inplace والعمليات غير inplace نفس الأحمال الذاكرة تقريبًا. التوصية: تجنب inplace؛ استخدم نمط التعيين df = df.method() لدلالات أوضح.
س ما هو Copy-on-Write؟
ج CoW هو السلوك الافتراضي في Pandas 3.x - البيانات تشترك في الذاكرة الأساسية، ولا يتم إنشاء نسخة إلا عند حدوث تعديل. الفوائد: الشرائح لا تنسخ (توفر الذاكرة)، التعيين المتسلسل آمن (لا تحذير SettingWithCopyWarning). التأثير: بعض عمليات inplace تتصرف بشكل مختلف وتتطلب إعادة التعيين.
س ماذا عن الملفات الكبيرة للغاية؟
ج استراتيجية من ثلاث طبقات: ① chunksize للقراءة المجزأة + التجميع (مثالي للإحصائيات القابلة للقسمة)؛ ② downcast/category لتقليل ذاكرة كل صف (تسعير المزيد من الصفوف في الذاكرة)؛ ③ تبديل الأدوات - Dask (واجهة Pandas الموزعة)، Polars (إطار بيانات فائق السرعة مكتوب بلغة Rust)، PySpark (على مستوى المجموعة). حد ذاكرة جهاز واحد هو تقريبًا 3-5 أضعاف حجم البيانات.
س هل apply أبطأ دائمًا من التجسيد؟
ج في معظم الحالات، نعم - apply يستدعي دالة Python صفًا بصف، بينما يتم تنفيذ التجسيد على مستوى C. الاستثناء النادر: عندما يتطلب التجسيد إنشاء مصفوفات وسيطة ضخمة متعددة، قد توفر apply ذاكرة (ولكن ليس وقتًا). الاستراتيجية: فضّل التجسيد أولًا، وجرّب eval إذا كانت الأداء غير كافٍ، واستخدم apply كملاذ أخير فقط.
س كيف تستخدم memory_usage؟
ج df.memory_usage(deep=True) تُعيد عدد البايتات لكل عمود. deep=True تحسب ذاكرة النصوص الفعلية لأعمدة الكائنات ( وإلا فتحسب أحجام المؤشرات فقط). الإجمالي: df.memory_usage(deep=True).sum(). استخدم هذه الوظيفة للمقارنة قبل وبعد التحسين. أعمدة الفئة (category) تخزن فقط الأكواد الصحيحة + قاموس، مما يستخدم ذاكرة أقل بكثير من الكائنات (كائن).

📖 ملخص


📝 تمارين

  1. أساسي (صعوبة ⭐): أنشئ إطار بيانات يحتوي على 10 آلاف صف، ثم احسب عمودًا جديدًا باستخدام كل من apply والتجسيد المتجهي (vectorization)، وقارن بين استهلاك الذاكرة (memory_usage).
  2. متوسط (صعوبة ⭐⭐): أنشئ إطار بيانات يحتوي على 100 ألف صف مع أعمدة كائنات (كائن)، ثم قم بتحسين استهلاك الذاكرة باستخدام category والتقليص (downcast)، واحسب نسبة التوفير.
  3. تحدي (صعوبة ⭐⭐⭐): أنشئ إطار بيانات يحتوي على 1 مليون صف (مع أعمدة من أنواع category/float/int)، ثم قم بما يلي: حساب متجهي → حساب باستخدام eval → مقارنة النتائج → تتبع استهلاك الذاكرة (memory_usage) على مراحل → إنتاج تقرير تحسين كامل.

← Previous: Visualization · Next: Styled Output →

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%