5 ترتيب البيانات (Data tidying)
5.1 مقدمة
“العائلات السعيدة كلها متشابهة، ولكن كل عائلة تعيسة تكون تعيسة بطريقتها الخاصة.” — ليو تولستوي
“مجموعات البيانات المرتبة كلها متشابهة، ولكن كل مجموعة بيانات غير مرتبة تكون غير مرتبة بطريقتها الخاصة.” — هادلي ويكهام
في هذا الفصل، ستتعلم طريقة متسقة لتنظيم بياناتك في لغة R باستخدام نظام يسمى البيانات المرتبة (tidy data). يتطلب تنظيم بياناتك في هذا التنسيق بعض الجهد المسبق، ولكن هذا الجهد يؤتي ثماره على المدى الطويل. بمجرد حصولك على بيانات مرتبة والأدوات المرتبة التي توفرها الحزم في منظومة tidyverse، ستقضي وقتاً أقل بكثير في تحويل البيانات من تمثيل إلى آخر، مما يتيح لك قضاء المزيد من الوقت في أسئلة البيانات التي تهتم بها.
في هذا الفصل، ستتعلم أولاً تعريف البيانات المرتبة وترى تطبيقه على مجموعة بيانات تجريبية بسيطة. بعد ذلك، سننتقل إلى الأداة الأساسية التي ستستخدمها لترتيب البيانات: تحويل المحاور (pivoting). يتيح لك تحويل المحاور تغيير شكل بياناتك دون تغيير أي من القيم.
5.1.1 المتطلبات الأساسية
في هذا الفصل، سنركز على حزمة tidyr، وهي حزمة توفر مجموعة من الأدوات للمساعدة في ترتيب مجموعات البيانات غير المرتبة. تعد حزمة tidyr عضواً في النواة الأساسية لمنظومة tidyverse.
بدءاً من هذا الفصل فصاعداً، سنقوم بحجب رسالة التحميل الصادرة عن استدعاء library(tidyverse).
5.2 البيانات المرتبة (Tidy data)
يمكنك تمثيل نفس البيانات الأساسية بطرق متعددة. يوضح المثال أدناه نفس البيانات منظمّة بثلاث طرق مختلفة. تعرض كل مجموعة بيانات نفس القيم لأربعة متغيرات: الدولة (country)، السنة (year)، السكان (population)، وعدد الحالات (cases) الموثقة لمرض السل، ولكن كل مجموعة بيانات تنظم القيم بطريقة مختلفة.
table1
#> # A tibble: 6 × 4
#> country year cases population
#> <chr> <dbl> <dbl> <dbl>
#> 1 Afghanistan 1999 745 19987071
#> 2 Afghanistan 2000 2666 20595360
#> 3 Brazil 1999 37737 172006362
#> 4 Brazil 2000 80488 174504898
#> 5 China 1999 212258 1272915272
#> 6 China 2000 213766 1280428583
table2
#> # A tibble: 12 × 4
#> country year type count
#> <chr> <dbl> <chr> <dbl>
#> 1 Afghanistan 1999 cases 745
#> 2 Afghanistan 1999 population 19987071
#> 3 Afghanistan 2000 cases 2666
#> 4 Afghanistan 2000 population 20595360
#> 5 Brazil 1999 cases 37737
#> 6 Brazil 1999 population 172006362
#> # ℹ 6 more rows
table3
#> # A tibble: 6 × 3
#> country year rate
#> <chr> <dbl> <chr>
#> 1 Afghanistan 1999 745/19987071
#> 2 Afghanistan 2000 2666/20595360
#> 3 Brazil 1999 37737/172006362
#> 4 Brazil 2000 80488/174504898
#> 5 China 1999 212258/1272915272
#> 6 China 2000 213766/1280428583هذه كلها تمثيلات لنفس البيانات الأساسية، ولكنها ليست متساوية في سهولة الاستخدام. أحدها، وهو table1، سيكون التعامل معه أسهل بكثير داخل منظومة tidyverse لأنه مرتب (tidy).
هناك ثلاث قواعد مترابطة تجعل مجموعة البيانات مرتبة:
- كل متغير يمثل عموداً؛ وكل عمود يمثل متغيراً.
- كل مشاهدة تمثل صفاً؛ وكل صف يمثل مشاهدة.
- كل قيمة تمثل خلية؛ وكل خلية تحتوي على قيمة واحدة فقط.
يوضح الشكل 5.1 هذه القواعد بصرياً.
لماذا نتأكد من أن البيانات مرتبة؟ هناك ميزتان رئيسيتان:
هناك ميزة عامة لاختيار طريقة واحدة متسقة لتخزين البيانات. إذا كان لديك بنية بيانات متسقة، فمن الأسهل تعلم الأدوات التي تعمل معها لأنها تمتلك اتساقاً وتناغماً أساسياً.
هناك ميزة محددة لوضع المتغيرات في أعمدة لأنها تتيح لطبيعة R المتجهة (vectorized) أن تتألق. كما تعلمت في قسم 3.3.1 و قسم 3.5.2، فإن معظم دالات R المدمجة تعمل مع متجهات من القيم. هذا يجعل تحويل البيانات المرتبة يبدو طبيعياً للغاية.
تم تصميم حزم dplyr و ggplot2 وجميع الحزم الأخرى في tidyverse للعمل مع البيانات المرتبة. إليك بعض الأمثلة الصغيرة التي توضح كيف يمكنك العمل مع جدول table1.
# حساب المعدل لكل 10,000
table1 |>
mutate(rate = cases / population * 10000)
#> # A tibble: 6 × 5
#> country year cases population rate
#> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 Afghanistan 1999 745 19987071 0.373
#> 2 Afghanistan 2000 2666 20595360 1.29
#> 3 Brazil 1999 37737 172006362 2.19
#> 4 Brazil 2000 80488 174504898 4.61
#> 5 China 1999 212258 1272915272 1.67
#> 6 China 2000 213766 1280428583 1.67
# حساب إجمالي الحالات لكل سنة
table1 |>
group_by(year) |>
summarize(total_cases = sum(cases))
#> # A tibble: 2 × 2
#> year total_cases
#> <dbl> <dbl>
#> 1 1999 250740
#> 2 2000 296920
# التصور البصري للتغيرات بمرور الوقت
ggplot(table1, aes(x = year, y = cases)) +
geom_line(aes(group = country), color = "grey50") +
geom_point(aes(color = country, shape = country)) +
scale_x_continuous(breaks = c(1999, 2000)) # محطات المحور السيني عند 1999 و 2000
5.2.1 تمارين
لكل جدول من جداول العينة، صف ما يمثله كل سطر (مشاهدة) وكل عمود.
-
ارسم مخططاً عاماً للعملية التي ستستخدمها لحساب
rateلكل منtable2وtable3. ستقتضي الحاجة تنفيذ أربع عمليات:أ. استخراج عدد حالات مرض السل (TB cases) لكل دولة في السنة.
ب. استخراج عدد السكان المقابل لكل دولة في السنة.
ج. قسمة عدد الحالات على عدد السكان، ثم الضرب في 10000.
د. إعادة تخزين النتيجة في المكان المناسب.
لم تتعلم بعد جميع الدالات التي ستحتاجها لإجراء هذه العمليات فعلياً، ولكن لا يزال بإمكانك التفكير في التحويلات التي ستحتاجها.
5.3 تزييد طول البيانات (Lengthening data)
قد تبدو مبادئ البيانات المرتبة واضحة للغاية لدرجة تجعلك تتساءل عما إذا كنت ستصادف يوماً ما مجموعة بيانات غير مرتبة. لسوء الحظ، فإن معظم البيانات الحقيقية تكون غير مرتبة، ويرجع ذلك لسببين رئيسيين:
غالباً ما يتم تنظيم البيانات لتسهيل هدف آخر غير التحليل. على سبيل المثال، من الشائع هيكلة البيانات لتسهيل إدخال البيانات وليس تسهيل تحليلها.
معظم الناس ليسوا على دراية بمبادئ البيانات المرتبة، ومن الصعب استنتاجها بنفسك ما لم تقضِ وقتاً طويلاً في العمل مع البيانات.
هذا يعني أن معظم التحليلات الحقيقية ستتطلب حتماً قليلاً من الترتيب. ستبدأ أولاً بتحديد المتغيرات والملاحظات الأساسية؛ قد يكون هذا سهلاً في بعض الأحيان، وفي أحيان أخرى ستحتاج إلى استشارة الأشخاص الذين قاموا بتوليد البيانات في الأصل. بعد ذلك، ستعمل على تحويل محاور (pivot) بياناتك إلى شكل مرتب، بحيث تكون المتغيرات في الأعمدة والمشاهدات في الصفوف.
توفر حزمة tidyr دالتين لتحويل محاور البيانات: pivot_longer() و pivot_wider(). سنبدأ أولاً بالدالة pivot_longer() لأنها الحالة الأكثر شيوعاً. دعنا ننتقل إلى بعض الأمثلة.
5.3.1 البيانات في أسماء الأعمدة (Data in column names)
تسجل مجموعة بيانات billboard تصنيف الأغاني في قوائم البيلبورد لعام 2000:
billboard
#> # A tibble: 317 × 79
#> artist track date.entered wk1 wk2 wk3 wk4 wk5
#> <chr> <chr> <date> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 2 Pac Baby Don't Cry (Ke… 2000-02-26 87 82 72 77 87
#> 2 2Ge+her The Hardest Part O… 2000-09-02 91 87 92 NA NA
#> 3 3 Doors Down Kryptonite 2000-04-08 81 70 68 67 66
#> 4 3 Doors Down Loser 2000-10-21 76 76 72 69 67
#> 5 504 Boyz Wobble Wobble 2000-04-15 57 34 25 17 17
#> 6 98^0 Give Me Just One N… 2000-08-19 51 39 34 26 26
#> # ℹ 311 more rows
#> # ℹ 71 more variables: wk6 <dbl>, wk7 <dbl>, wk8 <dbl>, wk9 <dbl>, …في مجموعة البيانات هذه، تمثل كل مشاهدة أغنية معينة. الأعمدة الثلاثة الأولى (artist و track و date.entered) هي متغيرات تصف الأغنية. بعد ذلك، لدينا 76 عموداً (wk1-wk76) تصف تصنيف الأغنية في كل أسبوع.1 هنا، تمثل أسماء الأعمدة متغيراً واحداً (وهو الأسبوع week) بينما تمثل قيم الخلايا متغيراً آخر (وهو التصنيف rank).
لترتيب هذه البيانات، سنستخدم الدالة pivot_longer():
billboard |>
pivot_longer(
cols = starts_with("wk"),
names_to = "week",
values_to = "rank"
)
#> # A tibble: 24,092 × 5
#> artist track date.entered week rank
#> <chr> <chr> <date> <chr> <dbl>
#> 1 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk1 87
#> 2 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk2 82
#> 3 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk3 72
#> 4 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk4 77
#> 5 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk5 87
#> 6 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk6 94
#> 7 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk7 99
#> 8 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk8 NA
#> 9 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk9 NA
#> 10 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk10 NA
#> # ℹ 24,082 more rowsبعد تحديد البيانات، هناك ثلاث وسيطات رئيسية:
-
cols: تحدد الأعمدة التي تحتاج إلى تحويل محاورها، أي الأعمدة التي لا تعد متغيرات في حد ذاتها. تستخدم هذه الوسيطة نفس صيغة الدالةselect()، لذا يمكننا هنا استخدام!c(artist, track, date.entered)أوstarts_with("wk"). -
names_to: تمنح اسماً للمتغير المخزن حالياً في أسماء الأعمدة، وقد أسمينا هذا المتغير الجديدweek. -
values_to: تمنح اسماً للمتغير المخزن حالياً في قيم الخلايا، وقد أسمينا هذا المتغير الجديدrank.
لاحظ أنه في الكود تم وضع علامات اقتباس حول "week" و "rank" لأنها متغيرات جديدة نقوم بإنشائها، وليست موجودة في البيانات عند تشغيل أمر pivot_longer().
دعنا الآن نوجه انتباهنا إلى إطار البيانات الناتج الأكثر طولاً. ماذا يحدث إذا بقيت الأغنية في قائمة أفضل 100 لمدة تقل عن 76 أسبوعاً؟ خذ أغنية “Baby Don’t Cry” للمغني 2 Pac على سبيل المثال. تشير المخرجات أعلاه إلى أنها كانت في قائمة أفضل 100 لمدة 7 أسابيع فقط، وأن جميع الأسابيع المتبقية تم ملؤها بقيم مفقودة. هذه القيم المفقودة NA لا تمثل مشاهدات مجهولة في الواقع؛ بل فُرِض وجودها بسبب بنية مجموعة البيانات،2 ولذلك يمكننا توجيه الدالة pivot_longer() للتخلص منها عن طريق تعيين values_drop_na = TRUE:
billboard |>
pivot_longer(
cols = starts_with("wk"),
names_to = "week",
values_to = "rank",
values_drop_na = TRUE
)
#> # A tibble: 5,307 × 5
#> artist track date.entered week rank
#> <chr> <chr> <date> <chr> <dbl>
#> 1 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk1 87
#> 2 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk2 82
#> 3 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk3 72
#> 4 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk4 77
#> 5 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk5 87
#> 6 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk6 94
#> # ℹ 5,301 more rowsأصبح عدد الصفوف الآن أقل بكثير، مما يشير إلى أنه تم إسقاط العديد من الصفوف التي تحتوي على قيم NA.
قد تتساءل أيضاً عما يحدث إذا بقيت أغنية في قائمة أفضل 100 لأكثر من 76 أسبوعاً؟ لا يمكننا معرفة ذلك من هذه البيانات، ولكن يمكنك تخمين أنه سيتم إضافة أعمدة إضافية مثل wk77 و wk78 وهكذا إلى مجموعة البيانات.
أصبحت هذه البيانات الآن مرتبة، ولكن يمكننا تسهيل العمليات الحسابية المستقبلية قليلاً عن طريق تحويل قيم المتغير week من سلاسل نصية إلى أرقام باستخدام الدالتين mutate() و readr::parse_number(). الدالة parse_number() هي دالة مفيدة للغاية تقوم باستخراج أول رقم تجده في السلسلة النصية، متجاهلة أي نصوص أخرى.
billboard_longer <- billboard |>
pivot_longer(
cols = starts_with("wk"),
names_to = "week",
values_to = "rank",
values_drop_na = TRUE
) |>
mutate(
week = parse_number(week)
)
billboard_longer
#> # A tibble: 5,307 × 5
#> artist track date.entered week rank
#> <chr> <chr> <date> <dbl> <dbl>
#> 1 2 Pac Baby Don't Cry (Keep... 2000-02-26 1 87
#> 2 2 Pac Baby Don't Cry (Keep... 2000-02-26 2 82
#> 3 2 Pac Baby Don't Cry (Keep... 2000-02-26 3 72
#> 4 2 Pac Baby Don't Cry (Keep... 2000-02-26 4 77
#> 5 2 Pac Baby Don't Cry (Keep... 2000-02-26 5 87
#> 6 2 Pac Baby Don't Cry (Keep... 2000-02-26 6 94
#> # ℹ 5,301 more rowsالآن بعد أن أصبح لدينا جميع أرقام الأسابيع في متغير واحد وجميع قيم التصنيفات في متغير آخر، أصبحنا في وضع جيد لتصور كيفية تغير تصنيفات الأغاني بمرور الوقت. يظهر الكود أدناه والنتيجة في الشكل 5.2. يمكننا أن نرى أن عدداً قليلاً جداً من الأغاني يظل في قائمة أفضل 100 لأكثر من 20 أسبوعاً.
billboard_longer |>
ggplot(aes(x = week, y = rank, group = track)) +
geom_line(alpha = 0.25) +
scale_y_reverse()
5.3.2 كيف يعمل تحويل المحاور؟
الآن بعد أن رأيت كيف يمكننا استخدام تحويل المحاور لإعادة تشكيل بياناتنا، دعنا نخصص بعض الوقت لاكتساب فهم منطقي وبديهي حول ما يفعله تحويل المحاور بالبيانات. لنبدأ بمجموعة بيانات بسيطة للغاية ليسهل رؤية ما يحدث. افترض أن لدينا ثلاثة مرضى بمعرفات (id) هي A و B و C، وقمنا بأخذ قياسين لضغط الدم لكل مريض. سنقوم بإنشاء البيانات باستخدام الدالة tribble()، وهي دالة مفيدة لبناء جداول ممتدة (tibbles) صغيرة يدوياً:
df <- tribble(
~id, ~bp1, ~bp2,
"A", 100, 120,
"B", 140, 115,
"C", 120, 125
)نريد أن تحتوي مجموعة البيانات الجديدة على ثلاثة متغيرات: المعرف id (موجود بالفعل)، والقياس measurement (أسماء الأعمدة الحالية)، والقيمة value (قيم الخلايا الحالية). لتحقيق ذلك, نحتاج إلى تحويل المحاور لـ df ليصبح أطول:
df |>
pivot_longer(
cols = bp1:bp2,
names_to = "measurement",
values_to = "value"
)
#> # A tibble: 6 × 3
#> id measurement value
#> <chr> <chr> <dbl>
#> 1 A bp1 100
#> 2 A bp2 120
#> 3 B bp1 140
#> 4 B bp2 115
#> 5 C bp1 120
#> 6 C bp2 125كيف تعمل إعادة التشكيل هذه؟ من الأسهل رؤية ذلك إذا فكرنا فيه عموداً بعمود. كما هو موضح في الشكل 5.3، فإن القيم الموجودة في العمود الذي كان يمثل متغيراً بالفعل في مجموعة البيانات الأصلية (id) تحتاج إلى التكرار، مرة واحدة لكل عمود يتم تحويل محاوره.
تصبح أسماء الأعمدة قيماً في متغير جديد، ويتم تحديد اسمه بواسطة الوسيطة names_to، كما هو موضح في الشكل 5.4. وهي تحتاج إلى التكرار مرة واحدة لكل سطر في مجموعة البيانات الأصلية.
تصبح قيم الخلايا أيضاً قيماً في متغير جديد، باسم يتم تحديده بواسطة الوسيطة values_to. ويتم فكها (unwound) سطراً بسطر. يوضح الشكل 5.5 هذه العملية.
5.3.3 متغيرات متعددة في أسماء الأعمدة
تحدث حالة أكثر صعوبة عندما يكون لديك أجزاء متعددة من المعلومات مكدسة داخل أسماء الأعمدة، وتريد تخزينها في متغيرات جديدة منفصلة. على سبيل المثال، لنأخذ مجموعة بيانات who2، وهي مصدر جدول table1 والجداول المصاحبة له التي رأيتها أعلاه:
who2
#> # A tibble: 7,240 × 58
#> country year sp_m_014 sp_m_1524 sp_m_2534 sp_m_3544 sp_m_4554
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 Afghanistan 1980 NA NA NA NA NA
#> 2 Afghanistan 1981 NA NA NA NA NA
#> 3 Afghanistan 1982 NA NA NA NA NA
#> 4 Afghanistan 1983 NA NA NA NA NA
#> 5 Afghanistan 1984 NA NA NA NA NA
#> 6 Afghanistan 1985 NA NA NA NA NA
#> # ℹ 7,234 more rows
#> # ℹ 51 more variables: sp_m_5564 <dbl>, sp_m_65 <dbl>, sp_f_014 <dbl>, …تسجل مجموعة البيانات هذه، التي جمعتها منظمة الصحة العالمية، معلومات حول تشخيصات مرض السل. هناك عمودان يمثلان متغيرات بالفعل ويسهل تفسيرهما: country و year. يليهما 56 عموداً مثل sp_m_014 و ep_m_4554 و rel_m_3544. إذا أمعنت النظر في هذه الأعمدة لفترة كافية، فستلاحظ وجود نمط معين؛ حيث يتكون كل اسم عمود من ثلاثة أجزاء تفصل بينها شرطة سفلية _. الجزء الأول، sp/rel/ep، يصف الطريقة المستخدمة في التشخيص، والجزء الثاني، m/f هو الجنس gender، (والمرمز كمتغير ثنائي في مجموعة البيانات هذه)، والجزء الثالث، 014/1524/2534/3544/4554/5564/65 هو الفئة العمرية (age، حيث يمثل 014 الفئة من 0 إلى 14 عاماً، على سبيل المثال).
لذلك، لدينا في هذه الحالة ستة أجزاء من المعلومات المسجلة في who2: الدولة والسنة (أعمدة بالفعل)؛ وطريقة التشخيص، وفئة الجنس، وفئة الفئة العمرية (الموجودة في أسماء الأعمدة الأخرى)؛ وعدد المرضى في تلك الفئة (قيم الخلايا). لتنظيم هذه الأجزاء الستة من المعلومات في ستة أعمدة منفصلة، نستخدم الدالة pivot_longer() مع تمرير متجه لأسماء الأعمدة لـ names_to وتوجيهات لتقسيم أسماء المتغيرات الأصلية إلى أجزاء عبر الوسيطة names_sep، بالإضافة إلى اسم عمود لـ values_to:
who2 |>
pivot_longer(
cols = !(country:year),
names_to = c("diagnosis", "gender", "age"),
names_sep = "_",
values_to = "count"
)
#> # A tibble: 405,440 × 6
#> country year diagnosis gender age count
#> <chr> <dbl> <chr> <chr> <chr> <dbl>
#> 1 Afghanistan 1980 sp m 014 NA
#> 2 Afghanistan 1980 sp m 1524 NA
#> 3 Afghanistan 1980 sp m 2534 NA
#> 4 Afghanistan 1980 sp m 3544 NA
#> 5 Afghanistan 1980 sp m 4554 NA
#> 6 Afghanistan 1980 sp m 5564 NA
#> # ℹ 405,434 more rowsالبديل للوسيطة names_sep هو names_pattern، والتي يمكنك استخدامها لاستخراج المتغيرات من سيناريوهات التسمية الأكثر تعقيداً، بمجرد أن تتعلم عن التعبيرات المنتظمة (regular expressions) في الفصل 15.
من الناحية المفاهيمية، يعد هذا مجرد اختلاف بسيط عن الحالة الأبسط التي رأيتها بالفعل. يوضح الشكل 5.6 الفكرة الأساسية: الآن، بدلاً من تحويل أسماء الأعمدة إلى عمود واحد، فإنها تتحول إلى أعمدة متعددة. يمكنك تخيل حدوث ذلك في خطوتين (التحويل أولاً ثم الفصل)، ولكن تحت الغطاء يحدث ذلك في خطوة واحدة لأن ذلك أسرع.
5.3.4 أسماء البيانات والمتغيرات في رؤوس الأعمدة
الخطوة التالية في التعقيد هي عندما تتضمن أسماء الأعمدة مزيجاً من قيم المتغيرات وأسماء المتغيرات. على سبيل المثال، لنأخذ مجموعة بيانات household:
household
#> # A tibble: 5 × 5
#> family dob_child1 dob_child2 name_child1 name_child2
#> <int> <date> <date> <chr> <chr>
#> 1 1 1998-11-26 2000-01-29 Susan Jose
#> 2 2 1996-06-22 NA Mark <NA>
#> 3 3 2002-07-11 2004-04-05 Sam Seth
#> 4 4 2004-10-10 2009-08-27 Craig Khai
#> 5 5 2000-12-05 2005-02-28 Parker Gracieتحتوي مجموعة البيانات هذه على بيانات حول خمس عائلات، مع أسماء وتواريخ ميلاد ما يصل إلى طفلين. التحدي الجديد في مجموعة البيانات هذه هو أن أسماء الأعمدة تحتوي على أسماء متغيرين (dob، name) وقيم متغير آخر (child، بالقيم 1 أو 2). لحل هذه المشكلة، نحتاج مرة أخرى إلى تزويد names_to بمتجه، ولكن هذه المرة نستخدم الحارس الخاص ".value"؛ هذا ليس اسماً لمتغير بل قيمة فريدة تخبر الدالة pivot_longer() بالقيام بشيء مختلف. هذا يتجاوز وسيطة values_to المعتادة لاستخدام المكون الأول من اسم العمود الذي تم تدويره كاسم متغير في المخرجات.
household |>
pivot_longer(
cols = !family,
names_to = c(".value", "child"),
names_sep = "_",
values_drop_na = TRUE
)
#> # A tibble: 9 × 4
#> family child dob name
#> <int> <chr> <date> <chr>
#> 1 1 child1 1998-11-26 Susan
#> 2 1 child2 2000-01-29 Jose
#> 3 2 child1 1996-06-22 Mark
#> 4 3 child1 2002-07-11 Sam
#> 5 3 child2 2004-04-05 Seth
#> 6 4 child1 2004-10-10 Craig
#> # ℹ 3 more rowsنستخدم مرة أخرى values_drop_na = TRUE، نظراً لأن شكل المدخلات يفرض إنشاء متغيرات مفقودة صريحة (على سبيل المثال، للعائلات التي لديها طفل واحد فقط).
يوضح الشكل 5.7 الفكرة الأساسية بمثال أبسط. عند استخدام ".value" في names_to، فإن أسماء الأعمدة في المدخلات تساهم في كل من القيم وأسماء المتغيرات في المخرجات.
names_to = c(".value", "num") إلى تقسيم أسماء الأعمدة إلى مكونين: الجزء الأول يحدد اسم عمود المخرجات (x أو y)، والجزء الثاني يحدد قيمة عمود num.
5.4 تزييد عرض البيانات (Widening data)
حتى الآن، استخدمنا الدالة pivot_longer() لحل الفئة الشائعة من المشاكل حيث انتهت القيم في أسماء الأعمدة. بعد ذلك، سننتقل إلى الدالة pivot_wider()، والتي تجعل مجموعات البيانات أكثر عرضاً (wider) عن طريق زيادة الأعمدة وتقليل الأسطر، وتساعد عندما تكون المشاهدة الواحدة موزعة عبر أسطر متعددة. يبدو أن هذا يظهر بشكل أقل شيوعاً في الواقع، ولكنه يتكرر كثيراً عند التعامل مع البيانات الحكومية.
سنبدأ بالنظر إلى cms_patient_experience، وهي مجموعة بيانات من مراكز خدمات الرعاية الطبية والخدمات الطبية (CMS) التي تجمع بيانات حول تجارب المرضى:
cms_patient_experience
#> # A tibble: 500 × 5
#> org_pac_id org_nm measure_cd measure_title prf_rate
#> <chr> <chr> <chr> <chr> <dbl>
#> 1 0446157747 USC CARE MEDICAL GROUP INC CAHPS_GRP_1 CAHPS for MIPS… 63
#> 2 0446157747 USC CARE MEDICAL GROUP INC CAHPS_GRP_2 CAHPS for MIPS… 87
#> 3 0446157747 USC CARE MEDICAL GROUP INC CAHPS_GRP_3 CAHPS for MIPS… 86
#> 4 0446157747 USC CARE MEDICAL GROUP INC CAHPS_GRP_5 CAHPS for MIPS… 57
#> 5 0446157747 USC CARE MEDICAL GROUP INC CAHPS_GRP_8 CAHPS for MIPS… 85
#> 6 0446157747 USC CARE MEDICAL GROUP INC CAHPS_GRP_12 CAHPS for MIPS… 24
#> # ℹ 494 more rowsالوحدة الأساسية التي يتم دراستها هي المنظمة (organization)، ولكن كل منظمة ممتدة عبر ستة صفوف، مع صف واحد لكل قياس تم إجراؤه في استطلاع المنظمة. يمكننا رؤية المجموعة الكاملة من القيم لـ measure_cd و measure_title باستخدام الدالة distinct():
cms_patient_experience |>
distinct(measure_cd, measure_title)
#> # A tibble: 6 × 2
#> measure_cd measure_title
#> <chr> <chr>
#> 1 CAHPS_GRP_1 CAHPS for MIPS SSM: Getting Timely Care, Appointments, and In…
#> 2 CAHPS_GRP_2 CAHPS for MIPS SSM: How Well Providers Communicate
#> 3 CAHPS_GRP_3 CAHPS for MIPS SSM: Patient's Rating of Provider
#> 4 CAHPS_GRP_5 CAHPS for MIPS SSM: Health Promotion and Education
#> 5 CAHPS_GRP_8 CAHPS for MIPS SSM: Courteous and Helpful Office Staff
#> 6 CAHPS_GRP_12 CAHPS for MIPS SSM: Stewardship of Patient Resourcesلن يشكل أي من هذين العمودين أسماء متغيرات جيدة بشكل خاص: لا يلمح measure_cd إلى معنى المتغير، و measure_title عبارة عن جملة طويلة تحتوي على مسافات. سنستخدم measure_cd كمصدر لأسماء أعمدتنا الجديدة في الوقت الحالي، ولكن في التحليل الحقيقي قد ترغب في إنشاء أسماء المتغيرات الخاصة بك والتي تكون قصيرة وذات مغزى في نفس الوقت.
تمتلك الدالة pivot_wider() واجهة معاكسة للدالة pivot_longer(): بدلاً من اختيار أسماء أعمدة جديدة، نحتاج إلى توفير الأعمدة الحالية التي تحدد القيم (values_from) واسم العمود (names_from):
cms_patient_experience |>
pivot_wider(
names_from = measure_cd,
values_from = prf_rate
)
#> # A tibble: 500 × 9
#> org_pac_id org_nm measure_title CAHPS_GRP_1 CAHPS_GRP_2
#> <chr> <chr> <chr> <dbl> <dbl>
#> 1 0446157747 USC CARE MEDICAL GROUP … CAHPS for MIPS… 63 NA
#> 2 0446157747 USC CARE MEDICAL GROUP … CAHPS for MIPS… NA 87
#> 3 0446157747 USC CARE MEDICAL GROUP … CAHPS for MIPS… NA NA
#> 4 0446157747 USC CARE MEDICAL GROUP … CAHPS for MIPS… NA NA
#> 5 0446157747 USC CARE MEDICAL GROUP … CAHPS for MIPS… NA NA
#> 6 0446157747 USC CARE MEDICAL GROUP … CAHPS for MIPS… NA NA
#> # ℹ 494 more rows
#> # ℹ 4 more variables: CAHPS_GRP_3 <dbl>, CAHPS_GRP_5 <dbl>, …المخرجات لا تبدو صحيحة تماماً؛ يبدو أنه لا يزال لدينا أسطر متعددة لكل منظمة. هذا لأننا بحاجة أيضاً إلى إخبار pivot_wider() بأي عمود أو أعمدة تحتوي على قيم تحدد كل سطر بشكل فريد؛ في هذه الحالة، هي المتغيرات التي تبدأ بـ "org":
cms_patient_experience |>
pivot_wider(
id_cols = starts_with("org"),
names_from = measure_cd,
values_from = prf_rate
)
#> # A tibble: 95 × 8
#> org_pac_id org_nm CAHPS_GRP_1 CAHPS_GRP_2 CAHPS_GRP_3 CAHPS_GRP_5
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 0446157747 USC CARE MEDICA… 63 87 86 57
#> 2 0446162697 ASSOCIATION OF … 59 85 83 63
#> 3 0547164295 BEAVER MEDICAL … 49 NA 75 44
#> 4 0749333730 CAPE PHYSICIANS… 67 84 85 65
#> 5 0840104360 ALLIANCE PHYSIC… 66 87 87 64
#> 6 0840109864 REX HOSPITAL INC 73 87 84 67
#> # ℹ 89 more rows
#> # ℹ 2 more variables: CAHPS_GRP_8 <dbl>, CAHPS_GRP_12 <dbl>هذا يعطينا المخرجات التي نبحث عنها.
5.4.1 كيف تعمل الدالة pivot_wider()؟
لفهم كيفية عمل الدالة pivot_wider()، دعنا نبدأ مرة أخرى بمجموعة بيانات بسيطة للغاية. هذه المرة لدينا مريضان بمعرفات id هما A و B، ولدينا ثلاثة قياسات لضغط الدم للمريض A وقياسان للمريض B:
df <- tribble(
~id, ~measurement, ~value,
"A", "bp1", 100,
"B", "bp1", 140,
"B", "bp2", 115,
"A", "bp2", 120,
"A", "bp3", 105
)سنأخذ القيم من عمود value والأسماء من عمود measurement:
df |>
pivot_wider(
names_from = measurement,
values_from = value
)
#> # A tibble: 2 × 4
#> id bp1 bp2 bp3
#> <chr> <dbl> <dbl> <dbl>
#> 1 A 100 120 105
#> 2 B 140 115 NAلبدء العملية، تحتاج الدالة pivot_wider() أولاً إلى معرفة ما الذي سيذهب في الصفوف والأعمدة. ستكون أسماء الأعمدة الجديدة هي القيم الفريدة لـ measurement.
بشكل افتراضي، يتم تحديد الصفوف في المخرجات بواسطة جميع المتغيرات التي لن تذهب إلى الأسماء أو القيم الجديدة. تسمى هذه بأعمدة الهوية id_cols. هنا يوجد عمود واحد فقط، ولكن بشكل عام يمكن أن يكون هناك أي عدد منها.
تقوم الدالة pivot_wider() بعد ذلك بدمج هذه النتائج لإنشاء إطار بيانات فارغ:
ثم تقوم بملء جميع القيم المفقودة باستخدام البيانات الموجودة في المدخلات. في هذه الحالة، ليست كل خلية في المخرجات لها قيمة مقابلة في المدخلات نظراً لعدم وجود قياس ثالث لضغط الدم للمريض B، وبالتالي تظل تلك الخلايا مفقودة. سنعود إلى هذه الفكرة المتمثلة في أن الدالة pivot_wider() يمكنها “إنشاء” قيم مفقودة في الفصل 18.
قد تتساءل أيضاً عما يحدث إذا كان هناك صفوف متعددة في المدخلات تتوافق مع خلية واحدة في المخرجات. المثال أدناه يحتوي على صفين يتوافقان مع المعرف id “A” والقياس measurement “bp1”:
df <- tribble(
~id, ~measurement, ~value,
"A", "bp1", 100,
"A", "bp1", 102,
"A", "bp2", 120,
"B", "bp1", 140,
"B", "bp2", 115
)إذا حاولنا تحويل محاور هذا الجدول، فسنحصل على مخرجات تحتوي على أعمدة القوائم (list-columns)، والتي ستتعلم المزيد عنها في الفصل 23:
df |>
pivot_wider(
names_from = measurement,
values_from = value
)
#> Warning: Values from `value` are not uniquely identified; output will contain
#> list-cols.
#> • Use `values_fn = list` to suppress this warning.
#> • Use `values_fn = {summary_fun}` to summarise duplicates.
#> • Use the following dplyr code to identify duplicates.
#> {data} |>
#> dplyr::summarise(n = dplyr::n(), .by = c(id, measurement)) |>
#> dplyr::filter(n > 1L)
#> # A tibble: 2 × 3
#> id bp1 bp2
#> <chr> <list> <list>
#> 1 A <dbl [2]> <dbl [1]>
#> 2 B <dbl [1]> <dbl [1]>نظراً لأنك لا تعرف كيفية العمل مع هذا النوع من البيانات بعد، فستحتاج إلى اتباع التلميح الموجود في التحذير لمعرفة مكان المشكلة:
الأمر متروك لك بعد ذلك لمعرفة الخطأ الذي حدث في بياناتك وإما إصلاح الخلل الأساسي أو استخدام مهارات التجميع والتلخيص لضمان أن كل تركيبة من قيم الصفوف والأعمدة تحتوي على صف واحد فقط.
5.5 ملخص
في هذا الفصل، تعلمت عن البيانات المرتبة (tidy data): البيانات التي تحتوي على متغيرات في الأعمدة ومشاهدات في الصفوف. تسهل البيانات المرتبة العمل في منظومة tidyverse، لأنها بنية متسقة تفهمها معظم الدالات، والتحدي الرئيسي يكمن في تحويل البيانات من أي بنية تتلقاها بها إلى تنسيق مرتب. وتحقيقاً لهذه الغاية، تعلمت عن الدالتين pivot_longer() و pivot_wider() اللتين تتيحان لك ترتيب العديد من مجموعات البيانات غير المرتبة. الأمثلة التي قدمناها هنا هي مجموعة مختارة من تلك الموجودة في دليل الحزمة vignette("pivot", package = "tidyr")، لذا إذا واجهت مشكلة لا يساعدك هذا الفصل في حلها، فإن هذا الدليل يعد مكاناً جيداً لتجربته بعد ذلك.
التحدي الآخر هو أنه بالنسبة لمجموعة بيانات معينة، قد يكون من المستحيل تصنيف النسخة الأطول أو الأعرض على أنها النسخة “المرتبة”. هذا يعكس جزئياً تعريفنا للبيانات المرتبة، حيث قلنا إن البيانات المرتبة تحتوي على متغير واحد في كل عمود، لكننا لم نحدد فعلياً ما هو المتغير (ومن الصعب بشكل مدهش القيام بذلك). من المقبول تماماً أن تكون برغماتياً (عملياً) وتقول إن المتغير هو كل ما يجعل تحليلك أسهل. لذا، إذا كنت عالقاً في معرفة كيفية إجراء بعض العمليات الحسابية، ففكر في تغيير تنظيم بياناتك؛ لا تخف من إلغاء الترتيب، والتحويل، وإعادة الترتيب حسب الحاجة!
إذا استمتعت بهذا الفصل وأردت معرفة المزيد عن النظرية الكامنة وراءه، يمكنك معرفة المزيد عن التاريخ والأسس النظرية في ورقة Tidy Data المنشورة في مجلة البرمجيات الإحصائية (Journal of Statistical Software).
الآن بعد أن أصبحت تكتب كمية كبيرة من أكواد R، فقد حان الوقت لمعرفة المزيد حول تنظيم الكود الخاص بك في ملفات ومجلدات. في الفصل القادم، ستتعلم كل شيء عن مزايا السكربتات والمشاريع (scripts and projects)، وبعض الأدوات العديدة التي توفرها لتجعل حياتك أسهل.