Dlaczego przypisywanie za pomocą [:] kontra iloc [:] daje różne wyniki w pandach?

13

Jestem tak pomylony z różnymi metodami indeksowania ilocużywanymi w pandach.

Powiedzmy, że próbuję przekonwertować ramkę danych 1-d na ramkę danych 2-d. Najpierw mam następującą 1-d ramkę danych

a_array = [1,2,3,4,5,6,7,8]
a_df = pd.DataFrame(a_array).T

Zamierzam przekonwertować to na 2-d ramkę danych o rozmiarze 2x4. Zaczynam od przygotowania dwuwymiarowej ramki danych w następujący sposób:

b_df = pd.DataFrame(columns=range(4),index=range(2))

Następnie używam pętli for, aby pomóc mi w konwersji a_df(1-d) na b_df(2-d) za pomocą następującego kodu

for i in range(2):
    b_df.iloc[i,:] = a_df.iloc[0,i*4:(i+1)*4]

Daje mi tylko następujące wyniki

     0    1    2    3
0    1    2    3    4
1  NaN  NaN  NaN  NaN

Ale kiedy się zmieniłem b_df.iloc[i,:]na b_df.iloc[i][:]. Wynik jest poprawny, jak poniżej, i tego właśnie chcę

   0  1  2  3
0  1  2  3  4
1  5  6  7  8

Czy ktoś mógłby mi wyjaśnić, jaka jest różnica między .iloc[i,:]i .iloc[i][:], i dlaczego .iloc[i][:]działał w moim przykładzie powyżej, ale nie.iloc[i,:]

Tommy Yip
źródło
To jest ciekawe. b_df.iloc[1] = a_df.iloc[0, 4:8]przypisuje serię z indeksem [4, 5, 6, 7]do serii z indeksem [0, 1, 2, 3]. Nie ma nakładania się, więc NaNs są przypisywane do wszystkich elementów. Do tego momentu ma to dla mnie sens. Ale podobnie jak ty nie jestem pewien, dlaczego b_df.iloc[1][:] = ...zachowuje się inaczej - sprawdzanie obiektów b_df.iloc[1]i b_df.iloc[1][:]nie ujawnia żadnej różnicy między wskaźnikami. Domyślam się, że przypisanie bezpośrednio do copy ( [:]) jest traktowane przez Pandas jako szczególny przypadek, co powoduje, że ignoruje on indeks cesjonariusza i powoduje tę rozbieżność.
Seb
Myślę, że wynika to z indeksu i sukcesu pierwszego rzędu, ponieważ ma ten sam indeks
Phung Duy Phong
1
Najważniejszą rzeczą, o której należy pamiętać w przypadku pand, jest to, że większość operacji w pandach wykorzystuje koncepcję zwaną „instruktażowym dopasowaniem danych”. Oznacza to, że prawie każda operacja wykonywana z pandami wyrówna indeksy obu stron instrukcji. Tutaj próbujesz ustawić indeks 1 za pomocą indeksu 0, pandy przypiszą nans, ponieważ po prawej stronie tego przypisania nie ma indeksu 0. Pamiętaj również, że nagłówki kolumn również są indeksem. Tak więc pandy wyrównają nagłówek kolumny do nagłówka kolumny.
Scott Boston,
3
Po drugie, użycie .iloc [i] [:] nazywa się łańcuchem indeksowym i ogólnie jest to dość duże „nie-nie” w pandach. Istnieją pewne isusy z pandami tworzącymi widoki obiektu lub tworzącymi zupełnie nowy obiekt w pamięci, które mogą dawać nieoczekiwane rezultaty.
Scott Boston,
Nie zapomnij głosować za wszystkimi działającymi odpowiedziami i zaakceptować tę, która najbardziej Ci się podoba. Prawdopodobnie wiesz o tym, ale ma to na celu poinformowanie społeczności, które odpowiedzi były przydatne, a także nagrodzenie ludzi za ich czas i wysiłek;) Zobacz to meta.stackexchange.com/questions/5234/ i meta.stackexchange.com/ pytania / 173399 /
alan.elkin

Odpowiedzi:

3

Istnieje bardzo, bardzo duża różnica pomiędzy, series.iloc[:]a series[:]przy cofaniu. (i)loczawsze sprawdza, aby upewnić się, że cokolwiek przypisujesz z, pasuje do indeksu cesjonariusza. Tymczasem [:]składnia przypisuje do podstawowej tablicy NumPy, pomijając wyrównanie indeksu.

s = pd.Series(index=[0, 1, 2, 3], dtype='float')  
s                                                                          

0   NaN
1   NaN
2   NaN
3   NaN
dtype: float64

# Let's get a reference to the underlying array with `copy=False`
arr = s.to_numpy(copy=False) 
arr 
# array([nan, nan, nan, nan])

# Reassign using slicing syntax
s[:] = pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])                 
s                                                                          

0    1
1    2
2    3
3    4
dtype: int64

arr 
# array([1., 2., 3., 4.]) # underlying array has changed

# Now, reassign again with `iloc`
s.iloc[:] = pd.Series([5, 6, 7, 8], index=[3, 4, 5, 6]) 
s                                                                          

0    NaN
1    NaN
2    NaN
3    5.0
dtype: float64

arr 
# array([1., 2., 3., 4.])  # `iloc` created a new array for the series
                           # during reassignment leaving this unchanged

s.to_numpy(copy=False)     # the new underlying array, for reference                                                   
# array([nan, nan, nan,  5.]) 

