Jak radzić sobie z SettingWithCopyWarning w Pandy?
Tło
Właśnie uaktualniłem moje pandy z 0.11 do 0.13. 0rc1. Teraz aplikacja wyskakuje wiele nowych ostrzeżeń. Jeden z nich taki:
E:\FinReporter\FM_EXT.py:449: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE
Chcę wiedzieć, co to dokładnie znaczy? Czy muszę coś zmienić?
Jak mam zawiesić ostrzeżenie, jeśli nalegam na użycie quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE
?
Funkcja, która daje błędy
def _decode_stock_quote(list_of_150_stk_str):
"""decode the webpage and return dataframe"""
from cStringIO import StringIO
str_of_all = "".join(list_of_150_stk_str)
quote_df = pd.read_csv(StringIO(str_of_all), sep=',', names=list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg')) #dtype={'A': object, 'B': object, 'C': np.float64}
quote_df.rename(columns={'A':'STK', 'B':'TOpen', 'C':'TPCLOSE', 'D':'TPrice', 'E':'THigh', 'F':'TLow', 'I':'TVol', 'J':'TAmt', 'e':'TDate', 'f':'TTime'}, inplace=True)
quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]
quote_df['TClose'] = quote_df['TPrice']
quote_df['RT'] = 100 * (quote_df['TPrice']/quote_df['TPCLOSE'] - 1)
quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE
quote_df['TAmt'] = quote_df['TAmt']/TAMT_SCALE
quote_df['STK_ID'] = quote_df['STK'].str.slice(13,19)
quote_df['STK_Name'] = quote_df['STK'].str.slice(21,30)#.decode('gb2312')
quote_df['TDate'] = quote_df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10])
return quote_df
Więcej komunikatów o błędach
E:\FinReporter\FM_EXT.py:449: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE
E:\FinReporter\FM_EXT.py:450: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
quote_df['TAmt'] = quote_df['TAmt']/TAMT_SCALE
E:\FinReporter\FM_EXT.py:453: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
quote_df['TDate'] = quote_df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10])
4 answers
SettingWithCopyWarning
został stworzony w celu oznaczania potencjalnie mylących "przykutych" zadań, takich jak poniższe, które nie zawsze działają zgodnie z oczekiwaniami, szczególnie gdy pierwsza selekcja zwraca kopię. [zobacz GH5390 i GH5597 do dyskusji w tle.]
df[df['A'] > 2]['B'] = new_val # new_val not set in df
Ostrzeżenie proponuje przepisanie w następujący sposób:
df.loc[df['A'] > 2, 'B'] = new_val
Jednak to nie pasuje do twojego użycia, co jest równoważne:
df = df[df['A'] > 2]
df['B'] = new_val
/ Align = "left" / zapisuje wracając do oryginalnej ramki (ponieważ przesadziłeś odniesienie do niej), niestety tego wzorca nie można odróżnić od pierwszego przykładzie przypisania łańcuchowego, stąd Ostrzeżenie (fałszywie dodatnie). Możliwość wystąpienia fałszywych alarmów jest omówiona w docs on indexing , Jeśli chcesz przeczytać dalej. Możesz bezpiecznie wyłączyć to nowe ostrzeżenie za pomocą następującego przypisania.
pd.options.mode.chained_assignment = None # default='warn'
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-15 07:54:48
Ogólnie rzecz biorąc, celem SettingWithCopyWarning
jest pokazanie użytkownikom (i nowym użytkownikom esp), że mogądziałać na kopii, a nie na oryginale, jak myślą. Nie są fałszywych pozytywnych (IOW wiesz, co robisz, więc ok ). Jedną z możliwości jest po prostu wyłączenie (domyślnie warn) ostrzeżenia, jak sugeruje @Garrett.
Oto inna opcja.
In [1]: df = DataFrame(np.random.randn(5,2),columns=list('AB'))
In [2]: dfa = df.ix[:,[1,0]]
In [3]: dfa.is_copy
Out[3]: True
In [4]: dfa['A'] /= 2
/usr/local/bin/ipython:1: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
#!/usr/local/bin/python
Można ustawić flagę is_copy
na False
, co skutecznie wyłączy sprawdź ,* dla tego obiektu "
In [5]: dfa.is_copy = False
In [6]: dfa['A'] /= 2
Jeśli będziesz kopiować to wiesz co robisz , więc żadne dalsze Ostrzeżenie się nie wydarzy.
In [7]: dfa = df.ix[:,[1,0]].copy()
In [8]: dfa['A'] /= 2
Kod pokazany powyżej, chociaż uzasadniony, i prawdopodobnie coś, co ja również robię, jest technicznie argumentem za tym ostrzeżeniem, a nie fałszywym wynikiem. Innym sposobem, aby Nie mieć Ostrzeżenie, byłoby wykonanie operacji wyboru przez reindex
, np.
quote_df = quote_df.reindex(columns=['STK',.......])
Lub
quote_df = quote_df.reindex(['STK',.......], axis=1) # v.0.21
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-27 10:18:45
Pandy dataframe copy warning
Kiedy pójdziesz i zrobisz coś takiego:
quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]
pandas.ix
w tym przypadku zwraca nową, samodzielną ramkę danych.
Jakiekolwiek wartości, które zdecydujesz się zmienić w tej ramce danych, nie zmieni oryginalnej ramki danych.
To właśnie pandy próbują was ostrzec.Dlaczego .ix
to zły pomysł
Obiekt .ix
próbuje zrobić więcej niż jedną rzecz, a dla każdego, kto przeczytał cokolwiek o czystym kodzie, to silny zapach.
Biorąc pod uwagę ten dataframe:
df = pd.DataFrame({"a": [1,2,3,4], "b": [1,1,2,2]})
Dwa zachowania:
dfcopy = df.ix[:,["a"]]
dfcopy.a.ix[0] = 2
Zachowanie pierwsze: dfcopy
jest teraz samodzielną ramką danych. Zmiana nie zmieni df
df.ix[0, "a"] = 3
Zachowanie drugie: zmienia to oryginalną ramkę danych.
Użyj .loc
zamiast
Twórcy pandas uznali, że obiekt .ix
był dość śmierdzący [spekulatywnie] i w ten sposób stworzyli dwa nowe obiekty, które pomagają w przystąpieniu i przypisaniu danych. (The inne byty .iloc
)
.loc
jest szybszy, ponieważ nie próbuje utworzyć kopii danych.
.loc
ma na celu modyfikację istniejącej ramki danych w miejscu, co jest bardziej wydajne pamięci.
.loc
jest przewidywalny, ma jedno zachowanie.
Rozwiązanie
To, co robisz w swoim przykładzie kodu, to Ładowanie dużego pliku z dużą ilością kolumn, a następnie modyfikowanie go, aby był mniejszy.
Funkcja pd.read_csv
może Ci w tym wiele pomóc i spraw, aby ładowanie pliku było znacznie szybsze.
Więc zamiast robić to
quote_df = pd.read_csv(StringIO(str_of_all), sep=',', names=list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg')) #dtype={'A': object, 'B': object, 'C': np.float64}
quote_df.rename(columns={'A':'STK', 'B':'TOpen', 'C':'TPCLOSE', 'D':'TPrice', 'E':'THigh', 'F':'TLow', 'I':'TVol', 'J':'TAmt', 'e':'TDate', 'f':'TTime'}, inplace=True)
quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]
Zrób to
columns = ['STK', 'TPrice', 'TPCLOSE', 'TOpen', 'THigh', 'TLow', 'TVol', 'TAmt', 'TDate', 'TTime']
df = pd.read_csv(StringIO(str_of_all), sep=',', usecols=[0,3,2,1,4,5,8,9,30,31])
df.columns = columns
Spowoduje to odczytanie tylko interesujących Cię kolumn i poprawne ich nazwanie. Nie ma potrzeby używania złego obiektu .ix
do robienia magicznych rzeczy.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-14 08:48:49
Aby usunąć wszelkie wątpliwości, moim rozwiązaniem było zrobienie głębokiej kopii kawałka zamiast zwykłej kopii. Może to nie mieć zastosowania w zależności od kontekstu (ograniczenia pamięci / rozmiar plasterka, potencjalne pogorszenie wydajności - zwłaszcza jeśli kopia występuje w pętli, jak to miało miejsce dla mnie, itp...)
Aby było jasne, oto Ostrzeżenie, które otrzymałem:
/opt/anaconda3/lib/python3.6/site-packages/ipykernel/__main__.py:54:
SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation:
http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
Ilustracja
Miałem wątpliwości, czy ostrzeżenie zostało rzucone z powodu kolumny, którą upuszczałem na kopię plaster. Chociaż technicznie nie próbowano ustawić wartości w kopii plasterka, nadal była to modyfikacja kopii plasterka. Poniżej znajdują się (uproszczone) kroki, które podjąłem, aby potwierdzić podejrzenie, mam nadzieję, że pomoże to tym z nas, którzy próbują zrozumieć Ostrzeżenie.
Przykład 1: upuszczenie kolumny na oryginał wpływa na kopię
Wiedzieliśmy, że już, ale to jest zdrowe przypomnienie. To jest Nie o czym jest Ostrzeżenie.
>> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]}
>> df1 = pd.DataFrame(data1)
>> df1
A B
0 111 121
1 112 122
2 113 123
>> df2 = df1
>> df2
A B
0 111 121
1 112 122
2 113 123
# Dropping a column on df1 affects df2
>> df1.drop('A', axis=1, inplace=True)
>> df2
B
0 121
1 122
2 123
Jest możliwe, aby uniknąć zmian wprowadzonych na df1, aby wpłynąć na df2
>> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]}
>> df1 = pd.DataFrame(data1)
>> df1
A B
0 111 121
1 112 122
2 113 123
>> import copy
>> df2 = copy.deepcopy(df1)
>> df2
A B
0 111 121
1 112 122
2 113 123
# Dropping a column on df1 does not affect df2
>> df1.drop('A', axis=1, inplace=True)
>> df2
A B
0 111 121
1 112 122
2 113 123
Przykład 2: upuszczenie kolumny na kopię może mieć wpływ na oryginał
To faktycznie ilustruje Ostrzeżenie.>> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]}
>> df1 = pd.DataFrame(data1)
>> df1
A B
0 111 121
1 112 122
2 113 123
>> df2 = df1
>> df2
A B
0 111 121
1 112 122
2 113 123
# Dropping a column on df2 can affect df1
# No slice involved here, but I believe the principle remains the same?
# Let me know if not
>> df2.drop('A', axis=1, inplace=True)
>> df1
B
0 121
1 122
2 123
Możliwe jest uniknięcie zmian wprowadzonych na df2, aby wpłynąć na df1
>> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]}
>> df1 = pd.DataFrame(data1)
>> df1
A B
0 111 121
1 112 122
2 113 123
>> import copy
>> df2 = copy.deepcopy(df1)
>> df2
A B
0 111 121
1 112 122
2 113 123
>> df2.drop('A', axis=1, inplace=True)
>> df1
A B
0 111 121
1 112 122
2 113 123
Zdrówko!Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-27 22:25:13