אזהרת SettingWithCopy ב־Pandas: כיצד לתקן את האזהרה הזו

SettingWithCopyWarning הוא אזהרה שיכולה להתפרסם על ידי Pandas כאשר אנו מבצעים השמה ל־DataFrame. זה עשוי לקרות כאשר אנו משתמשים בהשמות שנכנסים או כאשר אנו משתמשים ב־DataFrame שנוצר מחתיכה. זהו מקור נפוץ לבאגים בקוד של Pandas שכולנו נתקלים בו. זה עשוי להיות קשה לאיתור בגלל שהאזהרה עשויה להופיע בקוד שנראה כאילו צריך לעבוד כשורה.

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

במאמר זה, נסקור את SettingWithCopyWarning ב־pandas ואיך למנוע אותו. נדון גם בעתיד של Pandas וכיצד האפשרות copy_on_write תשנה את האופן בו אנו עובדים עם DataFrames.

תצוגות והעתקות DataFrame

כאשר אנו בוחרים חתיכה של DataFrame ומבצעים השמה למשתנה, אנו יכולים לקבל אם תצוגה או העתקה חדשה של DataFrame.

עם תצוגה, הזיכרון בין שני DataFrames משותף. זה אומר ששינוי בערך מתא שנמצא בשני DataFrames ישנה את שניהם.

עם העתק, זיכרון חדש מוקצה ונוצר DataFrame עצמאי עם אותם ערכים כמו המקורי. במקרה זה, שני DataFrames הם יישויות שונות, כך ששינוי בערך באחד מהם לא משפיע על השני.

פנדס מנסה למנוע יצירת העתק במידת האפשר כדי לייעל את הביצועים. אף על פי כן, אי אפשר לחזות מראש אם נקבל תצוגה או העתק. SettingWithCopyWarning מתריע כאשר אנו משוים ערך למסגרת נתונים (DataFrame) שברורות לאם היא העתק או תצוגה ממסגרת נתונים אחרת.

הבנת האזהרה SettingWithCopyWarning עם נתונים אמיתיים

נשתמש בקובץ הנתונים של Kaggle Real Estate Data London 2024 כדי ללמוד כיצד האזהרה SettingWithCopyWarning מתרחשת וכיצד לתקן אותה.

קובץ הנתונים מכיל נתוני נדל"ן רציניים מלונדון. הנה סקירה על העמודות הקיימות בקובץ הנתונים:

  • addedOn: התאריך בו נוסף הרישום.
  • title: הכותרת של הרישום.
  • descriptionHtml: תיאור HTML של הרישום.
  • propertyType: סוג הנכס. הערך יהיה "Not Specified" אם לא צוין סוג.
  • sizeSqFeetMax: הגודל המרבי ברגלי רבוע.
  • bedrooms: מספר החדרים.
  • listingUpdatedReason: סיבת עדכון הרישום (לדוגמה, רישום חדש, הורדת מחיר).
  • price: המחיר של הרישום בשקלים.

דוגמה עם משתנה זמני מפורש

נניח שניאמר כי הנכסים עם סוג נכס שלא צוין הם בתים. לכן נרצה לעדכן את כל השורות עם propertyType שווה ל-"Not Specified" להיות שוות ל-"House". אחת מהדרכים לעשות זאת היא לסנן את השורות עם סוג נכס שלא צוין למשתנה DataFrame זמני ולעדכן את ערכי עמודת propertyType כך:

import pandas as pd dataset_name = "realestate_data_london_2024_nov.csv" df = pd.read_csv(dataset_name) # קבל את כל השורות עם סוג נכס שלא צוין no_property_type = df[df["propertyType"] == "Not Specified"] # עדכן את סוג הנכס ל-"בית" בשורות אלו no_property_type["propertyType"] = "House"

בביצוע קוד זה יגרום ל-pandas לייצר את ההודעת SettingWithCopyWarning:

SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy no_property_type["propertyType"] = "House"

הסיבה לכך היא ש-pandas לא יכולה לדעת האם DataFrame no_property_type הוא תצוגה או העתק של df.

זהו בעיה משום שההתנהגות של הקוד הבא עשויה להיות שונה מאוד בהתאם לכך אם זה תצוגה או העתק.

בדוגמה זו, המטרה שלנו היא לשנות את DataFrame המקורי. זה יקרה רק אם no_property_type הוא תצוגה. אם שאר קטעי הקוד שלנו מניחים ש-df שונה, זה עשוי להיות שגוי מכיוון שאין אפשרות לוודא זאת. משום ההתנהגות הבלתי ודאית זו, Pandas מעלה את האזהרה כדי ליידע אותנו על העובדה הזו.

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

