los dtypes arruinan las cosas cuando se desplaza en el eje uno (columnas)

9

Considere el marco de datos df

df = pd.DataFrame(dict(A=[1, 2], B=['X', 'Y']))

df

   A  B
0  1  X
1  2  Y

Si me desplazo axis=0(el valor predeterminado)

df.shift()

     A    B
0  NaN  NaN
1  1.0    X

Empuja todas las filas hacia abajo una fila como se esperaba.

Pero cuando cambio axis=1

df.shift(axis=1)

    A    B
0 NaN  NaN
1 NaN  NaN

Todo es nulo cuando esperaba

     A  B
0  NaN  1
1  NaN  2

Entiendo por qué sucedió esto. Para axis=0, Pandas está operando columna por columna donde cada columna es una sola dtypey al cambiar, hay un protocolo claro sobre cómo lidiar con el NaNvalor introducido al principio o al final. Pero cuando axis=1avanzamos introducimos una posible ambigüedad de dtypeuna columna a la siguiente. En este caso, trato de forzar int64una objectcolumna y Pandas decide simplemente anular los valores.

Esto se vuelve más problemático cuando dtypesson int64yfloat64

df = pd.DataFrame(dict(A=[1, 2], B=[1., 2.]))

df

   A    B
0  1  1.0
1  2  2.0

Y sucede lo mismo

df.shift(axis=1)

    A   B
0 NaN NaN
1 NaN NaN

Mi pregunta

¿Cuáles son buenas opciones para crear un marco de datos que se desplaza axis=1en el que el resultado ha cambiado los valores y los tipos de letra?

Para el caso int64/ float64el resultado se vería así:

df_shifted

     A  B
0  NaN  1
1  NaN  2

y

df_shifted.dtypes

A    object
B     int64
dtype: object

Un ejemplo mas completo

df = pd.DataFrame(dict(A=[1, 2], B=[1., 2.], C=['X', 'Y'], D=[4., 5.], E=[4, 5]))

df

   A    B  C    D  E
0  1  1.0  X  4.0  4
1  2  2.0  Y  5.0  5

Debería verse así

df_shifted

     A  B    C  D    E
0  NaN  1  1.0  X  4.0
1  NaN  2  2.0  Y  5.0

df_shifted.dtypes

A     object
B      int64
C    float64
D     object
E    float64
dtype: object
piRSquared
fuente
A mí me parece un error, ¿qué sucede si haces los tipos de todas las columnas object?
EdChum
Funciona. Ya tengo un par de soluciones. Solo estoy empujando a la comunidad por algunas ideas.
piRSquared
Presentaría esto como un problema, al menos deberían ofrecer una opción para la promoción de dtype a un dtype mixto comoobject
EdChum
Lo haré ahora.
piRSquared el
1
@ EdChum-ReinstateMonica ¡Espera un minuto! El cambio ocurre blocks>. <Use esto en su lugar y veadf = pd.DataFrame(dict(A=[1, 2], B=[3., 4.], C=['X', 'Y'], D=[5., 6.], E=[7, 8], F=['W', 'Z']))
piRSquared el

Respuestas:

7

Resulta que Pandas está cambiando sobre bloques de similares dtypes

Definir dfcomo

df = pd.DataFrame(dict(
    A=[1, 2], B=[3., 4.], C=['X', 'Y'],
    D=[5., 6.], E=[7, 8], F=['W', 'Z']
))

df

#  i    f  o    f  i  o
#  n    l  b    l  n  b
#  t    t  j    t  t  j
#
   A    B  C    D  E  F
0  1  3.0  X  5.0  7  W
1  2  4.0  Y  6.0  8  Z

Desplazará los enteros a la siguiente columna entera, los flotantes a la siguiente columna flotante y los objetos a la siguiente columna de objetos.

df.shift(axis=1)

    A   B    C    D    E  F
0 NaN NaN  NaN  3.0  1.0  X
1 NaN NaN  NaN  4.0  2.0  Y

No sé si es una buena idea, pero eso es lo que está sucediendo.


Enfoques

astype(object) primero

dtypes = df.dtypes.shift(fill_value=object)
df_shifted = df.astype(object).shift(1, axis=1).astype(dtypes)

df_shifted

     A  B    C  D    E  F
0  NaN  1  3.0  X  5.0  7
1  NaN  2  4.0  Y  6.0  8

transpose

Lo haremos object

dtypes = df.dtypes.shift(fill_value=object)
df_shifted = df.T.shift().T.astype(dtypes)

df_shifted

     A  B    C  D    E  F
0  NaN  1  3.0  X  5.0  7
1  NaN  2  4.0  Y  6.0  8

itertuples

pd.DataFrame([(np.nan, *t[1:-1]) for t in df.itertuples()], columns=[*df])

     A  B    C  D    E  F
0  NaN  1  3.0  X  5.0  7
1  NaN  2  4.0  Y  6.0  8

Aunque probablemente haría esto

pd.DataFrame([
    (np.nan, *t[:-1]) for t in
    df.itertuples(index=False, name=None)
], columns=[*df])
piRSquared
fuente
44
Esto definitivamente es un error para mí, esto invalida todo el punto de tener columnas con clave y cambiar por N posiciones en columna
EdChum
1
Publicaré un problema después de mi reunión.
piRSquared el
Si es todo strtipo, entonces funciona correctamente, si haces lo mismo en este df df = pd.DataFrame(dict(C=['X', 'Y'], D=[5., 6.], E=[7, 8], F=['W', 'Z'])), cambia la 'XY'columna por completo 'F', esto definitivamente es incorrecto para mí, mi versión de pandas es 0.24.2, no debería hacer dtypepromoción y no cambiar las columnas en tal una manera
EdChum
Problema abierto
piRSquared el
1

Intenté usar un numpymétodo. El método funciona siempre que mantenga sus datos en una matriz numpy:

def shift_df(data, n):
    shifted = np.roll(data, n)
    shifted[:, :n] = np.NaN

    return shifted

shifted(df, 1)

array([[nan, 1, 1.0, 'X', 4.0],
       [nan, 2, 2.0, 'Y', 5.0]], dtype=object)

Pero cuando llama al DataFrameconstructor, todas las columnas se convierten objectaunque los valores en la matriz son float, int, object:

def shift_df(data, n):
    shifted = np.roll(data, n)
    shifted[:, :n] = np.NaN
    shifted = pd.DataFrame(shifted)

    return shifted

print(shift_df(df, 1),'\n')
print(shift_df(df, 1).dtypes)

     0  1  2  3  4
0  NaN  1  1  X  4
1  NaN  2  2  Y  5 

0    object
1    object
2    object
3    object
4    object
dtype: object
Erfan
fuente