SettingWithCopyWarning in Pandas: Hoe deze waarschuwing op te lossen

SettingWithCopyWarning is een waarschuwing die Pandas kan genereren wanneer we een toewijzing doen aan een DataFrame. Dit kan gebeuren wanneer we gekoppelde toewijzingen gebruiken of wanneer we een DataFrame gebruiken die is gemaakt van een slicebewerking. Het is een veelvoorkomende bron van bugs in Pandas-code waarmee we allemaal al eens te maken hebben gehad. Het kan moeilijk zijn om te debuggen omdat de waarschuwing kan verschijnen in code die eruitziet alsof deze prima zou moeten werken.

Het begrijpen van de SettingWithCopyWarning is belangrijk omdat het mogelijke problemen signaleert met gegevensmanipulatie. Deze waarschuwing suggereert dat uw code de gegevens mogelijk niet wijzigt zoals bedoeld, wat kan leiden tot onbedoelde gevolgen en obscure bugs die moeilijk te traceren zijn.

In dit artikel zullen we de SettingWithCopyWarning in pandas verkennen en hoe we deze kunnen vermijden. We zullen ook de toekomst van Pandas bespreken en hoe de optie copy_on_write zal veranderen hoe we werken met DataFrames.

DataFrame-weergaven en kopieën

Wanneer we een slicebewerking van een DataFrame selecteren en toewijzen aan een variabele, kunnen we ofwel een weergave of een nieuwe DataFrame-kopie krijgen.

Bij een weergave wordt het geheugen tussen beide DataFrames gedeeld. Dit betekent dat het wijzigen van een waarde van een cel die aanwezig is in beide DataFrames beide zal wijzigen.

Bij een kopie wordt nieuw geheugen toegewezen en wordt een onafhankelijke DataFrame met dezelfde waarden als de oorspronkelijke gemaakt. In dit geval zijn beide DataFrames afzonderlijke entiteiten, dus het wijzigen van een waarde in een van hen heeft geen invloed op de andere.

Pandas probeert het maken van een kopie te vermijden wanneer mogelijk om de prestaties te optimaliseren. Het is echter onmogelijk om van tevoren te voorspellen of we een weergave of een kopie zullen krijgen. De SettingWithCopyWarning wordt gegenereerd telkens wanneer we een waarde toewijzen aan een DataFrame waarbij onduidelijk is of het een kopie is of een weergave van een andere DataFrame.

Het begrijpen van de SettingWithCopyWarning met Echte Data

We zullen de Kaggle-dataset Real Estate Data London 2024 gebruiken om te leren hoe de SettingWithCopyWarning optreedt en hoe dit op te lossen. 

Deze dataset bevat recente vastgoedgegevens uit Londen. Hier is een overzicht van de aanwezige kolommen in de dataset:

  • addedOn: De datum waarop de vermelding is toegevoegd.
  • title: De titel van de vermelding.
  • descriptionHtml: Een HTML-beschrijving van de vermelding.
  • propertyType: Het type van het onroerend goed. De waarde zal "Niet Gespecificeerd" zijn als het type niet is gespecificeerd.
  • sizeSqFeetMax: De maximale grootte in vierkante voet.
  • bedrooms: Het aantal slaapkamers.
  • listingUpdatedReason: Reden voor het bijwerken van de vermelding (bijv. nieuwe vermelding, prijsverlaging).
  • price: De prijs van de vermelding in ponden.

Voorbeeld met een expliciete tijdelijke variabele

Zeg dat we te horen hebben gekregen dat de eigenschappen met een niet-gespecificeerd eigendomstype huizen zijn. We willen dus alle rijen met propertyType gelijk aan "Not Specified" bijwerken naar "House". Een manier om dit te doen is door de rijen met een niet-gespecificeerd eigendomstype te filteren in een tijdelijke DataFrame-variabele en de waarden in de kolom propertyType bij te werken als volgt:

import pandas as pd dataset_name = "realestate_data_london_2024_nov.csv" df = pd.read_csv(dataset_name) # Alle rijen met niet-gespecificeerd eigendomstype verkrijgen no_property_type = df[df["propertyType"] == "Not Specified"] # Het eigendomstype bijwerken naar "Huis" op die rijen no_property_type["propertyType"] = "House"

Het uitvoeren van deze code zal pandas de SettingWithCopyWarning laten genereren:

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"

De reden hiervoor is dat pandas niet kan weten of de DataFrame no_property_type een weergave of een kopie van df is. 

Dit is een probleem omdat het gedrag van de volgende code heel anders kan zijn afhankelijk van of het een weergave of een kopie is. 

In dit voorbeeld is ons doel om de originele DataFrame te wijzigen. Dit gebeurt alleen als no_property_type een weergave is. Als de rest van onze code ervan uitgaat dat df is gewijzigd, kan dit onjuist zijn omdat er geen garantie is dat dit het geval is. Vanwege dit onzekere gedrag geeft Pandas de waarschuwing om ons op de hoogte te stellen van dat feit.

