لديك ملف Excel قديم وملف جديد، وتريد معرفة ما الذي تغير بينهما: ما الصفوف التي أضيفت؟ ما السجلات التي حُذفت؟ وما القيم التي تغيرت داخل الصفوف الموجودة في الملفين؟ هذه من أكثر المهام العملية في تقارير المخزون والأسعار والموظفين والفواتير وقوائم العملاء.
المقارنة اليدوية تصبح صعبة عندما يحتوي كل ملف على مئات أو آلاف الصفوف. بدل فتح الملفين والبحث صفًا صفًا، تستطيع استخدام بايثون وPandas لبناء مقارنة تعتمد على مفتاح فريد مثل product_id أو invoice_id أو employee_id، ثم إخراج تقرير جديد يوضح الصفوف الجديدة والمحذوفة والمعدلة.
في هذا الدرس سنقارن ملفين Excel من البداية إلى النهاية، ونشرح لماذا لا يجب الاعتماد على ترتيب الصفوف، وكيف نكتشف القيم القديمة والجديدة، وكيف نتعامل مع اختلاف ترتيب البيانات وNaN والمسافات والـID المكرر، ثم نحفظ النتيجة في ملف Excel يحتوي Sheets منفصلة باسم Added وRemoved وChanged.
{alertInfo} الحل المختصر: استخدم مفتاحًا فريدًا مثلproduct_id، ثم نفذmerge(..., how="outer", indicator=True)لاكتشاف الصفوف الجديدة والمحذوفة، وبعد ذلك قارن الأعمدة في السجلات الموجودة في الملفين لاستخراج القيم المعدلة.
{getToc} $title={محتوى المقال}
متى تحتاج إلى مقارنة ملفين Excel؟
- مقارنة قائمة أسعار قديمة بقائمة أسعار جديدة.
- معرفة المنتجات التي أضيفت أو حذفت من المخزون.
- مقارنة بيانات الموظفين بين شهرين.
- اكتشاف العملاء الجدد أو الحسابات التي تغيرت بياناتها.
- مقارنة تقرير الأمس بتقرير اليوم.
- معرفة الفواتير الجديدة أو الملغاة.
- مقارنة قوائم SKU بين نسختين من نفس التقرير.
الفرق بين دمج ملفات Excel ومقارنتها
| المهمة | الأداة |
|---|---|
| جمع ملفات متشابهة فوق بعضها | pd.concat() |
| ربط سجل بنظيره باستخدام مفتاح | pd.merge() |
| مقارنة قيم جدولين متوافقين في البنية | DataFrame.compare() |
في ملفات الأعمال الواقعية تكون merge غالبًا نقطة البداية الأفضل، لأن ترتيب الصفوف قد يتغير وقد توجد سجلات أضيفت أو حذفت بالكامل.
مثال عملي للمقارنة
سنستخدم ملفين:
old.xlsx
new.xlsx
الملف القديم يحتوي مثلًا:
product_id product price status
P001 Keyboard 100 Active
P002 Mouse 50 Active
P003 Monitor 300 Active
والملف الجديد:
product_id product price status
P001 Keyboard 120 Active
P003 Monitor 300 Inactive
P004 Webcam 80 Active
النتيجة التي نريد الوصول إليها:
P004سجل جديد.P002سجل محذوف.- سعر
P001تغير من 100 إلى 120. - حالة
P003تغيرت من Active إلى Inactive.
تثبيت Pandas وopenpyxl
python -m pip install pandas openpyxl
قراءة ملفي Excel
import pandas as pd
old_df = pd.read_excel("old.xlsx")
new_df = pd.read_excel("new.xlsx")
print(old_df.head())
print(new_df.head())
أهم قاعدة: استخدم مفتاحًا فريدًا
لا تقارن الصف الأول بالصف الأول والثاني بالثاني. ترتيب الصفوف قد يتغير في أي وقت. اختر عمودًا ثابتًا يميز كل سجل.
key = "product_id"
قد يكون المفتاح في مشروع آخر:
invoice_idللفواتير.employee_idللموظفين.customer_idللعملاء.skuللمنتجات.
التحقق من وجود المفتاح في الملفين
if key not in old_df.columns:
raise ValueError("المفتاح غير موجود في الملف القديم")
if key not in new_df.columns:
raise ValueError("المفتاح غير موجود في الملف الجديد")
التحقق من IDs المكررة
old_duplicates = old_df[
old_df[key].duplicated(keep=False)
]
new_duplicates = new_df[
new_df[key].duplicated(keep=False)
]
print(old_duplicates)
print(new_duplicates)
إذا كان المفتاح يفترض أن يكون فريدًا ووجدت تكرارًا، لا تكمل المقارنة قبل فهم السبب. الـID المكرر قد يجعل merge تنتج صفوفًا إضافية لا تمثل فروقات حقيقية.
توحيد نوع المفتاح قبل المقارنة
قد يكون رقم المنتج نصًا في ملف ورقمًا في الآخر. وحّد النوع:
old_df[key] = (
old_df[key]
.astype("string")
.str.strip()
)
new_df[key] = (
new_df[key]
.astype("string")
.str.strip()
)
اكتشاف الصفوف الجديدة والمحذوفة باستخدام merge
merged = old_df.merge(
new_df,
on=key,
how="outer",
suffixes=("_old", "_new"),
indicator=True
)
print(merged)
ما معنى _merge؟
| القيمة | المعنى |
|---|---|
left_only | موجود في الملف القديم فقط، أي أنه محذوف من الجديد. |
right_only | موجود في الملف الجديد فقط، أي أنه سجل جديد. |
both | المفتاح موجود في الملفين ويمكن مقارنة قيمه. |
استخراج الصفوف الجديدة
added_ids = merged.loc[
merged["_merge"] == "right_only",
key
]
added = new_df[
new_df[key].isin(added_ids)
].copy()
استخراج الصفوف المحذوفة
removed_ids = merged.loc[
merged["_merge"] == "left_only",
key
]
removed = old_df[
old_df[key].isin(removed_ids)
].copy()
استخراج السجلات الموجودة في الملفين
both = merged[
merged["_merge"] == "both"
].copy()
وجود السجل في الملفين لا يعني أنه لم يتغير. نحتاج الآن إلى مقارنة الأعمدة المهمة.
تحديد الأعمدة التي نريد مقارنتها
columns_to_compare = [
"product",
"price",
"status"
]
لا تقارن عمودًا مثل exported_at إذا كان يتغير في كل تصدير ولا يمثل تغييرًا تجاريًا حقيقيًا.
التعامل الصحيح مع NaN
إذا كانت الخلية فارغة في الملفين فنريد اعتبارها متساوية. نكتب دالة مساعدة:
def series_changed(old_series, new_series):
same = (
old_series.eq(new_series)
|
(
old_series.isna()
&
new_series.isna()
)
)
return ~same
استخراج الصفوف المعدلة
changed_mask = pd.Series(
False,
index=both.index
)
for column in columns_to_compare:
changed_mask |= series_changed(
both[f"{column}_old"],
both[f"{column}_new"]
)
changed = both[
changed_mask
].copy()
unchanged = both[
~changed_mask
].copy()
عرض القيمة القديمة والجديدة
بفضل suffixes تحصل على أعمدة واضحة مثل:
price_old
price_new
status_old
status_new
وهذا يجعل تقرير التغييرات مفهومًا للمستخدم غير التقني أيضًا.
إضافة أسماء الحقول التي تغيرت
def get_changed_fields(row):
result = []
for column in columns_to_compare:
old_value = row[f"{column}_old"]
new_value = row[f"{column}_new"]
if pd.isna(old_value) and pd.isna(new_value):
continue
if old_value != new_value:
result.append(column)
return ", ".join(result)
changed["changed_fields"] = changed.apply(
get_changed_fields,
axis=1
)
قد تحصل على:
product_id changed_fields
P001 price
P003 status
حفظ الفروقات في ملف Excel واحد
with pd.ExcelWriter(
"excel_changes.xlsx",
engine="openpyxl"
) as writer:
added.to_excel(
writer,
sheet_name="Added",
index=False
)
removed.to_excel(
writer,
sheet_name="Removed",
index=False
)
changed.to_excel(
writer,
sheet_name="Changed",
index=False
)
حساب ملخص سريع للتغييرات
print("الجديد:", len(added))
print("المحذوف:", len(removed))
print("المعدل:", len(changed))
print("بدون تغيير:", len(unchanged))
مقارنة قائمتين أسعار واستخراج تغير السعر
إذا كان هدفك فقط معرفة المنتجات التي تغير سعرها:
prices = old_df.merge(
new_df,
on="product_id",
how="inner",
suffixes=("_old", "_new")
)
price_changes = prices[
prices["price_old"]
!=
prices["price_new"]
].copy()
حساب مقدار ونسبة تغير السعر
price_changes["price_difference"] = (
price_changes["price_new"]
-
price_changes["price_old"]
)
price_changes["change_percent"] = (
(
price_changes["price_new"]
- price_changes["price_old"]
)
/ price_changes["price_old"]
* 100
)
هذه الحالة مفيدة لقوائم الموردين والمشتريات ومتابعة الأسعار الشهرية.
مقارنة عمود واحد فقط بين ملفين Excel
comparison = old_df[
["employee_id", "status"]
].merge(
new_df[["employee_id", "status"]],
on="employee_id",
suffixes=("_old", "_new")
)
changed_status = comparison[
comparison["status_old"]
!=
comparison["status_new"]
]
مقارنة باستخدام أكثر من مفتاح
إذا كان المنتج يتكرر في أكثر من فرع:
keys = [
"branch_id",
"product_id"
]
merged = old_df.merge(
new_df,
on=keys,
how="outer",
suffixes=("_old", "_new"),
indicator=True
)
تنظيف أسماء الأعمدة قبل المقارنة
old_df.columns = old_df.columns.str.strip()
new_df.columns = new_df.columns.str.strip()
هذه الخطوة تمنع اختلافات مثل price وprice .
تنظيف المسافات داخل القيم النصية
text_columns = [
"product",
"status"
]
for column in text_columns:
old_df[column] = (
old_df[column]
.astype("string")
.str.strip()
)
new_df[column] = (
new_df[column]
.astype("string")
.str.strip()
)
مقارنة الأرقام العشرية
إذا كانت البيانات مالية وتريد المقارنة حتى منزلتين عشريتين:
old_df["price"] = old_df["price"].round(2)
new_df["price"] = new_df["price"].round(2)
مقارنة التواريخ بطريقة صحيحة
old_df["date"] = pd.to_datetime(
old_df["date"],
errors="coerce"
)
new_df["date"] = pd.to_datetime(
new_df["date"],
errors="coerce"
)
متى أستخدم DataFrame.compare()؟
DataFrame.compare() ممتازة عندما يكون الجدولان متوافقين من حيث labels والبنية. لكنها ليست بديلًا مباشرًا عن merge إذا كان هناك IDs أضيفت أو حذفت.
common_ids = (
set(old_df[key])
&
set(new_df[key])
)
old_common = (
old_df[old_df[key].isin(common_ids)]
.set_index(key)
.sort_index()
)
new_common = (
new_df[new_df[key].isin(common_ids)]
.set_index(key)
.sort_index()
)
differences = old_common.compare(new_common)
print(differences)
تجاهل أعمدة لا تريد مقارنتها
ignored_columns = {
"exported_at",
"last_sync"
}
columns_to_compare = [
column
for column in old_df.columns
if column != key
and column not in ignored_columns
and column in new_df.columns
]
معرفة الأعمدة الجديدة أو المحذوفة من بنية الملف
old_columns = set(old_df.columns)
new_columns = set(new_df.columns)
only_old_columns = old_columns - new_columns
only_new_columns = new_columns - old_columns
print("في القديم فقط:", only_old_columns)
print("في الجديد فقط:", only_new_columns)
هذا مهم لأن إضافة عمود جديد إلى الملف هي تغيير في بنية البيانات، وليست مجرد تغيير قيمة.
نسخة عملية كاملة
import pandas as pd
OLD_FILE = "old.xlsx"
NEW_FILE = "new.xlsx"
OUTPUT_FILE = "excel_changes.xlsx"
KEY = "product_id"
COLUMNS_TO_COMPARE = [
"product",
"price",
"status"
]
def prepare_data(df):
df = df.copy()
df.columns = df.columns.str.strip()
df[KEY] = (
df[KEY]
.astype("string")
.str.strip()
)
return df
def series_changed(old_series, new_series):
same = (
old_series.eq(new_series)
|
(
old_series.isna()
&
new_series.isna()
)
)
return ~same
old_df = prepare_data(
pd.read_excel(OLD_FILE)
)
new_df = prepare_data(
pd.read_excel(NEW_FILE)
)
if old_df[KEY].duplicated().any():
raise ValueError("يوجد مفتاح مكرر في الملف القديم")
if new_df[KEY].duplicated().any():
raise ValueError("يوجد مفتاح مكرر في الملف الجديد")
merged = old_df.merge(
new_df,
on=KEY,
how="outer",
suffixes=("_old", "_new"),
indicator=True
)
added_ids = merged.loc[
merged["_merge"] == "right_only",
KEY
]
removed_ids = merged.loc[
merged["_merge"] == "left_only",
KEY
]
added = new_df[
new_df[KEY].isin(added_ids)
].copy()
removed = old_df[
old_df[KEY].isin(removed_ids)
].copy()
both = merged[
merged["_merge"] == "both"
].copy()
changed_mask = pd.Series(
False,
index=both.index
)
for column in COLUMNS_TO_COMPARE:
changed_mask |= series_changed(
both[f"{column}_old"],
both[f"{column}_new"]
)
changed = both[changed_mask].copy()
unchanged = both[~changed_mask].copy()
with pd.ExcelWriter(
OUTPUT_FILE,
engine="openpyxl"
) as writer:
added.to_excel(
writer,
sheet_name="Added",
index=False
)
removed.to_excel(
writer,
sheet_name="Removed",
index=False
)
changed.to_excel(
writer,
sheet_name="Changed",
index=False
)
print(f"الجديد: {len(added)}")
print(f"المحذوف: {len(removed)}")
print(f"المعدل: {len(changed)}")
print(f"بدون تغيير: {len(unchanged)}")
print(f"تم إنشاء: {OUTPUT_FILE}")
مقارنة عدة Sheets داخل ملفي Excel
إذا كان الملفان يحتويان على عدة أوراق:
old_book = pd.ExcelFile("old.xlsx")
new_book = pd.ExcelFile("new.xlsx")
print(old_book.sheet_names)
print(new_book.sheet_names)
بعد ذلك تستطيع تحديد الأوراق المشتركة وقراءة كل Sheet ومقارنتها بشكل مستقل.
مقارنة Sheet محددة
old_df = pd.read_excel(
"old.xlsx",
sheet_name="Products"
)
new_df = pd.read_excel(
"new.xlsx",
sheet_name="Products"
)
أخطاء شائعة عند مقارنة ملفين Excel
1. المقارنة حسب ترتيب الصفوف
استخدم ID أو SKU أو مفتاحًا ثابتًا بدل الموقع.
2. ID مكرر
افحص duplicated() قبل merge، وإلا قد تنتج علاقة many-to-many.
3. اختلاف نوع ID
وحّد النوع إلى string أو نوع مناسب قبل المقارنة.
4. مسافات مخفية
استخدم str.strip() عندما تكون المسافات الزائدة غير مهمة.
5. NaN في الملفين
تعامل معها كحالة خاصة إذا كانت الخليتان الفارغتان تعنيان نفس الشيء.
6. مقارنة timestamp يتغير تلقائيًا
استبعد الأعمدة التي لا تمثل تغييرًا تجاريًا حقيقيًا.
كيف تختبر تقرير الفروقات؟
- اختبر أولًا على ملفين صغيرين تعرف الفرق بينهما.
- تأكد أن Added لا تحتوي IDs من القديم.
- تأكد أن Removed لا تحتوي IDs من الجديد.
- راجع بعض الصفوف المعدلة يدويًا.
- قارن عدد الصفوف قبل وبعد.
- اختبر القيم الفارغة والتواريخ والأرقام.
قراءة أعمدة محددة فقط للملفات الكبيرة
usecols = [
"product_id",
"product",
"price",
"status"
]
old_df = pd.read_excel(
"old.xlsx",
usecols=usecols
)
new_df = pd.read_excel(
"new.xlsx",
usecols=usecols
)
روابط داخلية مفيدة من بايثون العرب
بعد نشر مقال دمج ملفات Excel: أضف هنا رابطًا داخليًا مباشرًا إلى درس «دمج ملفات Excel متعددة في ملف واحد باستخدام بايثون وPandas» لأنه يمثل الخطوة السابقة الطبيعية لهذا الموضوع.
مصادر رسمية للتوسع
- توثيق pandas.read_excel الرسمي
- توثيق DataFrame.merge الرسمي
- توثيق DataFrame.compare الرسمي
- توثيق ExcelWriter الرسمي
- توثيق duplicated الرسمي
الخلاصة
مقارنة ملفين Excel باستخدام بايثون وPandas تصبح أكثر دقة عندما تتوقف عن التفكير في «الصف الأول مقابل الصف الأول» وتبدأ بالمقارنة باستخدام مفتاح فريد مثل product_id أو employee_id.
استخدم merge(..., how="outer", indicator=True) لاكتشاف السجلات الجديدة والمحذوفة، ثم قارن الأعمدة في السجلات الموجودة في الملفين لاستخراج الصفوف المعدلة والقيم القديمة والجديدة. نظف المفتاح والنصوص قبل المقارنة، وتعامل مع NaN، واستبعد الأعمدة التي تتغير تلقائيًا ولا تمثل تغييرًا حقيقيًا.
وأخيرًا، اجعل النتيجة قابلة للمراجعة بإنشاء ملف Excel يحتوي Sheets منفصلة للجديد والمحذوف والمعدل. بهذه الطريقة يتحول السكربت إلى أداة عملية لمقارنة الأسعار والمخزون والموظفين والفواتير والتقارير الدورية.
{alertSuccess} القاعدة المهمة: جودة مقارنة Excel تعتمد أولًا على اختيار مفتاح فريد صحيح وتنظيف البيانات قبل المقارنة. إذا كان المفتاح غير صحيح أو مكررًا، فلن تنقذك أي دالة مقارنة من نتيجة مضللة.
أسئلة شائعة
كيف أقارن ملفين Excel باستخدام بايثون؟
اقرأ الملفين باستخدام pd.read_excel()، اختر مفتاحًا فريدًا، ثم استخدم merge لاكتشاف الجديد والمحذوف، وقارن الأعمدة لاكتشاف القيم التي تغيرت.
كيف أعرف الصفوف الجديدة بين ملفين Excel؟
استخدم outer merge مع indicator=True، ثم اختر الصفوف التي تكون قيمة _merge فيها right_only.
كيف أعرف الصفوف المحذوفة؟
الصفوف التي تحمل left_only موجودة في الملف القديم فقط.
كيف أستخرج القيم التي تغيرت؟
اربط السجلات باستخدام ID ثم قارن أعمدة مثل price_old وprice_new.
كيف أقارن ملفين حتى لو تغير ترتيب الصفوف؟
لا تعتمد على الترتيب. استخدم ID أو SKU أو مفتاحًا ثابتًا.
كيف أقارن عمودًا واحدًا فقط؟
اقرأ المفتاح والعمود المطلوب من الملفين، نفذ merge باستخدام المفتاح، ثم قارن النسخة القديمة والجديدة من العمود.
كيف أقارن قائمة أسعار قديمة وجديدة؟
استخدم معرف المنتج كمفتاح، ثم قارن price_old وprice_new ويمكنك حساب فرق السعر ونسبة التغير.
ما الفرق بين merge وcompare في Pandas؟
merge تربط السجلات باستخدام مفتاح وتناسب وجود صفوف جديدة أو محذوفة، أما DataFrame.compare() فتناسب أكثر الجداول المتوافقة في البنية والفهارس.
لماذا تظهر فروقات رغم أن القيم تبدو متشابهة؟
قد توجد مسافات إضافية أو أنواع بيانات مختلفة أو NaN أو اختلاف في حالة الأحرف. نظف البيانات ووحد الأنواع قبل المقارنة.
كيف أحفظ الفروقات في ملف Excel جديد؟
استخدم pd.ExcelWriter() ثم احفظ Added وRemoved وChanged في Sheets منفصلة باستخدام to_excel().
كيف أقارن باستخدام أكثر من عمود كمفتاح؟
مرر قائمة أعمدة إلى on في merge، مثل on=["branch_id", "product_id"].
كيف أتأكد أن ID غير مكرر؟
استخدم df["id"].duplicated().any() أو duplicated(keep=False) لعرض السجلات المكررة.
هل يمكن مقارنة عدة Sheets؟
نعم. استخدم pd.ExcelFile() لمعرفة أسماء الأوراق ثم اقرأ كل Sheet وقارنها بشكل مستقل.
هل يمكن تلوين الخلايا التي تغيرت؟
نعم. بعد إنشاء تقرير الفروقات يمكنك استخدام openpyxl لتطبيق ألوان على الخلايا أو الصفوف المعدلة.



