0Pricing
Pandas & NumPy Academy · درس

التجميع التدريجي عبر الدفعات

راكم الأعداد والمجاميع والقيمتين الدنيا والعليا الجارية عبر الدفعات دون تخزين الملف كاملًا في الذاكرة.

التجميع التدريجي عبر الدفعات درس مجاني في Pandas & NumPy Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Pandas & NumPy Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Pandas & NumPy Academy 4 دروس في المجموع.

لماذا التجميع التزايدي؟

يُعد التجميع التزايدي المفتاح لتحليل مجموعات البيانات الأكبر من ذاكرة الوصول العشوائي، من دون توزيع الحسابات على عدة أجهزة. فبدلًا من تحميل جميع البيانات لحساب إحصائية نهائية، تحتفظ بقِيَم تراكمية — مثل المجاميع الجزئية والأعداد والقيم الصغرى/الكبرى — وتحدّثها مع كل دفعة. ثم تُنشأ النتيجة النهائية من هذه القيم التراكمية الخفيفة بعد فحص الملف بالكامل. ويمكن لهذا النمط التعامل مع ملفات بحجم تيرابايت واحد على حاسوب محمول واحد.

عدّ الصفوف وحساب المتوسط

يتطلب حساب المتوسط عبر الدفعات تتبع المجموع والعدد التراكميين بشكل منفصل. ولا يمكنك ببساطة حساب متوسط متوسطات الدفعات، لأن أحجام الدفعات قد تختلف. والصيغة الصحيحة هي total_sum / total_count. ويمتد هذا النمط إلى أي كمية يمكن تفكيكها، إذ توجد صيغ تزايدية للتباين والارتباط والمدرجات التكرارية أيضًا.

import pandas as pd

total_sum = 0.0
total_count = 0

for chunk in pd.read_csv('transactions.csv', chunksize=100000):
    total_sum += chunk['amount'].sum()
    total_count += chunk['amount'].notna().sum()

grand_mean = total_sum / total_count
print(f'Rows processed: {total_count:,}')
print(f'Grand mean: {grand_mean:.4f}')

الحد الأدنى والأقصى التزايديان

إن تتبع الحد الأدنى والحد الأقصى العامين عبر الدفعات أمر مباشر: ابدأ بالقيمتين float('inf') وfloat('-inf') في Python، ثم حدّثهما باستخدام الحد الأدنى/الأقصى لكل دفعة. ويؤدي ذلك إلى تجنب أي تخزين وسيط. ويمكن تعميم هذا النمط على الحد الأدنى/الأقصى لكل مجموعة عبر الاحتفاظ بقاموس يكون معرّف المجموعة مفتاحًا له.

import pandas as pd

global_min = float('inf')
global_max = float('-inf')

for chunk in pd.read_csv('prices.csv', chunksize=50000):
    chunk_min = chunk['price'].min()
    chunk_max = chunk['price'].max()
    if chunk_min < global_min:
        global_min = chunk_min
    if chunk_max > global_max:
        global_max = chunk_max

print(f'Price range: {global_min} to {global_max}')

حساب التكرارات بشكل تزايدي

بالنسبة إلى الأعمدة الفئوية، احتفظ بقاموس تكرارات تراكمي عبر إضافة نتيجة value_counts() لكل دفعة إلى مُجمِّع من نوع Pandas Series. وبما أن جمع Pandas Series يحاذي تسميات الفهارس، تُدرج الفئات غير المعروفة في الدفعات اللاحقة تلقائيًا. وبعد معالجة جميع الدفعات، رتّب حسب العدد لرؤية الفئات الأكثر شيوعًا عبر مجموعة البيانات بأكملها.

import pandas as pd

freq = pd.Series(dtype='int64')

for chunk in pd.read_csv('orders.csv',
                         chunksize=100000,
                         usecols=['category']):
    chunk_counts = chunk['category'].value_counts()
    freq = freq.add(chunk_counts, fill_value=0)

# Final sorted frequency table
print(freq.sort_values(ascending=False).head(10))

تجميع GroupBy بشكل تزايدي

لحساب مجموع أو عدد groupby عبر الدفعات، طبّق groupby().agg() داخل كل دفعة، وخزّن Series أو DataFrame الناتج. وبعد انتهاء الحلقة، ادمج جميع النتائج الجزئية وطبّق groupby ثانيًا لدمجها. ويتعامل هذا النهج ذو المرحلتين بشكل صحيح مع المجموعات التي تظهر في دفعات متعددة، وهو أمر شائع عندما تكون البيانات مرتبة حسب التاريخ بدلًا من المجموعة.

import pandas as pd

partials = []
for chunk in pd.read_csv('sales.csv',
                         chunksize=100000,
                         usecols=['region', 'product', 'revenue']):
    p = chunk.groupby(['region', 'product'])['revenue'].sum()
    partials.append(p)

final = (
    pd.concat(partials)
    .groupby(level=['region', 'product'])
    .sum()
    .sort_values(ascending=False)
)
print(final.head(10))