דוגמה עם משתנה זמני מוסתר

בדוגמה הקודמת, ברור שמשתמשים במשתנה זמני מכיוון שאנו משייכים באופן מפורש חלק מהDataFrame למשתנה ששמו no_property_type.

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

df[df["propertyType"] == "Not Specified"]["propertyType"] = "House"

ממבט ראשון, זה לא נראה כי מיוצר משתנה זמני. בכל זאת, הרצת הקוד מפיקה את ההתראה SettingWithCopyWarning גם כן.

הדרך בה הקוד מבוצעת היא:

  1. השורה df[df["propertyType"] == "Not Specified"] מהווה ומאוחסנת באופן זמני בזכרון.
  2. האינדקס ["propertyType"] של מיקום הזכרון הזמני נגיש.

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

tmp = df[df["propertyType"] == "Not Specified"] tmp["propertyType"] = "House"

דוגמה זו נקראת לעיתים קרובות כאינדקסים שנרקמים מכיוון שאנו משתמשים בגישה באמצעות אינדקסים רצופים באמצעות []. תחילה, אנו גישים [df["propertyType"] == "Not Specified"] ואז ["propertyType"].

איך לפתור את ההתראה SettingWithCopyWarning

בואו נלמד כיצד לכתוב את הקוד שלנו כך שלא יהיה אי דיוק וה-SettingWithCopyWarning לא יתפעל. למדנו שהאזהרה עולה מאי דיוק בנוגע לכך האם DataFrame היא תצוגה או העתק של DataFrame אחר.

הדרך לתקן את זה היא לוודא שכל DataFrame שאנו יוצרים הוא העתק אם רוצים שיהיה העתק או תצוגה אם רוצים שיהיה תצוגה.

לשנות בצורה בטוחה את DataFrame המקורי באמצעות loc 

בואו נתקן את הקוד מהדוגמה למעלה שבה אנו רוצים לשנות את DataFrame המקורי. כדי למנוע שימוש במשתנה זמני, נשתמש ב־נכס האינדקסר loc.

df.loc[df["propertyType"] == "Not Specified", "propertyType"] = "House"

עם קוד זה, אנו פועלים ישירות על DataFrame המקורי df דרך נכס האינדקסר loc, ולכן אין צורך במשתנים אמצעיים. זה מה שעלינו לעשות כאשר רוצים לשנות את DataFrame המקורי ישירות.

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

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

  1. שימוש ב־loc עם משתנה זמני:
# שימוש ב־loc יחד עם משתנה זמני אינו מפתר את הבעיה no_property_type = df.loc[df["propertyType"] == "Not Specified"] no_property_type["propertyType"] = "House"
  1. שימוש ב־loc ביחד עם אינדקס (זהה לאינדקסציה מקוונת):
# שימוש ב־loc יחד עם אינדקס הוא זהה לאינדקסציה מקוונת df.loc[df["propertyType"] == "Not Specified"]["propertyType"] = "House"

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

עבודה בצורה בטוחה עם העתק של מסגרת הנתונים המקורית באמצעות copy()

כאשר נרצה לוודא שאנו פועלים על העותק של DataFrame, נצטרך להשתמש בשיטת .copy().

נניח שנתבקש לנתח את מחיר הרגל הרבוע של הנכסים. אנו לא רוצים לשנות את המקורי. המטרה היא ליצור DataFrame חדש עם תוצאות הניתוח לשליחה לצוות אחר.

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

  • הסר את השורות בהן sizeSqFeetMax אינו מוגדר.
  • הסר את השורות בהן המחיר הוא "POA" (מחיר לפי בקשה).
  • המר את המחירים לערכים נומריים (בקובץ המקורי, המחירים הם מחרוזות בפורמט הבא: "£25,000,000")

ניתן לבצע את השלבים לעיל באמצעות הקוד הבא:

# 1. סנן את כל הנכסים ללא גודל או מחיר properties_with_size_and_price = df[df["sizeSqFeetMax"].notna() & (df["price"] != "POA")] # 2. הסר את התווים £ ו- , מעמודות המחיר properties_with_size_and_price["price"] = properties_with_size_and_price["price"].str.replace("£", "", regex=False).str.replace(",", "", regex=False) # 3. המר את עמודת המחיר לערכים נומריים properties_with_size_and_price["price"] = pd.to_numeric(properties_with_size_and_price["price"])

