16 العوامل
16.1 مقدمة
تُستخدم العوامل (Factors) للمتغيرات الفئوية (categorical variables)، وهي المتغيرات التي تحتوي على مجموعة محددة ومعروفة من القيم المحتملة. كما أنها مفيدة جداً عندما ترغب في عرض المتجهات النصية بترتيب غير أبجدي.
سنبدأ بتوضيح سبب أهمية العوامل في تحليل البيانات1 وكيفية إنشائها باستخدام الدالة factor(). ثم سنقدم لك مجموعة بيانات gss_cat التي تحتوي على العديد من المتغيرات الفئوية للتجريب عليها. ستستخدم بعد ذلك مجموعة البيانات تلك للممارسة على تعديل ترتيب وقيم العوامل، قبل أن نختم بمناقشة حول العوامل المرتبة (ordered factors).
16.1.1 المتطلبات المسبقة
توفر R الأساسية (Base R) بعض الأدوات الأساسية لإنشاء العوامل والتحكم فيها. سنكمل هذه الأدوات باستخدام حزمة forcats، والتي تُعد جزءاً من نواة tidyverse. توفر هذه الحزمة أدوات للتعامل مع المتغيرات الفئوية (وهي كلمة مرادفة لـ categorical، كما أن اسمها جناس تصحيفي لـ factors!) باستخدام مجموعة واسعة من الدوال المساعدة.
16.2 أساسيات العوامل
تخيل أن لديك متغيراً يسجل الأشهر:
x1 <- c("Dec", "Apr", "Jan", "Mar")استخدام سلسلة نصية لتسجيل هذا المتغير يواجه مشكلتين:
- هناك اثنا عشر شهراً محتملاً فقط، ولا يوجد ما يحميك من الأخطاء الإملائية:
x2 <- c("Dec", "Apr", "Jam", "Mar")- لا يتم فرزها وترتيبها بطريقة مفيدة إحصائياً:
sort(x1)
#> [1] "Apr" "Dec" "Jan" "Mar"يمكنك حل كلتا المشكلتين باستخدام عامل (factor). لإنشاء عامل، يجب أن تبدأ بإنشاء قائمة بالمستويات المعتمدة والمالوفة (levels):
month_levels <- c(
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
)الآن يمكنك إنشاء العامل:
وأي قيم غير موجودة في المستويات المحددة سيتم تحويلها تلقائياً وبصمت إلى NA:
y2 <- factor(x2, levels = month_levels)
y2
#> [1] Dec Apr <NA> Mar
#> Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Decيبدو هذا محفوفاً بالمخاطر، لذا قد ترغب في استخدام forcats::fct() بدلاً من ذلك:
y2 <- fct(x2, levels = month_levels)
#> Error in `fct()`:
#> ! All values of `x` must appear in `levels` or `na`
#> ℹ Missing level: "Jam"إذا قمت بحذف المستويات، فسيتم أخذها من البيانات بترتيب أبجدي:
factor(x1)
#> [1] Dec Apr Jan Mar
#> Levels: Apr Dec Jan Marالفرز الأبجدي فيه نوع من المخاطرة لأن ليس كل جهاز كمبيوتر يرتب النصوص بنفس الطريقة. لذلك تقوم forcats::fct() بالترتيب حسب أول ظهور للقيم في البيانات:
fct(x1)
#> [1] Dec Apr Jan Mar
#> Levels: Dec Apr Jan Marإذا احتجت في أي وقت إلى الوصول إلى مجموعة المستويات الصالحة مباشرة، يمكنك القيام بذلك باستخدام levels():
levels(y2)
#> [1] "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"يمكنك أيضاً إنشاء عامل عند قراءة بياناتك باستخدام حزمة readr عبر col_factor():
csv <- "
month,value
Jan,12
Feb,56
Mar,12"
df <- read_csv(csv, col_types = cols(month = col_factor(month_levels)))
#> Warning: The `file` argument of `read_csv()` should use `I()` for literal data as of
#> readr 2.2.0.
#>
#> # Bad (for example):
#> read_csv("x,y\n1,2")
#>
#> # Good:
#> read_csv(I("x,y\n1,2"))
df$month
#> [1] Jan Feb Mar
#> Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec16.4 تعديل ترتيب العوامل
غالباً ما يكون من المفيد تغيير ترتيب مستويات العامل في التمثيل البياني. على سبيل المثال، تخيل أنك تريد استكشاف متوسط عدد الساعات المقضية في مشاهدة التلفزيون يومياً عبر الديانات المختلفة:

