12 المتجهات المنطقية
12.1 مقدمة
ستتعلم في هذا الفصل الأدوات المخصصة للتعامل مع المتجهات المنطقية (logical vectors). تُعد المتجهات المنطقية أبسط أنواع المتجهات؛ لأن كل عنصر فيها لا يمكن أن يتخذ سوى واحدة من ثلاث قيم ممكنة: TRUE (صحيح)، وFALSE (خاطئ)، وNA (قيمة مفقودة). ومن النادر نسبياً أن تجد متجهات منطقية جاهزة في بياناتك الخام، لكنك ستنشئها وتتعامل معها وتجري عليها العمليات في كل تحليل بيانات تقوم به تقريباً.
سنبدأ بمناقشة الطريقة الأكثر شيوعاً لإنشاء المتجهات المنطقية: استخدام المقارنات العددية (numeric comparisons). ثم ستتعلم كيف يمكنك استخدام الجبر البولياني (Boolean algebra) لدمج متجهات منطقية مختلفة، بالإضافة إلى بعض المقاييس التلخيصية المفيدة. وسنختتم الفصل بشرح الدالتين if_else() وcase_when()، وهما دالتان نافعتان لإجراء تغييرات شرطية تعتمد على المتجهات المنطقية.
12.1.1 المتطلبات المسبقة
معظم الدوال التي ستتعلمها في هذا الفصل توفرها البيئة الأساسية لـ R (base R)، لذا لسنا بحاجة ماسة لحزمة tidyverse، لكننا سنقوم بتحميلها حتى نتمكن من استخدام mutate() وfilter() والدوال الشقيقة للتعامل مع إطارات البيانات. كما سنواصل استعراض الأمثلة التطبيقية باستخدام مجموعة بيانات الرحلات الجوية nycflights13::flights.
ومع ذلك، كلما بدأنا في تغطية المزيد من الأدوات، لن يتوفر دائماً مثال واقعي مثالي لكل حالة. لذا سنبدأ بإنشاء بعض البيانات الافتراضية باستخدام الدالة c():
x <- c(1, 2, 3, 5, 7, 11, 13)
x * 2
#> [1] 2 4 6 10 14 22 26يُسهل هذا أسلوب شرح الدوال المنفردة، لكنه يجعل رؤية كيفية تطبيقها على مشكلات البيانات الواقعية أشد صعوبة نوعاً ما. تذكر دائماً أن أي عملية نُجريها على متجه حر ومستقل، يمكنك تطبيقها على متغير داخل إطار بيانات باستخدام mutate() والدوال المماثلة.
12.2 المقارنات
تعد المقارنات العددية باستخدام الأدوات <, <=, >, >=, !=, و == طريقة شائعة جداً لإنشاء المتجهات المنطقية. وحتى الآن، كنا ننشئ المتغيرات المنطقية بشكل مؤقت داخل الدالة filter()؛ حيث يُحسب المتغير، يُستخدم، ثم يُتخلص منه. على سبيل المثال، يحدد الفلتر (filter) التالي جميع الرحلات المغادرة نهاراً والتي وصلت في الموعد المحدد تقريباً:
flights |>
filter(dep_time > 600 & dep_time < 2000 & abs(arr_delay) < 20)
#> # A tibble: 172,286 × 19
#> year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
#> <int> <int> <int> <int> <int> <dbl> <int> <int>
#> 1 2013 1 1 601 600 1 844 850
#> 2 2013 1 1 602 610 -8 812 820
#> 3 2013 1 1 602 605 -3 821 805
#> 4 2013 1 1 606 610 -4 858 910
#> 5 2013 1 1 606 610 -4 837 845
#> 6 2013 1 1 607 607 0 858 915
#> # ℹ 172,280 more rows
#> # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>, …من المفيد أن تعلم أن هذه الطريقة الاختصارية يمكن الاستعاضة عنها بإنشاء المتغيرات المنطقية الكامنة صراحة باستخدام mutate():
flights |>
mutate(
daytime = dep_time > 600 & dep_time < 2000,
approx_ontime = abs(arr_delay) < 20,
.keep = "used"
)
#> # A tibble: 336,776 × 4
#> dep_time arr_delay daytime approx_ontime
#> <int> <dbl> <lgl> <lgl>
#> 1 517 11 FALSE TRUE
#> 2 533 20 FALSE FALSE
#> 3 542 33 FALSE FALSE
#> 4 544 -18 FALSE TRUE
#> 5 554 -25 FALSE FALSE
#> 6 554 12 FALSE TRUE
#> # ℹ 336,770 more rowsتتجلى فائدة هذه الطريقة بوضوح عند التعامل مع المنطق البرمجي الأكثر تعقيداً؛ لأن تسمية الخطوات الوسيطة تسهل عليك قراءة الكود والتحقق من صحة حساب كل خطوة.
وبشكل عام، فإن الفلتر المبدئي يعادل تماماً الكود التالي:
12.2.1 مقارنة الأعداد ذات الفاصلة العائمة
احذر من استخدام معامل المساواة == مع الأعداد. على سبيل المثال، يبدو للوهلة الأولى أن هذا المتجه يحتوي على العددين 1 و 2:
لكن إذا اختبرت مساواتهما، ستكون النتيجة FALSE:
x == c(1, 2)
#> [1] FALSE FALSEما الذي يحدث هنا؟ تخزن أجهزة الحاسوب الأعداد بعدد ثابت من الخانات العشرية، لذا لا توجد طريقة لتمثيل الكسر 1/49 أو sqrt(2) بشكل دقيق مطلق، وتكون الحسابات اللاحقة غير دقيقة بفارق ضئيل جداً. يمكننا رؤية القيم الدقيقة تماماً باستدعاء الدالة print() مع تحديد الوسيط digits1:
print(x, digits = 16)
#> [1] 0.9999999999999999 2.0000000000000004يتضح لك الآن السبب في أن R تقوم بتقريب هذه الأعداد افتراضياً؛ لأنها في الحقيقة قريبة جداً مما تتوقعه.
والآن بعد أن رأيت سبب فشل المعامل ==، ما الذي يمكنك فعله حيال ذلك؟ أحد الخيارات المتاحة هو استخدام الدالة dplyr::near() والتي تتجاهل الفروق الضئيلة جداً:
12.2.2 القيم المفقودة
تمثل القيم المفقودة أشياء مجهولة، لذا فهي “معدية”: أي عملية تتضمن قيمة مجهولة ستكون نتيجتها مجهولة أيضاً:
NA > 5
#> [1] NA
10 == NA
#> [1] NAوالنتيجة الأكثر إرباكاً هي هذه النتيجة:
NA == NA
#> [1] NAتسهل ملامسة أسباب ذلك إذا أضفنا سياقاً توضيحياً بسيطاً:
# لا نعلم كم عمر ماري
age_mary <- NA
# لا نعلم كم عمر جون
age_john <- NA
# هل ماري وجون في نفس العمر؟
age_mary == age_john
#> [1] NA
# نحن لا نعلم!لذا، إذا أردت العثور على جميع الرحلات التي تكون فيها قيمة ووقت المغادرة dep_time مفقودة، فإن الكود التالي لن يعمل لأن dep_time == NA سيعطي النتيجة NA لكل صف، والدالة filter() تستبعد القيم المفقودة تلقائياً:
flights |>
filter(dep_time == NA)
#> # A tibble: 0 × 19
#> # ℹ 19 variables: year <int>, month <int>, day <int>, dep_time <int>,
#> # sched_dep_time <int>, dep_delay <dbl>, arr_time <int>, …بدلاً من ذلك، سنحتاج إلى أداة جديدة: الدالة is.na().
12.2.3 is.na()
تعمل الدالة is.na(x) مع أي نوع من المتجهات، وترجع القيمة TRUE مع القيم المفقودة و FALSE مع كل ما عداها:
يمكننا استخدام الدالة is.na() للبحث عن جميع الصفوف التي تحتوي على قيمة مفقودة في dep_time:
flights |>
filter(is.na(dep_time))
#> # A tibble: 8,255 × 19
#> year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
#> <int> <int> <int> <int> <int> <dbl> <int> <int>
#> 1 2013 1 1 NA 1630 NA NA 1815
#> 2 2013 1 1 NA 1935 NA NA 2240
#> 3 2013 1 1 NA 1500 NA NA 1825
#> 4 2013 1 1 NA 600 NA NA 901
#> 5 2013 1 2 NA 1540 NA NA 1747
#> 6 2013 1 2 NA 1620 NA NA 1746
#> # ℹ 8,249 more rows
#> # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>, …يمكن أن تكون is.na() مفيدة أيضاً داخل الدالة arrange(). تضع الدالة arrange() عادة جميع القيم المفقودة في النهاية، ولكن يمكنك تجاوز هذا السلوك الافتراضي بالترتيب أولاً حسب is.na():
flights |>
filter(month == 1, day == 1) |>
arrange(dep_time)
#> # A tibble: 842 × 19
#> year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
#> <int> <int> <int> <int> <int> <dbl> <int> <int>
#> 1 2013 1 1 517 515 2 830 819
#> 2 2013 1 1 533 529 4 850 830
#> 3 2013 1 1 542 540 2 923 850
#> 4 2013 1 1 544 545 -1 1004 1022
#> 5 2013 1 1 554 600 -6 812 837
#> 6 2013 1 1 554 558 -4 740 728
#> # ℹ 836 more rows
#> # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>, …
flights |>
filter(month == 1, day == 1) |>
arrange(desc(is.na(dep_time)), dep_time)
#> # A tibble: 842 × 19
#> year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
#> <int> <int> <int> <int> <int> <dbl> <int> <int>
#> 1 2013 1 1 NA 1630 NA NA 1815
#> 2 2013 1 1 NA 1935 NA NA 2240
#> 3 2013 1 1 NA 1500 NA NA 1825
#> 4 2013 1 1 NA 600 NA NA 901
#> 5 2013 1 1 517 515 2 830 819
#> 6 2013 1 1 533 529 4 850 830
#> # ℹ 836 more rows
#> # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>, …سنعود لتغطية القيم المفقودة بمزيد من التوسع والعمق في الفصل 18.
12.2.4 تمارين
- كيف تعمل الدالة
dplyr::near()؟ اكتبnearللظهور على الكود المصدري الخاص بها. هلsqrt(2)^2قريبة من 2؟ - استخدم
mutate()وis.na()وcount()معاً لوصف كيفية ارتباط القيم المفقودة في المتغيراتdep_timeوsched_dep_timeوdep_delayببعضها البعض.
12.3 الجبر البولياني (Boolean algebra)
بمجرد توفر متجهات منطقية متعددة لديك، يمكنك دمجها معاً باستخدام الجبر البولياني. في بيئة R، يمثل المعامل & المعامل المنطقي “و” (and)، ويمثل المعامل | المعامل المنطقي “أو” (or)، بينما يمثل المعامل ! المعامل المنطقي “ليس” (not)، وتمثل الدالة xor() معامل “أو الاستبعادي” (exclusive or)2. على سبيل المثال، الاستعلام df |> filter(!is.na(x)) يجد جميع الصفوف التي تكون فيها القيمة x غير مفقودة، بينما الاستعلام df |> filter(x < -10 | x > 0) يجد جميع الصفوف التي تكون فيها القيمة x أصغر من -10 أو أكبر من 0. يوضح الشكل 12.1 أمثلة على العمليات البوليانية الشائعة الاستخدام وكيفية عملها.
x الدائرة اليسرى، وتمثل y الدائرة اليمنى، بينما توضح المناطق المظللة الأجزاء التي يحددها كل معامل.
بالإضافة إلى المعاملات & و |، تتضمن بيئة R أيضاً المعاملين && و ||. لا تستخدمهما إطلاقاً داخل دوال حزمة dplyr! تُعرف هذه المعاملات بمعاملات التقصير (short-circuiting operators) ولا ترجع سوى قيمة فريدة واحدة إما TRUE أو FALSE؛ وهي أدوات موجهة للبرمجة المتقدمة وليست مخصصة لعلم البيانات.
12.3.1 القيم المفقودة
تُعد قواعد التعامل مع القيم المفقودة في الجبر البولياني معقدة في الشرح نوعاً ما لأنها تبدو غير متسقة للوهلة الأولى:
ولفهم ما يحدث هنا، فكّر في التعبير NA | TRUE (أي NA أو TRUE). تعني القيمة المفقودة في المتجه المنطقي أن القيمة الكامنة قد تكون إما TRUE أو FALSE. التعبيرات TRUE | TRUE و FALSE | TRUE تعطي كلاهما القيمة TRUE؛ لأن شرطاً واحداً على الأقل منهما محقق وصحيح. وبالتالي، فإن التعبير NA | TRUE يجب أن يعطي أيضاً القيمة TRUE؛ لأن القيمة المفقودة NA بغض النظر عن كونها TRUE أو FALSE لن تغير النتيجة. ولكن التعبير NA | FALSE يعطي القيمة NA؛ لأننا لا نعلم بعد هل القيمة المفقودة هي TRUE أم FALSE. وينطبق التفكير المنطقي ذاته على المعامل & مع مراعاة ضرورة تحقق كلا الشرطين معاً؛ لذا فإن NA & TRUE تعطي NA لأننا لا نعلم طبيعة القيمة المفقودة، بينما تعطي NA & FALSE القيمة FALSE؛ لأن أحد الشرطين غير محقق بالتأكيد.
12.3.2 ترتيب العمليات
لاحظ أن ترتيب العمليات لا يعمل كما هو الحال في اللغة الإنجليزية. تأمل الكود التالي الذي يحدد جميع الرحلات المغادرة في شهري نوفمبر أو ديسمبر:
flights |>
filter(month == 11 | month == 12)قد ترغب في كتابته كما تقول باللغة الإنجليزية: “أوجد جميع الرحلات التي غادرت في نوفمبر أو ديسمبر”:
flights |>
filter(month == 11 | 12)
#> # A tibble: 336,776 × 19
#> year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
#> <int> <int> <int> <int> <int> <dbl> <int> <int>
#> 1 2013 1 1 517 515 2 830 819
#> 2 2013 1 1 533 529 4 850 830
#> 3 2013 1 1 542 540 2 923 850
#> 4 2013 1 1 544 545 -1 1004 1022
#> 5 2013 1 1 554 600 -6 812 837
#> 6 2013 1 1 554 558 -4 740 728
#> # ℹ 336,770 more rows
#> # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>, …هذا الكود لا يعطي خطأ برمجياً ولكنه لا يعمل كما ينبغي. ما الذي يحدث هنا؟ تقوم R أولاً بتقييم month == 11 فتنشئ متجهاً منطقياً نسميه nov. ثم تحسب nov | 12. عندما تستخدم عدداً مع معامل منطقي، فإن R تحول أي عدد غير الصفر إلى TRUE؛ لذا فإن التعبير يعادل nov | TRUE والتي تكون دائماً TRUE؛ وبالتالي سيتم تحديد جميع الصفوف في مجموعة البيانات:
flights |>
mutate(
nov = month == 11,
final = nov | 12,
.keep = "used"
)
#> # A tibble: 336,776 × 3
#> month nov final
#> <int> <lgl> <lgl>
#> 1 1 FALSE TRUE
#> 2 1 FALSE TRUE
#> 3 1 FALSE TRUE
#> 4 1 FALSE TRUE
#> 5 1 FALSE TRUE
#> 6 1 FALSE TRUE
#> # ℹ 336,770 more rows
12.3.3 المعامل %in%
تعد طريقة %in% خياراً سهلاً لتفادي أخطاء الترتيب بين المعاملين == و |. يرجع التعبير x %in% y متجهاً منطقياً بنفس طول x تكون قيمته TRUE كلما كانت القيمة الموجودة في x متوفرة في أي مكان داخل y.
1:12 %in% c(1, 5, 11)
#> [1] TRUE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE TRUE FALSE
letters %in% c("a", "e", "i", "o", "u")
#> [1] TRUE FALSE FALSE FALSE TRUE FALSE FALSE FALSE TRUE FALSE FALSE FALSE
#> [13] FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE
#> [25] FALSE FALSEلذا للعثور على جميع الرحلات الجوية في شهري نوفمبر وديسمبر يمكننا كتابة:
لاحظ أن %in% تتبع قواعد مختلفة للقيم المفقودة NA مقارنة بـ ==؛ حيث إن NA %in% NA تعطي TRUE.
يمكن أن يوفر هذا طريقة اختصار مفيدة:
flights |>
filter(dep_time %in% c(NA, 0800))
#> # A tibble: 8,803 × 19
#> year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
#> <int> <int> <int> <int> <int> <dbl> <int> <int>
#> 1 2013 1 1 800 800 0 1022 1014
#> 2 2013 1 1 800 810 -10 949 955
#> 3 2013 1 1 NA 1630 NA NA 1815
#> 4 2013 1 1 NA 1935 NA NA 2240
#> 5 2013 1 1 NA 1500 NA NA 1825
#> 6 2013 1 1 NA 600 NA NA 901
#> # ℹ 8,797 more rows
#> # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>, …12.3.4 تمارين
- أوجد جميع الرحلات التي تكون فيها قيمة تأخير الوصول
arr_delayمفقودة بينما قيمة تأخير المغادرةdep_delayغير مفقودة. ثم أوجد جميع الرحلات التي لا تكون فيها قيمة وقت الوصولarr_timeولا وقت الوصول المجدولsched_arr_timeمفقودة، لكن قيمة تأخير الوصولarr_delayمفقودة. - كم عدد الرحلات التي تحتوي على قيمة مفقودة في ووقت المغادرة
dep_time؟ ما هي المتغيرات الأخرى المفقودة في هذه الصفوف؟ ماذا قد تمثل هذه الصفوف؟ - بفرض أن القيمة المفقودة في ووقت المغادرة
dep_timeتعني أن الرحلة قد أُلغيت، افحص عدد الرحلات المُلغاة يومياً. هل هناك نمط معين؟ هل توجد علاقة بين نسبة الرحلات المُلغاة ومتوسط التأخير للرحلات غير المُلغاة؟
12.4 الملخصات الإحصائية
تصف الأقسام التالية بعض التقنيات المفيدة لتلخيص المتجهات المنطقية. بالإضافة إلى الدوال التي تعمل خصيصاً مع المتجهات المنطقية، يمكنك أيضاً استخدام الدوال التي تعمل مع المتجهات العددية.
12.4.1 الملخصات المنطقية
هناك دالتان تلخيصيتان منطقيتان رئيسيتان: any() و all(). الدالة any(x) هي المكافئ للمعامل |؛ حيث ترجع TRUE إذا وجِدت أي قيمة TRUE واحدة على الأقل في المتجه x. الدالة all(x) هي المكافئ للمعامل &؛ حيث ترجع TRUE فقط إذا كانت جميع القيم في المتجه x هي TRUE. ومثل معظم دوال التلخيص، يمكنك استبعاد القيم المفقودة باستخدام الوسيط na.rm = TRUE.
على سبيل المثال، يمكننا استخدام all() و any() لمعرفة ما إذا كانت كل رحلة قد تأخرت عند المغادرة بساعة واحدة على الأكثر، أو ما إذا كانت هناك أي رحلات تأخرت عند الوصول بمقدار خمس ساعات أو أكثر. وتتيح لنا الدالة group_by() تطبيق ذلك لكل يوم على حدة:
flights |>
group_by(year, month, day) |>
summarize(
all_delayed = all(dep_delay <= 60, na.rm = TRUE),
any_long_delay = any(arr_delay >= 300, na.rm = TRUE),
.groups = "drop"
)
#> # A tibble: 365 × 5
#> year month day all_delayed any_long_delay
#> <int> <int> <int> <lgl> <lgl>
#> 1 2013 1 1 FALSE TRUE
#> 2 2013 1 2 FALSE TRUE
#> 3 2013 1 3 FALSE FALSE
#> 4 2013 1 4 FALSE FALSE
#> 5 2013 1 5 FALSE TRUE
#> 6 2013 1 6 FALSE FALSE
#> # ℹ 359 more rowsومع ذلك، في معظم الحالات تكون الدالتان any() و all() غير دقيقتين بما يكفي، ويكون من الأفضل معرفة المزيد من التفاصيل حول عدد القيم الصحيحة TRUE أو الخاطئة FALSE؛ وهو ما ينقلنا إلى الملخصات العددية.
12.4.2 الملخصات العددية للمتجهات المنطقية
عندما تستخدم متجهاً منطقياً في سياق عددي، تتحول القيمة TRUE إلى 1 وتتحول القيمة FALSE إلى 0. هذا يجعل الدالتين sum() و mean() مفيدتين جداً مع المتجهات المنطقية؛ حيث تعطي sum(x) عدد الحالات الصحيحة TRUE بينما تعطي mean(x) نسبة الحالات الصحيحة TRUE (لأن mean() هي ببساطة ناتج قسمة sum() على الطول length()).
يتيح لنا ذلك، على سبيل المثال، معرفة نسبة الرحلات التي تأخرت عند المغادرة بساعة واحدة على الأكثر وعدد الرحلات التي تأخرت عند الوصول بخمس ساعات أو أكثر:
flights |>
group_by(year, month, day) |>
summarize(
proportion_delayed = mean(dep_delay <= 60, na.rm = TRUE),
count_long_delay = sum(arr_delay >= 300, na.rm = TRUE),
.groups = "drop"
)
#> # A tibble: 365 × 5
#> year month day proportion_delayed count_long_delay
#> <int> <int> <int> <dbl> <int>
#> 1 2013 1 1 0.939 3
#> 2 2013 1 2 0.914 3
#> 3 2013 1 3 0.941 0
#> 4 2013 1 4 0.953 0
#> 5 2013 1 5 0.964 1
#> 6 2013 1 6 0.959 0
#> # ℹ 359 more rows12.4.3 الاقتطاع المنطقي (Logical subsetting)
هناك استخدام أخير للمتجهات المنطقية في التلخيص: يمكنك استخدام متجه منطقي لتصفية متغير واحد واقتطاع جزئية معينة تهتم بها. ويعتمد هذا على معامل الاقتطاع الأساسي [ (الأقواس المربعة)، والذي ستتعلم عنه المزيد في قسم 27.2.
تخيل أننا نريد حساب متوسط التأخير فقط للرحلات التي تأخرت بالفعل. إحدى الطرق للقيام بذلك هي تصفية الرحلات أولاً ثم حساب متوسط التأخير:
flights |>
filter(arr_delay > 0) |>
group_by(year, month, day) |>
summarize(
behind = mean(arr_delay),
n = n(),
.groups = "drop"
)
#> # A tibble: 365 × 5
#> year month day behind n
#> <int> <int> <int> <dbl> <int>
#> 1 2013 1 1 32.5 461
#> 2 2013 1 2 32.0 535
#> 3 2013 1 3 27.7 460
#> 4 2013 1 4 28.3 297
#> 5 2013 1 5 22.6 238
#> 6 2013 1 6 24.4 381
#> # ℹ 359 more rowsهذا يعمل، ولكن ماذا لو أردنا أيضاً حساب متوسط التأخير للرحلات التي وصلت مبكراً؟ سنحتاج إلى إجراء خطوة تصفية (filter) منفصلة، ثم معرفة كيفية دمج إطاري البيانات (data frames) معاً.3 بدلاً من ذلك، يمكنك استخدام الأقواس المربعة [ لإجراء تصفية مضمنة (inline filtering)؛ حيث إنَّ arr_delay[arr_delay > 0] سينتج عنه فقط قيم تأخير الوصول الموجبة.
يقودنا هذا إلى الكود التالي:
flights |>
group_by(year, month, day) |>
summarize(
behind = mean(arr_delay[arr_delay > 0], na.rm = TRUE),
ahead = mean(arr_delay[arr_delay < 0], na.rm = TRUE),
n = n(),
.groups = "drop"
)
#> # A tibble: 365 × 6
#> year month day behind ahead n
#> <int> <int> <int> <dbl> <dbl> <int>
#> 1 2013 1 1 32.5 -12.5 842
#> 2 2013 1 2 32.0 -14.3 943
#> 3 2013 1 3 27.7 -18.2 914
#> 4 2013 1 4 28.3 -17.0 915
#> 5 2013 1 5 22.6 -14.0 720
#> 6 2013 1 6 24.4 -13.6 832
#> # ℹ 359 more rowsلاحظ أيضاً الفرق في حجم المجموعة: في التجميع الأول يعطي n() عدد الرحلات المتأخرة يومياً، بينما في التجميع الثاني يعطي n() العدد الإجمالي للرحلات.
12.4.4 تمارين
12.5 التحويلات الشرطية (Conditional transformations)
من أقوى ميزات المتجهات المنطقية استخدامها في إجراء تحويلات شرطية؛ أي القيام بأمر معين عند تحقق الشرط x، وإجراء أمر مختلف تماماً عند تحقق الشرط y. وهناك أداتان رئيسيتان لتحقيق ذلك: الدالة if_else() والدالة case_when().
12.5.1 الدالة if_else()
إذا أردت استخدام قيمة معينة عندما يكون الشرط صحيحاً TRUE وقيمة أخرى عندما يكون الشرط خاطئاً FALSE، يمكنك استخدام الدالة dplyr::if_else()4. ستستخدم دائماً الوسائط الثلاثة الأولى والدائمة للدالة if_else()؛ الوسيط الأول condition وهو متجه منطقي، والوسيط الثاني true ويحدد المخرجات في حالة صحة الشرط، والوسيط الثالث false ويحدد المخرجات في حالة عدم صحة الشرط.
لنبدأ بمثال بسيط لتصنيف متجه عددي إما كقيمة موجبة “+ve” أو قيمة سالبة “-ve”:
يتوفر أيضاً وسيط رابع اختياري وهو missing يُستخدم لتحديد القيمة المستخرجة إذا كان المدخل قيمة مفقودة NA:
if_else(x > 0, "+ve", "-ve", "???")
#> [1] "-ve" "-ve" "-ve" "-ve" "+ve" "+ve" "+ve" "???"يمكنك أيضاً استخدام متجهات للوسيطين true و false. يتيح لنا هذا، على سبيل المثال، إنشاء تطبيق مبسط وبديل للدالة abs() (القيمة المطلقة):
if_else(x < 0, -x, x)
#> [1] 3 2 1 0 1 2 3 NAحتى الآن استخدمت جميع الوسائط نفس المتجهات، لكن بالطبع يمكنك المزج والتوفيق بين المتجهات المتنوعة. على سبيل المثال، يمكنك بناء نسخة مبسطة من الدالة coalesce() على النحو التالي:
ربما لاحظت دقة غير مكتملة في مثال تصنيف الأعداد السابق: الصفر ليس موجباً ولا سالباً. يمكننا حل هذه المشكلة بإضافة دالة if_else() متداخلة أخرى:
يصعب قراءة هذا الكود نوعاً ما، ويمكنك أن تتخيل مدى الصعوبة والتعقيد إذا كان لديك العديد من الشروط المتداخلة. بدلاً من ذلك، يمكنك الانتقال إلى استخدام الدالة المباشرة dplyr::case_when().
12.5.2 الدالة case_when()
مفهوم الدالة case_when() في حزمة dplyr مستوحى من جملة CASE في لغة SQL، وهي توفر طريقة مرنة للغاية لإجراء حسابات مختلفة وفقاً لشروط متعددة ومتنوعة. تتميز بتركيب برمجي خاص يتكون من أزواج بصيغة condition ~ output. يجب أن يكون condition متجهاً منطقياً؛ وعندما تكون قيمته صحيح TRUE يتم إرجاع النتيجة المقابلة في output.
هذا يعني أنه يمكننا إعادة بناء المثال المتداخل السابق لـ if_else() على النحو التالي:
يتطلب هذا صياغة أطول نوعاً ما، لكنه أكثر وضوحاً ودقة في التعبير.
لشرح كيفية عمل الدالة case_when()، دعنا نستكشف بعض الحالات البسيطة. إذا لم تتطابق أي من الحالات، ترجع النتيجة القيمة المفقودة NA:
case_when(
x < 0 ~ "-ve",
x > 0 ~ "+ve"
)
#> [1] "-ve" "-ve" "-ve" NA "+ve" "+ve" "+ve" NAاستخدم الوسيط .default إذا أردت تعيين قيمة افتراضية شاملة لكل الحالات الأخرى المتبقية:
case_when(
x < 0 ~ "-ve",
x > 0 ~ "+ve",
.default = "???"
)
#> [1] "-ve" "-ve" "-ve" "???" "+ve" "+ve" "+ve" "???"ولاحظ أنه إذا تطابقت شروط متعددة، فسيتم تطبيق وتنفيذ الشرط الأول الذي يتحقق فقط:
case_when(
x > 0 ~ "+ve",
x > 2 ~ "big"
)
#> [1] NA NA NA NA "+ve" "+ve" "+ve" NAتماماً كما هو الحال مع الدالة if_else()، يمكنك استخدام المتغيرات على كلي جانبي العلامة ~ والمزج بينها حسب متطلبات المسألة التحليلية لديك. على سبيل المثال، يمكننا استخدام case_when() لإضافة تسميات سهلة القراءة والتفسير لحالات تأخير الوصول:
flights |>
mutate(
status = case_when(
is.na(arr_delay) ~ "cancelled",
arr_delay < -30 ~ "very early",
arr_delay < -15 ~ "early",
abs(arr_delay) <= 15 ~ "on time",
arr_delay < 60 ~ "late",
arr_delay < Inf ~ "very late",
),
.keep = "used"
)
#> # A tibble: 336,776 × 2
#> arr_delay status
#> <dbl> <chr>
#> 1 11 on time
#> 2 20 late
#> 3 33 late
#> 4 -18 early
#> 5 -25 early
#> 6 12 on time
#> # ℹ 336,770 more rowsكن حذراً عند كتابة هذا النوع من تعبيرات case_when() المعقدة؛ فالمحاولات الأولى قد تتداخل فيها الشروط عند خلط معاملات أصغر من < وأكبر من > بشكل غير مقصود.
12.5.3 الأنواع المتوافقة (Compatible types)
لاحظ أن كلاً من الدالتين if_else() و case_when() تطلب وجود أنواع بيانات متوافقة في المخرجات. وإذا كانت غير متوافقة، ستواجه أخطاء برمجية مثل هذه:
وبشكل عام، فإن عدداً قليلاً نسبياً من الأنواع يُعد متوافقاً ضمناً؛ لأن التحويل التلقائي لمتجه من نوع إلى آخر هو سبب شائع للأخطاء البرمجية. إليك أهم الحالات المتوافقة:
- المتجهات العددية والمنطقية متوافقة معاً، كما ناقشنا في قسم 12.4.2.
- النصوص والعوامل (factors) متوافقة (الفصل 16)، لأنه يمكنك التفكير في العامل على أنه نص ذو مجموعة محددة ومقيدة من القيم.
- التواريخ والتواريخ؛ والوقت متوافقة والتي سنناقشها في الفصل 17، لأنه يمكنك التفكير في التاريخ كحالة خاصة من التاريخ والوقت.
- القيمة المفقودة
NA- والتي تعد تقنياً متجهاً منطقياً - متوافقة مع جميع الأنواع، لأن كل نوع من المتجهات يحتوي على آلية لتمثيل القيم المفقودة.
لا نتوقع منك حفظ هذه القواعد عن ظهر قلب، ولكنها ستصبح مع الوقت عادة مكتسبة كجزء أصيل من طبيعة العمل نظراً لتطبيقها بشكل متسق عبر كافة حزم tidyverse.
12.5.4 تمارين
يكون العدد زوجياً إذا كان يقبل القسمة على اثنين بدون باقٍ، والتي يمكنك معرفتها في بيئة R باستخدام التعبير
x %% 2 == 0. استخدم هذه الحقيقة والدالةif_else()لتحديد ما إذا كان كل عدد بين 0 و 20 زوجياً أم فردياً.بفرض توفر متجه لأيام الأسبوع مثل
x <- c("Monday", "Saturday", "Wednesday")، استخدم عبارةif_else()لتصنيف كل يوم كعطلة نهاية أسبوع (weekend) أو يوم عمل عادي (weekday).استخدم الدالة
if_else()لحساب القيمة المطلقة لمتجه عددي يسمىx.اكتب عبارة
case_when()تستخدم عامودي الشهرmonthواليومdayمن مجموعة بياناتflightsلتحديد وتسمية مجموعة مختارة من العطلات الرسمية المهمة في الولايات المتحدة (مثل: رأس السنة الجديدة، الرابع من يوليو، عيد الشكر، وعيد الميلاد). أنشئ أولاً عموداً منطقياً تتحدد قيمته بـTRUEأوFALSE، ثم أنشئ عموداً نصياً يعطي اسم العطلة الرسمية أو القيمة المفقودةNA.
12.6 ملخص
تعريف المتجه المنطقي بسيط جداً؛ لأن كل قيمة يجب أن تكون إما TRUE أو FALSE أو NA. ومع ذلك، توفر المتجهات المنطقية إمكانات فائقة في معالجة البيانات. تعلمت في هذا الفصل كيفية إنشاء المتجهات المنطقية باستخدام المعاملات >, <, <=, >=, ==, !=, و is.na()، وكيفية دمجها باستخدام المعاملات المنطقية !, &, و |، وكيفية تلخيصها إحصائياً باستخدام any(), all(), sum(), و mean(). كما تعلمت أيضاً الإمكانات القوية للدالتين if_else() و case_when() اللتين تتيحان لك إرجاع قيم متغيرة استناداً إلى القيمة المنطقية المحسوبة.
سنلتقي بالمتجهات المنطقية مجدداً وبشكل متكرر في الفصول القادمة. على سبيل المثال، ستتعلم في الفصل 14 عن الدالة str_detect(x, pattern) التي ترجع متجهاً منطقياً تكون قيمته TRUE للعناصر في x التي تطابق النمط pattern؛ وفي الفصل 17 ستنشئ متجهات منطقية ناتجة عن مقارنة التواريخ والأوقات. ولكن الآن، سننتقل إلى النوع الرئيسي التالي والأكثر أهمية من المتجهات: وهو المتجهات العددية (numeric vectors).
تقوم R عادة باستدعاء الدالة print تلقائياً (أي أن كتابة
xهي اختصار لـprint(x))، لكن استدعاءها صراحة يكون مفيداً إذا أردت تمرير وسائط إضافية.↩︎تعطي الدالة
xor(x, y)القيمة صحيح (TRUE) إذا كانت x صحيحة أو y صحيحة، ولكن ليس كلاهما معاً. وهذه هي الطريقة التي نستخدم بها كلمة “أو” عادةً في اللغة الإنجليزية؛ فعبارة “كلاهما” ليست إجابة مقبولة عادةً على السؤال: “هل تود تناول الآيس كريم أم الكعك؟”.↩︎تتشابه الدالة
if_else()في حزمة dplyr تشابهاً كبيراً مع الدالةifelse()في R الأساسية. ولكن تتميزif_else()بميزتين رئيسيتين: يمكنك تحديد كيفية التعامل مع القيم المفقودة، كما أنها أكثر دقة في إعطاء رسائل أخطاء واضحة ومفسرة إذا كانت متغيراتك ذات أنواع غير متوافقة.↩︎