שימו לב: על מנת להריץ את התאים ב-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

בשלב זה ניתן לשאול באופן טבעי כיצד יש לטפל בנתונים חסרים. בהמשך נראה דרכים לעשות זאת.

חיבור טבלאות לפי עמודות (אופקי)#

באופן אנלוגי, ניתן לחבר גם מספר טבלאות באופן אופקי כלומר, במקום לשרשר שורות, לשרשר עמודות. נעשה זאת ע”י העברת הפרמטר 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):

  1. להחליף את הערכים החסרים בערכים אחרים באמצעות המתודה ()fillna. ניתן, למשל, להציב לציון חסר ערך מיוחד כמו 200, או לבחור ערך מייצג אחר - אפס, ממוצע, חציון, או כל ערך שנראה מתאים לנתונים.

  2. להסיר שורות או עמודות המכילות ערכים חסרים, באמצעות ()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