من الصعب قراءة هذا المخطط لأنه لا يوجد نمط عام واضح. يمكننا تحسينه بإعادة ترتيب مستويات relig باستخدام fct_reorder(). تأخذ fct_reorder() ثلاثة وسائط:
-
.f: العامل الذي تريد تعديل مستوياته. -
.x: متجه عددي تريد استخدامه لإعادة ترتيب المستويات. - اختيارياً
.fun: دالة تُستخدم إذا كانت هناك قيم متعددة لـ.xلكل قيمة من.f. القيمة الافتراضية هيmedian(الوسيط).
ggplot(relig_summary, aes(x = tvhours, y = fct_reorder(relig, tvhours))) +
geom_point()
إعادة ترتيب الديانة تيسر كثيراً رؤية أن الأشخاص في فئة “لا أعرف” يشاهدون التلفزيون بشكل أكبر بكثير، بينما الديانة الهندوسية والديانات الشرقية الأخرى تشاهد التلفزيون بنسبة أقل بكثير.
عندما تبدأ في إجلاء التحويلات الأكثر تعقيداً، نوصي بنقلها خارج aes() وإدراجها في خطوة mutate() مستقلة. على سبيل المثال، يمكنك إعادة كتابة الرسم البياني أعلاه كالتالي:
relig_summary |>
mutate(
relig = fct_reorder(relig, tvhours)
) |>
ggplot(aes(x = tvhours, y = relig)) +
geom_point()ماذا لو أنشأنا مخططاً مشابهاً يوضح كيف يتغير متوسط العمر عبر مستويات الدخل المعلن عنها؟
rincome_summary <- gss_cat |>
group_by(rincome) |>
summarize(
age = mean(age, na.rm = TRUE),
n = n()
)
ggplot(rincome_summary, aes(x = age, y = fct_reorder(rincome, age))) +
geom_point()
هنا، إعادة ترتيب المستويات بشكل عشوائي ليست فكرة صائبة! ذلك لأن rincome لديه بالفعل ترتيب منطقي ومبدئي لا ينبغي لنا الإخلال به. احفظ دالة fct_reorder() للعوامل التي تكون مستوياتها مرتبة بشكل عشوائي.
ومع ذلك، من المنطقي نقل فئة “غير ذي صلة” (Not applicable) إلى المقدمة مع المستويات الخاصة الأخرى. يمكنك استخدام fct_relevel(). تأخذ هذه الدالة عاملاً .f، ثم أي عدد من المستويات التي تريد نقلها إلى بداية الصف.
ggplot(rincome_summary, aes(x = age, y = fct_relevel(rincome, "Not applicable"))) +
geom_point()
لماذا تعتقد أن متوسط العمر لفئة “غير ذي صلة” (Not applicable) مرتفع للغاية؟
نوع آخر من إعادة الترتيب يكون مفيداً عندما تقوم بتلوين الخطوط في المخطط البياني. تقوم fct_reorder2(.f, .x, .y) بإعادة ترتيب العامل .f بناءً على قيم .y المرتبطة بأكبر قيم لـ .x. هذا يجعل المخطط أسهل في القراءة لأن ألوان الخطوط في أقصى يمين الرسم البياني ستتطابق مع مفتاح الرسم (legend).
by_age <- gss_cat |>
filter(!is.na(age)) |>
count(age, marital) |>
group_by(age) |>
mutate(
prop = n / sum(n)
)
ggplot(by_age, aes(x = age, y = prop, color = marital)) +
geom_line(linewidth = 1) +
scale_color_brewer(palette = "Set1")
ggplot(by_age, aes(x = age, y = prop, color = fct_reorder2(marital, age, prop))) +
geom_line(linewidth = 1) +
scale_color_brewer(palette = "Set1") +
labs(color = "marital")

أخيراً، بالنسبة للمخططات الشريطية، يمكنك استخدام fct_infreq() لترتيب المستويات حسب التكرار التنازلي: هذا هو أبسط أنواع إعادة الترتيب لأنه لا يحتاج إلى أي متغيرات إضافية. اجمعها مع fct_rev() إذا كنت تريدها بترتيب تكرار تصاعدي بحيث تكون القيم الأكبر في المخطط الشريطِي على اليمين بدلاً من اليسار.

