Menu
flag Ar iconالعربيةdown icon

دمج أطر البيانات ووصلها في R (merge وleft_join)

جمع أطر البيانات عبر مفتاح مشترك: ‏merge() للوصل الداخلي واليساري واليميني والكامل، واختلاف أسماء المفاتيح عبر by.x/by.y، وعائلة الوصل في dplyr، وrbind() لتكديس الصفوف.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

جدولان ومفتاح واحد

نادرًا ما تعيش البيانات الحقيقية في جدول واحد. فالعملاء في إطار بيانات، وطلباتهم في إطار آخر، والسؤال "كم أنفق كل عميل؟" يحتاج إليهما معًا. والذي يربط بينهما هو المفتاح: عمود موجود في كل جدول يعرّف الكيان نفسه. وجمع الجداول عبر مفتاح يُسمى وصلًا (بلغة SQL) أو دمجًا (بلغة base R)، والعملية واحدة.

وهذان هما الجدولان اللذان تستخدمهما بقية الصفحة. لاحظ أن العميل رقم 5 قدّم طلبًا لكنه غير موجود في جدول العملاء، وأن العميلين 2 و4 لم يطلبا شيئًا قط:

عدم التطابق هنا مقصود، فما يفعله الوصل بالصفوف غير المتطابقة هو تحديدًا ما يميّز أنواع الوصل بعضها عن بعض.

‏merge(): وصل داخلي افتراضيًا

تطابق merge(x, y, by = "key") الصفوف ذات المفاتيح المتساوية وتلصق أعمدتها معًا. وهي تُبقي افتراضيًا المفاتيح الموجودة في الجدولين فقط، أي وصلًا داخليًا:

تعود ثلاثة صفوف. وتظهر Ana مرتين لأن لديها طلبين، فالوصل يُنتج صف مخرجات واحدًا لكل زوج متطابق. واختفى Ben وDana (لا طلبات لهما)، واختفى كذلك الطلب الغامض للعميل 5 (لا عميل له). فالوصل الداخلي يتخلص بصمت من الصفوف غير المتطابقة في الجدولين، وهو تصرف صحيح تمامًا حين تهتم بالأزواج الكاملة فقط، وخلل صامت في فقدان البيانات حين لا يكون كذلك.

الوصل اليساري واليميني والكامل: ‏all.x وall.y وall

للاحتفاظ بالصفوف غير المتطابقة، حدّد أي جدول تُعد صفوفه مقدسة. تُبقي all.x = TRUE كل صفوف الجدول الأول، وهذا هو الوصل اليساري، أكثر أنواع الوصل استخدامًا عمليًا:

الآن ينجو Ben وDana مع NA في amount، أي "هذا العميل موجود ولم يطابقه أي طلب". وقيم NA هذه مفيدة لا نفاية: فـ is.na(result$amount) هي بالضبط قائمة العملاء الذين لم يطلبوا قط (ويغطي مستند القيم المفقودة التعامل معها). أما الصيغ المتبقية فهي الفكرة نفسها موجّهة إلى جهة أخرى: تُبقي all.y = TRUE كل صفوف الجدول الثاني (الوصل اليميني، حيث ينجو طلب العميل المجهول 5 مع NA في name)، وتُبقي all = TRUE كل شيء من الجهتين (الوصل الكامل).

واختر بأن تسأل: صفوف مَن لا يُسمح لي بفقدانها؟ فإثراء جدول رئيسي ببيانات بحث يستدعي وصلًا يساريًا مع الجدول الرئيسي أولًا، أما تدقيق الاتجاهين فيستدعي وصلًا كاملًا.

أسماء مفاتيح مختلفة: ‏by.x وby.y

نادرًا ما تتفق الجداول على التسمية، فيكون id في أحدها وcustomer_id في الآخر. لا تعِد التسمية، بل أخبِر merge() بالاسمين:

تحتفظ المخرجات باسم المفتاح من الجدول الأول. وأمر ذو صلة: إذا اشترك الجدولان في أسماء أعمدة غير مفتاحية (كأن يحوي كلاهما عمود date)، تلحق merge بها اللاحقتين date.x وdate.y، فأعد تسميتها سريعًا لأن قراءتها سيئة للغاية.

أنواع الوصل في dplyr

تمنح dplyr كل نوع وصل فعلًا خاصًا به، فيصبح القصد في اسم الدالة بدل وسائط الأعلام (المثال ثابت، فالبيئة التجريبية تشغّل base R فقط):

library(dplyr)

left_join(customers, orders, by = "id")
inner_join(customers, orders, by = "id")
full_join(customers, orders, by = "id")
left_join(customers, orders, by = c("id" = "customer_id"))  # different names

وإلى جانب وضوح القراءة، تحافظ left_join() على ترتيب صفوف الجدول الأول (بينما تعيد merge() الترتيب حسب المفتاح) وتحذّر بوضوح من التطابقات متعددة إلى متعددة. راجع مقدمة dplyr للاطلاع على عائلة الأفعال.

