تقسيم ملف Excel إلى عدة ملفات حسب قيمة عمود باستخدام بايثون وPandas

تقسيم ملف Excel إلى عدة ملفات حسب قيمة عمود باستخدام بايثون

لتقسيم ملف Excel إلى ملف مستقل لكل قيمة موجودة في عمود، اقرأ الملف باستخدام Pandas ثم استخدم groupby()، ومرّ على كل مجموعة واحفظها باستخدام to_excel().

أبسط نسخة:

import pandas as pd

df = pd.read_excel("sales.xlsx")

for city, group in df.groupby("City"):
    group.to_excel(
        f"{city}.xlsx",
        index=False
    )

إذا كانت قيم عمود City هي Aden وTaiz وSana'a، سينتج ملف لكل مدينة. هذه النسخة توضح الفكرة فقط؛ لاحقًا سنبني سكربتًا أكثر أمانًا يتعامل مع القيم الفارغة، أسماء الملفات غير الصالحة، تضارب الأسماء، وتشغيل السكربت أكثر من مرة.

{alertWarning} قبل استخدام السكربت على ملف مهم، احتفظ بنسخة من ملف Excel الأصلي. هذا الحل ينشئ ملفات جديدة، لكنه يظل عملية معالجة ملفات ويجب تجربته أولًا على نسخة اختبارية.

{getToc} $title={محتوى المقال}

مثال Before / After

لنفترض أن الملف sales.xlsx يحتوي:

OrderCityAmount
001Aden120
002Taiz80
003Aden150
004Sana'a200
005Taiz90

بعد التشغيل نريد:

split_files/
├── Aden.xlsx
├── Taiz.xlsx
└── Sana'a.xlsx

ملف Aden.xlsx يحتوي صفوف Aden فقط، وTaiz.xlsx يحتوي صفوف Taiz، وهكذا.

مثال تقسيم ملف sales Excel إلى ملفات حسب المدينة

تثبيت Pandas وopenpyxl

سنستخدم Pandas لمعالجة البيانات وopenpyxl لقراءة وكتابة ملفات .xlsx:

python -m pip install pandas openpyxl

إذا كنت جديدًا على تثبيت الحزم، راجع شرح pip وتثبيت مكتبات بايثون.

قراءة ملف Excel

import pandas as pd

df = pd.read_excel(
    "sales.xlsx"
)

print(df.head())

إذا كان الملف في مسار آخر، استخدم المسار الصحيح. وإذا كانت البيانات في Sheet محددة:

df = pd.read_excel(
    "sales.xlsx",
    sheet_name="Sales"
)

المقال يفترض أن البيانات التي ستقسمها موجودة في Sheet واحدة؛ تقسيم جميع Sheets داخل Workbook مهمة مختلفة.

تحقق من أسماء الأعمدة أولًا

print(df.columns)

هذا مهم لأن City ليست نفسها city، وقد يحتوي اسم العمود على مسافة مثل "Sales Region". وتعمل groupby("Sales Region") طبيعيًا؛ اسم العمود لا يحتاج أن يكون كلمة واحدة.

لماذا نستخدم groupby()؟

بدل كتابة فلتر يدوي لكل قيمة:

aden = df[
    df["City"] == "Aden"
]

taiz = df[
    df["City"] == "Taiz"
]

استخدم:

for value, group in df.groupby(
    "City"
):
    print(value)
    print(group)

groupby() تنشئ مجموعة لكل قيمة موجودة مهما كان عدد المدن أو الأقسام أو الموظفين.

طريقة استخدام groupby لتقسيم Excel حسب قيمة العمود في Pandas

إنشاء مجلد منفصل للملفات الناتجة

from pathlib import Path

output_dir = Path(
    "split_files"
)

output_dir.mkdir(
    exist_ok=True
)

بهذا تبقى الملفات الناتجة منظمة داخل مجلد واحد بدل انتشارها بجانب السكربت.

التعامل مع الخلايا الفارغة في عمود التقسيم

بشكل افتراضي قد تُستبعد القيم المفقودة من بعض عمليات التجميع. هنا نستخدم:

df.groupby(
    "City",
    dropna=False,
    sort=False
)

dropna=False تُبقي مجموعة للخلايا الفارغة، وسنسمي ملفها blank.xlsx. أما sort=False فيحافظ على ترتيب ظهور المجموعات قدر الإمكان بدل إعادة ترتيبها حسب قيمة المفتاح.

