שימו לב: על מנת להריץ את התאים ב-Live Code, יש לייבא תחילה את ספרית pandas ע”י הרצת השורת הראשונה בתא למטה.
בנוסף, נגביל את מספר השורות והעמודות שתופענה בהדפסת הטבלאות ע”י שורות הקוד השניה והשלישית:
import pandas as pd
pd.options.display.max_rows=5
pd.options.display.max_columns=5
חיבור בין טבלאות#
בחלק זה נעמיק ביכולת לשלב ולאחד בין טבלאות שונות.
ב־pandas קיימות שתי פונקציות עיקריות לביצוע פעולות אלה - concat ו־merge.
הפונקציה concat משמשת לחיבור טבלאות זו אל זו, בצורה אנכית (הוספת שורות) או אופקית (הוספת עמודות). למעשה, כבר פגשנו אותה כשעסקנו בהוספת שורות ועמודות לטבלה קיימת.
בהמשך נראה כיצד להשתמש בה באופן רחב יותר, וגם נלמד על merge, המאפשרת איחוד חכם ומבוסס־מפתחות בין טבלאות שונות.
הפעולות להלן יודגמו טבלת הציונים "files/StudentsGrades.csv":
inputFileName = "files/StudentsGrades.csv"
df = pd.read_csv(inputFileName)
display(df)
| Name | Programming | ... | Planet Survival | Art | |
|---|---|---|---|---|---|
| 0 | Yael | 50 | ... | 65 | 91 |
| 1 | Nadav | 61 | ... | 52 | 88 |
| ... | ... | ... | ... | ... | ... |
| 11 | Tom | 98 | ... | 92 | 80 |
| 12 | Adi | 76 | ... | 84 | 70 |
13 rows × 8 columns
חיבור באמצעות concat#
חיבור טבלאות לפי שורות (אנכי)#
ביחידות הקודמות ראינו כיצד ניתן להשתמש בconcat על מנת לחבר שורה או עמודה בודדת.
תזכורת: הפונקציה concat
concat מחברת בין טבלאות לפי:
שמות העמודות - במקרה של הוספת שורות/
axis=0שמות שורות - במקרה של הוספת עמודות/
axis=1.
לדוגמא, מקודם ראינו כי כך מוסיפים שורה חדשה לטבלה:
new_student = [{'Name':"Dana",'Programming':76.0,'Marine Biology':65.0,'Stellar Cartography':98.0,'Math':8.07,
'History':65.0,'Planet Survival':89.0,'Art':100.0}]
pd.DataFrame(new_student)
df_new = pd.concat((df, pd.DataFrame(new_student)))
באופן אינטואיטיבי, כפי שחיברנו לטבלה בעלת שורה בודדת לטבלה אחרת, ניתן לחבר גם טבלה בעלת מספר שורות.
יתרה מכך, ניתן אף לחבר מספר טבלאות. כל מה שצריך זה להעביר בארגומנט הראשון טאפל או רשימה המכילים מספר טבלאות בעלות אותן עמודות (אחרת נקבל תאים ריקים, או NaNים).
שימו לב
גם פה יש להקפיד להעביר לconcat את הפרמטר ignore_index=True, אחרת נקבל שמות שורות כפולים
להלן דוגמא לחיבור אנכי (כלומר, לפי שורות) בין שתי טבלאות:
inputFileName = "files/StudentsGrades.csv"
df = pd.read_csv(inputFileName)
# df=df.head(3) # For simplicity, we take only the first three rows of the table
display(df)
| Name | Programming | ... | Planet Survival | Art | |
|---|---|---|---|---|---|
| 0 | Yael | 50 | ... | 65 | 91 |
| 1 | Nadav | 61 | ... | 52 | 88 |
| ... | ... | ... | ... | ... | ... |
| 11 | Tom | 98 | ... | 92 | 80 |
| 12 | Adi | 76 | ... | 84 | 70 |
13 rows × 8 columns
lst_staff = \
[{'Name':"Shahar",'Programming':45.0,'Marine Biology':15.0,'Stellar Cartography':87.0,'Math':81.0,
'History':100.0,'Planet Survival':82.0,'Art':24.0},
{'Name':"Hagai",'Programming':100.0,'Marine Biology':62.0,'Stellar Cartography':78.0,'Math':10.0,
'History':62.0,'Planet Survival':69.0,'Art':100.0},
{'Name':"Inbal",'Programming':100.0,'Marine Biology':25.0,'Stellar Cartography':84.0,'Math':100.0,
'History':95.0,'Planet Survival':99.0,'Art':30.0}]
df_staff = pd.DataFrame(lst_staff)
display(df_staff)
| Name | Programming | ... | Planet Survival | Art | |
|---|---|---|---|---|---|
| 0 | Shahar | 45.0 | ... | 82.0 | 24.0 |
| 1 | Hagai | 100.0 | ... | 69.0 | 100.0 |
| 2 | Inbal | 100.0 | ... | 99.0 | 30.0 |
3 rows × 8 columns
pd.concat([df, df_staff], ignore_index=True) # ignores the original indices to avoid duplicated indices
| Name | Programming | ... | Planet Survival | Art | |
|---|---|---|---|---|---|
| 0 | Yael | 50.0 | ... | 65.0 | 91.0 |
| 1 | Nadav | 61.0 | ... | 52.0 | 88.0 |
| ... | ... | ... | ... | ... | ... |
| 14 | Hagai | 100.0 | ... | 69.0 | 100.0 |
| 15 | Inbal | 100.0 | ... | 99.0 | 30.0 |
16 rows × 8 columns
עבודה עם ערכים חסרים#
בעת עבודה עם נתונים בטבלה, ניתקל לעיתים בערכים חסרים (NaN – Not a Number), שמציינים מידע שלא קיים או שלא נמדד. ערכים אלו עלולים להשפיע על חישובים, גרפים וניתוחים סטטיסטיים, ולכן חשוב לזהות ולטפל בהם באופן מושכל לפני המשך העיבוד.
בקוד שלהלן ניסו לחבר בין טבלת הציונים שלנו לבין טבלת ציונים נוספת של סטודנטים מהטכניון, והתקבלו תאים ריקים (NaNים).
lst_grades_from_technion = \
[{'Name':"Shahar", 'Machine Learning':96.0,'Spanish':100.0,'Karting':100.0, 'Yoga': 97.0},
{'Name':"Hagai", 'Machine Learning':98.0,'Spanish':65.0,'Karting':87.0, 'Yoga': 100.0},
{'Name':"Inbal", 'Machine Learning':100.0,'Spanish':92.0,'Karting':95.0, 'Yoga': 86.0},
{'Name':"Rotem", 'Machine Learning':80.0,'Spanish':100.0,'Karting':100.0, 'Yoga': 100.0},
]
df_from_technion = pd.DataFrame(lst_grades_from_technion)
display(df)
display(df_from_technion)
df_after_concat = pd.concat([df, df_from_technion], ignore_index=True)
display(df_after_concat)
| Name | Programming | ... | Planet Survival | Art | |
|---|---|---|---|---|---|
| 0 | Yael | 50 | ... | 65 | 91 |
| 1 | Nadav | 61 | ... | 52 | 88 |
| ... | ... | ... | ... | ... | ... |
| 11 | Tom | 98 | ... | 92 | 80 |
| 12 | Adi | 76 | ... | 84 | 70 |
13 rows × 8 columns
| Name | Machine Learning | Spanish | Karting | Yoga | |
|---|---|---|---|---|---|
| 0 | Shahar | 96.0 | 100.0 | 100.0 | 97.0 |
| 1 | Hagai | 98.0 | 65.0 | 87.0 | 100.0 |
| 2 | Inbal | 100.0 | 92.0 | 95.0 | 86.0 |
| 3 | Rotem | 80.0 | 100.0 | 100.0 | 100.0 |
| Name | Programming | ... | Karting | Yoga | |
|---|---|---|---|---|---|
| 0 | Yael | 50.0 | ... | NaN | NaN |
| 1 | Nadav | 61.0 | ... | NaN | NaN |
| ... | ... | ... | ... | ... | ... |
| 15 | Inbal | NaN | ... | 95.0 | 86.0 |
| 16 | Rotem | NaN | ... | 100.0 | 100.0 |
17 rows × 12 columns
עצרו וחישבו: מדעו קיבלנו ערכים ריקים במהלך חיבור הטבלאות?
שימו לב ששמות העמודות בטבלת הסטודנטים מהטכניון שונים משמות העמודות בטבלת תל אביב. בעת ביצוע חיבור אנכי (concat), pandas מבצעת יישור לפי שמות העמודות.
כלומר, עמודות עם אותו שם מתאחדות, אך עמודות ששמן שונה אינן נחשבות תואמות.
לכן, לסטודנטים מתל אביב אין ערכים בעמודת “ספרדית”, ואילו לסטודנטים מהטכניון אין ערכים בעמודת “היסטוריה”.
במקומות שבהם חסר מידע, pandas ממלאת ערך ריק (NaN).
באופן דומה, אם נבצע חיבור אופקי כאשר שמות השורות אינם חופפים, נקבל NaN.
בשלב זה ניתן לשאול באופן טבעי כיצד יש לטפל בנתונים חסרים. בהמשך נראה דרכים לעשות זאת.
חיבור טבלאות לפי עמודות (אופקי)#
באופן אנלוגי, ניתן לחבר גם מספר טבלאות באופן אופקי כלומר, במקום לשרשר שורות, לשרשר עמודות. נעשה זאת ע”י העברת הפרמטר axis=1 בעת הקריאה לconcat.
פה יש להקפיד כי לטבלאות יהיו שמות שורות זהים (אחרת נקבל תאים ריקים, או NaNים).
בקוד שלהלן ניסו לחבר לטבלת הציונים שלנו טבלת ציונים נוספת של סטונדטים שעברו מהטכניון לתל אביב, כלומר, יש להם ציונים בקורסים הנלמדים ב2 המוסדות. הניחו כי השמות הפרטיים יחודיים (כלומר, לא קיימים 2 סטונדטים בעלי אותו שם).
קבעו מדוע מופיעים תאים ריקים (NaNים) בטבלה המאוחדת (האחרונה שמודפסת).
lst_grades_from_technion = \
[{'Name':"Tom", 'Machine Learning':96.0,'Spanish':100.0,'Karting':100.0, 'Yoga': 97.0},
{'Name':"Adi", 'Machine Learning':98.0,'Spanish':65.0,'Karting':87.0, 'Yoga': 100.0}
]
df_from_technion = pd.DataFrame(lst_grades_from_technion)
display(df)
display(df_from_technion)
df_after_concat = pd.concat([df, df_from_technion], axis=1)
display(df_after_concat)
| Name | Programming | ... | Planet Survival | Art | |
|---|---|---|---|---|---|
| 0 | Yael | 50 | ... | 65 | 91 |
| 1 | Nadav | 61 | ... | 52 | 88 |
| ... | ... | ... | ... | ... | ... |
| 11 | Tom | 98 | ... | 92 | 80 |
| 12 | Adi | 76 | ... | 84 | 70 |
13 rows × 8 columns
| Name | Machine Learning | Spanish | Karting | Yoga | |
|---|---|---|---|---|---|
| 0 | Tom | 96.0 | 100.0 | 100.0 | 97.0 |
| 1 | Adi | 98.0 | 65.0 | 87.0 | 100.0 |
| Name | Programming | ... | Karting | Yoga | |
|---|---|---|---|---|---|
| 0 | Yael | 50 | ... | 100.0 | 97.0 |
| 1 | Nadav | 61 | ... | 87.0 | 100.0 |
| ... | ... | ... | ... | ... | ... |
| 11 | Tom | 98 | ... | NaN | NaN |
| 12 | Adi | 76 | ... | NaN | NaN |
13 rows × 13 columns
מה הבעיה בטבלה לעיל?
בטבלה המאוחדת (האחרונה שמודפסת) הסטודנטים שעברו מהטכניון הוצלבו עם השורות הלא נכונות. ההצלבה נעשתה לפי שם השורה (0,1,2) ולא לפי שמות הסטודנטים (Tom, Adi).
ניתן לתקן זאת בשתי דרכים:
נוכל להגדיר את שמות הסטודנטים כשמות השורות, ואז לבצע concat כרגיל.
df.index=df['Name']
df_from_technion.index=df_from_technion['Name']
display(pd.concat([df, df_from_technion], axis=1))
| Name | Programming | ... | Karting | Yoga | |
|---|---|---|---|---|---|
| Name | |||||
| Yael | Yael | 50 | ... | NaN | NaN |
| Nadav | Nadav | 61 | ... | NaN | NaN |
| ... | ... | ... | ... | ... | ... |
| Tom | Tom | 98 | ... | 100.0 | 97.0 |
| Adi | Adi | 76 | ... | 87.0 | 100.0 |
13 rows × 13 columns
פתרון זה לכאורה עובד, אך הוא בעיתי משתי סיבות:
עלינו לשנות את שמות השורות למפתח לפיו נרצה לעשות את האיחוד. לא תמיד יש לנו הבטחה שאין כפילויות בעמודת המפתח!
העמודה
Nameכעת מופיעה גם כעמודה וגם בשמות השורות. נצטרך לבצע פעולה נוספת.
פתרון נוסף הוא שימוש בפונקציה חדשה בשם merge כפי שנראה כעת.
הפונקציה merge#
merge מחברת זוג טבלאות ובחיבור אופקי בלבד (כלומר, חיבור עמודות). הייחודיות של merge היא האפשרות לבצע חיבור טבלאות על בסיס עמודה, או מספר עמודות בעלות ערכים תואמים. כלומר, merge עובדת על-ידי הגדרת מפתח משותף על פיו מחברים את הטבלאות.
ההבדל העיקרי הוא אם החיבור מתבצע בצורה ישירה (כמו חיבור רשימות) באמצעות concat, או בהתאמה חכמה בין שורות באמצעות merge. הבחירה בין השתיים תלויה בשאלה האם מדובר בטבלאות “באותו מבנה” או בטבלאות שונות שצריך לקשר ביניהן דרך ערכים משותפים.
לפונקציה merge קיימים 2 פרמטרים חשובים:
on: מגדיר את העמודה או העמודות שעל פיהן יתבצע החיבור. עמודה או עמודות אלו נקראות גם לעיתים מפתח.
אם מכניסים לonמפתח שהוא מחרוזת, לדוגמה'x', החיבור ייעשה לפי ערכים תואמים בעמודהxבלבד. אם מציינים רשימה כמוon=['x','y'], החיבור ייעשה רק במקומות שבהם יש התאמה גם בעמודהxוגם בעמודהy. כך ניתן לשלוט בדיוק באילו תנאים הטבלאות יתחברו.how: מגדיר את אופן החיבור בין הטבלאות. ערך זה מקבל את אחת מארבע המחרוזות הבאות:'inner', 'outer', 'left', 'right'.inner: התוצאה תכלול רק את השורות שיש להן ערכים תואמים בעמודות שעליהן מתבצע החיבור – כלומר חיתוך (intersection).outer: התוצאה תכלול את כל השורות משתי הטבלאות, גם אם אין להן התאמה, כך שמתקבל איחוד (union) מלא עם ערכיNaNבמקום שאין התאמה.left: התוצאה תשמור את כל השורות מהטבלה השמאלית (הראשונה שהועברה ל־merge) ומוסיפה להן נתונים מהטבלה הימנית רק במקומות שבהם יש התאמה.right: המצב הפוך – כל השורות מהטבלה הימנית נשמרות, ואליהן מצורפים נתונים מהטבלה השמאלית רק כאשר יש התאמה.
נחזור לדוגמא שלנו, תחילה נטען מחדש את הטבלאות (כדי לאפס את כל השינויים שעשינו להן עד עכשיו):
inputFileName = "files/StudentsGrades.csv"
df = pd.read_csv(inputFileName)
lst_grades_from_technion = \
[{'Name':"Tom", 'Machine Learning':96.0,'Spanish':100.0,'Karting':100.0, 'Yoga': 97.0},
{'Name':"Adi", 'Machine Learning':98.0,'Spanish':65.0,'Karting':87.0, 'Yoga': 100.0}
]
df_from_technion = pd.DataFrame(lst_grades_from_technion)
כעת, נבצע merge לפי שמות הסטונדטים, כלומר, על העמודה Name המשותפת בין הטבלאות.
אם נרצה רק את הסטודנטים שהיו בשני המוסדות, נגדיר את how להיות 'inner':
display(pd.merge(df, df_from_technion, on='Name', how='inner'))
| Name | Programming | ... | Karting | Yoga | |
|---|---|---|---|---|---|
| 0 | Tom | 98 | ... | 100.0 | 97.0 |
| 1 | Adi | 76 | ... | 87.0 | 100.0 |
2 rows × 12 columns
display(df_after_merge_outer)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[12], line 1
----> 1 display(df_after_merge_outer)
NameError: name 'df_after_merge_outer' is not defined
לעומת זאת, אם נרצה את הטבלה המאוחדת עם כל הסטונדטים, נגדיר את how להיות 'outer':
df_merged=pd.merge(df, df_from_technion, on='Name', how='outer')
display(df_merged)
| Name | Programming | Marine Biology | Stellar Cartography | Math | History | Planet Survival | Art | Machine Learning | Spanish | Karting | Yoga | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Adi | 76 | 87 | 34 | 90 | 88 | 84 | 70 | 98.0 | 65.0 | 87.0 | 100.0 |
| 1 | Avi | 78 | 64 | 54 | 88 | 97 | 76 | 100 | NaN | NaN | NaN | NaN |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 11 | Yaniv | 100 | 64 | 62 | 95 | 61 | 88 | 90 | NaN | NaN | NaN | NaN |
| 12 | Yarden | 63 | 62 | 70 | 74 | 92 | 98 | 94 | NaN | NaN | NaN | NaN |
13 rows × 12 columns
כפי שאנו רואים, ישנם הרבה תאים ריקים (NaNים). האם ניתן לטפל בהם בצורה כלשהי?
טיפול בערכים חסרים#
ב־pandas יש שתי דרכים עיקריות להתמודד עם ערכים חסרים (NaN):
להחליף את הערכים החסרים בערכים אחרים באמצעות המתודה
()fillna. ניתן, למשל, להציב לציון חסר ערך מיוחד כמו 200, או לבחור ערך מייצג אחר - אפס, ממוצע, חציון, או כל ערך שנראה מתאים לנתונים.להסיר שורות או עמודות המכילות ערכים חסרים, באמצעות
()dropna, או רק לאתר אילו מקומות חסרים בעזרת()isnull.
הבחירה בין שתי הגישות תלויה באופי הנתונים ובמטרת הניתוח: אם יש מעט ערכים חסרים, ייתכן שעדיף להסירם; אך אם הם מופיעים בתדירות גבוהה או במשתנים חשובים, עדיף למלא אותם בערכים מתוקנים כדי לשמור על שלמות הנתונים.
דרך ראשונה: להחליף את כל הערכים החסרים ב-200.
df_merged.fillna(200)
| Name | Programming | Marine Biology | Stellar Cartography | Math | History | Planet Survival | Art | Machine Learning | Spanish | Karting | Yoga | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Adi | 76 | 87 | 34 | 90 | 88 | 84 | 70 | 98.0 | 65.0 | 87.0 | 100.0 |
| 1 | Avi | 78 | 64 | 54 | 88 | 97 | 76 | 100 | 200.0 | 200.0 | 200.0 | 200.0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 11 | Yaniv | 100 | 64 | 62 | 95 | 61 | 88 | 90 | 200.0 | 200.0 | 200.0 | 200.0 |
| 12 | Yarden | 63 | 62 | 70 | 74 | 92 | 98 | 94 | 200.0 | 200.0 | 200.0 | 200.0 |
13 rows × 12 columns
דרך שנייה: נסנן החוצה את כל הסטודנטים שאין להם ציון בKarting, לדוגמא.
לצורך כך, ניתן להשתמש ב-Masking עם המתודה isnull, המחזירה מסכה המסמנת בTrue את התאים הריקים:
df_merged[~df_merged['Karting'].isnull()]
| Name | Programming | Marine Biology | Stellar Cartography | Math | History | Planet Survival | Art | Machine Learning | Spanish | Karting | Yoga | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Adi | 76 | 87 | 34 | 90 | 88 | 84 | 70 | 98.0 | 65.0 | 87.0 | 100.0 |
| 9 | Tom | 98 | 76 | 98 | 100 | 98 | 92 | 80 | 96.0 | 100.0 | 100.0 | 97.0 |
שימו לב
כיוון שisnull מחזיר True עבור ערכים ריקים, הפעלנו אופרטור שלילה ~ על מנת להפוך בין הערכים הבוליאניים.
תרגול#
החליפו את את כל ערכים הריקים ב-Karting ל-0, ואת כל הערכים הריקים בYoga ל-200.
# Write your code here
רמז
השתמשו בשתי פעולות Masking.