-
Notifications
You must be signed in to change notification settings - Fork 0
/
05-pivot-and-join.qmd
157 lines (105 loc) · 8.7 KB
/
05-pivot-and-join.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# מבנה וחיבור נתונים {#sec-pivot-and-join}
```{r load packages}
#| echo: false
#| include: false
```
## מבנה רחב/ארוך
אחת מהפעולות השימושיות היא העברה של דאטה בין פורמט ארוך לפורמט רחב. לדוגמה כשרוצים שמשתנים מסוימים יהפכו לתצפיות נוספות, או להיפך. נדגים זאת עם הנתונים של `penguins`, שראינו בפרקים הקודמים. נוסיף שינוי קטן לקובץ שמזהה כל תצפית באמצעות מזהה `penguin_id` לשם כך אנו משתמשים בפונקציה `seq_along` שפשוט נותנת וקטור של מספר רץ בהתאם לתצפיות.
### רחב לארוך
המבנה הסטנדרטי של הקובץ הוא מבנה רחב - כל פינגויין מופיע בשורה אחת עם כל המשתנים שלו. נניח שאנחנו רוצים להפוך את המבנה לארוך, ושכל פינגויין יופיע מספר פעמים בטבלה, בהתאם לסוג המידע המדווח עליו. לצורך זה נשתמש ב-`pivot_longer`. הנה תזכורת על המבנה הקיים, והקוד שהופך את המבנה למבנה ארוך:
```{r penguins pivot}
#| warning: false
#| message: false
library(tidyverse)
library(palmerpenguins)
penguins_w_id <- penguins %>%
mutate(penguin_id = seq_along(species))
penguins_w_id
longer_penguins <- penguins_w_id %>%
select(penguin_id, species, contains("_mm")) %>%
pivot_longer(cols = -c(species, penguin_id),
names_to = "measurement_type",
values_to = "measurement_value")
longer_penguins
```
הארגומנט הראשון קובע את המשתנים שהולכים לעבור "pivot". במקרה הזה הגדרנו אותו על דרך השלילה (`-species` אומר שכל המשתנים צריכים להשתתף ב-pivot למעט המשתנה species שנותר כעמודה מופרדת).
הארגומנט `names_to` קובע את שם העמודה שתכיל את שמות המשתנים במקור, והארגומנט `values_to` קובע את שם העמודה שתכיל את הערכים שהכילו המשתנים במקור.
### ארוך לרחב
הפעולה ההפוכה (הפיכת טבלה ארוכה לטבלה רחבה) יכולה להיעשות באמצעות הפונקציה `pivot_wider`, באופן הבא:
```{r penguins pivot wider}
longer_penguins %>%
pivot_wider(id_cols = c(species, penguin_id),
names_from = measurement_type,
values_from = measurement_value)
```
התחביר של הפקודה מאוד דומה לתחביר של הפקודה הקודמת. שימו לב שבמקרה זה אפשר להגדיר את שמות המשתנים ללא מרכאות, משום שאלו משתנים קיימים ב-`longer_penguins` והפקודה יודעת לבחור אותם גם ללא ציון שלהם במרכאות.
::: callout-tip
נסו להריץ את אותן הפקודות (`pivot_longer` ואז `pivot_wider`) על `penguins` (במקום על `penguins_id`).
מה שונה בפלט של כל אחת מהפקודות, ומדוע?
:::
## חיבור טבלאות
לעיתים בעבודה עם נתונים נדרש חיבור של נתונים ממקורות שונים (או מאותו מקור המאוחסן בטבלאות שונות). זה מאוד מקובל בעבודה עם דאטהבייס. לדוגמה (בהפשטה) חשבו על חברת אשראי שמצד אחד מאחסנת נתונים על לקוחות (גיל, מגדר, כתובת), ומצד שני מאחסנת נתונים של טראנזקציות (פעולות, קרי, חיובי אשראי. שדות כגון מיקום החיוב וסכום החיוב).
אין סיבה שהטבלה שמאחסנת נתונים של טראנזקציות תכיל גם נתונים של גיל, מגדר, וכתובת, משום שאלו לרוב לא משתנים בין חיוב לחיוב.
אבל יש מקרים שבהם נרצה לחבר בין הנתונים כדי לנתח אותם ביחד. לדוגמה בשביל לחשב מה ממוצע ההוצאה של גברים לעומת נשים. זה דורש חיבור של טבלת הנתונים על לקוחות עם טבלת הטראנזקציות.
נדגים זאת בדוגמה קטנה שבה שתי טבלאות. בטבלה הראשונה נתונים על ארבעה לקוחות פיקטיביים של חברת אשראי. אנחנו נשתמש בפקודה `tribble` כדי להגדיר את הטבלה בקוד.
```{r fictive customers}
customers <- tribble(~customer_id, ~gender, ~home_address,
1, "גבר", "חיפה",
2, "אשה", "תל-אביב",
3, "גבר", "תל-אביב",
4, "אשה", "חיפה")
customers
```
כעת נגדיר טבלה נוספת של הרכישות שביצעו הלקוחות:
```{r fictive transactions}
transactions <- tribble(~customer_id, ~expense, ~dealer,
1, 80, "Motty's Shawarma",
1, 12, "Marina's coffee",
2, 350, "Dekek fuel",
4, 35, "Train",
4, 12, "Rokadin's Crossons")
```
ניתן לראות שיש לקוחות שביצעו שתי עסקאות, יש לקוחות שביצעו עסקה אחת, ויש לקוחות שלא ביצעו עסקאות בכלל. אנחנו רוצים לחשב כמה כסף בסך הכל הוציאו לקוחות שמתגוררים בחיפה לעומת לקוחות שמתגוררים בתל-אביב.
נשתמש בפקודה `left_join` שמחברת בין טבלאות. המשתנה המשותף הוא `customer_id` והוא יאפשר לנו לחבר בין הטבלאות.
```{r joining tables}
customers_transactions <- transactions %>%
left_join(customers)
customers_transactions
```
הטבלה המאוחדת מציגה עבור כל עסקה מה המגדר של הלקוח ואיפה גר (בשתי העמודות האחרונות). כעת נוכל להשתמש בפקודות שלמדנו בפרק זה לפעולות לפי קיבוצים:
```{r expanse by city}
customers_transactions %>%
group_by(home_address) %>%
summarize(total_expense = sum(expense))
```
מעבר לפקודה שבה השתמשנו `left_join` יש עוד פקודות חיבור. הפקודה `right_join` עושה את אותו הדבר רק במקום לחבר את הטבלה השניה לראשונה היא מחברת את הראשונה לשניה:
```{r right join example}
customers %>%
right_join(transactions)
```
הפקודה `full_join` תשמר ערכים שאין להם התאמה באחת הטבלאות. לדוגמה לקוח מספר 3 (והם יופיעו עם ערך חסר):
```{r full join example}
customers %>%
full_join(transactions)
```
שימו לב, הפקודות הללו מזהות לבד מהם המשתנים החופפים ומחברות בהתאם. ניתן גם להגדיר חפיפות אם שמות המשתנים אינם זהים בין שתי הטבלאות, על ידי שימוש בארגומנט `by`.
::: callout-note
**לגבי שיבושי עברית/אנגלית בקוד ובפלט... 😮💨**
בהתייחס לטבלת ה-customers שבה הגדרנו ערכי שדות בעברית: שימו לב שהשדה gender הוגדר לפני השדה home_address אבל מכיוון שאנחנו משתמשים בעברית בהמשך שורות הקוד, זה נראה "כאילו" התוכן של מגדר מופיע אחרי התוכן של עיר (הפוך).
כמו כן גם הטקסט עצמו בעברית בפלט משתבש.
זה בגלל עניין היישור של הקוד משמאל לימין. זו בעיה נפוצה, כשמערבים עברית ואנגלית בקוד, ולוקח זמן להתרגל אליה. תתחילו להתאמן.
עוד על עברית ב-R תוכלו למצוא ב[פרק -@sec-hebrew-tips].
:::
### סינון מטבלה באמצעות join
ניתן להשתמש בפקודות `anti_join` ו-`semi_join` על מנת לסנן שורות. למעשה פקודות אלו אינן מחברות נתונים, אלא רק מסננות שורות.
לדוגמה, הקוד הבא משתמש ב-`anti_join` בשביל לחלץ את הלקוחות שאינם מופיעים בטבלת הפעולות:
```{r use of anti_join}
customers %>%
anti_join(transactions)
```
לא התווספו שדות לטבלת הלקוחות. קיבלנו רק את לקוח 3 שלא הופיעו עבורו פעולות.
::: callout-tip
# תרגיל: semi_join לעומת anti_join
השתמשו בפקודה `semi_join` במקום `anti_join`. מה עושה הפקודה?
:::
::: end-page