Teraz, gdy rozumiesz różnicę, spójrzmy na to, co dzieje się w kodzie. Po prostu wydrukuj RHS swoich pętli, aby zobaczyć, co przypisujesz:

for i in range(2): 
    print(a_df.iloc[0, i*4:(i+1)*4]) 

# output - first row                                                                   
0    1
1    2
2    3
3    4
Name: 0, dtype: int64
# second row. Notice the index is different
4    5
5    6
6    7
7    8
Name: 0, dtype: int64   

Podczas przypisywania do b_df.iloc[i, :]w drugiej iteracji indeksy są różne, więc nic nie jest przypisywane i widoczne są tylko NaN. Jednak zmiana b_df.iloc[i, :]na b_df.iloc[i][:]oznaczać będzie przypisanie do podstawowej tablicy NumPy, więc wyrównanie indeksowania zostanie pominięte. Ta operacja jest lepiej wyrażona jako

for i in range(2):
    b_df.iloc[i, :] = a_df.iloc[0, i*4:(i+1)*4].to_numpy()

b_df                                                                       

   0  1  2  3
0  1  2  3  4
1  5  6  7  8

Warto również wspomnieć, że jest to forma przypisania łańcuchowego, co nie jest dobrą rzeczą , a także sprawia, że ​​kod jest trudniejszy do odczytania i zrozumienia.

cs95
źródło
1
Teraz to rozumiem, dziękuję. Zanim przyznam nagrodę, czy mógłbyś dodać odniesienie do tego: „ [:]składnia przypisuje do podstawowej tablicy NumPy”?
Seb
@Seb Tak naprawdę nie znajdziesz w dokumentacji odniesień do tego, ponieważ jest to w pewnym stopniu szczegół implementacji. Może być łatwiej znaleźć kod na GitHub, który jest za to odpowiedzialny, ale myślę, że najłatwiej jest po prostu zademonstrować, co się dzieje. Zredagowałem mały przykład na górze mojej odpowiedzi, aby pokazać, w jaki sposób manipulowana jest podstawowa tablica podczas różnych rodzajów zmiany przypisania. Mam nadzieję, że wszystko się wyjaśni!
cs95
Dziękuję bardzo! Teraz jest o wiele wyraźniej.
Tommy Yip
0

Różnica polega na tym, że w pierwszym przypadku interpreter języka Python wykonał kod jako:

b_df.iloc[i,:] = a_df.iloc[0,i*4:(i+1)*4]
#as
b_df.iloc.__setitem__((i, slice(None)), value)

gdzie wartość byłaby prawą stroną równania. Podczas gdy w drugim przypadku interpreter języka Python wykonał kod jako:

b_df.iloc[i][:] = a_df.iloc[0,i*4:(i+1)*4]
#as
b_df.iloc.__getitem__(i).__setitem__(slice(None), value)

gdzie znowu wartość byłaby prawą stroną równania.

W każdym z tych dwóch przypadków inna metoda byłaby wywoływana wewnątrz setitem z powodu różnicy w klawiszach (i, plasterek (Brak)) i plasterek (Brak) Dlatego mamy inne zachowanie.

MaPy
źródło
b_df.iloc[i]i b_df.iloc[i][:]mają te same wskaźniki. Dlaczego możesz przypisać serię z niepasującym indeksem do jednego, ale nie do drugiego?
Seb
w pierwszym przypadku _set_item będzie wywołany w drugim one_setitem_slice będzie wywołaniem. Podejrzewam, że z powodu różnicy tych metod mamy powyższe zachowanie
MaPy
0

Czy ktoś może mi wyjaśnić, jaka jest różnica między .iloc[i,:]i .iloc[i][:]jest

Różnica między .iloc[i,:]i.iloc[i][:]

W przypadku .iloc[i,:], gdy uzyskujesz bezpośredni dostęp do konkretnej opcji DataFrame, wybierając wszystkie ( :) kolumny z itego rzędu. O ile wiem, równoważne jest pozostawienie drugiego wymiaru nieokreślonego ( .iloc[i]).

W przypadku .iloc[i][:]wykonujesz 2 łańcuchu operacji. Tak więc na wynik .iloc[i]będzie miał wpływ [:]. Używanie tego do ustawiania wartości jest odradzane przez samą Pandas tutaj z ostrzeżeniem, więc nie powinieneś jej używać:

To, czy kopia lub odwołanie zostanie zwrócone dla operacji ustawiania, może zależeć od kontekstu. Czasami nazywa się to przypisaniem łańcuchowym i należy tego unikać


... i dlaczego .iloc[i][:]działał w moim przykładzie powyżej, ale nie.iloc[i,:]

Jak wspomniał @Scott w komentarzach OP, wyrównanie danych jest nieodłączne , więc indeksy po prawej stronie =nie zostaną uwzględnione, jeśli nie będą obecne po lewej stronie. Dlatego NaNw drugim rzędzie są wartości.

Aby pozostawić sprawę jasną, możesz wykonać następujące czynności:

for i in range(2):
    # Get the slice
    a_slice = a_df.iloc[0, i*4:(i+1)*4]
    # Reset the indices
    a_slice.reset_index(drop=True, inplace=True)
    # Set the slice into b_df
    b_df.iloc[i,:] = a_slice

Lub możesz przekonwertować na listzamiast używać reset_index:

for i in range(2):
    # Get the slice
    a_slice = a_df.iloc[0, i*4:(i+1)*4]
    # Convert the slice into a list and set it into b_df
    b_df.iloc[i,:] = list(a_slice)
alan.elkin
źródło