والعضو المبخوس حقه هو anti_join(): تُرجع صفوف الجدول الأول التي لا تطابق شيئًا في الثاني، بلا إضافة أعمدة، بل البقايا فقط:

anti_join(customers, orders, by = "id")   # customers who never ordered

وهذا السؤال، أي "أي الصفوف أخفقت في التطابق؟"، يتكرر باستمرار في تنظيف البيانات (المعرّفات غير المتطابقة، والسجلات اليتيمة، وعمليات البحث الفاشلة)، وتجيب عنه anti_join() في استدعاء واحد بينما يحتاج base R إلى customers[!(customers$id %in% orders$id), ].

تكديس الصفوف: ‏rbind()

يجمع الوصل أعمدة جدولين. أما حين تكون لديك دفعتان من الصفوف من النوع نفسه، كطلبات يناير وطلبات فبراير، فتكدّسهما بـ rbind():

والشرط صارم: يجب أن يحمل الإطاران أسماء الأعمدة نفسها (بأي ترتيب، فـ rbind تطابق بالاسم). ووجود عمود ناقص أو زائد خطأ لا يُملأ بـ NA. وحين تتباعد أعمدة الدفعات، تكون bind_rows() في dplyr أكثر تسامحًا، إذ تحاذي بالاسم وتملأ الفجوات بـ NA.

انفجار المفاتيح المكررة

الحادث الكلاسيكي في الوصل: مفاتيح ظننتها فريدة وهي ليست كذلك في كلا الجدولين. فكل تطابق يقترن بكل تطابق، وتتضاعف الصفوف:

صفان موصولان بصفين يعطيان أربعة، إذ يقترن كل x بكل y. وعلى البيانات الحقيقية، هكذا يتحول جدول من 10,000 صف إلى 3 ملايين صف وإلى إجمالي إيرادات مضاعف. وتفعل merge() ذلك بصمت. والدفاع عادة بسيطة: قبل الوصل، تحقق من المفتاح الذي تفترض أنه فريد، إذ ينبغي أن تكون anyDuplicated(customers$id) مساوية 0، وبعد الوصل تحقق من nrow() مقابل ما توقعته.

الخلاصة

  • الصيغة merge(x, y, by = "key") وصل داخلي، والصفوف غير المتطابقة تختفي بصمت.
  • تُبقي all.x = TRUE (يساري) وall.y = TRUE (يميني) وall = TRUE (كامل) الصفوف غير المتطابقة وتملأها بـ NA.
  • لأسماء مفاتيح مختلفة: by.x / by.y في base، وby = c("a" = "b") في dplyr.
  • تسمّي dplyr كل نوع وصل، وanti_join()، أي الصفوف التي لم تتطابق، هي المبخوسة حقها.
  • تكدّس rbind() الجداول المتطابقة الشكل، ويجب أن تتطابق الأعمدة بالاسم.
  • المفاتيح المكررة تضاعف الصفوف بصمت، فتحقق من anyDuplicated() قبل ومن nrow() بعد.

في المستند التالي: إعادة تشكيل البيانات بين الصيغتين العريضة والطويلة عبر pivot_longer() وpivot_wider().

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

كيف أدمج إطارَي بيانات في R؟

استخدم merge(x, y, by = "key") مع عمود المفتاح المشترك. وهي تنفّذ افتراضيًا وصلًا داخليًا، فلا ينجو إلا الصفوف التي يظهر مفتاحها في الإطارين معًا. أضف all.x = TRUE للوصل اليساري، أو all.y = TRUE للوصل اليميني، أو all = TRUE للوصل الكامل.

كيف أنفّذ وصلًا يساريًا في R؟

في base R: تُبقي merge(x, y, by = "key", all.x = TRUE) كل صفوف x وتملأ أعمدة y بـ NA حيث لا يوجد تطابق. وفي dplyr: left_join(x, y, by = "key") تعطي النتيجة نفسها، وتحافظ أيضًا على ترتيب صفوف x الأصلي، وهو ما لا تفعله merge().

كيف أدمج أطر البيانات حين تختلف أسماء أعمدة المفتاح؟

أخبِر merge() بالاسمين: merge(x, y, by.x = "id", by.y = "customer_id"). والمكافئ في dplyr هو left_join(x, y, by = c("id" = "customer_id"))، أو مع المساعد الحديث by = join_by(id == customer_id).

ما الفرق بين merge وrbind في R؟

كل منهما يجمع في بُعد مختلف. تطابق merge() صفوف جدولين عبر مفتاح وتجمع أعمدتهما، أي أنها وصل. أما rbind() فتكدّس صفوف جدول تحت صفوف آخر، ويجب أن يتطابق اسما الأعمدة. فالملفات الشهرية ذات الأعمدة المتطابقة تحتاج rbind()، أما العملاء مع الطلبات فيحتاجون merge().

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن