א.22 — PyPlot — גרפים ותרשימים

בשני הפרקים הקודמים למדנו לחשב על מערכים גדולים של מספרים. אבל מערך של אלף תוצאות שמודפס על המסך אינו אומר לנו הרבה: קשה לראות בו מגמה, קפיצה חריגה או קשר בין שני משתנים. בני אדם מבינים תמונה הרבה יותר מהר מטור מספרים, ולכן כמעט כל עבודה עם נתונים מתחילה בציור שלהם.

PyPlot הוא מודול בספריית Matplotlib שמאפשר להציג נתונים כגרפים ותרשימים. במקום לקרוא רשימה ארוכה של מספרים, אפשר לראות על גבי תרשים איך הם משתנים ומה הקשר ביניהם.

למשל, זוגות של ערכי x ו־y הופכים לנקודות על מערכת צירים, ואפשר לחבר אותן בקו. נתחיל בתרשים פשוט, ובהמשך נוסיף צבעים, סימונים, כותרות ומקרא שיעזרו להבין את הנתונים. כל סעיף בפרק מסתיים בתרשים שהקוד שלו מייצר, כי הדרך להבין פקודת ציור היא לראות מה היא מציירת.

למי שלומד למידת מכונה, PyPlot הוא כלי עבודה יומיומי ולא רק קישוט. בחלק ג נצייר את נקודות הנתונים ואת הקו שהמודל למד כדי לראות אם הוא מתאים להן; נצייר את עקומת ההפסד לאורך האימון כדי לבדוק שהרשת אכן משתפרת ולא „מתבדרת”; ונציג תמונות שהרשת מסווגת. לכן כדאי להכיר כאן היטב את שלוש הפעולות הבסיסיות — קו, נקודות והצבת כמה תרשימים זה לצד זה — שבהן נשתמש שוב ושוב.

פתיחת המחברת ב־Colab · השיעור וההרצאה באתר הקורס

מריצים תחילה את תא הייבוא, ואחריו את הדוגמאות לפי הסדר. כל קריאה ל־plt.figure() בדוגמאות פותחת תרשים חדש, כדי שהשרטוטים לא יצטברו בטעות על אותה מסגרת.

מחברות האוניברסיטה הפתוחה — 14 חבילות

ייבוא הספרייה

Matplotlib היא ספרייה גרפית שמאפשרת להציג מידע באמצעות גרפים ותרשימים. בהתקנה מקומית מתקינים אותה פעם אחת בפקודה pip install matplotlib; ב־Colab היא כבר מותקנת ואין צורך להתקין. מרבית הפקודות שנשתמש בהן נמצאות במודול matplotlib.pyplot, ולכן מייבאים אותו בשם הקצר plt. נייבא גם את NumPy, כי הנתונים לציור יגיעו לעיתים קרובות כמערכים:

import matplotlib.pyplot as plt
import numpy as np

מכאן ואילך כל פקודת ציור תתחיל ב־plt.. תא הייבוא אינו מצייר דבר; הוא רק מכין את הכלים, ולכן זה הסעיף היחיד בפרק שאין לו תרשים. את שאר הדוגמאות בפרק מריצים אחרי שתא זה הורץ פעם אחת.

קו בין שתי נקודות

התרשים הפשוט ביותר הוא קו המחבר שתי נקודות. מוסרים ל־plot את הקואורדינטות של הנקודות בשתי רשימות: הראשונה מכילה את ערכי x והשנייה את ערכי y.

x = [1, 3]
y = [2, 9]
plt.figure()
plt.plot(x, y)
plt.show()
קו כחול ישר המחבר את הנקודה (1, 2) לנקודה (3, 9)
קו המחבר את הנקודה (1, 2) לנקודה (3, 9).

כל זוג איברים תואמים ב־x וב־y מייצג נקודה: האיבר הראשון בכל רשימה נותן את (1, 2), והשני את (3, 9). שתי הרשימות צריכות להיות באותו אורך. figure() פותחת מסגרת חדשה; show() מציגה את התרשים. שימו לב לסדר הקבוע שילווה את כל הדוגמאות: פותחים מסגרת, מוסיפים אליה מה שרוצים לצייר, ורק בסוף מציגים. PyPlot בוחרת בעצמה את תחום הצירים כך שכל הנקודות ייראו, ומציירת את הקו בצבע כחול כברירת מחדל.

קו שעובר בכמה נקודות

בדרך כלל יש לנו יותר משתי נקודות, למשל מדידה בכל יום לאורך שבוע. מוסרים ל־plot את כל הנקודות באותו אופן, והיא מחברת אותן בזו אחר זו. אפשר להעביר ל־plot רשימות או מערכי NumPy. בדוגמה נשתמש במערכים, ונוסיף שני פרמטרים: color קובע את צבע הקו, ו־marker מסמן כל נקודה בסימן, כאן כוכב.

x = np.array([1, 3, 2, 5])
y = np.array([2, 4, 7, 3])
plt.figure()
plt.plot(x, y, color="red", marker="*")
plt.show()
קו אדום עם כוכב בכל נקודה, המחבר ארבע נקודות לפי סדר הנתונים
קו אדום המחבר ארבע נקודות לפי סדר הנתונים, עם כוכב בכל נקודה.

הנקודות מתחברות לפי סדר הנתונים, גם אם ערכי x אינם ממוינים. אפשר לראות שהקו „חוזר אחורה”: מהנקודה (3, 4) הוא ממשיך ל־(2, 7), כי זה הסדר שבו הנקודות נמסרו. כשמציירים גרף של פונקציה, נקפיד למסור את ערכי x ממוינים.

צבע הקו

את הצבע אפשר לציין בכמה דרכים. הפשוטה ביותר היא שם צבע באנגלית, כמו "red", "green", "hotpink" או "skyblue". לצבעים הנפוצים יש גם קיצור של אות אחת:

קיצור צבע
"r" אדום
"g" ירוק
"b" כחול
"k" שחור
"y" צהוב
"m" סגול-ורוד (magenta)
"c" טורקיז (cyan)

אפשר גם למסור צבע מדויק בקוד הקסדצימלי כמו בדפי אינטרנט, למשל "#1f77b4", שהוא הכחול של ברירת המחדל.

סימון הנקודות

הפרמטר marker מקבל תו שמתאר את צורת הסימן שיצויר בכל נקודה. הסימנים השימושיים ביותר:

סימן צורה
"o" עיגול
"*" כוכב
"s" ריבוע
"^" משולש
"x" איקס
"+" פלוס
"D" מעוין
"." נקודה קטנה

הרשימה המלאה של הסימנים נמצאת בדף הסימונים של w3schools.

סגנון הקו

הפרמטר linestyle קובע את צורת הקו עצמו: "-" הוא קו רציף (ברירת המחדל), "--" קו מקווקו, ":" קו מנוקד ו־"-." שילוב של קו ונקודה. בדוגמה הבאה נצייר שלושה קווים על אותו תרשים, כל אחד בצבע, בסימון ובסגנון משלו. קריאות חוזרות ל־plot לפני show מוסיפות קווים לאותו תרשים:

x = [1, 2, 3, 4]
plt.figure()
plt.plot(x, [1, 2, 3, 4], color="red",
         marker="*")
plt.plot(x, [2, 4, 6, 8], color="g",
         marker="o", linestyle="--")
plt.plot(x, [3, 6, 9, 12], color="#1f77b4",
         marker="s", linestyle=":")
plt.show()
שלושה קווים: אדום רציף עם כוכבים, ירוק מקווקו עם עיגולים וכחול מנוקד עם ריבועים
שלושה קווים על אותו תרשים, כל אחד בצבע, בסימון ובסגנון קו משלו.

נקודות ללא קו מחבר

לא תמיד יש היגיון בחיבור הנקודות בקו. כשכל נקודה היא מדידה נפרדת, למשל גובה ומשקל של תלמיד, הקו המחבר רק מטעה. במקרים כאלה מציגים את הנקודות בלבד. כדי להציג את מיקומי הנקודות בלי לחבר ביניהן, מוסרים ל־plot ארגומנט שלישי: המחרוזת "o". נשתמש באותן ארבע נקודות מהדוגמה הקודמת:

x = np.array([1, 3, 2, 5])
y = np.array([2, 4, 7, 3])
plt.figure()
plt.plot(x, y, "o", color="red")
plt.show()
ארבעה עיגולים אדומים במיקומי הנקודות, ללא קו מחבר
אותן ארבע נקודות, הפעם כעיגולים בלבד וללא קו.

הארגומנט השלישי הוא מחרוזת עיצוב: כשהיא מכילה סימן נקודה בלבד, plot מציירת את הסימן ומוותרת על הקו. אפשר לשלב במחרוזת גם צבע וסגנון קו, למשל "ro" לעיגולים אדומים, או "o--" לעיגולים המחוברים בקו מקווקו. כאן מסרנו את הצבע בנפרד באמצעות color, וגם זה תקין.

ישר שאינו מוגבל לשתי נקודות

