Pandas: الدمج والانضمام.
آخر تحديث: 2026-08-26
البيانات الحقيقية لا تعيش أبداً في جدول واحد — معلومات المستخدم في جدول، وسجلات الطلبات في آخر، وتفاصيل المنتجات في جدول ثالث. يتطلب التحليل ربطها ببعضها، وهذا بالضبط ما تقوم به عملية الدمج (merge). تنفذ مكتبة Pandas عملية الدمج (merge) جميع أنواع الربط المستخدمة في SQL (JOIN). في هذا القسم، نستخدم مخططات Mermaid لوضوح تأثير جميع أنواع الربط الأربعة حتى تتمكن من فهم "ما يُحتفظ به من الجدول الأيسر وما يُحتفظ به من الجدول الأيمن" بشكل واضح.
1. What You Will Learn
- ❶ The 4 merge join types
- ❷ on / left_on / right_on
- ❸ Multi-key merges
- ❹ suffixes and indicator
- ❺ validate and cross join
2. ارتباط المستخدم بالطلب الخاص به في تجربة بوب
(١) المشكلة: جدولان منفصلان، لا توجد طريقة للتحليل
لدى بوب جدول للمستخدمين وجدول للطلبات. يريد أن يرى "ماذا اشترى كل مستخدم":
import pandas as pd
users = pd.DataFrame({
'user_id': [1, 2, 3, 4],
'name': ['Alice', 'Bob', 'Charlie', 'Carol']
})
orders = pd.DataFrame({
'order_id': ['O001', 'O002', 'O003'],
'user_id': [1, 2, 2],
'amount': [120, 85, 200]
})
# How to combine? Need to link by user_id
> **الإخراج:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبته مسبقاً. يرجى تثبيتها محلياً (`pip install pandas`) والمتابعة. قد تختلف القيم الفعليه قليلاً حسب إصدار pandas.
(٢) الحل: عملية الدمج في سطر واحد
▶ مثال
> **الإخراج:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبته مسبقاً. يرجى تثبيتها محلياً (`pip install pandas`) والمتابعة. قد تختلف القيم الفعليه قليلاً حسب إصدار pandas.
: دمج أساسي (الصعوبة ⭐)
import pandas as pd
users = pd.DataFrame({
'user_id': [1, 2, 3, 4],
'name': ['Alice', 'Bob', 'Charlie', 'Carol']
})
orders = pd.DataFrame({
'order_id': ['O001', 'O002', 'O003'],
'user_id': [1, 2, 2],
'amount': [120, 85, 200]
})
# Inner join (default) — only matching user_ids
result = pd.merge(users, orders, on='user_id')
print(result)
# user_id name order_id amount
# 0 1 Alice O001 120
# 1 2 Bob O002 85
# 2 2 Bob O003 200
> **الإخراج:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبته مسبقاً. يرجى تثبيتها محلياً (`pip install pandas`) والمتابعة. قد تختلف القيم الفعليه قليلاً حسب إصدار pandas.
3. الأنواع الأربعة للانضمام
(1) مخطط ميرميد
graph TB
subgraph Inner["الداخلي — الاحتفاظ فقط بالتطابقات"]
I1["user_id 1: Alice+O001"]
I2["user_id 2: Bob+O002"]
I3["user_id 2: Bob+O003"]
end
subgraph Left["اليساري — الاحتفاظ بكل الصفوف من الجدول الأيسر"]
L1["user_id 1: Alice+O001"]
L2["user_id 2: Bob+O002"]
L3["user_id 2: Bob+O003"]
L4["user_id 3: Charlie+NaN"]
L5["user_id 4: Carol+NaN"]
end
subgraph Right["اليميني — الاحتفاظ بكل الصفوف من الجدول الأيمن"]
R1["user_id 1: Alice+O001"]
R2["user_id 2: Bob+O002"]
R3["user_id 2: Bob+O003"]
end
subgraph Outer["الخارجي — الاحتفاظ بكل الصفوف من كلا الجانبين"]
O1["user_id 1: Alice+O001"]
O2["user_id 2: Bob+O002"]
O3["user_id 2: Bob+O003"]
O4["user_id 3: Charlie+NaN"]
O5["user_id 4: Carol+NaN"]
end
> **المخرجات:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبته مسبقاً. يرجى تثبيته محلياً (`pip install pandas`) واتباع الأمثلة. قد تتغير القيم الفعلية بشكل طفيف حسب إصدار pandas.
(2) مقارنة بين الأنواع الأربعة للانضمام
| النوع | الكيفية | جدول أيسر بدون تطابق | جدول أيمن بدون تطابق | عدد الصفوف |
|---|---|---|---|---|
| داخلي | 'inner' | محذوف | محذوف | ≤أصغر(يسار، يمين) |
| يساري | 'left' | محتفظ به (مملوء بـ NaN) | محذوف | = صفوف اليسار × التطابقات |
| يميني | 'right' | محذوف | محتفظ به (مملوء بـ NaN) | = صفوف اليمين × التطابقات |
| خارجي | 'outer' | محتفظ به (مملوء بـ NaN) | محتفظ به (مملوء بـ NaN) | ≥أكبر(يسار، يمين) |
▶ مثال
> **المخرجات:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبته مسبقاً. يرجى تثبيته محلياً (`pip install pandas`) واتباع الأمثلة. قد تتغير القيم الفعلية بشكل طفيف حسب إصدار pandas.
: مقارنة بين أنواع الانضمام الأربعة (الصعوبة ⭐⭐)
import pandas as pd
left = pd.DataFrame({
'id': [1, 2, 3],
'name': ['Alice', 'Bob', 'Charlie']
})
right = pd.DataFrame({
'id': [2, 3, 4],
'score': [85, 92, 78]
})
# داخلي — فقط id 2، 3 (كلا الجدولين يحتويان عليهما)
print("داخلي (INNER):")
print(pd.merge(left, right, on='id', how='inner'))
# id name score
# 0 2 Bob 85
# 1 3 Charlie 92
# يساري — كل صفوف الجدول الأيسر، الجدول الأيمن يملأ NaN حيث لا يوجد تطابق
print("\nيساري (LEFT):")
print(pd.merge(left, right, on='id', how='left'))
# id name score
# 0 1 Alice NaN ← لا يوجد تطابق في الأيمن
# 1 2 Bob 85.0
# 2 3 Charlie 92.0
# يميني — كل صفوف الجدول الأيمن
print("\nيميني (RIGHT):")
print(pd.merge(left, right, on='id', how='right'))
# id name score
# 0 2 Bob 85
# 1 3 Charlie 92
# 2 4 NaN 78 ← لا يوجد تطابق في الأيسر
# خارجي — كل الصفوف من كلا الجانبين
print("\nخارجي (OUTER):")
print(pd.merge(left, right, on='id', how='outer'))
# id name score
# 0 1 Alice NaN
# 1 2 Bob 85.0
# 2 3 Charlie 92.0
# 3 4 NaN 78.0
> **المخرجات:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبته مسبقاً. يرجى تثبيته محلياً (`pip install pandas`) واتباع الأمثلة. قد تتغير القيم الفعلية بشكل طفيف حسب إصدار pandas.
4. الدمج متعدد المفاتيح وأسماء الأعمدة المختلفة
(1) الدمج متعدد المفاتيح
▶ مثال
> **الإخراج:** قم بالتشغيل في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على مكتبة pandas مثبتة مسبقًا. يرجى تثبيتها محليًا (`pip install pandas`) والمتابعة. قد تختلف القيم الفعلية قليلاً حسب إصدار المكتبة.
: الدمج متعدد المفاتيح (الصعوبة ⭐⭐)
import pandas as pd
sales = pd.DataFrame({
'region': ['North', 'North', 'South', 'South'],
'category': ['Electronics', 'Clothing', 'Electronics', 'Clothing'],
'revenue': [5000, 800, 2000, 600]
})
targets = pd.DataFrame({
'region': ['North', 'North', 'South', 'South'],
'category': ['Electronics', 'Clothing', 'Electronics', 'Clothing'],
'target': [4500, 1000, 2500, 500]
})
# الدمج على مفاتيح متعددة
result = pd.merge(sales, targets, on=['region', 'category'])
print(result)
# region category revenue target
# 0 North Electronics 5000 4500
# 1 North Clothing 800 1000
# 2 South Electronics 2000 2500
# 3 South Clothing 600 500
# حساب معدل الإنجاز
result['achievement'] = (result['revenue'] / result['target'] * 100).round(1)
print(result[['region', 'category', 'achievement']])
> **الإخراج:** قم بالتشغيل في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على مكتبة pandas مثبتة مسبقًا. يرجى تثبيتها محليًا (`pip install pandas`) والمتابعة. قد تختلف القيم الفعلية قليلاً حسب إصدار المكتبة.
(2) الدمج عند تباين أسماء الأعمدة
▶ مثال
> **الإخراج:** قم بالتشغيل في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على مكتبة pandas مثبتة مسبقًا. يرجى تثبيتها محليًا (`pip install pandas`) والمتابعة. قد تختلف القيم الفعلية قليلاً حسب إصدار المكتبة.
: left_on/right_on (الصعوبة ⭐⭐)
import pandas as pd
employees = pd.DataFrame({
'emp_id': [1, 2, 3],
'name': ['Alice', 'Bob', 'Charlie']
})
salaries = pd.DataFrame({
'employee_id': [1, 2, 3],
'salary': [75000, 92000, 68000]
})
# أسماء الأعمدة تختلف: emp_id مقابل employee_id
result = pd.merge(
employees, salaries,
left_on='emp_id', right_on='employee_id',
how='inner'
)
print(result)
# emp_id name employee_id salary
# 0 1 Alice 1 75000
# 1 2 Bob 2 92000
# 2 3 Charlie 3 68000
# إسقاط العمود الزائد
result = result.drop(columns='employee_id')
> **الإخراج:** قم بالتشغيل في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على مكتبة pandas مثبتة مسبقًا. يرجى تثبيتها محليًا (`pip install pandas`) والمتابعة. قد تختلف القيم الفعلية قليلاً حسب إصدار المكتبة.
5. اللاحقات والمُشير
(1) التعامل مع تعارضات أعمدة الأسماء
▶ مثال
> **الخرج:** قم بالتشغيل في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبتًا مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) ومتابعة الشرح. القيم الفعلية قد تختلف قليًّا حسب إصدار pandas.
: لاحقات لأسماء الأعمدة المكررة (الصعوبة ⭐)
import pandas as pd
df1 = pd.DataFrame({
'id': [1, 2],
'value': [100, 200]
})
df2 = pd.DataFrame({
'id': [1, 2],
'value': [300, 400]
})
# اللاحقات الافتراضية: _x و _y
result = pd.merge(df1, df2, on='id')
print(result)
# id value_x value_y
# 0 1 100 300
# 1 2 200 400
# لاحقات مخصصة
result2 = pd.merge(df1, df2, on='id', suffixes=('_left', '_right'))
print(result2)
# id value_left value_right
> **الخرج:** قم بالتشغيل في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبتًا مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) ومتابعة الشرح. القيم الفعلية قد تختلف قليًّا حسب إصدار pandas.
(2) مُشير لتشخيص مصادر الدمج
▶ مثال
> **الخرج:** قم بالتشغيل في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبتًا مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) ومتابعة الشرح. القيم الفعلية قد تختلف قليًّا حسب إصدار pandas.
: مُشير لتتبع أصل الصفوف (الصعوبة ⭐⭐)
import pandas as pd
left = pd.DataFrame({'id': [1, 2, 3], 'name': ['Alice', 'Bob', 'Charlie']})
right = pd.DataFrame({'id': [2, 3, 4], 'score': [85, 92, 78]})
# إضافة عمود _merge لمعرفة مصدر كل صف
result = pd.merge(left, right, on='id', how='outer', indicator=True)
print(result)
# id name score _merge
# 0 1 Alice NaN left_only
# 1 2 Bob 85.0 both
# 2 3 Charlie 92.0 both
# 3 4 NaN 78.0 right_only
# التصفية حسب مصدر الدمج
only_left = result[result['_merge'] == 'left_only']
print(f"موجود فقط في الجدول الأيسر: {len(only_left)} صفوف") # 1 (Alice)
# التحقق من سلامة البيانات - إيجاد السجلات غير المتطابقة
unmatched = result[result['_merge'] != 'both']
print(f"السجلات غير المتطابقة: {len(unmatched)}") # 2
> **الخرج:** قم بالتشغيل في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبتًا مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) ومتابعة الشرح. القيم الفعلية قد تختلف قليًّا حسب إصدار pandas.
6. التحقق والدمج التقاطعي
(1) التحقق من التحقق من علاقات الدمج
▶ مثال
> **المخرجات:** قم بالتشغيل في بيئة بايثون محلية (pandas 2.x). لا يحتوي خادم Pandas على مكتبة pandas مثبتة مسبقاً. يرجى تثبيتها محلياً (`pip install pandas`) والمتابعة. قد تختلف القيم الفعلي قليلاً حسب إصدار pandas.
: التحقق من صحة الدمج (الصعوبة ⭐⭐)
import pandas as pd
users = pd.DataFrame({'user_id': [1, 2, 3], 'name': ['Alice', 'Bob', 'Charlie']})
orders = pd.DataFrame({'order_id': ['O1', 'O2', 'O3'], 'user_id': [1, 2, 2]})
# التحقق: يجب أن يكون لكل مستخدم طلب واحد على الأكثر (1:1)
# سيفشل هذا لأن المستخدم 2 لديه 2 طلب
try:
result = pd.merge(users, orders, on='user_id', validate='1:1')
except Exception as e:
print(f"فشل التحقق: {e}")
# التحقق: يمكن للمستخدم أن يكون لديه عدة طلبات (1:m) — يمر
result = pd.merge(users, orders, on='user_id', validate='1:m')
print(result) # مقبول
# أنواع التحقق: '1:1', '1:m', 'm:1', 'm:m'
> **المخرجات:** قم بالتشغيل في بيئة بايثون محلية (pandas 2.x). لا يحتوي خادم Pandas على مكتبة pandas مثبتة مسبقاً. يرجى تثبيتها محلياً (`pip install pandas`) والمتابعة. قد تختلف القيم الفعلي قليلاً حسب إصدار pandas.
(2) الدمج التقاطعي (Cross Join)
▶ مثال
> **المخرجات:** قم بالتشغيل في بيئة بايثون محلية (pandas 2.x). لا يحتوي خادم Pandas على مكتبة pandas مثبتة مسبقاً. يرجى تثبيتها محلياً (`pip install pandas`) والمتابعة. قد تختلف القيم الفعلي قليلاً حسب إصدار pandas.
: الدمج التقاطعي (الصعوبة ⭐)
import pandas as pd
colors = pd.DataFrame({'color': ['Red', 'Blue']})
sizes = pd.DataFrame({'size': ['S', 'M', 'L']})
# كل التوليفات (الجداء الديكارتي)
result = pd.merge(colors, sizes, how='cross')
print(result)
# color size
# 0 Red S
# 1 Red M
# 2 Red L
# 3 Blue S
# 4 Blue M
# 5 Blue L
# 6 صفوف = 2 لون × 3 مقاسات
> **المخرجات:** قم بالتشغيل في بيئة بايثون محلية (pandas 2.x). لا يحتوي خادم Pandas على مكتبة pandas مثبتة مسبقاً. يرجى تثبيتها محلياً (`pip install pandas`) والمتابعة. قد تختلف القيم الفعلي قليلاً حسب إصدار pandas.
7. merge مقابل join
| الميزة | merge | join |
|---|---|---|
| نمط الاستدعاء | pd.merge(df1, df2) |
df1.join(df2) |
| مفتاح الربط الافتراضي | العمود المحدد بواسطة on | الفهرس (Index) |
| الربط بأعمدة متعددة | ✅ on=['a','b'] | ❌ الفهرس فقط |
| أنواع الربط | inner/left/right/outer | left (الافتراضي) |
| المرونة | عالية | منخفضة |
| الأفضل لـ | الربط لأغراض عامة | محاذاة الفهارس |
▶ مثال
> **الإخراج:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبتًا مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) والعمل وفقًا لذلك. قد تتغير القيم الفعليّة بشكل طفيف اعتمادًا على إصدار pandas.
: اختصار join (الصعوبة ⭐)
import pandas as pd
df1 = pd.DataFrame({'A': [1, 2, 3]}, index=['x', 'y', 'z'])
df2 = pd.DataFrame({'B': [4, 5]}, index=['x', 'y'])
# join يستخدم الفهرس بشكل افتراضي
result = df1.join(df2) # how='left' by default
print(result)
# A B
# x 1 4.0
# y 2 5.0
# z 3 NaN ← left join يبقي جميع صفوف اليسار
# join مع معامل how
result2 = df1.join(df2, how='inner')
> **الإخراج:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبتًا مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) والعمل وفقًا لذلك. قد تتغير القيم الفعليّة بشكل طفيف اعتمادًا على إصدار pandas.
8. مثال كامل: تحليل الربط بين ثلاث جداول
▶ المثال
> **الخرج:** يتم التشغيل في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على مكتبة pandas مثبتة مسبقاً. يرجى تثبيتها محليًا (`pip install pandas`) والاتباع. قد تتغير القيم الفعلية قليلاً حسب إصدار pandas.
: سير عمل الدمج الثلاثي للجداول (الصعوبة ⭐⭐⭐)
import pandas as pd
# ============================================
# مثال شامل: دمج 3 جداول
# المستخدمون + الطلبات + المنتجات → تحليل كامل
# ============================================
# 1. جدول المستخدمين
users = pd.DataFrame({
'user_id': [1, 2, 3, 4, 5],
'name': ['Alice', 'Bob', 'Charlie', 'Carol', 'David'],
'region': ['North', 'South', 'East', 'North', 'West']
})
# 2. جدول الطلبات
orders = pd.DataFrame({
'order_id': ['O001', 'O002', 'O003', 'O004', 'O005', 'O006'],
'user_id': [1, 2, 2, 3, 1, 5],
'product_id': ['P01', 'P02', 'P03', 'P01', 'P04', 'P02'],
'quantity': [2, 1, 3, 1, 5, 2]
})
# 3. جدول المنتجات
products = pd.DataFrame({
'product_id': ['P01', 'P02', 'P03', 'P04', 'P05'],
'product_name': ['Laptop', 'Phone', 'Tablet', 'Mouse', 'Keyboard'],
'price': [999, 699, 349, 29, 79]
})
# الخطوة 1: الطلبات + المنتجات → تفاصيل الطلب مع السعر
order_details = pd.merge(orders, products, on='product_id', how='left')
order_details['total'] = order_details['quantity'] * order_details['price']
# الخطوة 2: تفاصيل_الطلب + المستخدمون → الصورة الكاملة
full = pd.merge(order_details, users, on='user_id', how='left')
# الخطوة 3: التحليل حسب المنطقة
region_sales = full.groupby('region')['total'].sum().sort_values(ascending=False)
print("=== المبيعات حسب المنطقة ===")
print(region_sales)
# الخطوة 4: أفضل العملاء
top_customers = full.groupby('name')['total'].sum().sort_values(ascending=False)
print("\n=== أفضل العملاء ===")
print(top_customers)
# الخطوة 5: المنتجات غير المتطابقة (في المنتجات ولكن لم تُطلب أبداً)
unmatched = pd.merge(
products, orders[['product_id']].drop_duplicates(),
on='product_id', how='left', indicator=True
)
never_ordered = unmatched[unmatched['_merge'] == 'left_only']
print(f"\n=== منتجات لم تُطلَب أبداً ===")
print(never_ordered[['product_id', 'product_name']])
> **الخرج:** يتم التشغيل في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على مكتبة pandas مثبتة مسبقاً. يرجى تثبيتها محليًا (`pip install pandas`) والاتباع. قد تتغير القيم الفعلية قليلاً حسب إصدار pandas.
❓ أسئلة شائعة
pd.merge(a, b, left_on='emp_id', right_on='employee_id'). بعد الدجم، يحتفظ العمودان معاً — استخدم drop لإزالة العمود الزائد. إذا كان أحد المفاتيح فهرساً، استخدم left_index=True / right_index=True.df.rename(columns={'value': 'value_a'}).📖 ملخص
- تحتوي عملية الدمج على 4 أنواع من الربط: الداخلي (مطابقة فقط) / الأيسر (الاحتفاظ بجميع صفوف الجدول الأيسر) / الأيمن (الاحتفاظ بجميع صفوف الجدول الأيمن) / الخارجي (الاحتفاظ بكل شيء من كلا الجانبين)
- الدمج متعدد المفاتيح يستخدم on=['key1','key2']; أسماء الأعمدة المختلفة تستخدم left_on/right_on
- اللاحقات (suffixes) تتعامل مع أسماء الأعمدة المكررة؛ المؤشر (indicator) يتبع أصل الصفوف
- التحقق (validate) يحدد علاقات الدمج (1:1 / 1:m / m:1) لمنع المفاجآت
- الربط التقاطعي (cross join) ينتج حاصل ضرب ديكارت — استخدمه فقط مع الجداول الصغيرة
- join هو اختصار لـ merge (يستخدم فهرس الربط + ربط أيسر بشكل افتراضي)
- دائماً تحقق من عدد الصفوف والمؤشر بعد الدمج للتأكد من الصحة
📝 تمارين
- أساسي (صعوبة ⭐): أنشئ جدولًا للطلاب وجدولًا للدرجات (يشاركان student_id). قم بعمليات الدمج الداخلية/اليسرى/الخارجية ولاحظ كيف يتغير عدد الصفوف.
- متوسط (صعوبة ⭐⭐): أنشئ جدول الأقسام (dept_id/dept_name) وجدول الموظفين (emp_id/dept_id/salary). ادمجهما، واحسب متوسط الراتب لكل قسم، واستخدم المؤشر لإيجاد الأقسام التي ليس لديها موظفون.
- تحدي (صعوبة ⭐⭐⭐): قم بمحاكاة ثلاث جداول (users/orders/products) وأكمل ما يلي: الدمج الداخلي لدمجها → حساب إجمالي الإنفاق لكل مستخدم → الدمج اليسري لإيجاد المستخدمين الذين انفاقهم صفر → validate للتحقق من العلاقات → indicator لتشخيص المصادر.
← Previous: GroupBy and Aggregation · Next: Concat and Append →