Zelfs als onze code correct wordt uitgevoerd omdat we een weergave hebben gekregen, kunnen we een kopie krijgen in latere runs, en de code werkt dan niet zoals bedoeld. Daarom is het belangrijk om deze waarschuwing niet te negeren en ervoor te zorgen dat onze code altijd doet wat we willen dat het doet.

Voorbeeld met een verborgen tijdelijke variabele

In het vorige voorbeeld is het duidelijk dat er een tijdelijke variabele wordt gebruikt omdat we expliciet een deel van het DataFrame toewijzen aan een variabele genaamd no_property_type.

Echter, in sommige gevallen is dit niet zo expliciet. Het meest voorkomende voorbeeld waar SettingWithCopyWarning optreedt, is bij geketend indexeren. Stel dat we de laatste twee regels vervangen door een enkele regel:

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

Op het eerste gezicht lijkt het niet alsof er een tijdelijke variabele wordt aangemaakt. Echter, bij uitvoering resulteert dit ook in een SettingWithCopyWarning.

De manier waarop deze code wordt uitgevoerd is:

  1. df[df["propertyType"] == "Not Specified"] wordt geëvalueerd en tijdelijk opgeslagen in het geheugen.
  2. De index ["propertyType"] van die tijdelijke geheugenlocatie wordt benaderd.

Indexbenaderingen worden één voor één geëvalueerd, en daarom leidt geketend indexeren tot dezelfde waarschuwing omdat we niet weten of de tussenresultaten weergaven of kopieën zijn. De bovenstaande code is in feite hetzelfde als:

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

Dit voorbeeld wordt vaak aangeduid als geketend indexeren omdat we geïndexeerde toegangen koppelen met behulp van []. Eerst benaderen we [df["propertyType"] == "Not Specified"] en dan ["propertyType"].

Hoe de SettingWithCopyWarning op te lossen

Laten we leren hoe we onze code kunnen schrijven zodat er geen ambiguïteit is en de SettingWithCopyWarning niet wordt geactiveerd. We hebben geleerd dat de waarschuwing ontstaat door onduidelijkheid over of een DataFrame een weergave of een kopie is van een andere DataFrame.

De manier om dit op te lossen is ervoor te zorgen dat elke DataFrame die we maken een kopie is als we willen dat het een kopie is, of een weergave als we willen dat het een weergave is.

Het oorspronkelijke DataFrame veilig wijzigen met loc 

Laten we de code uit het bovenstaande voorbeeld repareren waar we het oorspronkelijke DataFrame willen wijzigen. Om een tijdelijke variabele te vermijden, gebruiken we de eigenschap indexer van loc.

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

Met deze code handelen we rechtstreeks op het oorspronkelijke df DataFrame via de eigenschap indexer van loc, dus er is geen noodzaak voor tussenliggende variabelen. Dit is wat we moeten doen als we het oorspronkelijke DataFrame direct willen wijzigen.

Dit kan op het eerste gezicht op geketende indexering lijken omdat er nog steeds parameters zijn, maar dat is het niet. Wat elke indexering definieert, zijn de vierkante haken [].

Merk op dat het gebruik van loc alleen veilig is als we rechtstreeks een waarde toekennen, zoals we hierboven hebben gedaan. Als we in plaats daarvan een tijdelijke variabele gebruiken, vallen we opnieuw in hetzelfde probleem. Hier zijn twee voorbeelden van code die het probleem niet oplossen:

  1. Het gebruik van loc met een tijdelijke variabele:
# Het gebruiken van loc samen met een tijdelijke variabele lost het probleem niet op no_property_type = df.loc[df["propertyType"] == "Not Specified"] no_property_type["propertyType"] = "House"
  1. Het gebruik van loc samen met een index (hetzelfde als geketend indexeren):
# Het gebruiken van loc samen met indexeren is hetzelfde als geketend indexeren df.loc[df["propertyType"] == "Not Specified"]["propertyType"] = "House"

Beide voorbeelden zorgen vaak voor verwarring omdat er vaak wordt gedacht dat zolang er een loc is, we de originele gegevens aanpassen. Dit is onjuist. De enige manier om ervoor te zorgen dat de waarde wordt toegewezen aan het oorspronkelijke DataFrame is door deze direct toe te wijzen met een enkele loc zonder apart indexeren.

Veilig werken met een kopie van het originele DataFrame met copy()

Wanneer we er zeker van willen zijn dat we werken met een kopie van de DataFrame, moeten we de methode .copy() gebruiken.

Stel dat we gevraagd worden om de prijs per vierkante voet van de woningen te analyseren. We willen de originele gegevens niet wijzigen. Het doel is om een nieuwe DataFrame te maken met de analyseresultaten om naar een ander team te sturen.

De eerste stap is om enkele rijen te filteren en de gegevens op te schonen. Specifiek moeten we:

  • Verwijder de rijen waar sizeSqFeetMax niet gedefinieerd is.
  • Verwijder de rijen waar de price "POA" is (prijs op aanvraag).
  • Converteer de prijzen naar numerieke waarden (in het oorspronkelijke dataset zijn de prijzen strings met het volgende formaat: "£25,000,000")

We kunnen de bovenstaande stappen uitvoeren met behulp van de volgende code:

