Pandas & NumPy Academy · درس

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

عالج الملفات التي تتجاوز سعة RAM بقراءتها على دفعات باستخدام chunksize في read_csv، وتجميع النتائج تدريجيًا.

الدرس 4 من 413 خطوة

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

عندما تتجاوز الملفات سعة RAM

من الاختناقات الشائعة في مسارات معالجة البيانات في بيئة الإنتاج وجود ملف CSV أكبر من سعة RAM المتاحة. فإذا كان حجم الملف 20 GB وكانت ذاكرة RAM في الجهاز 16 GB، فسيتعطل pd.read_csv('file.csv') بسبب MemoryError. والحل هو القراءة على دفعات: تحميل الملف على أجزاء ثابتة الحجم، ومعالجة كل جزء، وتجميع النتائج. يتيح لك ذلك تحليل ملفات كبيرة بلا حدود تقريبًا من دون تحميلها كاملةً إلى الذاكرة.

import pandas as pd
import numpy as np

# Simulate a large CSV by writing one
np.random.seed(0)
sample = pd.DataFrame({
    'date': pd.date_range('2023-01-01', periods=100000, freq='h'),
    'region': np.random.choice(['North','South','East','West'], 100000),
    'sales': np.random.randint(100, 1000, 100000)
})
sample.to_csv('/tmp/large_sales.csv', index=False)
print(f'File size: {pd.io.common.get_handle("/tmp/large_sales.csv", "r").handle.seek(0, 2)/1e6:.1f} MB... (simulated)')
print('Rows:', len(sample))

معامل chunksize في read_csv

مرّر chunksize=n إلى pd.read_csv() لإرجاع مكرّر TextFileReader بدلًا من DataFrame. تُرجع كل عملية تكرار الصفوف التالية وعددها n على شكل DataFrame. وبهذه الطريقة لا يوجد في الذاكرة في كل مرة سوى n صفًا. وتُعد قيمة 100,000 صف نقطة بداية جيدة لـ chunksize — فهي صغيرة بما يكفي لتلائم RAM، وكبيرة بما يكفي للحفاظ على تكاليف عمليات الإدخال والإخراج ضمن حدود معقولة. اضبط القيمة وفقًا لسعة RAM المتاحة وعدد الأعمدة.

import pandas as pd

# Read file in chunks of 25,000 rows
chunk_iter = pd.read_csv('/tmp/large_sales.csv', chunksize=25000)

# Peek at the first chunk
first_chunk = next(chunk_iter)
print('First chunk shape:', first_chunk.shape)
print(first_chunk.head(3))

التكرار على الدفعات

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

import pandas as pd

total_rows = 0
total_sales = 0.0
chunk_count = 0

for chunk in pd.read_csv('/tmp/large_sales.csv', chunksize=25000):
    total_rows += len(chunk)
    total_sales += chunk['sales'].sum()
    chunk_count += 1

print(f'Chunks processed: {chunk_count}')
print(f'Total rows: {total_rows:,}')
print(f'Total sales: {total_sales:,.0f}')
print(f'Avg sales per row: {total_sales/total_rows:.2f}')

تصفية الصفوف أثناء القراءة على دفعات

طبّق مرشحات الصفوف داخل الحلقة لاستبعاد البيانات غير المطلوبة قبل تراكمها. يحافظ ذلك على انخفاض استخدام الذاكرة، إذ لا يتم الاحتفاظ إلا بالصفوف المطابقة لمعاييرك. على سبيل المثال، لاستخراج جميع صفوف منطقة 'North' من ملف حجمه 10 GB، رشّح كل دفعة قبل إضافتها إلى قائمة النتائج. عندئذٍ لا يتعامل pd.concat النهائي إلا مع المجموعة المصفّاة، وهي أصغر بكثير.

import pandas as pd

filtered_chunks = []

for chunk in pd.read_csv('/tmp/large_sales.csv', chunksize=25000):
    # Keep only North region rows
    north = chunk[chunk['region'] == 'North']
    if len(north) > 0:
        filtered_chunks.append(north)

north_df = pd.concat(filtered_chunks, ignore_index=True)
print(f'North region rows: {len(north_df):,}')
print(f'North total sales: {north_df["sales"].sum():,.0f}')

تجميع GroupBy التزايدي

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

import pandas as pd

partials = []

for chunk in pd.read_csv('/tmp/large_sales.csv', chunksize=25000):
    # Compute sum and count per region in this chunk
    partial = chunk.groupby('region')['sales'].agg(['sum', 'count'])
    partials.append(partial)