تنظيف أسماء الملفات غير الصالحة

على Windows توجد محارف لا تصلح داخل اسم الملف مثل / و: و* و?. لذلك لا نستخدم قيمة العمود مباشرة بلا فحص.

import re

def safe_filename(value):
    if pd.isna(value):
        return "blank"

    name = str(value).strip()

    name = re.sub(
        r'[<>:"/\\|?*]',
        "_",
        name,
    )

    name = name.rstrip(
        " ."
    )

    return name or "blank"

مثلًا A/B تصبح A_B. والسكربت النهائي يتعامل أيضًا مع أسماء Windows المحجوزة مثل CON وPRN.

منع تضارب أسماء الملفات

قد تتحول قيمتان مختلفتان إلى الاسم نفسه بعد التنظيف. مثلًا:

  • A/BA_B
  • A:BA_B

لذلك لا نسمح للملف الثاني بالكتابة فوق الأول. سيصبح الناتج مثل:

A_B.xlsx
A_B_2.xlsx

وعند تشغيل السكربت مرة ثانية، إذا كان الملف موجودًا بالفعل، نختار اسمًا جديدًا بدل استبداله بصمت.

السكربت النهائي الآمن

النسخة التالية تجمع الفحوصات الأساسية المطلوبة لهذه المهمة:

from pathlib import Path
import re

import pandas as pd


INPUT_FILE = Path("sales.xlsx")
OUTPUT_DIR = Path("split_files")
COLUMN_NAME = "City"
SHEET_NAME = 0


WINDOWS_RESERVED_NAMES = {
    "CON",
    "PRN",
    "AUX",
    "NUL",
    "COM1",
    "COM2",
    "COM3",
    "COM4",
    "COM5",
    "COM6",
    "COM7",
    "COM8",
    "COM9",
    "LPT1",
    "LPT2",
    "LPT3",
    "LPT4",
    "LPT5",
    "LPT6",
    "LPT7",
    "LPT8",
    "LPT9",
}


def safe_filename(value):
    if pd.isna(value):
        return "blank"

    name = str(value).strip()

    name = re.sub(
        r'[<>:"/\\|?*]',
        "_",
        name,
    )

    name = name.rstrip(" .")

    if not name:
        name = "blank"

    first_part = name.split(".", 1)[0].upper()

    if first_part in WINDOWS_RESERVED_NAMES:
        name = f"_{name}"

    return name


def next_available_path(
    output_dir,
    base_name,
    used_paths,
):
    candidate = (
        output_dir
        / f"{base_name}.xlsx"
    )

    number = 2

    while (
        candidate in used_paths
        or candidate.exists()
    ):
        candidate = (
            output_dir
            / f"{base_name}_{number}.xlsx"
        )
        number += 1

    used_paths.add(candidate)

    return candidate


if not INPUT_FILE.is_file():
    raise FileNotFoundError(
        f"File not found: {INPUT_FILE}"
    )


df = pd.read_excel(
    INPUT_FILE,
    sheet_name=SHEET_NAME,
)


if COLUMN_NAME not in df.columns:
    raise ValueError(
        f"Column not found: {COLUMN_NAME}. "
        f"Available columns: "
        f"{list(df.columns)}"
    )


OUTPUT_DIR.mkdir(
    parents=True,
    exist_ok=True,
)


used_paths = set()
created_files = []


for value, group in df.groupby(
    COLUMN_NAME,
    dropna=False,
    sort=False,
):
    base_name = safe_filename(
        value
    )

    output_file = next_available_path(
        OUTPUT_DIR,
        base_name,
        used_paths,
    )

    group.to_excel(
        output_file,
        index=False,
    )

    created_files.append(
        output_file
    )

    print(
        f"Created: {output_file} "
        f"({len(group)} rows)"
    )


print(
    f"Created {len(created_files)} files."
)

ماذا يفعل السكربت النهائي؟

  • يتأكد أن ملف الإدخال موجود.
  • يقرأ Sheet المحددة.
  • يتأكد أن العمود موجود ويعرض الأعمدة المتاحة عند الخطأ.
  • ينشئ مجلد split_files.
  • يستخدم groupby(..., dropna=False, sort=False).
  • يسمي الخلايا الفارغة blank.
  • ينظف محارف أسماء الملفات غير الصالحة على Windows.
  • يمنع الكتابة فوق ملف موجود.
  • يمنع التصادم الناتج عن قيم مثل A/B وA:B.
  • يطبع اسم كل ملف وعدد الصفوف التي حُفظت فيه.

