¿Por qué la asignación con [:] versus iloc [:] produce resultados diferentes en pandas?

13

Estoy muy confundido con los diferentes métodos de indexación que se usan ilocen los pandas.

Digamos que estoy tratando de convertir un Dataframe 1-d en un Dataframe 2-d. Primero tengo el siguiente marco de datos 1-d

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

Y voy a convertir eso en un Dataframe 2-d con el tamaño de 2x4. Comienzo preseleccionando el Dataframe 2-d de la siguiente manera:

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

Luego uso for-loop para ayudarme a convertir a_df(1-d) a b_df(2-d) con el siguiente código

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

Solo me da los siguientes resultados

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

Pero cuando me cambié b_df.iloc[i,:]a b_df.iloc[i][:]. El resultado es correcto como el siguiente, que es lo que quiero

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

¿Alguien podría explicarme cuál es la diferencia entre .iloc[i,:]y .iloc[i][:], y por qué .iloc[i][:]funcionó en mi ejemplo anterior pero no.iloc[i,:]

Tommy Yip
fuente
Esto es curioso b_df.iloc[1] = a_df.iloc[0, 4:8]asigna una serie con índice [4, 5, 6, 7]a una serie con índice [0, 1, 2, 3]. No hay superposición, por lo que NaNse asignan a todos los elementos. Hasta este punto tiene sentido para mí. Pero, como usted, no tengo claro por qué se b_df.iloc[1][:] = ...comporta de manera diferente: inspeccionar los objetos b_df.iloc[1]y b_df.iloc[1][:]no revela diferencias entre los índices. Mi mejor conjetura sería que la asignación directa a una copia ( [:]) es tratada como un caso especial por Pandas, lo que hace que ignore el índice del cesionario y cree esta discrepancia.
Seb
Creo que se debe al índice y al éxito de la primera fila porque tiene el mismo índice
Phung Duy Phong,
1
Lo que debo recordar sobre los pandas es que la mayoría de las operaciones en pandas utilizan un concepto llamado 'alineación de datos instrínicos'. Lo que significa que casi cualquier operación que realice con pandas alineará los índices de ambos lados de la declaración. Aquí está tratando de establecer el índice 1 usando el índice 0, los pandas asignarán nans porque no hay índice 0 en el lado derecho de esa asignación. Recuerde también que los encabezados de columna también son un índice. Entonces, los pandas alinearán el encabezado de la columna con el encabezado de la columna.
Scott Boston
3
En segundo lugar, el uso de .iloc [i] [:] se denomina encadenamiento de índice y generalmente es un gran "no-no" en pandas. Hay algunos usos con pandas que crean vistas de un objeto o crean un objeto nuevo en la memoria que puede producir algunos resultados inesperados.
Scott Boston
Por favor, no olvide votar todas las respuestas de trabajo y acepte la que más le guste. Probablemente lo sepas, pero esto es para que la comunidad sepa qué respuestas fueron útiles y también para recompensar a las personas por su tiempo y esfuerzo;) Vea esto meta.stackexchange.com/questions/5234/ y meta.stackexchange.com/ preguntas / 173399 /
alan.elkin

Respuestas:

3

Hay una muy, muy grande diferencia entre series.iloc[:]y series[:], al asignar de nuevo. (i)locsiempre verifica que lo que sea que esté asignando coincida con el índice del cesionario. Mientras tanto, la [:]sintaxis se asigna a la matriz subyacente NumPy, sin pasar por la alineación del í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.]) 

Ahora que comprende la diferencia, veamos qué sucede en su código. Simplemente imprima el RHS de sus bucles para ver lo que está asignando:

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   

Al asignar a b_df.iloc[i, :]en la segunda iteración, los índices son diferentes, por lo que no se asigna nada y solo se ven los NaN. Sin embargo, cambiar b_df.iloc[i, :]a b_df.iloc[i][:]significará que asigna a la matriz NumPy subyacente, por lo que se omite la alineación de indexación. Esta operación se expresa mejor 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

También vale la pena mencionar que esta es una forma de asignación encadenada, que no es algo bueno , y también hace que su código sea más difícil de leer y comprender.

cs95
fuente
1
Ahora lo entiendo, gracias. Antes de otorgar la recompensa, ¿podría agregar una referencia para esto: "la [:]sintaxis se asigna a la matriz subyacente NumPy"?
Seb
@Seb Realmente no encontrará referencias a esto en la documentación porque es algo así como un detalle de implementación. Puede ser más fácil encontrar el código en GitHub que es responsable de esto, pero creo que la forma más fácil es simplemente demostrar lo que sucede. He editado el pequeño ejemplo en la parte superior de mi respuesta para mostrar cómo se manipula la matriz subyacente durante los diferentes tipos de reasignación. ¡Espero que aclare las cosas!
cs95
Muchas gracias! Está mucho más claro ahora.
Tommy Yip
0

La diferencia es que en el primer caso el intérprete de Python ejecutó el 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)

donde el valor sería el lado derecho de la ecuación. Mientras que en el segundo caso, el intérprete de Python ejecutó el 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)

donde nuevamente el valor sería el lado derecho de la ecuación.

En cada uno de esos dos casos, se llamaría un método diferente dentro del setitem debido a la diferencia en las teclas (i, slice (None)) y slice (None) Por lo tanto, tenemos un comportamiento diferente.

MaPy
fuente
b_df.iloc[i]y b_df.iloc[i][:]tienen los mismos índices sin embargo. ¿Por qué puede asignar una serie con índice no coincidente a una pero no a la otra?
Seb
en el primer caso, el _set_item se llamaría en el segundo one_setitem_slice se llamaría. Entonces, sospeche que debido a la diferencia de esos métodos, tenemos el comportamiento anterior
MaPy
0

Podría alguien explicarme cuál es la diferencia entre el .iloc[i,:]y .iloc[i][:]es

La diferencia entre .iloc[i,:]y.iloc[i][:]

En el caso de .iloc[i,:]que esté accediendo directamente a una posición específica de DataFrame, seleccionando todas las :columnas ( ) de la ifila th. Que yo sepa, es equivalente a dejar la segunda dimensión sin especificar (.iloc[i] ).

En el caso de .iloc[i][:]que esté realizando 2 operaciones encadenadas. Entonces, el resultado de .iloc[i]será afectado por [:]. El uso de esto para establecer valores es desaconsejado por Pandas mismo aquí con una advertencia, por lo que no debe usarlo:

Si se devuelve una copia o una referencia para una operación de configuración, puede depender del contexto. Esto a veces se llama asignación encadenada y debe evitarse


... y por qué .iloc[i][:]funcionó en mi ejemplo anterior pero no.iloc[i,:]

Como @Scott mencionó en los comentarios de OP, la alineación de datos es intrínseca , por lo que los índices en el lado derecho =no se incluirán si no están presentes en el lado izquierdo. Por eso hayNaN valores en la segunda fila.

Entonces, para dejar las cosas claras, puede hacer lo siguiente:

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

O puede convertir a en listlugar 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
fuente