العريضة مقابل الطويلة: البيانات نفسها بشكلين
كل سؤال عن إعادة التشكيل يعود إلى تمييز واحد، فلنَرَه بأعيننا. هذه مجموعة بيانات صغيرة واحدة، أي المبيعات الفصلية لمدينتين، مبنية بالشكلين معًا:
المعلومات متطابقة والهندسة مختلفة. فالصيغة العريضة فيها صف واحد لكل مدينة وعمود واحد لكل ربع، ويعيش الربع في أسماء الأعمدة. أما الصيغة الطويلة ففيها صف واحد لكل ملاحظة (مدينة × ربع)، مع تنزيل الربع إلى عمود عادي ووضع كل القياسات في عمود sales واحد.
ولا يوجد شكل "صحيح"، فكل منهما يخدم سيدًا مختلفًا. العريضة هي ما يقرأه البشر وما تصدّره جداول البيانات: مضغوطة وقابلة للمقارنة بنظرة واحدة. والطويلة هي ما تستهلكه الأدوات، ويُسمى التحويل بينهما محورة (أو إعادة تشكيل، وهما شيء واحد).
لماذا تفوز الصيغة الطويلة في التحليل
انظر ماذا فعلت الصيغة العريضة بمفهوم "الربع": لقد مزّقت متغيرًا واحدًا إلى أسماء أعمدة. ولا شيء يستطيع الحساب باستخدام اسم عمود. أما في الصيغة الطويلة فيعود quarter بيانات مرة أخرى، وتُفتح كل صندوق أدوات التحليل المجمّع:
-
aggregate(sales ~ quarter, ...)أوgroup_by(quarter)، وهو مستحيل في الصيغة العريضة حيث يكون كل ربع عمودًا منفصلًا تعالجه يدويًا (الملخصات المجمّعة كلها تفترض بيانات طويلة). - تربط ggplot2 الأعمدة بالسمات الجمالية: فـ
aes(x = quarter, y = sales, color = city)تتطلب وجود تلك الأعمدة الثلاثة. ورسم البيانات العريضة صراع دائم، بينما رسم البيانات الطويلة سطر واحد. - إضافة الربع الثالث تعني إضافة صفوف بلا تغيير في البنية، بينما تكتسب الصيغة العريضة عمودًا جديدًا يجب أن تتعلمه كل خطوة لاحقة.
والقاعدة العملية: خزّن وحلّل بالصيغة الطويلة، واعرض بالصيغة العريضة. ولهذا يكثر استخدام التحويلين أدناه، إذ تصل البيانات عريضة من جداول البيانات، ثم تُطوَّل للتحليل، ثم تُعرَّض مجددًا للجدول النهائي في التقرير.
من العريضة إلى الطويلة: pivot_longer()
تملك حزمة tidyr (شقيقة dplyr في tidyverse) إعادة التشكيل الحديثة. ومقتطفاتها هنا ثابتة، فالبيئة التجريبية تشغّل base R فقط. ويتطلب التطويل ثلاثة قرارات، لكل منها وسيط:
library(tidyr)
long <- pivot_longer(wide,
cols = c(Q1, Q2), # which columns to stack
names_to = "quarter", # new column that receives the old NAMES
values_to = "sales" # new column that receives the cell VALUES
)
لنمرّ عليها: يسمّي cols الأعمدة التي ستُذاب (وتصلح أيضًا صيغة المدى Q1:Q2 والمساعدات مثل starts_with("Q")، وهي مفيدة حين يكون عددها عشرين). وتصبح كل خلية مختارة صفًا، ويحل العمود الذي أتت منه في quarter، ويحل الرقم نفسه في sales. أما الأعمدة غير المذكورة في cols (وهنا city) فتُعامل معرّفات وتتكرر عبر الصفوف. والنتيجة هي بالضبط إطار long المطبوع أعلاه.
من الطويلة إلى العريضة: pivot_wider()
تتطلب الرحلة العكسية قرارين: من أين تأتي أسماء الأعمدة الجديدة، وما الذي يملؤها:
wide <- pivot_wider(long,
names_from = quarter, # distinct values here become new columns
values_from = sales # these values fill the cells
)
تصبح كل قيمة مميزة من quarter (أي Q1 وQ2) عمودًا، وتُملأ كل خلية بقيمة sales من الصف المطابق لتلك المدينة وذلك الربع. وتعمل الأعمدة المتبقية (city) معرّفات للصفوف، فينتج صف واحد لكل تركيبة مميزة. وتحصل المدينة التي ينقصها ربع على NA في تلك الخلية (ويستبدل الوسيط values_fill شيئًا آخر، مثل values_fill = 0 لبيانات العد).
وستصادف الجيل السابق في الدروس الأقدم: فـ spread() هي pivot_wider() وgather() هي pivot_longer()، وقد استُبدلتا منذ tidyr 1.0، ولا تزالان تعملان، ولا تستحقان التعلّم إلا بقدر التعرف عليهما.
يملك base R دالة reshape(): تحذير صادق
يستطيع base R فعل ذلك دون حزم عبر reshape()، وهي دالة اشتُهرت بإرباكها حتى إن توثيقها نفسه اعتذر عنها تاريخيًا. فقد صُممت حول مفردات الدراسات الطولية (idvar وtimevar وdirection)، وتتطابق أسماء وسائطها بصعوبة مع البيانات اليومية، وينساها الجميع بين استخدام وآخر. وهي تعمل فعلًا:
لاحظ أسماء المخرجات: sales.Q1 وsales.Q2، إذ يُدمج اسم عمود القيم في كل منها. لا بأس بذلك عند الضرورة، أو في بيئة بلا اعتماديات، أو حين تصادفها في شيفرة قديمة. لكن إذا كنت تعيد تشكيل البيانات أكثر من مرة في السنة فإن install.packages("tidyr") هي التوصية الصادقة، فهذه هي المهمة الوحيدة في معالجة البيانات التي تكلّف فيها أداة base R أكثر مما توفره من اعتماديات.
مصيدة المفاتيح المكررة عند التعريض
يفترض التعريض أن كل تركيبة من معرّف واسم تحدد قيمة واحدة. فإذا كان لدى Lima صفان لـ Q1، فأي رقم يوضع في خلية Q1 الوحيدة؟ ترفض pivot_wider() التخمين: فتطلق تحذيرًا (values are not uniquely identified) وتضع عمود قائمة في الخلية، أي إطار بيانات يحوي قوائم متداخلة، وهو ما يكسر ما تفعله به بعد ذلك.
وحين ترى هذا التحذير، لا تلجأ أولًا إلى مخرج الطوارئ values_fn، بل اسأل لماذا توجد تكرارات. فالبيانات غالبًا عند حبيبة أدق مما ظننت (صفوف يومية لا فصلية، فلخّص أولًا ثم عرّض)، أو أن وصلًا سابقًا كرّر الصفوف. والحل values_fn = mean (أو sum) مشروع فقط حين تستطيع أن تقول بصوت عالٍ أي تجميع ينبغي أن تنطوي عليه التكرارات. أما reshape() فأسوأ هنا: إذ تُبقي التكرار الأول بصمت وتُسقط البقية، مع تحذير فقط يسهل تجاوزه بالتمرير.
الخلاصة
- العريضة: متغيرات مختبئة في أسماء الأعمدة، مصممة للقراءة. الطويلة: صف واحد لكل ملاحظة، مصممة للتحليل ولـ ggplot2.
- خزّن وحلّل بالصيغة الطويلة، واعرض بالصيغة العريضة.
- تكدّس
pivot_longer(cols, names_to, values_to)الأعمدة في صفوف، وتصبح الأعمدة غير المذكورة معرّفات متكررة. - تنشر
pivot_wider(names_from, values_from)الصفوف في أعمدة، وتُملأ الفجوات بـNA. -
spread()/gather()هما الاسمان القديمان، وتعملreshape()في base لكنها مربكة بشهرة. - تكرار أزواج المعرّف والاسم يجعل التعريض ملتبسًا، فلخّص أولًا ولا تكتفِ بإسكات التحذير.
في المستند التالي: قراءة البيانات الحقيقية من الملفات عبر read.csv() وحوافها الحادة.
الأسئلة الشائعة
ما الفرق بين البيانات العريضة والطويلة في R؟
البيانات نفسها بشكلين مختلفين. تنشر الصيغة العريضة متغيرًا عبر الأعمدة (عمود لكل ربع مثلًا)، بصف واحد لكل مفردة، وهي مصممة للقراءة البشرية. أما الصيغة الطويلة فتكدّسه في صفوف، بصف واحد لكل ملاحظة، مع عمود للأسماء وعمود للقيم، وهي مصممة للتحليل المجمّع ولـ ggplot2.
كيف أحوّل البيانات العريضة إلى طويلة في R؟
تكدّس pivot_longer(df, cols = Q1:Q4, names_to = "quarter", values_to = "sales") من tidyr الأعمدة المختارة في عمودين جديدين: تذهب أسماء الأعمدة القديمة إلى عمود names_to، وتذهب قيم الخلايا إلى عمود values_to.
كيف أحوّل البيانات الطويلة إلى عريضة في R؟
تنشر pivot_wider(df, names_from = quarter, values_from = sales) من tidyr الصفوف في أعمدة: تصبح كل قيمة مميزة في names_from عمودًا، يُملأ بمدخلات values_from المطابقة. وتصبح التركيبات المفقودة NA.
ما الذي حل محل spread وgather في R؟
حلّت pivot_wider() محل spread() وحلّت pivot_longer() محل gather() في tidyr 1.0 (عام 2019). ولا تزال الدالتان القديمتان تعملان وستصادفهما في الدروس الأقدم، لكن كل شيفرة جديدة ينبغي أن تستخدم الزوج pivot_*، فوسائطه أوضح وميزاته أكثر.