לפעמים אנחנו רוצים לצייר לא קטע אלא ישר שלם, למשל הקו שמודל רגרסיה לינארית למד: הוא מתאר קשר בין x ל־y ותקף גם מחוץ לנקודות שמדדנו. plot מחברת את הנקודות שנמסרו ועוצרת בהן. axline מגדירה ישר העובר בשתי נקודות ונמשך עד גבולות אזור התצוגה. בדוגמה נצייר ישר אדום באמצעות axline, ולצידו קטע כחול רגיל באמצעות plot, כדי לראות את ההבדל:

plt.figure()
plt.axline(xy1=(-2, 2), xy2=(4, 5),
           color="red")
plt.plot([-1, 4], [2, 6])
plt.show()
ישר אדום החוצה את כל התרשים וקטע כחול קצר יותר לצידו
הישר האדום נמשך עד שולי התרשים; הקטע הכחול מתחיל ונגמר בנקודות שנמסרו.

xy1 ו־xy2 הן שתי הנקודות שדרכן עובר הישר. שימו לב שהישר האדום ממשיך מעבר לשתי הנקודות האלה עד קצות התרשים, ואילו הקטע הכחול מתחיל ב־(-1, 2) ונגמר ב־(4, 6). ב־Colab, ציור של פקודה בודדת מוצג גם בלי show(), ולצידו מודפס תיאור טכני של אובייקט הקו; show() מציגה את התרשים בלי ההדפסה הזאת ופועלת גם מחוץ ל־Colab.

נקודות בלבד באמצעות scatter

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

x = np.array([5, 7.1, 8, 7, 2, 17, 2,
              9, 4, 11, 12, 9, 6])
y = np.array([99, 86, 87, 88, 111, 86, 103,
              87, 94, 78, 77, 85, 86])
plt.figure()
plt.scatter(x, y, color="hotpink", marker="*")

x = np.array([2, 2, 8, 1, 15, 8, 12, 9,
              7, 3, 11, 4, 7, 14, 12])
y = np.array([100, 105, 84, 105, 90, 99, 90, 95,
              94, 100, 79, 112, 91, 80, 85])
plt.scatter(x, y, color="lightgreen")
plt.show()
פיזור של כוכבים ורודים ועיגולים ירוקים בהירים על אותו תרשים
שתי קבוצות נקודות על אותו תרשים: כוכבים ורודים ועיגולים ירוקים.

כל קריאה ל־scatter מוסיפה קבוצת נקודות לאותו תרשים, ולכן שתי הקבוצות מופיעות יחד. ההבדל בין scatter לבין plot עם "o" הוא ש־scatter יכולה לתת לכל נקודה גודל וצבע משלה, למשל באמצעות הפרמטר s לגודל; ב־plot כל הנקודות של אותה קריאה נראות זהות. כדי שהצופה יידע איזו קבוצה היא איזו, נוסיף בסעיף הבא מקרא.

הגדרות התרשים: כותרת, צירים, גבולות ומקרא

תרשים ללא כותרת ושמות לצירים הוא כמו טבלה ללא כותרות עמודות: המספרים נכונים, אבל אי אפשר לדעת מה הם מייצגים. לתרשים אפשר להוסיף ארבע הגדרות: כותרת, שמות לצירים, גבולות לצירים ומקרא. title מוסיפה כותרת, xlabel ו־ylabel שמות לצירים, xlim ו־ylim קובעות את תחום התצוגה של כל ציר, ו־legend מציגה מקרא. בדוגמה נצייר שני קווים על אותו תרשים:

xPoints = np.array([1, 3])
yPoints = np.array([2, 4])
plt.figure()
plt.plot(xPoints, yPoints, color="r",
         label="label1")
plt.plot(yPoints, xPoints, color="b",
         label="label2")
plt.title("Title of plot")
plt.xlabel("x-points")
plt.ylabel("y-points")
plt.xlim(-1, 6)
plt.ylim(0, 5)
plt.legend()
plt.show()
שני קווים, אדום וכחול, עם כותרת, שמות לצירים, גבולות צירים ומקרא בפינה
שני קווים עם כותרת, שמות לצירים, גבולות ומקרא.

label מגדירה שם לכל קו, ו־legend() מציגה את השמות במקרא לצד דוגמת הצבע של כל קו; בלי הקריאה ל־legend() השמות לא יופיעו. xlim(-1, 6) מציגה את ציר x מ־-1 עד 6 במקום התחום שהיה נבחר אוטומטית, ולכן נשאר מרווח מסביב לקווים. הקו הכחול משתמש באותם מערכים בסדר הפוך: ערכי yPoints משמשים בו כ־x וערכי xPoints כ־y.