# Combine all partial results
combined = pd.concat(partials).groupby(level=0).sum()
combined['mean'] = combined['sum'] / combined['count']

print('Regional aggregation (chunked):')
print(combined.round(2))

تحديد أنواع موفّرة للذاكرة عند التحميل

قلّل الذاكرة أثناء التحميل على دفعات بتحديد dtypes للأعمدة مسبقًا في read_csv. يمنع ذلك Pandas من تخصيص أنواع واسعة لكل دفعة ثم التخلص منها. مرّر قاموسًا مثل dtype={'region': 'category', 'sales': 'int32'} إلى read_csv(). وبالاقتران مع القراءة على دفعات، يمكن أن يقلل ذلك ذروة استخدام الذاكرة إلى أقل من 10% من التحميل الساذج للملف كاملًا.

import pandas as pd

specified_dtypes = {
    'region': 'category',
    'sales': 'int32'
}

total_sales = 0
for chunk in pd.read_csv(
    '/tmp/large_sales.csv',
    chunksize=25000,
    dtype=specified_dtypes,
    parse_dates=['date']
):
    total_sales += chunk['sales'].sum()
    # Only 25k rows * (category + int32 + datetime) in RAM at once

print(f'Total sales (memory-efficient): {total_sales:,.0f}')

كتابة النتائج تدريجيًا

عندما تحتاج أيضًا إلى كتابة ناتج معالجة كل دفعة في ملف، اكتب كل دفعة معالجة فورًا بدلًا من تجميع كل شيء في الذاكرة. استخدم mode='a' (الإلحاق) وheader=False (بعد الدفعة الأولى) مع to_csv(). يحافظ ذلك على بصمة ذاكرة الإدخال والإخراج عند حجم الدفعة، ما يجعل مسار المعالجة قادرًا على معالجة ملفات كبيرة بلا حدود تقريبًا على جهاز محدود الذاكرة.

import pandas as pd
import os

output_path = '/tmp/filtered_output.csv'

# Remove previous output if it exists
if os.path.exists(output_path):
    os.remove(output_path)

first_chunk = True
for chunk in pd.read_csv('/tmp/large_sales.csv', chunksize=25000):
    # Process: keep only high-value rows
    processed = chunk[chunk['sales'] > 800].copy()
    
    # Write: header only on first chunk, append thereafter
    processed.to_csv(output_path,
                     mode='a',
                     header=first_chunk,
                     index=False)
    first_chunk = False

result = pd.read_csv(output_path)
print(f'High-value rows written: {len(result):,}')
print(f'Average sales (>800): {result["sales"].mean():.1f}')

مقدمة إلى Dask كبديل مباشر

يوفّر Dask واجهة برمجة شبيهة بـ Pandas، وينفّذ العمليات بشكل كسول ومتوازٍ عبر الدفعات تلقائيًا. بدلًا من كتابة حلقة يدوية للدفعات، اكتب dask_df = dd.read_csv('file.csv') ثم استخدم عمليات Pandas نفسها — مثل groupby وfilter وmerge. استدعِ .compute() في النهاية لبدء التنفيذ. يُعد Dask الحل العملي الأنسب عندما يتضمن مسار المعالجة عمليات معقدة متعددة الخطوات يصعب تنفيذها يدويًا باستخدام الدفعات.

# pip install dask
# import dask.dataframe as dd

# Read the large CSV as a Dask DataFrame (lazy — no data loaded yet)
# ddf = dd.read_csv('/tmp/large_sales.csv')

# Operations are lazy — no computation happens until .compute()
# result = ddf.groupby('region')['sales'].mean().compute()
# print(result)

# Key Dask advantages:
# - Automatic chunking (no manual chunksize loops)
# - Parallel execution (multi-core)
# - Familiar Pandas API
# - Works with files larger than RAM

print('Dask extends Pandas to datasets larger than RAM with minimal API changes.')

اختيار حجم الدفعة

يوازن حجم الدفعة المثالي بين عاملين متعارضين: الحجم الصغير جدًا (مثل 1,000 صف) يسبب تكلفة إضافية كبيرة لكل دفعة، مثل إعداد إدخال وإخراج الملف وإنشاء DataFrame، فتستغرق الحلقة وقتًا أطول. أما الحجم الكبير جدًا (مثل 10M صف) فيقوّض الهدف من القراءة على دفعات بتحميل كمية كبيرة من البيانات إلى RAM دفعةً واحدة. نقطة بداية عملية هي استهداف دفعات يتراوح حجمها في الذاكرة بين 50 و200 MB. في DataFrame يضم 10 أعمدة، بمتوسط 8 بايتات لكل قيمة، تعادل 100,000 صف نحو 8 MB لكل دفعة — وهو افتراض آمن يترك مساحة كافية.