هل يبقى عمود التقسيم داخل كل ملف؟

نعم، وهذا هو السلوك الافتراضي في السكربت. إذا قسمت حسب City سيظل عمود City موجودًا داخل Aden.xlsx، وهذا غالبًا أوضح عند مراجعة الملف.

إذا كنت تريد حذفه اختياريًا:

group.drop(
    columns=[COLUMN_NAME]
).to_excel(
    output_file,
    index=False
)

لا تجعل هذا هو السلوك الافتراضي إلا إذا كان عمود التقسيم غير مطلوب في الملفات الناتجة.

إذا كانت القيم النصية غير نظيفة

قد تكون لديك قيم تمثل المدينة نفسها لكنها مكتوبة هكذا:

Aden
aden
Aden 
 ADEN

Pandas ستتعامل معها كمجموعات مختلفة إذا لم توحدها. إذا كانت المسافات الزائدة فقط غير مهمة:

df["City"] = (
    df["City"]
    .astype("string")
    .str.strip()
)

يمكنك استخدام lower() أو title() أيضًا عندما يكون ذلك منطقيًا، لكن لا تغير حالة الأحرف تلقائيًا إذا كان الاختلاف يحمل معنى في بياناتك.

الأرقام والتواريخ في عمود التقسيم

إذا كانت القيم أرقامًا مثل 1001 و1002، فإن السكربت يحولها إلى نص وتصبح أسماء الملفات مثل 1001.xlsx.

أما التواريخ فقد تظهر كـTimestamp طويل يحتوي وقتًا. إذا كان المطلوب ملف لكل يوم، من الأفضل تنسيق العمود قبل التجميع:

df["Date"] = (
    pd.to_datetime(
        df["Date"],
        errors="coerce"
    )
    .dt.strftime(
        "%Y-%m-%d"
    )
)

استخدم هذا فقط عندما يكون التجميع حسب اليوم هو النتيجة المطلوبة.

مثال آخر: تقسيم الموظفين حسب القسم

إذا كان الملف employees.xlsx والعمود اسمه Department، لا تحتاج إلى إعادة كتابة السكربت؛ غيّر الإعدادات:

INPUT_FILE = Path(
    "employees.xlsx"
)

COLUMN_NAME = "Department"

إذا كانت القيم HR وFinance وIT، فستحصل على ملفات مستقلة بهذه الأسماء.

هل يحافظ Pandas على تنسيق Excel الأصلي؟

لا تتوقع ذلك. Pandas ممتازة لمعالجة البيانات الجدولية، لكن قراءة الملف ثم حفظ DataFrame باستخدام to_excel() لا تعني نسخ Workbook بكل خصائصه.

قد لا تُحفظ كما هي:

  • الألوان والخطوط.
  • عرض الأعمدة وارتفاع الصفوف.
  • الخلايا المدمجة.
  • الرسوم البيانية.
  • تصميم Sheet الأصلي.
  • بعض الصيغ والخصائص المرتبطة بالمصنف.
{alertWarning} إذا كان هدفك الحفاظ على تصميم Workbook أو Charts أو الصيغ المعقدة أو Macros بدقة، فهذه مهمة مختلفة تحتاج معالجة أكثر تفصيلًا باستخدام أدوات مثل openpyxl. هذا المقال مخصص لتقسيم البيانات الجدولية.

ماذا عن ملفات .xlsm والصيغ؟

لا تستخدم هذا السكربت على ملف Macro-enabled وأنت تتوقع الحفاظ على VBA/Macros تلقائيًا. كما أن Pandas تتعامل أساسًا مع قيم البيانات، وليس مع إعادة إنتاج سلوك Workbook الأصلي بكل الصيغ والتنسيقات.

الملفات الكبيرة جدًا

read_excel() تحمل البيانات إلى DataFrame في الذاكرة. إذا كان ملف Excel ضخمًا جدًا، فقد يستهلك ذاكرة كبيرة. معالجة ملفات Excel الضخمة تحتاج تصميمًا مختلفًا، ولا نضيف Chunking معقدًا هنا.