כדי לחשב את המחיר לרגל רבוע, אנו יוצרים עמודה חדשה שערכיה הן התוצאה של חלוקת העמודה price על ידי עמודת sizeSqFeetMax:

properties_with_size_and_price["pricePerSqFt"] = properties_with_size_and_price["price"] / properties_with_size_and_price["sizeSqFeetMax"]

אם נבצע את הקוד הזה, נקבל שוב את ההתראה SettingWithCopyWarning. זה אמור לא להיות הפתעה מכיוון שיצרנו ושינינו באופן ברור משתנה זמני של DataFrame בשם properties_with_size_and_price.

מכיוון שאנו רוצים לעבוד על העתק של הנתונים במקום על המסגרת המקורית, נוכל לתקן את הבעיה על ידי וודא ש-properties_with_size_and_price היא העתק של DataFrame ולא תצוגה באמצעות אובייקט .copy() בשורה הראשונה:

properties_with_size_and_price = df[df["sizeSqFeetMax"].notna() & (df["price"] != "POA")].copy()

הוספת עמודות חדשות בצורה בטוחה

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

אם רוצים לעבוד עם עותק של הנתונים, עלינו להעתיק אותם באופן ברור באמצעות השימוש בשיטת .copy(). לאחר מכן, אנו יכולים להקצות עמודה חדשה בכל דרך שנרצה. עשינו זאת כאשר יצרנו את העמודה pricePerSqFt בדוגמה הקודמת.

מצד שני, אם רוצים לשנות את DataFrame המקורי יש שני מקרים לשקול.

  1. אם העמודה החדשה מורכבת מכל השורות, אפשר לשנות באופן ישיר את DataFrame המקורי. זה לא יגרום לאזהרה מאחר שלא נבחר תת-קבוצת שורות. לדוגמה, נוכל להוסיף עמודת note לכל שורה שבה סוג הבית חסר:
df["notes"] = df["propertyType"].apply(lambda house_type: "Missing house type" if house_type == "Not Specified" else "")
  1. אם העמודה החדשה מגדירה ערכים רק עבור תת-קבוצת השורות, אז ניתן להשתמש במאפיין האינדקסר loc. לדוגמה:
df.loc[df["propertyType"] == "Not Specified", "notes"] = "Missing house type"

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

SettingWithCopyWarning שגיאה ב־Pandas 3.0

כרגע, SettingWithCopyWarning היא רק אזהרה ולא שגיאה. הקוד שלנו עדיין מתבצע, ו־Pandas פשוט מעידה עלינו להיות זהירים. 

לפי תיעוד ה-Pandas הרשמי, SettingWithCopyWarning לא תשמש עוד החל מגרסה 3.0 ותוחלף ב-שגיאה ברירת מחדל, שמכבידה על סטנדרטים קוד קשים יותר.

כדי לוודא שהקוד שלנו יישאר תואם לגרסאות העתידיות של pandas, מומלץ לעדכן אותו מראש כך שיעלה שגיאה במקום אזהרה.

זה נעשה על ידי הגדרת האפשרות הבאה לאחר ייבוא pandas:

import pandas as pd pd.options.mode.copy_on_write = True

הוספת זאת לקוד הקיים תבטיח כי נתמודד עם כל השמות הדו חדות בקוד שלנו ותבטיח כי הקוד עדיין יעבוד כאשר נעדכן ל-pandas 3.0.

מסקנה

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

  • אם נרצה לעבוד עם העתק, עלינו להעתיק אותו באופן ברור באמצעות שיטת copy().
  • אם נרצה לשנות את DataFrame המקורי, עלינו להשתמש במאפיין האינדקסר loc ולהקצות את הערך ישירות בעת גישה לנתונים בלי להשתמש במשתנים אמצעיים.

למרות שזה לא שגיאה, אנו לא צריכים להתעלם מהאזהרה הזו מאחר והיא עשויה להביא לתוצאות בלתי צפויות. תוכלות, החל מגרסה 3.0 של Pandas, זה יהפוך לשגיאה כבר בדיפולט, לכן עלינו להבטיח קוד לעתיד על ידי הפעלת Copy-on-Write בקוד הנוכחי שלנו באמצעות pd.options.mode.copy_on_write = True. זה יבטיח שהקוד יישאר פונקציונלי לגרסאות העתידיות של Pandas.

Source:
https://www.datacamp.com/tutorial/settingwithcopywarning-pandas