# 1. Filter alle eigenschappen zonder een grootte of een prijs properties_with_size_and_price = df[df["sizeSqFeetMax"].notna() & (df["price"] != "POA")] # 2. Verwijder de £ en , tekens uit de prijskolommen properties_with_size_and_price["price"] = properties_with_size_and_price["price"].str.replace("£", "", regex=False).str.replace(",", "", regex=False) # 3. Converteer de prijskolom naar numerieke waarden properties_with_size_and_price["price"] = pd.to_numeric(properties_with_size_and_price["price"])

Om de prijs per vierkante voet te berekenen, maken we een nieuwe kolom aan waarvan de waarden het resultaat zijn van het delen van de kolom price door de kolom sizeSqFeetMax:

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

Als we deze code uitvoeren, krijgen we opnieuw de SettingWithCopyWarning. Dit mag geen verrassing zijn, omdat we expliciet een tijdelijke DataFrame-variabele properties_with_size_and_price hebben aangemaakt en gewijzigd.

Aangezien we willen werken met een kopie van de gegevens in plaats van het originele DataFrame, kunnen we het probleem oplossen door ervoor te zorgen dat properties_with_size_and_price een nieuwe DataFrame-kopie is en geen weergave door gebruik te maken van de .copy() methode op de eerste regel:

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

Veilig nieuwe kolommen toevoegen

Het aanmaken van nieuwe kolommen gedraagt zich op dezelfde manier als waarden toekennen. Wanneer het onduidelijk is of we werken met een kopie of een weergave, zal pandas een SettingWithCopyWarning genereren.

Als we met een kopie van de gegevens willen werken, moeten we deze expliciet kopiëren met de methode .copy(). Vervolgens kunnen we op welke manier dan ook een nieuwe kolom toewijzen. Dit hebben we gedaan toen we de kolom pricePerSqFt hebben gemaakt in het vorige voorbeeld.

Aan de andere kant, als we het originele DataFrame willen aanpassen, zijn er twee gevallen om te overwegen.

  1. Als de nieuwe kolom alle rijen omvat, kunnen we het originele DataFrame direct aanpassen. Dit zal geen waarschuwing veroorzaken omdat we geen subset van de rijen selecteren. Bijvoorbeeld, we kunnen een note kolom toevoegen voor elke rij waar het huis type ontbreekt:
df["notes"] = df["propertyType"].apply(lambda house_type: "Missing house type" if house_type == "Not Specified" else "")
  1. Als de nieuwe kolom alleen waarden definieert voor een subset van de rijen, dan kunnen we de loc indexer eigenschap gebruiken. Bijvoorbeeld:
df.loc[df["propertyType"] == "Not Specified", "notes"] = "Missing house type"

Merk op dat in dit geval de waarde in de kolommen die niet zijn geselecteerd ongedefinieerd zal zijn, dus de eerste benadering heeft de voorkeur omdat het ons in staat stelt om een waarde voor elke rij te specificeren.

SettingWithCopyWarning Fout in Pandas 3.0

Op dit moment is SettingWithCopyWarning slechts een waarschuwing, geen fout. Onze code wordt nog steeds uitgevoerd en Pandas informeert ons eenvoudig om voorzichtig te zijn.

Volgens de officiële Pandas-documentatie, SettingWithCopyWarning zal niet langer worden gebruikt vanaf versie 3.0 en zal worden vervangen door een daadwerkelijke fout standaard, waardoor strengere code normen worden gehandhaafd.

Om ervoor te zorgen dat onze code compatibel blijft met toekomstige versies van pandas, wordt het aanbevolen om deze nu al bij te werken om een foutmelding te genereren in plaats van een waarschuwing.

Dit kan worden gedaan door de volgende optie in te stellen na het importeren van pandas:

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

Het toevoegen hiervan aan bestaande code zorgt ervoor dat we elk dubbelzinnige toewijzing in onze code aanpakken en ervoor zorgen dat de code nog steeds werkt wanneer we upgraden naar pandas 3.0.

Conclusie

De SettingWithCopyWarning treedt op wanneer onze code het onduidelijk maakt of een waarde die we modificeren een weergave of een kopie is. We kunnen dit oplossen door altijd expliciet te zijn over wat we willen:

  • Als we willen werken met een kopie, moeten we deze expliciet kopiëren met behulp van de copy() methode.
  • Als we het oorspronkelijke DataFrame willen wijzigen, moeten we de indexeigenschap loc gebruiken en de waarde rechtstreeks toewijzen bij het benaderen van de gegevens zonder tussenliggende variabelen te gebruiken.

Ondanks dat het geen foutmelding is, zouden we deze waarschuwing niet moeten negeren, omdat dit kan leiden tot onverwachte resultaten. Bovendien zal het vanaf Pandas 3.0 standaard een foutmelding worden, dus we moeten ons code toekomstbestendig maken door Copy-on-Write in te schakelen in onze huidige code met behulp van pd.options.mode.copy_on_write = True. Dit zorgt ervoor dat de code functioneel blijft voor toekomstige versies van Pandas.

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