import pandas as pd
import timeit

# Benchmark different chunk sizes
for chunksize in [10000, 50000, 100000]:
    t = timeit.timeit(
        lambda: sum(chunk['sales'].sum()
                    for chunk in pd.read_csv('/tmp/large_sales.csv', chunksize=chunksize)),
        number=3
    )
    print(f'chunksize={chunksize:>7,}: {t/3:.3f}s per pass')

Parquet مقابل CSV للبيانات الكبيرة

إذا كنت تتحكم في تنسيق الملف، ففضّل Parquet على CSV لمجموعات البيانات الكبيرة. Parquet تنسيق ثنائي عمودي يقوم بما يلي: يحمّل الأعمدة المطلوبة فقط (استبعاد الأعمدة غير المطلوبة)، ويضغط البيانات بكفاءة أكبر بكثير من CSV، ويحافظ على أنواع البيانات (من دون إعادة استنتاجها عند التحميل)، ويقرأ البيانات بسرعة أكبر من CSV المكافئ بمقدار يتراوح بين 5 و20 مرة. حوّل ملف CSV كبيرًا إلى Parquet مرة واحدة باستخدام pd.read_csv('file.csv', chunksize=500000) + to_parquet، ثم استخدم pd.read_parquet(columns=['col1','col2']) في جميع عمليات القراءة اللاحقة.

import pandas as pd

# Convert our CSV to Parquet (do this once)
df = pd.read_csv('/tmp/large_sales.csv', parse_dates=['date'])
df['region'] = df['region'].astype('category')
df['sales'] = df['sales'].astype('int32')
df.to_parquet('/tmp/large_sales.parquet', index=False)

# Now read only the columns needed — much faster than CSV
sales_by_region = pd.read_parquet(
    '/tmp/large_sales.parquet',
    columns=['region', 'sales']
)
print('Parquet read result:')
print(sales_by_region.groupby('region')['sales'].mean().round(1))
print('\ndtypes preserved:', sales_by_region.dtypes.to_dict())

النمط الكامل لمسار المعالجة على دفعات

نمط متكامل لمعالجة الملفات الكبيرة: 1) حدّد أنواع البيانات وقت القراءة. 2) رشّح الصفوف في أقرب وقت ممكن داخل الحلقة. 3) احسب التجميعات الجزئية لكل دفعة (الجمع + العدد، وليس المتوسط مباشرةً). 4) بعد انتهاء الحلقة، ادمج النتائج الجزئية واحسب الإحصاءات النهائية. 5) اكتب الناتج تدريجيًا إذا كانت النتائج كبيرة. يتعامل هذا النمط مع الملفات مهما كان حجمها وعلى أي جهاز، مع ضبط chunksize بما يناسب.

تحقق سريع

اختبر مدى فهمك للقراءة على دفعات التي تناولها هذا الدرس.

مراجعة الدرس

تعلّمت في هذا الدرس أن chunksize in read_csv يعيد مكرّرًا يُرجع DataFrame واحدًا لكل دفعة، ما يتيح معالجة ملفات أكبر من RAM بشكل تدريجي، وأن partial aggregations (الجمع + العدد) تتراكم بصورة صحيحة عبر الدفعات، بينما لا يمكن حساب المتوسط بأخذ متوسطات الدفعات مباشرةً، وأن Parquet format هو البديل الأفضل على المدى الطويل لـ CSV مع مجموعات البيانات الكبيرة بفضل الضغط والسرعة والحفاظ على أنواع البيانات. وبذلك تكتمل دورة نصائح تحسين أداء Pandas — وأنت مستعد لدورات B2 المتقدمة!

البدء مجانًا

تعلم Python مع معلم ذكاء اصطناعي — مجانًا

اكتب وقم بتشغيل أكوادك الفعلية في المتصفح، واحصل على مساعدة فورية من معلم ذكاء اصطناعي متاح 24/7، واستمر من حيث توقفت على الويب أو في التطبيق.

الدورات
30
الدروس
120

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

هل درس «القراءة على دفعات للملفات الكبيرة» مجاني؟

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

ماذا ستتعلم في «القراءة على دفعات للملفات الكبيرة»؟

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

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

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

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

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

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

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

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

  1. التوصيف باستخدام timeit وmemory_profiler
  2. تجنب iterrows وحلقات Python
  3. أنواع بيانات فعّالة لتقليل الذاكرة
  4. القراءة على دفعات للملفات الكبيرة
← العودة إلى Pandas & NumPy Academy