19 عمليات الدمج (Joins)
19.1 مقدمة
من النادر أن يتضمن تحليل البيانات إطار بيانات واحداً فقط. عادة ما تكون لديك العديد من إطارات البيانات، ويجب عليك دمجها (join) معا للإجابة على الأسئلة التي تهتم بها. سيقدم لك هذا الفصل نوعين مهمين من عمليات الدمج:
- عمليات الدمج التحويرية (Mutating joins). والتي تضيف متغيرات جديدة إلى إطار بيانات واحد من الملاحظات المطابقة في إطار بيانات آخر.
- عمليات الدمج الترشيحية (Filtering joins). والتي ترشح الملاحظات من إطار بيانات واحد بناءً على ما إذا كانت تطابق ملاحظة في إطار بيانات آخر أم لا.
سنبدأ بمناقشة المفاتيح (keys)، وهي المتغيرات المستخدمة لربط زوج من إطارات البيانات في عملية الدمج. سنثبت النظرية بفحص المفاتيح في مجموعات البيانات من حزمة nycflights13، ثم نستخدم تلك المعرفة للبدء في دمج إطارات البيانات معاً. بعد ذلك، سنناقش كيفية عمل عمليات الدمج، مع التركيز على تأثيرها على الصفوف. سننتهي بمناقشة عمليات الدمج غير المتكافئة (non-equi joins)، وهي عائلة من عمليات الدمج توفر طريقة أكثر مرونة لمطابقة المفاتيح من علاقة المساواة الافتراضية.
19.1.1 المتطلبات المسبقة
في هذا الفصل، سنستكشف مجموعات البيانات الخمس المترابطة من حزمة nycflights13 باستخدام دوال الدمج من حزمة dplyr.
19.2 المفاتيح (Keys)
لفهم عمليات الدمج، عليك أولاً فهم كيفية ربط جدولين من خلال زوج من المفاتيح داخل كل جدول. في هذا القسم، ستتعلم عن نوعي المفاتيح وسترى أمثلة على كليهما في مجموعات بيانات حزمة nycflights13. ستتعلم أيضاً كيفية التحقق من صحة مفاتيحك، وماذا تفعل إذا كان جدولك يفتقر إلى مفتاح.
19.2.1 المفاتيح الأساسية والخارجية
تتضمن كل عملية دمج زوجاً من المفاتيح: مفتاح أساسي ومفتاح خارجي. المفتاح الأساسي (Primary key) هو متغير أو مجموعة متغيرات تحدد بشكل فريد كل ملاحظة. عندما يلزم أكثر من متغير واحد، يسمى المفتاح مفتاحاً مركباً (compound key). على سبيل المثال، في nycflights13:
- يسجل جدول
airlinesقطعتين من البيانات عن كل شركة طيران: رمز الناقل واسمها الكامل. يمكنك تحديد شركة الطيران برمز الناقل المكون من حرفين، مما يجعلcarrierهو المفتاح الأساسي.
airlines
#> # A tibble: 16 × 2
#> carrier name
#> <chr> <chr>
#> 1 9E Endeavor Air Inc.
#> 2 AA American Airlines Inc.
#> 3 AS Alaska Airlines Inc.
#> 4 B6 JetBlue Airways
#> 5 DL Delta Air Lines Inc.
#> 6 EV ExpressJet Airlines Inc.
#> # ℹ 10 more rows- يسجل جدول
airportsبيانات عن كل مطار. يمكنك تحديد كل مطار بواسطة رمز المطار المكون من ثلاثة أحرف، مما يجعلfaaهو المفتاح الأساسي.
airports
#> # A tibble: 1,458 × 8
#> faa name lat lon alt tz dst
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr>
#> 1 04G Lansdowne Airport 41.1 -80.6 1044 -5 A
#> 2 06A Moton Field Municipal Airport 32.5 -85.7 264 -6 A
#> 3 06C Schaumburg Regional 42.0 -88.1 801 -6 A
#> 4 06N Randall Airport 41.4 -74.4 523 -5 A
#> 5 09J Jekyll Island Airport 31.1 -81.4 11 -5 A
#> 6 0A9 Elizabethton Municipal Airpo… 36.4 -82.2 1593 -5 A
#> # ℹ 1,452 more rows
#> # ℹ 1 more variable: tzone <chr>- يسجل جدول
planesبيانات عن كل طائرة. يمكنك تحديد الطائرة برقم ذيلها، مما يجعلtailnumهو المفتاح الأساسي.
planes
#> # A tibble: 3,322 × 9
#> tailnum year type manufacturer model engines
#> <chr> <int> <chr> <chr> <chr> <int>
#> 1 N10156 2004 Fixed wing multi… EMBRAER EMB-145XR 2
#> 2 N102UW 1998 Fixed wing multi… AIRBUS INDUSTR… A320-214 2
#> 3 N103US 1999 Fixed wing multi… AIRBUS INDUSTR… A320-214 2
#> 4 N104UW 1999 Fixed wing multi… AIRBUS INDUSTR… A320-214 2
#> 5 N10575 2002 Fixed wing multi… EMBRAER EMB-145LR 2
#> 6 N105UW 1999 Fixed wing multi… AIRBUS INDUSTR… A320-214 2
#> # ℹ 3,316 more rows
#> # ℹ 3 more variables: seats <int>, speed <int>, engine <chr>- يسجل جدول
weatherبيانات عن الطقس في مطارات المغادرة. يمكنك تحديد كل ملاحظة من خلال تجميعة الموقع والوقت، مما يجعلoriginوtime_hourهما المفتاح الأساسي المركب.
weather
#> # A tibble: 26,115 × 15
#> origin year month day hour temp dewp humid wind_dir
#> <chr> <int> <int> <int> <int> <dbl> <dbl> <dbl> <dbl>
#> 1 EWR 2013 1 1 1 39.0 26.1 59.4 270
#> 2 EWR 2013 1 1 2 39.0 27.0 61.6 250
#> 3 EWR 2013 1 1 3 39.0 28.0 64.4 240
#> 4 EWR 2013 1 1 4 39.9 28.0 62.2 250
#> 5 EWR 2013 1 1 5 39.0 28.0 64.4 260
#> 6 EWR 2013 1 1 6 37.9 28.0 67.2 240
#> # ℹ 26,109 more rows
#> # ℹ 6 more variables: wind_speed <dbl>, wind_gust <dbl>, …المفتاح الخارجي (Foreign key) هو متغير (أو مجموعة متغيرات) يتوافق مع مفتاح أساسي في جدول آخر. على سبيل المثال:
-
flights$tailnumهو مفتاح خارجي يتوافق مع المفتاح الأساسيplanes$tailnum. -
flights$carrierهو مفتاح خارجي يتوافق مع المفتاح الأساسيairlines$carrier. -
flights$originهو مفتاح خارجي يتوافق مع المفتاح الأساسيairports$faa. -
flights$destهو مفتاح خارجي يتوافق مع المفتاح الأساسيairports$faa. -
flights$origin-flights$time_hourهو مفتاح خارجي مركب يتوافق مع المفتاح الأساسي المركبweather$origin-weather$time_hour.
يتم تلخيص هذه العلاقات بصرياً في الشكل 19.1.
ستلاحظ ميزة جيدة في تصميم هذه المفاتيح: المفاتيح الأساسية والخارجية لها تقريباً نفس الأسماء دائماً، مما سيجعل حياة الدمج أسهل بكثير كما سترى قريباً. من الجدير بالذكر أيضاً العلاقة العكسية: تقريباً كل اسم متغير مستخدم في جداول متعددة له نفس المعنى في كل مكان. هناك استثناء واحد فقط: year يعني سنة المغادرة في flights وسنة الصنع في planes. سيصبح هذا مهماً عندما نبدأ فعلياً في دمج الجداول معاً.
19.2.2 التحقق من المفاتيح الأساسية
الآن بعد أن حددنا المفاتيح الأساسية في كل جدول، من الممارسات الجيدة التحقق من أنها تحدد كل ملاحظة بشكل فريد بالفعل. إحدى الطرق للقيام بذلك هي استخدام count() مع المفاتيح الأساسية والبحث عن المدخلات التي يكون فيها n أكبر من واحد. يكشف هذا أن planes و weather يبدوان جيدين:
يجب عليك أيضاً التحقق من وجود قيم مفقودة في مفاتيحك الأساسية — إذا كانت القيمة مفقودة، فلا يمكنها تحديد ملاحظة!
planes |>
filter(is.na(tailnum))
#> # A tibble: 0 × 9
#> # ℹ 9 variables: tailnum <chr>, year <int>, type <chr>, manufacturer <chr>,
#> # model <chr>, engines <int>, seats <int>, speed <int>, engine <chr>
weather |>
filter(is.na(time_hour) | is.na(origin))
#> # A tibble: 0 × 15
#> # ℹ 15 variables: origin <chr>, year <int>, month <int>, day <int>,
#> # hour <int>, temp <dbl>, dewp <dbl>, humid <dbl>, wind_dir <dbl>, …19.2.3 المفاتيح البديلة (Surrogate keys)
حتى الآن لم نتحدث عن المفتاح الأساسي لجدول flights. إنه ليس مهماً جداً هنا، لأنه لا توجد إطارات بيانات تستخدمه كمفتاح خارجي، لكن لا يزال من المفيد مراعاته لأنه من الأسهل العمل مع الملاحظات إذا كان لدينا طريقة لوصفها للآخرين.
بعد القليل من التفكير والتجربة، حددنا أن هناك ثلاثة متغيرات تحدد كل رحلة بشكل فريد معاً:
هل غياب التكرارات يجعل time_hour-carrier-flight مفتاحاً أساسياً تلقائياً؟ إنها بالتأكيد بداية جيدة، لكنها لا تضمن ذلك. على سبيل المثال، هل الارتفاع وخط العرض مفتاح أساسي جيد لجدول airports؟
تحديد المطار من خلال ارتفاعه وخط العرض هو بوضوح فكرة سيئة، وبشكل عام ليس من الممكن معرفة ما إذا كانت مجموعة من المتغيرات تشكل مفتاحاً أساسياً جيداً من البيانات وحدها. ولكن بالنسبة للرحلات الجوية، يبدو التجميع بين time_hour و carrier و flight معقولاً لأنه سيكون مربكاً حقاً لشركة الطيران وعملائها إذا كانت هناك رحلات متعددة بنفس رقم الرحلة في الجو في نفس الوقت.
ومع ذلك، قد نكون بأفضل حال عند تقديم مفتاح بديل عددي بسيط باستخدام رقم الصف:
flights2 <- flights |>
mutate(id = row_number(), .before = 1)
flights2
#> # A tibble: 336,776 × 20
#> id year month day dep_time sched_dep_time dep_delay arr_time
#> <int> <int> <int> <int> <int> <int> <dbl> <int>
#> 1 1 2013 1 1 517 515 2 830
#> 2 2 2013 1 1 533 529 4 850
#> 3 3 2013 1 1 542 540 2 923
#> 4 4 2013 1 1 544 545 -1 1004
#> 5 5 2013 1 1 554 600 -6 812
#> 6 6 2013 1 1 554 558 -4 740
#> # ℹ 336,770 more rows
#> # ℹ 12 more variables: sched_arr_time <int>, arr_delay <dbl>, …يمكن أن تكون المفاتيح البديلة مفيدة بشكل خاص عند التواصل مع البشر الآخرين: إنه أسهل بكثير أن تقول لشخص ما أن ينظر إلى الرحلة رقم 2001 من أن تقول انظر إلى UA430 التي غادرت الساعة 9 صباحاً 2013-01-03.
19.2.4 تمارين
نسينا رسم العلاقة بين
weatherوairportsفي الشكل 19.1. ما هي العلاقة وكيف ينبغي أن تظهر في المخطط؟يحتوي جدول
weatherفقط على معلومات لمطارات المغادرة الثلاثة في نيويورك. إذا كان يحتوي على سجلات الطقس لجميع المطارات في الولايات المتحدة، فما هو الربط الإضافي الذي سيجريه معflights؟تشكل المتغيرات
yearوmonthوdayوhourوoriginمفتاحاً مركباً لجدولweatherتقريباً، ولكن هناك ساعة واحدة تحتوي على ملاحظات مكررة. هل يمكنك معرفة ما المميز في تلك الساعة؟نعلم أن بعض أيام السنة مميزة ويسافر فيها عدد أقل من الناس عن المعتاد (على سبيل المثال، عشية الميلاد ويوم الميلاد). كيف يمكنك تمثيل تلك البيانات كإطار بيانات؟ ماذا سيكون المفتاح الأساسي؟ كيف سيتصل بإطارات البيانات الحالية؟
ارسم مخططاً يوضح الروابط بين إطارات البيانات
BattingوPeopleوSalariesفي حزمة Lahman. ارسم مخططاً آخر يوضح العلاقة بينPeopleوManagersوAwardsManagers. كيف تصف العلاقة بين إطارات البياناتBattingوPitchingوFielding؟
19.3 عمليات الدمج الأساسية
الآن بعد أن فهمت كيفية اتصال إطارات البيانات عبر المفاتيح، يمكننا البدء في استخدام عمليات الدمج لفهم مجموعة بيانات flights بشكل أفضل. توفر حزمة dplyr ست دوال للدمج: left_join() و inner_join() و right_join() و full_join() و semi_join() و anti_join(). جميعها لها نفس الواجهة: تأخذ زوجاً من إطارات البيانات (x و y) وترجع إطار بيانات. ترتيب الصفوف والأعمدة في المخرجات يتحدد بشكل أساسي بواسطة x.
في هذا القسم، ستتعلم كيفية استخدام نوع واحد من عمليات الدمج التحويرية وهو left_join()، ونوعين من عمليات الدمج الترشيحية وهما semi_join() و anti_join(). في القسم التالي، ستتعلم بالضبط كيفية عمل هذه الدوال، وعن الدوال المتبقية inner_join() و right_join() و full_join().
19.3.1 عمليات الدمج التحويرية (Mutating joins)
يسمح لك الدمج التحويلي (mutating join) بدمج المتغيرات من إطارين للبيانات (two data frames): حيث يقوم أولاً بمطابقة المشاهدات بناءً على مفاتيحها (keys)، ثم نسخ المتغيرات من أحد إطاري البيانات إلى الآخر. وعلى غرار الدالة mutate()، تضيف دالات الدمج المتغيرات جهة اليمين، لذا إذا كانت مجموعة البيانات لديك تحتوي على العديد من المتغيرات، فلن تتمكن من رؤية المتغيرات الجديدة. ومن أجل هذه الأمثلة، سنقوم بتسهيل رؤية ما يحدث عبر إنشاء مجموعة بيانات أكثر ضيقاً تحتوي على ستة متغيرات فقط1:
flights2 <- flights |>
select(year, time_hour, origin, dest, tailnum, carrier)
flights2
#> # A tibble: 336,776 × 6
#> year time_hour origin dest tailnum carrier
#> <int> <dttm> <chr> <chr> <chr> <chr>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA
#> # ℹ 336,770 more rowsهناك أربعة أنواع من الدمج التحويلي (mutating join)، ولكن هناك نوعاً واحداً ستستخدمه في معظم الأوقات تقريباً: وهو left_join(). ما يميز هذا النوع هو أن المخرجات ستحتوي دائماً على نفس عدد صفوف إطار البيانات x (وهو إطار البيانات الذي تقوم بالدمج إليه)2. الاستخدام الأساسي للدالة left_join() هو إضافة بيانات وصفية (metadata) إضافية. على سبيل المثال، يمكننا استخدام left_join() لإضافة اسم شركة الطيران الكامل إلى بيانات flights2:
flights2 |>
left_join(airlines)
#> Joining with `by = join_by(carrier)`
#> # A tibble: 336,776 × 7
#> year time_hour origin dest tailnum carrier name
#> <int> <dttm> <chr> <chr> <chr> <chr> <chr>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA United Air Lines In…
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA United Air Lines In…
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA American Airlines I…
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 JetBlue Airways
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL Delta Air Lines Inc.
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA United Air Lines In…
#> # ℹ 336,770 more rowsأو يمكننا معرفة درجة الحرارة وسرعة الرياح عندما غادرت كل طائرة:
flights2 |>
left_join(weather |> select(origin, time_hour, temp, wind_speed))
#> Joining with `by = join_by(time_hour, origin)`
#> # A tibble: 336,776 × 8
#> year time_hour origin dest tailnum carrier temp wind_speed
#> <int> <dttm> <chr> <chr> <chr> <chr> <dbl> <dbl>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA 39.0 12.7
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA 39.9 15.0
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA 39.0 15.0
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 39.0 15.0
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL 39.9 16.1
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA 39.0 12.7
#> # ℹ 336,770 more rowsأو حجم الطائرة التي كانت تطير:
flights2 |>
left_join(planes |> select(tailnum, type, engines, seats))
#> Joining with `by = join_by(tailnum)`
#> # A tibble: 336,776 × 9
#> year time_hour origin dest tailnum carrier type
#> <int> <dttm> <chr> <chr> <chr> <chr> <chr>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA Fixed wing multi en…
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA Fixed wing multi en…
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA Fixed wing multi en…
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 Fixed wing multi en…
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL Fixed wing multi en…
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA Fixed wing multi en…
#> # ℹ 336,770 more rows
#> # ℹ 2 more variables: engines <int>, seats <int>عندما تفشل left_join() في العثور على مطابقة لصف في x، فإنها تملأ المتغيرات الجديدة بقيم مفقودة. على سبيل المثال، لا توجد معلومات عن الطائرة برقم الذيل N3ALAA لذا ستكون type و engines و seats مفقودة:
flights2 |>
filter(tailnum == "N3ALAA") |>
left_join(planes |> select(tailnum, type, engines, seats))
#> Joining with `by = join_by(tailnum)`
#> # A tibble: 63 × 9
#> year time_hour origin dest tailnum carrier type engines seats
#> <int> <dttm> <chr> <chr> <chr> <chr> <chr> <int> <int>
#> 1 2013 2013-01-01 06:00:00 LGA ORD N3ALAA AA <NA> NA NA
#> 2 2013 2013-01-02 18:00:00 LGA ORD N3ALAA AA <NA> NA NA
#> 3 2013 2013-01-03 06:00:00 LGA ORD N3ALAA AA <NA> NA NA
#> 4 2013 2013-01-07 19:00:00 LGA ORD N3ALAA AA <NA> NA NA
#> 5 2013 2013-01-08 17:00:00 JFK ORD N3ALAA AA <NA> NA NA
#> 6 2013 2013-01-16 06:00:00 LGA ORD N3ALAA AA <NA> NA NA
#> # ℹ 57 more rowsسنعود إلى هذه المشكلة عدة مرات في بقية الفصل.
19.3.2 تحديد مفاتيح الدمج
افتراضياً، ستستخدم left_join() جميع المتغيرات التي تظهر في كلا إطاري البيانات كمفتاح للدمج، وهو ما يسمى الدمج الطبيعي (natural join). هذا أسلوب حدسي مفيد، ولكنه لا يعمل دائماً. على سبيل المثال، ماذا يحدث إذا حاولنا دمج flights2 مع مجموعة بيانات planes الكاملة؟
flights2 |>
left_join(planes)
#> Joining with `by = join_by(year, tailnum)`
#> # A tibble: 336,776 × 13
#> year time_hour origin dest tailnum carrier type manufacturer
#> <int> <dttm> <chr> <chr> <chr> <chr> <chr> <chr>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA <NA> <NA>
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA <NA> <NA>
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA <NA> <NA>
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 <NA> <NA>
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL <NA> <NA>
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA <NA> <NA>
#> # ℹ 336,770 more rows
#> # ℹ 5 more variables: model <chr>, engines <int>, seats <int>, …نحصل على الكثير من التطابقات المفقودة لأن الدمج يحاول استخدام tailnum و year كمفتاح مركب. يحتوي كل من flights و planes على عمود year ولكنهما يعنيان أشياء مختلفة: flights$year هي السنة التي حدثت فيها الرحلة و planes$year هي السنة التي تم فيها تصنيع الطائرة. نريد فقط الدمج على tailnum لذا نحتاج إلى تقديم تحديد صريح باستخدام join_by():
flights2 |>
left_join(planes, join_by(tailnum))
#> # A tibble: 336,776 × 14
#> year.x time_hour origin dest tailnum carrier year.y
#> <int> <dttm> <chr> <chr> <chr> <chr> <int>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA 1999
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA 1998
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA 1990
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 2012
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL 1991
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA 2012
#> # ℹ 336,770 more rows
#> # ℹ 7 more variables: type <chr>, manufacturer <chr>, model <chr>, …لاحظ أنه تم توضيح متغيرات year في المخرجات بلاحقة (year.x و year.y)، والتي تخبرك ما إذا كان المتغير قادماً من المعامل x أو y. يمكنك إبطال اللواحق الافتراضية باستخدام المعامل suffix.
عبارة join_by(tailnum) هي اختصار لـ join_by(tailnum == tailnum). من المهم معرفة هذا الشكل الأكثر اكتمالاً لسببين. أولاً، يصف العلاقة بين الجدولين: يجب أن تكون المفاتيح متساوية. لهذا السبب يُطلق على هذا النوع من الدمج غالباً اسم الدمج المتكافئ (equi join). ستتعلم عن عمليات الدمج غير المتكافئة في قسم 19.5.
ثانياً، هذه هي الطريقة التي تحدد بها مفاتيح دمج مختلفة في كل جدول. على سبيل المثال، هناك طريقتان لدمج جدول flights2 وجدول airports: إما عن طريق dest أو origin:
flights2 |>
left_join(airports, join_by(dest == faa))
#> # A tibble: 336,776 × 13
#> year time_hour origin dest tailnum carrier name
#> <int> <dttm> <chr> <chr> <chr> <chr> <chr>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA George Bush Interco…
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA George Bush Interco…
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA Miami Intl
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 <NA>
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL Hartsfield Jackson …
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA Chicago Ohare Intl
#> # ℹ 336,770 more rows
#> # ℹ 6 more variables: lat <dbl>, lon <dbl>, alt <dbl>, tz <dbl>, …
flights2 |>
left_join(airports, join_by(origin == faa))
#> # A tibble: 336,776 × 13
#> year time_hour origin dest tailnum carrier name
#> <int> <dttm> <chr> <chr> <chr> <chr> <chr>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA Newark Liberty Intl
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA La Guardia
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA John F Kennedy Intl
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 John F Kennedy Intl
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL La Guardia
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA Newark Liberty Intl
#> # ℹ 336,770 more rows
#> # ℹ 6 more variables: lat <dbl>, lon <dbl>, alt <dbl>, tz <dbl>, …في الأكواد القديمة، قد ترى طريقة مختلفة لتحديد مفاتيح الدمج باستخدام متجه نصي:
-
by = "x"تتوافق معjoin_by(x). -
by = c("a" = "x")تتوافق معjoin_by(a == x).
الآن بعد وجودها، نفضل join_by() لأنها توفر تحديداً أوضح وأكثر مرونة.
تمتلك inner_join() و right_join() و full_join() نفس واجهة left_join(). الفرق يكمن في الصفوف التي تحتفظ بها: يحتفظ الدمج الأيسر (left join) بجميع الصفوف في x، ويحتفظ الدمج الأيمن (right join) بجميع الصفوف في y، ويحتفظ الدمج الكامل (full join) بجميع الصفوف في أي من x أو y، بينما يحتفظ الدمج الداخلي (inner join) فقط بالصفوف التي تظهر في كل من x و y. سنعود إلى هذه التفاصيل بمزيد من العمق لاحقاً.
19.3.3 عمليات الدمج الترشيحية (Filtering joins)
كما قد تخمن، فإن الإجراء الأساسي لـ الدمج الترشيحي هو ترشيح الصفوف. هناك نوعان: الدمج شبه الداخلي (semi-joins) والدمج العكسي (anti-joins). تضم عمليات الدمج شبه الداخلي (Semi-joins) جميع الصفوف في x التي لها مطابقة في y. على سبيل المثال، يمكننا استخدام semi-join لترشيح مجموعة بيانات airports لإظهار مطارات المغادرة فقط:
airports |>
semi_join(flights2, join_by(faa == origin))
#> # A tibble: 3 × 8
#> faa name lat lon alt tz dst tzone
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr> <chr>
#> 1 EWR Newark Liberty Intl 40.7 -74.2 18 -5 A America/New_York
#> 2 JFK John F Kennedy Intl 40.6 -73.8 13 -5 A America/New_York
#> 3 LGA La Guardia 40.8 -73.9 22 -5 A America/New_Yorkأو الوجهات فقط:
airports |>
semi_join(flights2, join_by(faa == dest))
#> # A tibble: 101 × 8
#> faa name lat lon alt tz dst tzone
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr> <chr>
#> 1 ABQ Albuquerque Internati… 35.0 -107. 5355 -7 A America/Denver
#> 2 ACK Nantucket Mem 41.3 -70.1 48 -5 A America/New_Yo…
#> 3 ALB Albany Intl 42.7 -73.8 285 -5 A America/New_Yo…
#> 4 ANC Ted Stevens Anchorage… 61.2 -150. 152 -9 A America/Anchor…
#> 5 ATL Hartsfield Jackson At… 33.6 -84.4 1026 -5 A America/New_Yo…
#> 6 AUS Austin Bergstrom Intl 30.2 -97.7 542 -6 A America/Chicago
#> # ℹ 95 more rowsعمليات الدمج العكسي (Anti-joins) هي العكس: فهي ترجع جميع الصفوف في x التي ليس لها مطابقة في y. إنها مفيدة لإيجاد القيم المفقودة الضمنية في البيانات، وهو موضوع قسم 18.3. القيم المفقودة ضمناً لا تظهر كـ NAs ولكنها توجد فقط كغياب. على سبيل المثال، يمكننا العثور على الصفوف المفقودة من airports من خلال البحث عن الرحلات الجوية التي ليس لها مطار وصول مطابق:
أو يمكننا معرفة أرقام الذيل tailnum المفقودة من جدول planes:
19.3.4 تمارين
أوجد الـ 48 ساعة (على مدار العام بأكمله) التي شهدت أسوأ حالات التأخير. قارنها ببيانات الطقس
weather. هل يمكنك رؤية أي أنماط؟-
تخيل أنك وجدت أفضل 10 وجهات الأكثر شعبية باستخدام هذا الكود:
كيف يمكنك العثور على جميع الرحلات الجوية إلى تلك الوجهات؟
هل تمتلك كل رحلة مغادرة بيانات طقس مقابلة لتلك الساعة؟
ما الذي تشترك فيه أرقام الذيل (tail numbers) التي ليس لها سجل مطابق في
planes؟ (تلميح: متغير واحد يفسر ~90% من المشاكل.)أضف عموداً إلى
planesيسرد كل شركة طيرانcarrierقامت بالتحليق بتلك الطائرة. قد تتوقع أن هناك علاقة ضمنية بين الطائرة وشركة الطيران، لأن كل طائرة تُطير بواسطة شركة طيران واحدة. أكد أو ارفض هذه الفرضية باستخدام الأدوات التي تعلمتها في الفصول السابقة.أضف خط العرض وخط الطول لمطار المغادرة و الوصول إلى
flights. هل من الأسهل إعادة تسمية الأعمدة قبل أم بعد الدمج؟-
احسب متوسط التأخير حسب الوجهة، ثم ادمجه مع إطار البيانات
airportsحتى تتمكن من إظهار التوزيع المكاني للتأخيرات. إليك طريقة سهلة لرسم خريطة للولايات المتحدة:airports |> semi_join(flights, join_by(faa == dest)) |> ggplot(aes(x = lon, y = lat)) + borders("state") + geom_point() + coord_quickmap()قد ترغب في استخدام حجم
sizeأو لونcolorالنقاط لعرض متوسط التأخير لكل مطار. ماذا حدث في 13 يونيو 2013؟ ارسم خريطة للتأخيرات، ثم استخدم Google للمقارنة مع حالة الطقس.
19.4 كيف تعمل عمليات الدمج؟
الآن بعد أن استخدمت عمليات الدمج عدة مرات، حان الوقت لمعرفة المزيد عن كيفية عملها، مع التركيز على كيفية مطابقة كل صف في x لصفوف في y. سنبدأ بتقديم تمثيل بصري لعمليات الدمج، باستخدام جداول tibble البسيطة المحددة أدناه والموضحة في الشكل 19.2. في هذه الأمثلة، سنستخدم مفتاحاً واحداً يسمى key وعمود قيمة واحداً (val_x و val_y)، ولكن الأفكار تعمم جميعها على مفاتيح متعددة وقيم متعددة.
key الملونة لون الخلفية حسب قيمة المفتاح. تمثل الأعمدة الرمادية أعمدة “القيمة” التي تُحمل معها للرحلة.
يقدم الشكل 19.3 الأساس لتمثيلنا البصري. فهو يظهر جميع التطابقات المحتملة بين x و y كتقاطع بين الخطوط المرسومة من كل صف من x وكل صف من y. تتحدد الصفوف والأعمدة في المخرجات بشكل أساسي بواسطة x، لذا فإن الجدول x أفقي ومحاذٍ للمخرجات.
لوصف نوع معين من الدمج، نشير إلى التطابقات بنقاط. تحدد التطابقات الصفوف في المخرجات، وهو إطار بيانات جديد يحتوي على المفتاح وقيم x وقيم y. على سبيل المثال، يظهر الشكل 19.4 دمجاً داخلياً (inner join)، حيث يتم الاحتفاظ بالصفوف إذا وفقط إذا كانت المفاتيح متساوية.
x مع الصف في y الذي يحتوي على نفس قيمة key. تصبح كل مطابقة صفاً في المخرجات.
يمكننا تطبيق نفس المبادئ لشرح عمليات الدمج الخارجي (outer joins)، والتي تحتفظ بالملاحظات التي تظهر في إطار بيانات واحد على الأقل. تعمل عمليات الدمج هذه عن طريق إضافة ملاحظة “افتراضية” إضافية إلى كل إطار بيانات. تحتوي هذه الملاحظة على مفتاح يطابق إذا لم يطابق أي مفتاح آخر، وقيم مملوءة بـ NA. هناك ثلاثة أنواع من عمليات الدمج الخارجي:
-
الدمج الأيسر (Left join) يحتفظ بجميع الملاحظات في
x، الشكل 19.5. يتم الحفاظ على كل صف منxفي المخرجات لأنه يمكنه الاعتماد على مطابقة صف منNAs فيy.
x في المخرجات.
-
الدمج الأيمن (Right join) يحتفظ بجميع الملاحظات في
y، الشكل 19.6. يتم الحفاظ على كل صف منyفي المخرجات لأنه يمكنه الاعتماد على مطابقة صف منNAs فيx. تظل المخرجات تطابقxقدر الإمكان؛ وتُضاف أي صفوف إضافية منyإلى النهاية.
y في المخرجات.
-
الدمج الكامل (Full join) يحتفظ بجميع الملاحظات التي تظهر في
xأوy، الشكل 19.7. يتم تضمين كل صف منxوyفي المخرجات لأن كلاً منxوyيحتويان على صف احتياطي منNAs. مرة أخرى، تبدأ المخرجات بجميع الصفوف منx، تليها صفوفyالمتبقية غير المطابقة.
x و y في المخرجات.
طريقة أخرى لإظهار كيفية اختلاف أنواع الدمج الخارجي هي باستخدام مخطط فين (Venn diagram)، كما في الشكل 19.8. ومع ذلك، ليس هذا تمثيلاً ممتازاً لأنه بينما قد ينشط ذاكرتك بشأن الصفوف التي تم الاحتفاظ بها، فإنه يفشل في توضيح ما يحدث مع الأعمدة.
عمليات الدمج المعروضة هنا هي ما يسمى عمليات الدمج المتكافئة (equi joins)، حيث تتطابق الصفوف إذا كانت المفاتيح متساوية. عمليات الدمج المتكافئة هي النوع الأكثر شيوعاً من عمليات الدمج، لذا سنحذف عادةً البادئة “متكافئ”، ونقول ببساطة “دمج داخلي” بدلاً من “دمج داخلي متكافئ”. سنعود إلى عمليات الدمج غير المتكافئة في قسم 19.5.
19.4.1 مطابقة الصفوف
حتى الآن، استكشفنا ما يحدث إذا كان الصف في x يطابق صفراً أو صفاً واحداً في y. ماذا يحدث إذا كان يطابق أكثر من صف واحد؟ لفهم ما يحدث، دعنا نضيق تركيزنا أولاً على inner_join() ثم نرسم صورة، الشكل 19.9.
x. تطابق x1 صفاً واحداً في y، وتطابق x2 صفين في y، وتطابق x3 صفر صفوف في y. لاحظ أنه بينما يوجد ثلاثة صفوف في x وثلاثة صفوف في المخرجات، لا يوجد تناظر مباشر بين الصفوف.
هناك ثلاث نتائج محتملة لصف في x:
- إذا لم يطابق أي شيء، يتم إسقاطه.
- إذا كان يطابق صفاً واحداً في
y، يتم الاحتفاظ به. - إذا كان يطابق أكثر من صف واحد في
y، فيتم تكراره مرة واحدة لكل مطابقة.
من حيث المبدأ، هذا يعني أنه لا يوجد توافق مضمون بين الصفوف في المخرجات والصفوف في x، ولكن عملياً، نادراً ما يسبب هذا مشاكل. ومع ذلك، هناك حالة واحدة خطيرة بشكل خاص يمكن أن تسبب انفجاراً توافقياً للصفوف. تخيل دمج الجدولين التاليين:
بينما يطابق الصف الأول في df1 صفاً واحداً فقط في df2، فإن الصفين الثاني والثالث يطابقان صفين. يُطلق على هذا أحياناً اسم دمج متعدد إلى متعدد (many-to-many)، وسيؤدي إلى إصدار تحذير من dplyr:
df1 |>
inner_join(df2, join_by(key))
#> Warning in inner_join(df1, df2, join_by(key)): Detected an unexpected many-to-many relationship between `x` and `y`.
#> ℹ Row 2 of `x` matches multiple rows in `y`.
#> ℹ Row 2 of `y` matches multiple rows in `x`.
#> ℹ If a many-to-many relationship is expected, set `relationship =
#> "many-to-many"` to silence this warning.
#> # A tibble: 5 × 3
#> key val_x val_y
#> <dbl> <chr> <chr>
#> 1 1 x1 y1
#> 2 2 x2 y2
#> 3 2 x2 y3
#> 4 2 x3 y2
#> 5 2 x3 y3إذا كنت تفعل ذلك عن قصد، يمكنك ضبط relationship = "many-to-many"، كما يقترح التحذير.
19.4.2 عمليات الدمج الترشيحية (Filtering joins)
يحدد عدد التطابقات أيضاً سلوك عمليات الدمج الترشيحية. يحتفظ الدمج شبه الداخلي (semi-join) بالصفوف في x التي لها مطابقة واحدة أو أكثر في y، كما في الشكل 19.10. يحتفظ الدمج العكسي (anti-join) بالصفوف في x التي تطابق صفر صفوف في y، كما في الشكل 19.11. في كلتا الحالتين، وجود المطابقة فقط هو المهم؛ لا يهم عدد مرات تطابقها. هذا يعني أن عمليات الدمج الترشيحية لا تكرر الصفوف أبداً كما تفعل عمليات الدمج التحويرية.
y على المخرجات.
x التي لها مطابقة في y.
19.5 عمليات الدمج غير المتكافئة (Non-equi joins)
حتى الآن رأيت فقط عمليات الدمج المتكافئة، وهي عمليات الدمج التي تتطابق فيها الصفوف إذا كان مفتاح x يساوي مفتاح y. الآن سنخفف هذا القيد ونناقش طرقاً أخرى لتحديد ما إذا كان زوج من الصفوف متطابقاً.
ولكن قبل أن نتمكن من القيام بذلك، نحتاج إلى مراجعة تبسيط قمنا به أعلاه. في عمليات الدمج المتكافئة تكون مفاتيح x و y متساوية دائماً، لذا نحتاج فقط إلى إظهار واحد منها في المخرجات. يمكننا طلب الاحتفاظ بكلا المفتاحين من dplyr باستخدام keep = TRUE، مما يؤدي إلى الكود أدناه وإعادة رسم inner_join() في الشكل 19.12.
x |> inner_join(y, join_by(key == key), keep = TRUE)
#> # A tibble: 2 × 4
#> key.x val_x key.y val_y
#> <dbl> <chr> <dbl> <chr>
#> 1 1 x1 1 y1
#> 2 2 x2 2 y2
x و y في المخرجات.
عندما نبتعد عن عمليات الدمج المتكافئة سنظهر دائماً المفاتيح، لأن قيم المفاتيح غالباً ما تكون مختلفة. على سبيل المثال، بدلاً من المطابقة فقط عندما يكون x$key و y$key متساويين، يمكننا المطابقة كلما كان x$key أكبر من أو يساوي y$key، مما يؤدي إلى الشكل 19.13. تفهم دوال الدمج في dplyr هذا التمييز بين عمليات الدمج المتكافئة وغير المتكافئة، لذا ستظهر دائماً كلا المفتاحين عند إجراء دمج غير متكافئ.
x أكبر من أو يساوي مفتاح y. تولد العديد من الصفوف تطابقات متعددة.
الدمج غير المتكافئ ليس مصطلحاً مفيداً بشكل خاص لأنه يخبرك فقط بما ليس عليه الدمج، وليس ما هو عليه. تساعد dplyr من خلال تحديد أربعة أنواع مفيدة بشكل خاص من عمليات الدمج غير المتكافئة:
- عمليات الدمج التقاطعي (Cross joins). تطابق كل زوج من الصفوف.
-
عمليات الدمج غير المتساوية (Inequality joins). تستخدم
<,<=,>, و>=بدلاً من==. - عمليات الدمج المتدحرجة (Rolling joins). تشبه عمليات الدمج غير المتساوية ولكنها تجد فقط أقرب مطابقة.
- عمليات دمج التداخل (Overlap joins). هي نوع خاص من الدمج غير المتساوي مصمم للعمل مع الفترات/المجالات.
يتم وصف كل منها بمزيد من التفصيل في الأقسام التالية.
19.5.1 عمليات الدمج التقاطعي (Cross joins)
يطابق الدمج التقاطعي كل شيء، كما في الشكل 19.14، مما يولد حاصل الضرب الديكارتي للصفوف. هذا يعني أن المخرجات ستكون تحتوي على عدد صفوف يساوي nrow(x) * nrow(y).
x مع كل صف في y.
تكون عمليات الدمج التقاطعي مفيدة عند توليد التباديل. على سبيل المثال، يولد الكود أدناه كل زوج محتمل من الأسماء. بما أننا ندمج df مع نفسه، يطلق على هذا أحياناً اسم الدمج الذاتي (self-join). تستخدم عمليات الدمج التقاطعي دالة دمج مختلفة لأنه لا يوجد تمييز بين داخلي/أيسر/أيمن/كامل عندما تطابق كل صف.
df <- tibble(name = c("John", "Simon", "Tracy", "Max"))
df |> cross_join(df)
#> # A tibble: 16 × 2
#> name.x name.y
#> <chr> <chr>
#> 1 John John
#> 2 John Simon
#> 3 John Tracy
#> 4 John Max
#> 5 Simon John
#> 6 Simon Simon
#> # ℹ 10 more rows19.5.2 عمليات الدمج غير المتساوية (Inequality joins)
تستخدم عمليات الدمج غير المتساوية <, <=, >=, أو > بتقييد مجموعة التطابقات المحتملة، كما في الشكل 19.13 و الشكل 19.15.
x بـ y في الصفوف التي يكون فيها مفتاح x أصغر من مفتاح y. هذا يصنع شكلاً مثلثاً في الزاوية العلوية اليسرى.
عمليات الدمج غير المتساوية عامة جداً، لدرجة أنه من الصعب التفكير في حالات استخدام محددة ذات معنى. تقنية صغيرة واحدة مفيدة هي استخدامها لتقييد الدمج التقاطعي بحيث بدلاً من توليد كل التباديل، نولد كل التوافيق:
df <- tibble(id = 1:4, name = c("John", "Simon", "Tracy", "Max"))
df |> inner_join(df, join_by(id < id))
#> # A tibble: 6 × 4
#> id.x name.x id.y name.y
#> <int> <chr> <int> <chr>
#> 1 1 John 2 Simon
#> 2 1 John 3 Tracy
#> 3 1 John 4 Max
#> 4 2 Simon 3 Tracy
#> 5 2 Simon 4 Max
#> 6 3 Tracy 4 Max19.5.3 عمليات الدمج المتدحرجة (Rolling joins)
عمليات الدمج المتدحرجة هي نوع خاص من الدمج غير المتساوي حيث بدلاً من الحصول على كل صف يحقق المتباينة، تحصل فقط على أقرب صف، كما في الشكل 19.16. يمكنك تحويل أي دمج غير متساوي إلى دمج متدحرج عن طريق إضافة closest(). على سبيل المثال، join_by(closest(x <= y)) يطابق أصغر y أكبر من أو يساوي x، و join_by(closest(x > y)) يطابق أكبر y أصغر من x.
عمليات الدمج المتدحرجة مفيدة بشكل خاص عندما يكون لديك جدولان للتواريخ لا يتطابقان تماماً وتريد العثور على (على سبيل المثال) أقرب تاريخ في الجدول 1 يأتي قبل (أو بعد) تاريخ ما في الجدول 2.
على سبيل المثال، تخيل أنك مسؤول عن لجنة تخطيط الحفلات لمكتبك. شركتك مقتصدة إلى حد ما، لذا بدلاً من إقامة حفلات فردية، تقيمون حفلاً مرة واحدة كل ربع سنة. القواعد المتبعة لتحديد وقت إقامة الحفلة معقدة قليلاً: الحفلات تكون دائماً يوم الاثنين، وتتخطى الأسبوع الأول من يناير لأن الكثير من الناس في عطلة، والاثنين الأول من الربع الثالث لعام 2022 هو 4 يوليو، لذا يجب تأجيله لمدة أسبوع. هذا يؤدي إلى أيام الحفلات التالية:
الآن تخيل أن لديك جدولاً لأعياد ميلاد الموظفين:
set.seed(123)
employees <- tibble(
name = sample(babynames::babynames$name, 100),
birthday = ymd("2022-01-01") + (sample(365, 100, replace = TRUE) - 1)
)
employees
#> # A tibble: 100 × 2
#> name birthday
#> <chr> <date>
#> 1 Kemba 2022-01-22
#> 2 Orean 2022-06-26
#> 3 Kirstyn 2022-02-11
#> 4 Amparo 2022-11-11
#> 5 Belen 2022-03-25
#> 6 Rayshaun 2022-01-11
#> # ℹ 94 more rowsولكل موظف نريد العثور على آخر تاريخ حفلة يأتي قبل (أو في) عيد ميلاده. يمكننا التعبير عن ذلك بدمج متدحرج:
employees |>
left_join(parties, join_by(closest(birthday >= party)))
#> # A tibble: 100 × 4
#> name birthday q party
#> <chr> <date> <int> <date>
#> 1 Kemba 2022-01-22 1 2022-01-10
#> 2 Orean 2022-06-26 2 2022-04-04
#> 3 Kirstyn 2022-02-11 1 2022-01-10
#> 4 Amparo 2022-11-11 4 2022-10-03
#> 5 Belen 2022-03-25 1 2022-01-10
#> 6 Rayshaun 2022-01-11 1 2022-01-10
#> # ℹ 94 more rowsومع ذلك، هناك مشكلة واحدة في هذا النهج: الأشخاص الذين تقع أعياد ميلادهم قبل 10 يناير لا يحصلون على حفلة:
حل هذه المشكلة سنحتاج إلى معالجة المسألة بطريقة مختلفة، باستخدام عمليات دمج التداخل (overlap joins).
19.5.4 عمليات دمج التداخل (Overlap joins)
توفر عمليات دمج التداخل ثلاث دوال مساعدة تستخدم عمليات الدمج غير المتساوية لجعل العمل مع الفترات أسهل:
-
between(x, y_lower, y_upper)هي اختصار لـx >= y_lower, x <= y_upper. -
within(x_lower, x_upper, y_lower, y_upper)هي اختصار لـx_lower >= y_lower, x_upper <= y_upper. -
overlaps(x_lower, x_upper, y_lower, y_upper)هي اختصار لـx_lower <= y_upper, x_upper >= y_lower.
دعنا نواصل مثال عيد الميلاد لنرى كيف يمكنك استخدامها. هناك مشكلة واحدة في الاستراتيجية التي استخدمناها أعلاه: لا توجد حفلة تسبق أعياد الميلاد من 1 إلى 9 يناير. لذا قد يكون من الأفضل أن نكون صريحين بشأن النطاقات الزمنية التي تمتد عبرها كل حفلة، وعمل حالة خاصة لأعياد الميلاد المبكرة تلك:
parties <- tibble(
q = 1:4,
party = ymd(c("2022-01-10", "2022-04-04", "2022-07-11", "2022-10-03")),
start = ymd(c("2022-01-01", "2022-04-04", "2022-07-11", "2022-10-03")),
end = ymd(c("2022-04-03", "2022-07-11", "2022-10-02", "2022-12-31"))
)
parties
#> # A tibble: 4 × 4
#> q party start end
#> <int> <date> <date> <date>
#> 1 1 2022-01-10 2022-01-01 2022-04-03
#> 2 2 2022-04-04 2022-04-04 2022-07-11
#> 3 3 2022-07-11 2022-07-11 2022-10-02
#> 4 4 2022-10-03 2022-10-03 2022-12-31هادلي سيء للغاية في إدخال البيانات، لذا أراد أيضاً التحقق من أن فترات الحفلات لا تتداخل. إحدى الطرق للقيام بذلك هي استخدام دمج ذاتي للتحقق مما إذا كانت أي فترة بداية-نهاية تتداخل مع أخرى:
parties |>
inner_join(parties, join_by(overlaps(start, end, start, end), q < q)) |>
select(start.x, end.x, start.y, end.y)
#> # A tibble: 1 × 4
#> start.x end.x start.y end.y
#> <date> <date> <date> <date>
#> 1 2022-04-04 2022-07-11 2022-07-11 2022-10-02عفواً، هناك تداخل، لذا دعنا نصلح تلك المشكلة ونتابع:
الآن يمكننا مطابقة كل موظف مع حفلته. هذا مكان جيد لاستخدام unmatched = "error" لأننا نريد أن نعرف بسرعة ما إذا كان هناك أي موظفين لم يتم تعيين حفلة لهم.
employees |>
inner_join(parties, join_by(between(birthday, start, end)), unmatched = "error")
#> # A tibble: 100 × 6
#> name birthday q party start end
#> <chr> <date> <int> <date> <date> <date>
#> 1 Kemba 2022-01-22 1 2022-01-10 2022-01-01 2022-04-03
#> 2 Orean 2022-06-26 2 2022-04-04 2022-04-04 2022-07-10
#> 3 Kirstyn 2022-02-11 1 2022-01-10 2022-01-01 2022-04-03
#> 4 Amparo 2022-11-11 4 2022-10-03 2022-10-03 2022-12-31
#> 5 Belen 2022-03-25 1 2022-01-10 2022-01-01 2022-04-03
#> 6 Rayshaun 2022-01-11 1 2022-01-10 2022-01-01 2022-04-03
#> # ℹ 94 more rows19.5.5 تمارين
- هل يمكنك شرح ما يحدث للمفاتيح في هذا الدمج المتكافئ؟ لماذا هي مختلفة؟
x |> full_join(y, join_by(key == key))
#> # A tibble: 4 × 3
#> key val_x val_y
#> <dbl> <chr> <chr>
#> 1 1 x1 y1
#> 2 2 x2 y2
#> 3 3 x3 <NA>
#> 4 4 <NA> y3
x |> full_join(y, join_by(key == key), keep = TRUE)
#> # A tibble: 4 × 4
#> key.x val_x key.y val_y
#> <dbl> <chr> <dbl> <chr>
#> 1 1 x1 1 y1
#> 2 2 x2 2 y2
#> 3 3 x3 NA <NA>
#> 4 NA <NA> 4 y3- عند العثور على ما إذا كانت أي فترة حفلة تتداخل مع فترة حفلة أخرى استخدمنا
q < qفيjoin_by()؟ لماذا؟ ماذا يحدث إذا قمت بإزالة عدم المساواة هذه؟
19.6 ملخص
في هذا الفصل، تعلمت كيفية استخدام عمليات الدمج التحويرية والترشيحية لجمع البيانات من زوج من إطارات البيانات. على طول الطريق، تعلمت كيفية تحديد المفاتيح، والفرق بين المفاتيح الأساسية والخارجية. كما تفهم كيفية عمل عمليات الدمج وكيفية معرفة عدد الصفوف التي ستتكون منها المخرجات. أخيراً، أخذت لمحة عن قوة عمليات الدمج غير المتكافئة ورأيت بضع حالات استخدام مثيرة للاهتمام.
يختتم هذا الفصل قسم “التحويل (Transform)” من الكتاب حيث كان التركيز على الأدوات التي يمكنك استخدامها مع الأعمدة الفردية وجداول tibbles. تعلمت عن dplyr والدوال الأساسية للعمل مع المتجهات المنطقية والأعداد والجداول الكاملة، ودوال stringr للعمل مع النصوص، ودوال lubridate للعمل مع التواريخ والأوقات، ودوال forcats للعمل مع العوامل (factors).
في الجزء التالي من الكتاب، ستتعلم المزيد عن إدخال أنواع مختلفة من البيانات إلى R بشكل منسق ونظيف (tidy form).