רחב מול ארוך: אותם נתונים, שתי צורות
כל שאלה של שינוי צורה מסתכמת בהבחנה אחת, אז בואו נראה אותה. הנה מאגר נתונים קטן אחד, מכירות רבעוניות של שתי ערים, שבנוי בשתי הצורות:
מידע זהה, גאומטריה שונה. ברחב יש שורה אחת לכל עיר ועמודה אחת לכל רבעון: הרבעון חי בשמות העמודות. בארוך יש שורה אחת לכל תצפית (עיר × רבעון), כשהרבעון יורד בדרגה לעמודה רגילה וכל מדידה נמצאת בעמודת sales אחת.
אף צורה אינה "נכונה": הן משרתות אדונים שונים. רחב הוא מה שבני אדם קוראים וגיליונות אלקטרוניים מייצאים: קומפקטי, קל להשוואה במבט. ארוך הוא מה שכלים צורכים, וההמרה ביניהם נקראת pivoting (או reshaping, אותו דבר).
למה פורמט ארוך מנצח בניתוח
הסתכלו מה הפורמט הרחב עשה למושג "רבעון": הוא גרס משתנה אחד לשמות של עמודות. אי אפשר לחשב כלום עם שם של עמודה. בפורמט ארוך, quarter הוא שוב נתונים, וכל ארגז הכלים של ניתוח לפי קבוצות נדלק:
aggregate(sales ~ quarter, ...)אוgroup_by(quarter): בלתי אפשרי בפורמט רחב, שבו כל רבעון הוא עמודה נפרדת שהייתם מטפלים בה ידנית (סיכומים לפי קבוצות כולם מניחים נתונים ארוכים).- ggplot2 ממפה עמודות לאסתטיקות:
aes(x = quarter, y = sales, color = city)צריך ששלוש העמודות האלה יהיו קיימות. ציור גרף של נתונים רחבים הוא מאבק מתמיד; ציור גרף של נתונים ארוכים הוא שורה אחת. - הוספת Q3 היא הוספת שורות, בלי שינוי סכמה, בעוד שבפורמט רחב נוספת עמודה חדשה שכל שלב בהמשך צריך ללמוד עליה.
הכלל המעשי: אחסנו ונתחו בארוך, הציגו ברחב. ולכן שתי ההמרות שלמטה כל כך שימושיות: נתונים מגיעים רחבים מגיליונות אלקטרוניים, מוארכים לניתוח, ומורחבים שוב לטבלה הסופית בדוח.
מרחב לארוך: pivot_longer()
החבילה tidyr (האחות של dplyr ב-tidyverse) אחראית על שינוי הצורה המודרני. הקטעים שלה סטטיים כאן, כי סביבת ההרצה מריצה 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) מטופלות כמזהים וחוזרות לאורך השורות. התוצאה היא בדיוק ה-frame 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, עדיין עובדים, ולא שווה ללמוד אותם מעבר לזיהוי.
ל-R הבסיסי יש reshape(): אזהרה כנה
R הבסיסי יכול לעשות את זה בלי חבילות, דרך reshape(), פונקציה מבלבלת כל כך שהתיעוד שלה עצמו התנצל עליה בעבר. היא תוכננה סביב אוצר המילים של מחקרי אורך (idvar, timevar, direction), שמות הארגומנטים ממופים בצורה מגושמת לנתונים יומיומיים, וכולם שוכחים אותם בין שימוש לשימוש. היא כן עובדת:
שימו לב לשמות בפלט: sales.Q1, sales.Q2, כשהשם של עמודת הערך מודבק לכל אחד. בסדר במצב חירום, בסביבה בלי תלויות, או כשפוגשים את זה בקוד ישן. אבל אם אתם משנים צורת נתונים יותר מפעם בשנה, install.packages("tidyr") היא ההמלצה הכנה: זו משימת העיבוד היחידה שבה הכלי של R הבסיסי באמת עולה יותר מהתלות שהוא חוסך.
מלכודת המפתחות הכפולים בהרחבה
הרחבה מניחה שכל צירוף של מזהה + שם מצביע על ערך אחד. אם ל-Lima יש שתי שורות של Q1, איזה מספר נכנס לתא ה-Q1 היחיד? pivot_wider() מסרב לנחש: הוא מפיק אזהרה (values are not uniquely identified) ושם list-column בתא, data frame עם רשימות מקוננות בתוכו, מה ששובר את הדבר הבא שתעשו איתו.
כשאתם רואים את האזהרה הזו, אל תפנו קודם לפתח המילוט values_fn: שאלו למה יש כפילויות. בדרך כלל הנתונים ברזולוציה עדינה יותר ממה שחשבתם (שורות יומיות, לא רבעוניות, אז סכמו קודם ואז הרחיבו) או ש-join קודם שכפל שורות. values_fn = mean (או sum) הוא התיקון הלגיטימי רק אחרי שאתם יכולים לומר בקול לאיזה סיכום הכפילויות צריכות להתכווץ. reshape() גרוע יותר כאן: הוא שומר בשקט את הכפילות הראשונה ומשליך את השאר, עם אזהרה בלבד שקל לגלול מעבר לה.
מה לקחת מכאן
- רחב: משתנים שמתחבאים בשמות עמודות, נועד לקריאה. ארוך: שורה אחת לכל תצפית, נועד לניתוח ול-ggplot2.
- אחסנו ונתחו בארוך, הציגו ברחב.
pivot_longer(cols, names_to, values_to)עורם עמודות לשורות; עמודות שלא פורטו הופכות למזהים חוזרים.pivot_wider(names_from, values_from)פורש שורות לעמודות; פערים מתמלאים ב-NA.spread()/gather()הם השמות הישנים;reshape()הבסיסי עובד אבל ידוע כמגושם.- זוגות כפולים של מזהה + שם הופכים הרחבה לדו משמעית: סכמו קודם, אל תשתיקו סתם את האזהרה.
בהמשך: קריאת נתונים אמיתיים מקבצים, 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_*, שיש לו ארגומנטים ברורים יותר ויותר יכולות.