תרשימים מרובים — subplot

לעיתים קרובות רוצים לראות כמה תרשימים יחד: למשל, באימון רשת נוירונים מציגים בתרשים אחד את ההפסד על נתוני האימון ובתרשים סמוך את הדיוק, או מציגים כמה תמונות זו לצד זו. subplot מאפשרת לצייר כמה תרשימים בעת ובעונה אחת באותה מסגרת. subplot(rows, columns, index) מקבלת שלושה מספרים: מספר השורות, מספר העמודות והמספר הסידורי של התרשים שמציירים כעת. המספור מתחיל ב־1 ורץ משמאל לימין ומלמעלה למטה. בדוגמה נחלק את המסגרת לשתי שורות ושתי עמודות:

x1 = np.array([0, 1, 2, 3])
y1 = np.array([3, 8, 1, 10])
x2 = np.array([0, 1, 2, 3])
y2 = np.array([10, 20, 30, 40])

plt.figure()
plt.subplot(2, 2, 1)
plt.plot(x1, y1, color="red")
plt.plot(y1, x1, color="b")

plt.subplot(2, 2, 2)
plt.plot(x2, y2, color="b")

plt.subplot(2, 2, 3)
plt.plot(x1, y1)

plt.subplot(2, 2, 4)
plt.plot(x2, y2)

plt.tight_layout()
plt.show()
ארבעה תרשימים ברשת של שתי שורות ושתי עמודות
ארבעה תרשימים באותה מסגרת: התרשים הראשון מכיל שני קווים, והשאר קו אחד כל אחד.

הקריאה subplot(2, 2, 1) בוחרת את התרשים השמאלי העליון, ומרגע זה כל פקודת ציור מתייחסת אליו; לכן שני הקווים, האדום והכחול, נכנסו לאותו תרשים. subplot(2, 2, 2) עוברת לתרשים הימני העליון, וכך הלאה. tight_layout() מתאימה את הריווח בין התרשימים כדי שהמספרים על הצירים לא יתנגשו זה בזה.

אובייקטים של התרשים — Figure ו־Axes

בדרך שראינו עד כה, כל פקודת plt פועלת על „התרשים הפעיל”, ואנחנו צריכים לזכור איזה תרשים פעיל ברגע נתון. כשיש כמה תרשימים זה נעשה מבלבל. PyPlot מציעה דרך מפורשת יותר: לקבל אובייקט לכל תרשים ולפנות אליו בשמו. שתי הדרכים ישמשו אותנו בהמשך הספר, ולכן כדאי להכיר את שתיהן.

אובייקט Figure הוא המסגרת שכוללת את התרשימים; אובייקט Axes הוא התרשים עצמו. בסעיף הקודם יצרנו מסגרת אחת עם ארבעה תרשימי משנה. אפשר ליצור גם כמה מסגרות, שכל אחת מהן כוללת תרשים אחד או כמה תרשימים. הפעולה subplots יוצרת מסגרת ומחזירה אותה יחד עם התרשימים שבה. בדוגמה ניצור שתי מסגרות: אחת עם ארבעה תרשימים ואחת עם תרשים יחיד, ושתיהן יוצגו יחד:

x1 = np.array([0, 1, 2, 3])
y1 = np.array([3, 8, 1, 10])
x2 = np.array([0, 1, 2, 3])
y2 = np.array([10, 20, 30, 40])

fig, ax_list = plt.subplots(2, 2, figsize=(7, 7))
ax_list[0, 0].plot((1, 2), (1, 3))
ax_list[0, 1].plot(x1, y1)
ax_list[1, 0].plot(x2, y2)
ax_list[1, 1].plot(x1, y1, "o", color="r")

fig1, ax1 = plt.subplots(1, 1)
ax1.plot(x1, y1, marker="*")
ax1.set_title("Title of plot")
ax1.set_xlabel("x-points")
ax1.set_ylabel("y-points")

plt.show()
מסגרת ריבועית עם ארבעה תרשימים: קו קצר, קו שבור, קו עולה ונקודות אדומות
המסגרת הראשונה: ארבעה תרשימים ברשת של שתיים על שתיים, ברוחב ובגובה של שבעה אינצ׳ים.
מסגרת עם תרשים יחיד: קו עם כוכבים, כותרת ושמות לצירים
המסגרת השנייה: תרשים יחיד עם כותרת ושמות לצירים.