حساب التباين بشكل تزايدي (طريقة Welford)

يُعد حساب التباين عبر الدفعات أكثر تعقيدًا من حساب المتوسط. فالصيغة الساذجة E[X²] - E[X]² تعاني من فقدان كارثي للدقة عند التعامل مع متوسطات كبيرة. وتحافظ خوارزمية Welford التكرارية على متوسط تراكمي ومجموع الانحرافات التربيعية، وتحدّثهما مع كل قيمة بطريقة مستقرة عدديًا. وعلى الرغم من أن SciPy تنفذ هذه الخوارزمية، فإن فهم هذا النمط يتيح لك توسيعه ليشمل التباين الموزون والتغاير.

import pandas as pd
import numpy as np

# Simple two-pass approach using stored chunk stats
chunk_stats = []
for chunk in pd.read_csv('data.csv',
                         chunksize=100000,
                         usecols=['value']):
    n = chunk['value'].count()
    mean = chunk['value'].mean()
    var = chunk['value'].var(ddof=1)
    chunk_stats.append((n, mean, var))

# Combine: use pooled variance formula
total_n = sum(s[0] for s in chunk_stats)
total_mean = sum(s[0]*s[1] for s in chunk_stats) / total_n
pooled_var = sum((s[0]-1)*s[2] + s[0]*(s[1]-total_mean)**2
                 for s in chunk_stats) / (total_n - 1)
print(f'Grand variance: {pooled_var:.4f}')

إنشاء مدرج تكراري تزايدي

يتطلب حساب توزيع عمود عبر ملف أكبر من سعة ذاكرة الوصول العشوائي إنشاء مدرج تكراري تزايدي. ثبّت حدود الفئات مسبقًا (استنادًا إلى عينة صغيرة أو إلى معرفة بالمجال)، ثم استخدم np.histogram(chunk_values, bins=edges) داخل كل دفعة وتراكم الأعداد. وفي النهاية، ارسم الأعداد المجمعة في صورة مخطط أعمدة. وهذه هي الطريقة التي تحسب بها أنظمة البث مثل Kafka Streams وFlink مدرجات تكرارية تقريبية.

import pandas as pd
import numpy as np

# Decide bin edges from a sample
sample = pd.read_csv('amounts.csv', nrows=5000)
bins = np.linspace(sample['amount'].min(),
                   sample['amount'].max(), 21)
counts = np.zeros(len(bins) - 1, dtype='int64')

for chunk in pd.read_csv('amounts.csv',
                         chunksize=100000,
                         usecols=['amount']):
    chunk_counts, _ = np.histogram(
        chunk['amount'].dropna(), bins=bins
    )
    counts += chunk_counts

print('Histogram counts:', counts[:5], '...')

تتبع القيم الفريدة بشكل تقريبي

يتطلب حساب عدد القيم المميزة بدقة عبر الدفعات تخزين جميع القيم الفريدة، وقد يصل عددها إلى الملايين. ولحسابات تقريبية على نطاق واسع، استخدم بنية HyperLogLog المتاحة في Python عبر مكتبة hyperloglog. وبديلًا عن ذلك، يمكنك تتبع القيم الفريدة لكل دفعة باستخدام مجموعة set ثم حساب اتحادها، لكن هذا الاتحاد ينمو بلا حدود. وللحصول على تقدير تقريبي منخفض التكلفة، استخدم pd.Series.nunique() لكل دفعة وأبلغ عن المتوسط — وهو ليس دقيقًا، لكنه غالبًا ما يكون كافيًا لتوصيف البيانات.

import pandas as pd

unique_ids = set()
for chunk in pd.read_csv('events.csv',
                         chunksize=100000,
                         usecols=['user_id']):
    unique_ids.update(chunk['user_id'].dropna().unique())

print(f'Distinct user IDs: {len(unique_ids):,}')
# Warning: the set may grow large for high-cardinality columns

إظهار التقدم أثناء عمليات التشغيل الطويلة

قد تستغرق معالجة ملف بحجم عدة غيغابايت دقائق. أضف إظهارًا للتقدم حتى تعرف أن خط الأنابيب قيد التشغيل وتتمكن من تقدير الوقت المتبقي. احسب عدد وحدات البايت أو الصفوف التي عولجت، وقارنها بحجم الملف. وتجعل مكتبة tqdm ذلك أمرًا بسيطًا باستخدام غلافها tqdm(reader). وحتى من دون tqdm، فإن طباعة سطر حالة كل 10 دفعات توفر معلومات مفيدة أثناء مهام المعالجة الدفعية الطويلة.

import pandas as pd
import time

chunksize = 100000
start = time.time()
rows_processed = 0

for i, chunk in enumerate(pd.read_csv('big.csv',
                                       chunksize=chunksize)):
    rows_processed += len(chunk)
    # Report every 10 chunks
    if (i + 1) % 10 == 0:
        elapsed = time.time() - start
        rate = rows_processed / elapsed
        print(f'Chunk {i+1}: {rows_processed:,} rows '
              f'@ {rate/1000:.0f}k rows/sec')

