Por que atribuir com [:] versus iloc [:] produz resultados diferentes em pandas?

13

Estou tão confuso com diferentes métodos de indexação usando ilocem pandas.

Digamos que estou tentando converter um Dataframe 1-d para um Dataframe 2-d. Primeiro, tenho o seguinte Dataframe 1-d

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

E eu vou converter isso em um Dataframe 2D com o tamanho de 2x4 . Começo predefinindo o Dataframe 2-d da seguinte maneira:

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

Então eu uso o loop for para me ajudar a converter a_df(1-d) emb_df (2-d) com o seguinte código

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

Apenas me fornece os seguintes resultados

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

Mas quando mudei b_df.iloc[i,:]parab_df.iloc[i][:] . O resultado está correto como o seguinte, que é o que eu quero

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

Alguém poderia me explicar qual é a diferença entre .iloc[i,:]e .iloc[i][:]é e por que .iloc[i][:]funcionou no meu exemplo acima, mas não.iloc[i,:]

Tommy Yip
fonte
Isso é curioso. b_df.iloc[1] = a_df.iloc[0, 4:8]atribui uma série com índice [4, 5, 6, 7]a uma série com índice [0, 1, 2, 3]. Não há sobreposição, portanto, NaNé atribuído a todos os elementos. Até este ponto, faz sentido para mim. Mas, como você, não sei por que b_df.iloc[1][:] = ...se comporta de maneira diferente - inspecionando os objetos b_df.iloc[1]e b_df.iloc[1][:]não revela diferença entre os índices. Meu melhor palpite seria que atribuir diretamente a uma cópia ( [:]) é tratado como um caso especial pelo Pandas, o que faz com que ignore o índice do responsável e crie essa discrepância.
Seb
Eu acho que é por causa do índice, e o sucesso da primeira linha porque tem o mesmo índice
Phung Duy Phong
11
O ponto principal a lembrar sobre os pandas é que quase todas as operações no pandas usam um conceito chamado 'alinhamento de dados instrinic'. Significando que quase todas as operações que você faz com os pandas alinham os índices de ambos os lados da instrução. Aqui você está tentando definir o índice 1 usando o índice 0, os pandas atribuirão nans porque não há índice 0 no lado direito dessa atribuição. Lembre-se também de que os cabeçalhos das colunas também são um índice. Portanto, os pandas alinham o cabeçalho da coluna ao cabeçalho da coluna.
Scott Boston
3
Em segundo lugar, o uso de .iloc [i] [:] é chamado de encadeamento de índice e geralmente é um grande "não-não" em pandas. Existem alguns isuses com os pandas criando visualizações de um objeto ou criando um novo objeto na memória que pode produzir resultados inesperados.
Scott Boston
Não esqueça de votar em todas as respostas úteis e aceitar a que você mais gosta. Provavelmente você sabe disso, mas isso permite que a comunidade saiba quais respostas foram úteis e também recompensa as pessoas pelo tempo e esforço;) Consulte este meta.stackexchange.com/questions/5234/ e meta.stackexchange.com/ perguntas / 173399 /
alan.elkin

Respostas:

3

Há uma diferença muito, muito grande entre series.iloc[:]e series[:], ao atribuir de volta. (i)locsempre verifica se o que você está atribuindo corresponde ao índice do responsável. Enquanto isso, a [:]sintaxe é atribuída à matriz NumPy subjacente, ignorando o alinhamento do índice.

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.]) 

Agora que você entende a diferença, vejamos o que acontece no seu código. Basta imprimir o RHS dos seus loops para ver o que você está atribuindo:

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   

Ao atribuir a b_df.iloc[i, :]na segunda iteração, os índices são diferentes, portanto nada é atribuído e você vê apenas NaNs. No entanto, alterar b_df.iloc[i, :]para b_df.iloc[i][:]significa que você atribui à matriz NumPy subjacente, portanto, o alinhamento da indexação é ignorado. Esta operação é melhor expressa como

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

Também vale a pena mencionar que esta é uma forma de atribuição encadeada, o que não é uma coisa boa e também torna seu código mais difícil de ler e entender.

cs95
fonte
11
Agora eu entendo, obrigado. Antes de conceder a recompensa, você poderia adicionar uma referência a isso: "a [:]sintaxe atribui à matriz NumPy subjacente"?
Seb
@Seb Você realmente não encontrará referências a isso na documentação porque é um detalhe de implementação. Pode ser mais fácil encontrar o código no GitHub responsável por isso, mas acho que a maneira mais fácil é apenas demonstrar o que acontece. Editei o pequeno exemplo na parte superior da minha resposta para mostrar como a matriz subjacente é manipulada durante os diferentes tipos de reatribuição. Espero que isso torne as coisas mais claras!
cs95 29/02
Muito obrigado! É muito mais claro agora.
Tommy Yip
0

A diferença é que, no primeiro caso, o interpretador Python executou o código como:

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

onde o valor seria o lado direito da equação. Enquanto no segundo caso, o interpretador Python executou o código como:

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

onde novamente o valor seria o lado direito da equação.

Em cada um desses dois casos, um método diferente seria chamado dentro do setitem devido à diferença nas chaves (i, fatia (Nenhuma)) e fatia (Nenhuma). Portanto, temos um comportamento diferente.

MaPy
fonte
b_df.iloc[i]e b_df.iloc[i][:]ter os mesmos índices. Por que você pode atribuir uma série com índice não correspondente a uma, mas não a outra?
Seb
no primeiro caso, o _set_item seria chamado no segundo one_setitem_slice seria chamado. Então, suspeito, devido à diferença desses métodos, temos o comportamento acima
MaPy 21/02
0

Alguém poderia me explicar qual é a diferença entre .iloc[i,:]e .iloc[i][:]é

A diferença entre .iloc[i,:]e.iloc[i][:]

No caso de .iloc[i,:]você estar acessando diretamente uma posição específica do DataFrame, selecionando todas as :colunas da ilinha th. Até onde eu sei, é equivalente deixar a 2ª dimensão não especificada ( .iloc[i]).

No caso de .iloc[i][:]você estar executando duas operações encadeadas. Portanto, o resultado de .iloc[i]será afetado por [:]. Usar isso para definir valores é desencorajado pelo próprio Pandas aqui com um aviso, portanto você não deve usá-lo:

Se uma cópia ou uma referência é retornada para uma operação de configuração, pode depender do contexto. Isso às vezes é chamado de atribuição encadeada e deve ser evitado


... e por que .iloc[i][:]funcionou no meu exemplo acima, mas não.iloc[i,:]

Como o @Scott mencionado nos comentários do OP, o alinhamento de dados é intrínseco , portanto, os índices no lado direito do =não serão incluídos se não estiverem presentes no lado esquerdo. É por isso que existem NaNvalores na 2ª linha.

Portanto, para deixar as coisas claras, você pode fazer o seguinte:

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

Ou você pode converter para em listvez de usar 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
fonte