أخطاء شائعة

FileNotFoundError

تحقق من اسم الملف والمسار. السكربت يتوقف برسالة واضحة إذا لم يجد INPUT_FILE.

اسم العمود غير صحيح

نفذ print(df.columns) وانسخ اسم العمود كما هو، بما في ذلك المسافات.

Missing optional dependency 'openpyxl'

ثبّت openpyxl من نفس بيئة بايثون التي تشغّل منها السكربت.

PermissionError

قد يكون ملف ناتج مفتوحًا في Excel أو لا تملك صلاحية الكتابة في المجلد. أغلق الملف المفتوح ثم أعد التشغيل.

خلايا فارغة في عمود التقسيم

السكربت يحفظها في مجموعة باسم blank.xlsx، أو اسم لاحق مثل blank_2.xlsx إذا كان الملف موجودًا.

قيم مختلفة بسبب المسافات أو حالة الأحرف

Aden وaden وAden يمكن أن تنتج مجموعات مختلفة. نظّف العمود قبل groupby() فقط إذا كانت هذه القيم تمثل المعنى نفسه.

تشغيل السكربت مرة ثانية

النسخة النهائية لا تكتب فوق الملفات الموجودة؛ تنشئ اسمًا جديدًا مثل Aden_2.xlsx.

تنظيف البيانات قبل التقسيم

إذا كان الملف يحتوي صفوفًا مكررة وتريد تنظيفها قبل التقسيم، راجع حذف الصفوف المكررة من Excel باستخدام بايثون وPandas. لا تحذف التكرار تلقائيًا إلا بعد أن تحدد معنى السجل المكرر في بياناتك.

وإذا كانت لديك المهمة العكسية — عدة ملفات Excel وتريد جمعها في ملف واحد — راجع دمج ملفات Excel متعددة في ملف واحد باستخدام بايثون وPandas.

مصادر رسمية

الخلاصة

لتقسيم ملف Excel إلى عدة ملفات حسب قيمة عمود، اقرأ الملف باستخدام Pandas ثم استخدم groupby() لإنشاء مجموعة لكل قيمة، واحفظ كل مجموعة باستخدام to_excel().

في الملفات الحقيقية لا تتوقف عند المثال القصير: تحقق من وجود العمود، احتفظ بالقيم الفارغة، نظف أسماء الملفات، امنع الكتابة فوق ملفات موجودة، وتذكر أن Pandas مخصصة أساسًا لمعالجة البيانات وليست لنسخ تنسيق Workbook بكل خصائصه.

{alertSuccess} القاعدة العملية: قيمة العمود تحدد المجموعة، وكل مجموعة تُحفظ في ملف مستقل، مع اسم ملف آمن وفريد.

أسئلة شائعة

كيف أقسم ملف Excel إلى عدة ملفات باستخدام بايثون؟

اقرأ الملف باستخدام pd.read_excel() ثم استخدم groupby() على العمود المطلوب واحفظ كل مجموعة باستخدام to_excel().

كيف أقسم Excel حسب قيمة عمود؟

استخدم df.groupby("ColumnName"). كل قيمة مختلفة في العمود تنتج مجموعة مستقلة يمكن حفظها في ملف.

كيف أحفظ كل مجموعة في ملف Excel مستقل؟

داخل حلقة groupby() أنشئ مسارًا لكل مجموعة ثم نفذ group.to_excel(output_file, index=False).

ماذا يحدث للخلايا الفارغة في عمود التقسيم؟

استخدم dropna=False حتى لا تُستبعد. السكربت النهائي يسمي المجموعة الفارغة blank.

كيف أختار Sheet محددة؟

مرر sheet_name="Sales" أو اسم الورقة المطلوبة إلى pd.read_excel().

هل يحافظ Pandas على تنسيق Excel الأصلي؟

ليس بصورة كاملة. الهدف الأساسي هو معالجة البيانات، وليس نسخ الألوان والخطوط والخلايا المدمجة والرسوم والـMacros كما هي.

كيف أتجنب أسماء الملفات غير الصالحة؟

نظف قيمة العمود قبل استخدامها كاسم ملف، واستبدل المحارف غير المسموحة في Windows، ثم تحقق من عدم وجود اسم مستخدم مسبقًا.

إرسال تعليق

أحدث أقدم