print(f'Total: {rows_processed:,} rows in {time.time()-start:.1f}s')

التصفية قبل التجميع

طبّق عوامل التصفية داخل كل دفعة قبل التجميع لتجنب تراكم البيانات غير المطلوبة. فعلى سبيل المثال، إذا كنت تهتم فقط بالطلبات التي تعود إلى عام 2024، فصفِّ عمود التاريخ في الدفعة قبل تنفيذ groupby. ويقلل ذلك الذاكرة المطلوبة للنتائج الجزئية، كما يسرّع خطوة الدمج النهائية. احرص دائمًا على تطبيق عوامل التصفية في أقرب مرحلة ممكنة من بداية خط الأنابيب — فهذا مبدأ أساسي في معالجة البيانات بكفاءة.

import pandas as pd

partials = []
for chunk in pd.read_csv('orders.csv',
                         chunksize=100000,
                         parse_dates=['order_date']):
    # Filter early: only 2024 orders
    mask = chunk['order_date'].dt.year == 2024
    filtered = chunk.loc[mask, ['category', 'revenue']]

    if len(filtered) > 0:
        p = filtered.groupby('category')['revenue'].sum()
        partials.append(p)

if partials:
    result = pd.concat(partials).groupby(level=0).sum()
    print(result)

حفظ النتائج الوسيطة

بالنسبة إلى المهام التي تستغرق وقتًا طويلًا جدًا، احفظ النتائج الوسيطة دوريًا حتى تتمكن من الاستئناف من نقطة حفظ إذا توقفت العملية. اكتب التجميعات الخاصة بكل مجموعة في ملف Parquet أو CSV بعد كل N مجموعات. فإذا فشلت المهمة عند المجموعة 800 من أصل 1000، يمكنك إعادة تحميل التجميعات المحفوظة والمتابعة من حيث توقفت بدلًا من إعادة معالجة الملف بأكمله. يُعد نمط المرونة هذا أساسيًا في مسارات معالجة البيانات الإنتاجية.

import pandas as pd
import os

CHECKPOINT = 'checkpoint.csv'
running_total = 0.0
running_count = 0

# Resume from checkpoint if it exists
if os.path.exists(CHECKPOINT):
    ckpt = pd.read_csv(CHECKPOINT)
    running_total = ckpt['total'].iloc[0]
    running_count = int(ckpt['count'].iloc[0])
    print(f'Resuming from checkpoint: {running_count:,} rows')

for chunk in pd.read_csv('huge.csv', chunksize=100000):
    running_total += chunk['value'].sum()
    running_count += len(chunk)

# Save checkpoint
pd.DataFrame({'total': [running_total],
              'count': [running_count]}).to_csv(CHECKPOINT, index=False)
print(f'Final mean: {running_total / running_count:.4f}')

تحقق سريع

اختبر مدى فهمك لمفاهيم تحليل البيانات التي تناولها هذا الدرس.

مراجعة الدرس

تعلمت في هذا الدرس أن المراكمات الجارية (المجموع، والعدد، والحد الأدنى/الأقصى، وSeries التكرارات) تتيح إجراء التجميع باستخدام ذاكرة ثابتة عبر الملفات الكبيرة، وأن التجميع على مرحلتين باستخدام groupby (إجراء groupby جزئي لكل مجموعة، ثم الدمج وإعادة التجميع) يتعامل مع المجموعات الممتدة عبر عدة مجموعات على نحو صحيح، وأن التصفية المبكرة داخل كل مجموعة تقلل تكلفة خطوة التجميع. بعد ذلك سنستكشف Dask DataFrames بوصفها بديلًا متوازيًا مباشرًا لـ Pandas عند التعامل مع مجموعات البيانات الكبيرة.

الأسئلة الشائعة

هل درس «التجميع التدريجي عبر الدفعات» مجاني؟

نعم — نص درس «التجميع التدريجي عبر الدفعات» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Pandas & NumPy Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Pandas & NumPy Academy 4 دروس في المجموع.

ماذا ستتعلم في «التجميع التدريجي عبر الدفعات»؟

راكم الأعداد والمجاميع والقيمتين الدنيا والعليا الجارية عبر الدفعات دون تخزين الملف كاملًا في الذاكرة. تتمرن على Pandas & NumPy Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Pandas & NumPy Academy؟

لا تُشترط خبرة سابقة. Pandas & NumPy Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «التجميع التدريجي عبر الدفعات»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Pandas & NumPy Academy هذا؟

نعم. كل درس في Pandas & NumPy Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. بث CSV باستخدام chunksize
  2. التجميع التدريجي عبر الدفعات
  3. مقدمة إلى Dask DataFrames
  4. Parquet: تخزين عمودي سريع
← العودة إلى Pandas & NumPy Academy