subplots(2, 2) מחזירה שני ערכים: את המסגרת fig ואת ax_list, מערך דו־ממדי של תרשימים. פונים לתרשים לפי שורה ועמודה, והאינדקסים מתחילים באפס: ax_list[0, 1] הוא הימני בשורה העליונה, ו־ax_list[1, 0] הוא השמאלי בשורה התחתונה. הפרמטר figsize קובע את רוחב המסגרת ואת גובהה באינצ׳ים. ב־subplots(1, 1) מתקבל אובייקט Axes יחיד ולא מערך, ולכן פונים אליו ישירות בשם ax1.

בגישה הזאת כותבים ax1.set_title(...) במקום plt.title(...), ו־ax1.set_xlabel(...) במקום plt.xlabel(...); כך ברור לאיזה תרשים מוסיפים את הכותרת, גם כשיש כמה מסגרות פתוחות. קריאה אחת ל־show() בסוף מציגה את כל המסגרות שנוצרו.

גרף של פונקציה

כעת נחבר את מה שלמדנו בפרק הקודם לציור. למחשב אין דרך לצייר עקומה „חלקה” ישירות; במקום זאת מחשבים את הפונקציה בהרבה נקודות צפופות ומחברים אותן בקווים קצרים, והעין רואה עקומה. כדי לשרטט פונקציה, נחשב את ערכה בנקודות רבות ונציג את זוגות הקלט והפלט. NumPy מבצעת את החישוב באמצעות linspace והפעולות איבר מול איבר, ו־PyPlot מציירת; בדוגמה נציג את x² cos(x) ב־400 נקודות בין -20 ל־20:

x = np.linspace(-20, 20, 400)
y = x ** 2 * np.cos(x)
plt.figure()
plt.plot(x, y, color="green")
plt.grid()
plt.show()
עקומה ירוקה של איקס בריבוע כפול קוסינוס איקס, עם תנודות הולכות וגדלות משני צידי האפס, על רקע רשת
הגרף של x² cos(x) בתחום שבין ‎-20 ל־20, עם רשת עזר.

ככל שמתרחקים מאפס, גורם מגדיל את גובה התנודות; grid() מוסיפה רשת לקריאת המיקומים. ארבע מאות הנקודות צפופות מספיק כדי שהקווים הקצרים ביניהן ייראו כעקומה אחת.

תרשים עמודות

תרשים עמודות משווה ערכים בין קטגוריות; למשל מספר פריטים מכל סוג. הגובה מייצג את הערך, ולא מיקום של נקודה ברצף זמן. bar מקבלת את שמות הקטגוריות ואת הערכים:

categories = ["A", "B", "C", "D"]
values = [4, 7, 1, 8]
plt.figure()
plt.bar(categories, values, color="skyblue")
plt.title("Bar Chart")
plt.xlabel("Categories")
plt.ylabel("Values")
plt.show()
ארבע עמודות תכולות בגבהים 4, 7, 1 ו-8
ארבע עמודות, אחת לכל קטגוריה.

barh מציגה את אותה השוואה בעמודות אופקיות. שימו לב שהתפקידים של הצירים מתחלפים, ולכן גם שמות הצירים מתחלפים:

plt.figure()
plt.barh(categories, values, color="lightgreen")
plt.title("Horizontal Bar Chart")
plt.xlabel("Values")
plt.ylabel("Categories")
plt.show()
ארבע עמודות אופקיות ירוקות בהירות באורכים 4, 7, 1 ו-8
אותם נתונים בעמודות אופקיות.

היסטוגרמה

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

values = [10, 12, 15, 21, 23, 25, 29, 35]
plt.figure()
plt.hist(values, bins=[0, 10, 20, 30, 40])
plt.xlabel("Value")
plt.ylabel("Count")
plt.show()
היסטוגרמה עם ארבעה טווחים: הטווח הראשון ריק, ואחריו עמודות בגבהים 3, 4 ו-1
היסטוגרמה של שמונה ערכים בארבעה טווחים ברוחב עשר.

הרשימה bins מגדירה כאן ארבעה טווחים: 0–10, 10–20, 20–30 ו־30–40. בטווח הראשון אין ערכים, ובשלושת הטווחים האחרונים יש בהתאמה 3, 4 ו־1 ערכים, וזה בדיוק גובה העמודות בתרשים. במחברת מודגמת גם היסטוגרמה של נתונים אקראיים; התרשים המדויק שלה עשוי להשתנות בין הרצות. בפרק ניתוח הנתונים נראה היסטוגרמת גילים אמיתית מתוך Titanic.