16.4.1 تمارين
هناك بعض الأرقام المرتفعة بشكل مريب في
tvhours. هل المتوسط الحسابي يلخص البيانات بشكل جيد هنا؟لكل عامل في
gss_catحدد ما إذا كان ترتيب المستويات عشوائياً أم مبنياً على ترتيب منطقي/مبدئي.لماذا أدى نقل فئة “Not applicable” إلى مقدمة المستويات إلى نقلها إلى أسفل المخطط البياني؟
16.5 تعديل قيم المستويات
الأكثر قوة من تغيير ترتيب المستويات هو تغيير قيمها ذاتها. يتيح لك ذلك توضيح المسميات والنصوص لأغراض النشر، وتجميع المستويات للعروض التقديمية عالية المستوى. الأداة الأكثر شمولاً وقوة هي fct_recode(). تتيح لك إعادة ترميز أو تغيير قيمة كل مستوى. على سبيل المثال، خذ متغير partyid من جدول بيانات gss_cat:
gss_cat |> count(partyid)
#> # A tibble: 10 × 2
#> partyid n
#> <fct> <int>
#> 1 No answer 154
#> 2 Don't know 1
#> 3 Other party 393
#> 4 Strong republican 2314
#> 5 Not str republican 3032
#> 6 Ind,near rep 1791
#> # ℹ 4 more rowsالمستويات موجزة وغير متسقة. دعنا نعدلها لتصبح أطول وتتبع بناءً متوازياً. مثل معظم دوال إعادة التسمية وإعادة الترميز في tidyverse، تذهب القيم الجديدة على اليسار، والقيم القديمة على اليمين:
gss_cat |>
mutate(
partyid = fct_recode(partyid,
"Republican, strong" = "Strong republican",
"Republican, weak" = "Not str republican",
"Independent, near rep" = "Ind,near rep",
"Independent, near dem" = "Ind,near dem",
"Democrat, weak" = "Not str democrat",
"Democrat, strong" = "Strong democrat"
)
) |>
count(partyid)
#> # A tibble: 10 × 2
#> partyid n
#> <fct> <int>
#> 1 No answer 154
#> 2 Don't know 1
#> 3 Other party 393
#> 4 Republican, strong 2314
#> 5 Republican, weak 3032
#> 6 Independent, near rep 1791
#> # ℹ 4 more rowsستترك fct_recode() المستويات التي لم تذكرها صراحة كما هي، وستحذرك إذا أشرت بطريق الخطأ إلى مستوى غير موجود.
لتدمج المجموعات، يمكنك إسناد مستويات قديمة متعددة إلى نفس القيمة الجديدة:
gss_cat |>
mutate(
partyid = fct_recode(partyid,
"Republican, strong" = "Strong republican",
"Republican, weak" = "Not str republican",
"Independent, near rep" = "Ind,near rep",
"Independent, near dem" = "Ind,near dem",
"Democrat, weak" = "Not str democrat",
"Democrat, strong" = "Strong democrat",
"Other" = "No answer",
"Other" = "Don't know",
"Other" = "Other party"
)
)استخدم هذه التقنية بحذر: إذا قمت بتجميع فئات مختلفة تماماً معاً، فستنتهي بنتائج مضللة.
إذا كنت تريد دمج وتقليص الكثير من المستويات، فإن fct_collapse() تُعدّ خياراً بديلًا ومفيداً للدالة fct_recode(). لكل متغير جديد، يمكنك تقديم متجه من المستويات القديمة:
gss_cat |>
mutate(
partyid = fct_collapse(partyid,
"other" = c("No answer", "Don't know", "Other party"),
"rep" = c("Strong republican", "Not str republican"),
"ind" = c("Ind,near rep", "Independent", "Ind,near dem"),
"dem" = c("Not str democrat", "Strong democrat")
)
) |>
count(partyid)
#> # A tibble: 4 × 2
#> partyid n
#> <fct> <int>
#> 1 other 548
#> 2 rep 5346
#> 3 ind 8409
#> 4 dem 7180في بعض الأحيان ترغب فقط في تجميع المجموعات الصغيرة معاً لجعل الرسم البياني أو الجدول أسهل وأبسط. هذه هي مهمة عائلة دوال fct_lump_*(). تُعد fct_lump_lowfreq() نقطة بداية بسيطة تقوم بتجميع الفئات ذات التكرار الأقل تدريجياً في فئة “Other”، مع الحفاظ دائماً على “Other” كأصغر فئة.
gss_cat |>
mutate(relig = fct_lump_lowfreq(relig)) |>
count(relig)
#> # A tibble: 2 × 2
#> relig n
#> <fct> <int>
#> 1 Protestant 10846
#> 2 Other 10637في هذه الحالة ليس الأمر مفيداً جداً: صحيح أن غالبية الأمريكيين في هذا المسح من البروتستانت، لكننا نود على الأرجح رؤية المزيد من التفاصيل! بدلاً من ذلك، يمكننا استخدام fct_lump_n() لتحديد أننا نريد بالضبط 10 مجموعات:
gss_cat |>
mutate(relig = fct_lump_n(relig, n = 10)) |>
count(relig, sort = TRUE)
#> # A tibble: 10 × 2
#> relig n
#> <fct> <int>
#> 1 Protestant 10846
#> 2 Catholic 5124
#> 3 None 3523
#> 4 Christian 689
#> 5 Other 458
#> 6 Jewish 388
#> # ℹ 4 more rowsاقرأ وثائق الحزمة للتعرف على fct_lump_min() و fct_lump_prop() المفيدتين في حالات أخرى.
16.5.1 تمارين
كيف تغيرت نسب الأشخاص الذين يعرّفون أنفسهم كديمقراطيين، وجمهوريين، ومستقلين عبر الزمن؟
كيف يمكنك دمج
rincomeفي مجموعة صغيرة من الفئات؟لاحظ وجود 9 مجموعات (باستثناء Other) في مثال
fct_lumpأعلاه. لماذا ليست 10؟ (تلميح: اكتب?fct_lumpوستجد أن القيمة الافتراضية للوسيطother_levelهي “Other”.)
16.6 العوامل المرتبة
قبل أن نتابع، من المهم التنويه بشكل سريع إلى نوع خاص من العوامل: العوامل المرتبة (ordered factors). تُنشأ باستخدام الدالة ordered()، وتقتضي ترتيباً صارماً بين المستويات، ولكنها لا تحدد أي شيء عن مقدار الاختلاف الفعلي بين هذه المستويات. تستخدم العوامل المرتبة عندما تعلم أن المستويات ذات رتبة معينة، ولكن لا يوجد ترتيب عددي دقيق بينها.
يمكنك التعرف على العامل المرتب عند طباعته لأنه يستخدم رمز < بين مستويات العامل:
في كل من R الأساسية و tidyverse، تتصرف العوامل المرتبة بشكل مشابه جداً للعوامل العادية. هناك مكانان فقط قد تلاحظ فيهما سلوكاً مختلفاً:
- إذا قمت بربط عامل مرتب باللون (color) أو التعبئة (fill) في ggplot2، فسيتم استخدام السلم اللونِي
scale_color_viridis()/scale_fill_viridis()كخيار افتراضي، وهو سلم ألوان يرمز إلى الرتب والتدرج. - إذا استخدمت متغيراً تنبؤياً مرتباً في نموذج خطي (linear model)، فسوف يستخدم “المقارنات متعددة الحدود” (polynomial contrasts). هذه مفيدة نوعاً ما، لكن من غير المرجح أن تكون قد سمعت عنها ما لم تكن حاصلاً على الدكتوراه في الإحصاء، وحتى ذلك الحين ربما لا تقوم بتفسيرها بشكل روتيني. إذا كنت تريد معرفة المزيد، نوصي بقراءة
vignette("contrasts", package = "faux")بواسطة ليزا ديبروين (Lisa DeBruine).
لأغراض هذا الكتاب، فإن التمييز الصحيح بين العوامل العادية والمرتبة ليس ذا أهمية بالغة. ولكن بشكل أوسع، تستخدم مجالات معينة (خاصة العلوم الاجتماعية) العوامل المرتبة مكثفاً. في هذه السياقات، من المهم تحديدها بشكل صحيح حتى تتمكن حزم التحليل الأخرى من تقديم السلوك المناسب.
16.7 ملخص
قدم لك هذا الفصل حزمة forcats العملية للعمل مع العوامل وتغطية الدوال الأكثر استخداماً. تحتوي forcats على مجموعة واسعة من الدوال المساعدة الأخرى التي لم تتسع المساحة لمناقشتها هنا، لذا عندما تواجه تحدياً في تحليل العوامل لم تصادفه من قبل، أوصيك بشدة بإلقاء نظرة سريعة على فهرس المراجع لمعرفة ما إذا كانت هناك دالة جاهزة يمكنها مساعدتك في حل مشكلتك.
إذا كنت تريد معرفة المزيد عن العوامل بعد قراءة هذا الفصل، نوصي بقراءة ورقة أميليا مكنامارا ونيكولاس هورتون، Wrangling categorical data in R. تستعرض هذه الورقة بعض التاريخ المناقش في stringsAsFactors: An unauthorized biography و stringsAsFactors = <sigh>، وتقارن نهج tidyverse للبيانات الفئوية الموضح في هذا الكتاب مع طرق R الأساسية. ساعدت نسخة مبكرة من الورقة في تحفيز وتحديد نطاق حزمة forcats؛ فشكراً لأميليا ونيك!
في الفصل التالي، سنغير الاتجاه لنبدأ في التعلم عن التواريخ والأوقات في R. تبدو التواريخ والأوقات بسيطة بشكل مخادع، ولكن كما ستكتشف قريباً، كلما تعلمت المزيد عنها، كلما بدت أكثر تعقيداً!
كما أنها مهمة للغاية في النمذجة الإحصائية (modelling).↩︎