Pandas: ERROR: concat ordena de forma no deseada los nombres de las columnas de DataFrame si difieren

Creado en 17 ago. 2013  ·  36Comentarios  ·  Fuente: pandas-dev/pandas

Cuando se concatenan DataFrames, los nombres de las columnas se ordenan alfanuméricamente si hay alguna diferencia entre ellos. Si son idénticos en DataFrames, no se ordenan.
Este tipo es indocumentado y no deseado. Ciertamente, el comportamiento predeterminado debería ser no ordenar. EDITAR: el orden estándar como en SQL sería: columnas de df1 (mismo orden que en df1), columnas (únicamente) de df2 (menos las columnas comunes) (mismo orden que en df2). Ejemplo:

df4a = DataFrame(columns=['C','B','D','A'], data=np.random.randn(3,4))
df4b = DataFrame(columns=['C','B','D','A'], data=np.random.randn(3,4))
df5  = DataFrame(columns=['C','B','E','D','A'], data=np.random.randn(3,5))

print "Cols unsorted:", concat([df4a,df4b])
# Cols unsorted:           C         B         D         A

print "Cols sorted", concat([df4a,df5])
# Cols sorted           A         B         C         D         E
``'
API Design Reshaping

Comentario más útil

Este comportamiento es bastante inesperado y también me tropecé con él.

 >>> df = pd.DataFrame()

>>> df['b'] = [1,2,3]
>>> df['c'] = [1,2,3]
>>> df['a'] = [1,2,3]
>>> print(df)
   b  c  a
0  1  1  1
1  2  2  2
2  3  3  3

[3 rows x 3 columns]
>>> df2 = pd.DataFrame({'a':[4,5]})
>>> df3 = pd.concat([df, df2])

Ingenuamente, uno esperaría que se conservara el orden de las columnas. En cambio, las columnas están ordenadas:

>>> print(df3)
   a   b   c
0  1   1   1
1  2   2   2
2  3   3   3
0  4 NaN NaN
1  5 NaN NaN

[5 rows x 3 columns]

Esto se puede corregir volviendo a indexar con las columnas originales de la siguiente manera:

>>> df4 = df3.reindex_axis(df.columns, axis=1)
>>> print(df4)
    b   c  a
0   1   1  1
1   2   2  2
2   3   3  3
0 NaN NaN  4
1 NaN NaN  5

[5 rows x 3 columns]

Aún así, parece contrario a la intuición que esta clasificación automática se lleve a cabo y no pueda desactivarse hasta donde yo sé.

Todos 36 comentarios

Mirando esto brevemente, creo que esto proviene de Index.intersection, cuya cadena de documentos dice:

Forme la intersección de dos objetos Index. La clasificación del resultado no está garantizada.

No estoy seguro de en qué casos aparecen / están ordenados, pero el caso en el que las columnas son iguales (en la primera) tiene mayúsculas especiales para devolver el mismo resultado ...

@smcierney, ¿qué orden esperarías en su lugar?

Encontré que la ordenación automática también era un poco molesta (bueno, debería decir que depende de su propósito), porque estaba tratando de concaminar un marco a un marco vacío en un bucle (como agregar un elemento a una lista). Entonces me di cuenta de que el orden de mis columnas había cambiado. Este cambio también se aplica al índice, si está concatenando a lo largo del eje = 1.

En un caso similar al de @smcinerney , espero el pedido final de CBDAE. E aparece en último lugar porque el orden CBDA aparece primero al concatenar.

Por lo tanto, escribí un "truco" (aunque un poco tonto)

sorted = pd.concat (frameList, axis = axis, join = join, join_axes = join_axes, ignore_index = False, keys = None, levels = None, names = None, verify_integrity = False)

if join_axes:
    return sorted
elif sort:
    return sorted
else:
    # expand all original orders in each frame
    sourceOrder = []
    for frame in frameList:
        sourceOrder.extend(frame.Columns()) if axis == 0 else sourceOrder.extend(frame.Indices())
    sortedOrder = sorted.Columns() if axis == 0 else sorted.Indices()

    positions = []
    positionsSorted = []
    for i in sortedOrder:
        positions.append(sourceOrder.index(i))
        positionsSorted.append(sourceOrder.index(i))
    positionsSorted.sort()

    unsortedOrder = []
    for i in positionsSorted:
        unsortedOrder.append(sortedOrder[positions.index(i)])

    return sorted.ReorderCols(unsortedOrder) if axis == 0 else sorted.ReorderRows(unsortedOrder)

¡La función está incluida en mi módulo personal llamado kungfu! Cualquiera puede adoptar el algoritmo anterior o echar un vistazo a mi módulo en https://github.com/jerryzhujian9/kungfu

Por último, agradezco enormemente el trabajo del equipo de desarrollo de este gran módulo.

Este comportamiento es bastante inesperado y también me tropecé con él.

 >>> df = pd.DataFrame()

>>> df['b'] = [1,2,3]
>>> df['c'] = [1,2,3]
>>> df['a'] = [1,2,3]
>>> print(df)
   b  c  a
0  1  1  1
1  2  2  2
2  3  3  3

[3 rows x 3 columns]
>>> df2 = pd.DataFrame({'a':[4,5]})
>>> df3 = pd.concat([df, df2])

Ingenuamente, uno esperaría que se conservara el orden de las columnas. En cambio, las columnas están ordenadas:

>>> print(df3)
   a   b   c
0  1   1   1
1  2   2   2
2  3   3   3
0  4 NaN NaN
1  5 NaN NaN

[5 rows x 3 columns]

Esto se puede corregir volviendo a indexar con las columnas originales de la siguiente manera:

>>> df4 = df3.reindex_axis(df.columns, axis=1)
>>> print(df4)
    b   c  a
0   1   1  1
1   2   2  2
2   3   3  3
0 NaN NaN  4
1 NaN NaN  5

[5 rows x 3 columns]

Aún así, parece contrario a la intuición que esta clasificación automática se lleve a cabo y no pueda desactivarse hasta donde yo sé.

Yo también me encontré con esto.

new_data = pd.concat([churn_data, numerical_data])

Produjo un DataFrame:

     churn  Var1  Var10  Var100  Var101 
0      -1   NaN    NaN     NaN     NaN     
1      -1   NaN    NaN     NaN     NaN

¡Parecería más natural que el DataFrame numérico se concatene sin ser ordenado primero!

Bueno, esto es un poco de trabajo para arreglar. ¡pero se aceptan solicitudes de extracción!

Me encontré con este mismo problema cuando estaba concatenando DataFrames . Es un poco molesto si no conoce este problema, pero en realidad hay un remedio rápido:

digamos que dfs es una lista de DataFrames que desea concatenar, puede simplemente tomar el orden de la columna original y retroalimentarlo en:

df = pd.concat(dfs, axis=0)
df = df[dfs[0].columns]

Creo que append causa el mismo comportamiento, FYI

Es el comportamiento predeterminado en todos los ámbitos. Por ejemplo, si aplica una función, f, a un groupby () que devuelve un número variable de columnas, la concatenación que tiene lugar detrás de la escena también ordena automáticamente las columnas.

df.groupby (algunos_ts) .aplicar (f)

Probablemente porque el orden conocido de las columnas está abierto a interpretación.

Sin embargo, esto también sucede para MultiIndices y todas las jerarquías en MultiIndices. Por lo tanto, puede concatizar marcos de datos que coincidan con las columnas de nivel0 y todas las columnas de un nivel1 de la barra, y todos los niveles de los índices múltiples se clasificarán automáticamente debido a una falta de coincidencia dentro de una columna de nivel0. No imagino que eso sea deseable.

Me encantaría ayudar, pero desafortunadamente solucionar este problema está más allá de mi capacidad. Gracias por el arduo trabajo a todos.

+1 para esta función

De acuerdo, +1. La clasificación inesperada ocurre todo el tiempo para mí.

+1, ¡esta fue una sorpresa desagradable!

+1, odio tener las columnas ordenadas después de cada append .

+1 de mí también.

Porque incluso si quisiera reordenar manualmente después de un concat, cuando trato de imprimir los nombres y posiciones de más de 60 columnas en mi marco de datos:

 for id, value in enumerate(df.columns):
      print id, value

Las más de 60 columnas se emiten en orden alfabético, no en su posición real en el marco de datos.

Eso significa que después de la concatenación, tengo que escribir manualmente una lista de 60 columnas para reordenar. Ay.

Mientras estoy aquí, ¿alguien tiene una forma de imprimir el nombre de la columna y la posición que me falta?

+1 para esta función, acabo de encontrar el mismo trato.

@summerela Obtenga el índice de la columna y luego vuelva a indexar su nuevo marco de datos utilizando el índice de la columna original

# assuming you have two dataframes, `df_train` & `df_test` (with the same columns) 
# that you want to concatenate

# get the columns from one of them
all_columns = df_train.columns

# concatenate them
df_concat = pd.concat([df_train,
                       df_test])

# finally, re-index the new dataframe using the original column index
df_concat = df_concat.ix[:, all_columns]

Por el contrario, si necesita volver a indexar un subconjunto más pequeño de columnas, puede usar esta función que hice. También puede operar con índices relativos. Por ejemplo, si desea mover una columna al final de un marco de datos, pero no está seguro de cuántas columnas pueden quedar después de los pasos de procesamiento anteriores en su secuencia de comandos (tal vez esté eliminando columnas de varianza cero, por ejemplo), podría pasar una posición de índice relativa a new_indices -> new_indices = [-1] y se encargará del resto.

def reindex_columns(dframe=None, columns=None, new_indices=None):
    """
    Reorders the columns of a dataframe as specified by
    `reorder_indices`. Values of `columns` should align with their
    respective values in `new_indices`.

    `dframe`: pandas dataframe.

    `columns`: list,pandas.core.index.Index, or numpy array; columns to
    reindex.

    `reorder_indices`: list of integers or numpy array; indices
    corresponding to where each column should be inserted during
    re-indexing.
    """
    print("Re-indexing columns.")
    try:
        df = dframe.copy()

        # ensure parameters are of correct type and length
        assert isinstance(columns, (pd.core.index.Index,
                                    list,
                                    np.array)),\
        "`columns` must be of type `pandas.core.index.Index` or `list`"

        assert isinstance(new_indices,
                          list),\
        "`reorder_indices` must be of type `list`"

        assert len(columns) == len(new_indices),\
        "Length of `columns` and `reorder_indices` must be equal"

        # check for negative values in `new_indices`
        if any(idx < 0 for idx in new_indices):

            # get a list of the negative values
            negatives = [value for value
                         in new_indices
                         if value < 0]

            # find the index location for each negative value in
            # `new_indices`
            negative_idx_locations = [new_indices.index(negative)
                                      for negative in negatives]

            # zip the lists
            negative_zipped = list(zip(negative_idx_locations,
                                       negatives))

            # replace the negatives in `new_indices` with their
            # absolute position in the index
            for idx, negative in negative_zipped:
                new_indices[idx] = df.columns.get_loc(df.columns[
                                                          negative])

        # re-order the index now
        # get all columns
        all_columns = df.columns

        # drop the columns that need to be re-indexed
        all_columns = all_columns.drop(columns)

        # now re-insert them at the specified locations
        zipped_columns = list(zip(new_indices,
                                  columns))

        for idx, column in zipped_columns:
            all_columns = all_columns.insert(idx,
                                             column)
        # re-index the dataframe
        df = df.ix[:, all_columns]

        print("Successfully re-indexed dataframe.")

    except Exception as e:
        print(e)
        print("Could not re-index columns. Something went wrong.")

    return df

Editar: el uso se vería así:

# move 'Column_1' to the end, move 'Column_2' to the beginning
df = reindex_columns(dframe=df,
                     columns=['Column_1', 'Column_2'],
                     new_indices=[-1, 0])

Encontré esto (con 0.13.1) de un caso de borde no mencionado: combinar marcos de datos que contienen columnas únicas. Una reasignación ingenua de nombres de columnas no funcionó:

dat = pd.concat([out_dust, in_dust, in_air, out_air])
dat.columns = [out_dust.columns + in_dust.columns + in_air.columns + out_air.columns]

Las columnas aún se ordenan. Sin embargo, el uso de listas resolvió cosas de manera intermedia:

Editar: hablé demasiado pronto ...


Seguimiento: fwiw, el orden de las columnas se puede conservar con llamadas .join encadenadas en objetos singulares:

df1.join([df2, df3]) # sorts columns
df1.join(df2).join(df3) # column order retained

¿Podría haber un parámetro al crear dataFrame sobre el pedido de columnas? Como orden = Falso. Muchas gracias

me encontré con esto mientras creaba un marco de datos a partir de un diccionario. Me sorprendió totalmente, fue contradictorio y derrotó todo mi propósito ...

Los nombres de las columnas deben ser para mayor claridad y la ubicación de las columnas cerca una de la otra es una elección organizacional del usuario para mantener la coherencia.

@patricktokeeffe
Gracias por el puntero a join . Los objetos de la serie no tienen ese método, así que terminé escribiendo una función:

def concat_fixed(ndframe_seq, **kwargs):
    """Like pd.concat but fixes the ordering problem.

    Converts Series objects to DataFrames to access join method
    Use kwargs to pass through to repeated join method
    """
    indframe_seq = iter(ndframe_seq)
    # Use the first ndframe object as the base for the final
    final_df = pd.DataFrame(next(indframe_seq))
    for dataframe in indframe_seq:
        if isinstance(dataframe, pd.Series):
            dataframe = pd.DataFrame(dataframe)
        # Iteratively build final table
        final_df = final_df.join(dataframe, **kwargs)
    return final_df

¿Cómo es la eficiencia en esto?

El miércoles 30 de agosto de 2017 a la 1:58 p. M., Bryce Guinta [email protected]
escribió:

@patricktokeeffe https://github.com/patricktokeeffe
Gracias por el puntero para unirse. Los objetos de serie no tienen ese método, así que
Terminé escribiendo una función:

def concat_fixed (ndframe_seq, ** kwargs):
"" "Como pd.concat pero soluciona el problema de pedido.

Converts Series objects to DataFrames to access join method
Use kwargs to pass through to repeated join method
"""
indframe_seq = iter(ndframe_seq)
# Use the first ndframe object as the base for the final
final_df = pd.DataFrame(next(indframe_seq))
for dataframe in indframe_seq:
    if isinstance(dataframe, pd.Series):
        # Convert Series objects into DataFrames since
        # series objects do not have a join method
        dataframe = pd.DataFrame(dataframe)
    # Iteratively build final table
    final_df = final_df.join(dataframe, **kwargs)
return final_df

-
Estás recibiendo esto porque comentaste.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/pandas-dev/pandas/issues/4588#issuecomment-326086636 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AG999MucF-NH5vHuKe-Zczq-jy9ziYkRks5sdbDogaJpZM4A6TeA
.

@ MikeTam1021

No voy a compararlo con atm, pero creo que sería una función del tamaño de sus ndframes, la cantidad de ellos. Crea un nuevo marco de datos para cada ndframe, así que imagino que es mucho menos eficiente que pd.concat .

Funciona bien para mis propósitos, pero estoy usando una pequeña cantidad de ndframes (alrededor de 10 1 ) y una cantidad relativamente pequeña de records para cada ndframe (alrededor de 10 2 ).

Mi objetivo es incluir todos los registros de cada marco de datos conservando el orden de esos registros, incluso si no todos los ndframes contienen datos para un registro determinado.

No veo por qué preservar el orden de las columnas (tanto como sea posible) no es el comportamiento predeterminado de concat ().

Mi solución utiliza unique_everseen de las recetas de Itertools .

columns = unique_everseen([column for df in dfs for column in df.columns])
df = pd.concat(dfs)[columns]

¿Alguna actualización sobre el estado de este hilo? Actualmente estoy usando la versión 0.22.0 y todavía parece que no hay una solución adecuada. La procrastinación parece ser un gran problema aquí ...

También me gustaría señalar que se puede encontrar un comportamiento similar al concatenar columnas, es decir, axis=1 , pero solo cuando se pasan los marcos de datos en un diccionario:

>>> df4a = DataFrame(columns=['C','B','D','A'], data=np.random.randn(3,4))
>>> df4b = DataFrame(columns=['C','B','D','A'], data=np.random.randn(3,4))
>>> df5  = DataFrame(columns=['C','B','E','D','A'], data=np.random.randn(3, 5))

>>> pd.concat([df4a, df5], axis=1).columns
Index(['C', 'B', 'D', 'A', 'C', 'B', 'E', 'D', 'A'], dtype='object')
>>> pd.concat({'df4a': df4a, 'df4b': df4b}, axis=1).columns.levels
FrozenList([['df4a', 'df4b'], ['C', 'B', 'D', 'A']])
>>> pd.concat({'df4a': df4a, 'df5': df5}, axis=1).columns.levels
FrozenList([['df4a', 'df5'], ['A', 'B', 'C', 'D', 'E']])

¿Alguna actualización sobre el estado de este hilo?

Sigue abierto.

La procrastinación parece ser un gran problema aquí ...

¿Dilación? Tenemos muchos problemas abiertos. Si desea asegurarse de que esto se solucione en la próxima versión, lo mejor es armar un PR. Háganos saber si necesita ayuda para comenzar.

@jtratner : en caso de que no fuera obvio a partir de mis ejemplos en la parte superior, esperaría que el orden sea:

  • columnas compartidas, sin clasificar
  • columnas exclusivas de df1, sin clasificar (es decir, en el orden en que aparecen en df1)
  • columnas exclusivas de df2, sin clasificar (es decir, en el orden en que aparecen en df2)

Esto es lo que se obtiene en otros paquetes o lenguajes, por ejemplo, SQL. Nunca debería haber una clasificación automática no deseada. Si el usuario desea ordenar los nombres de las columnas, déjelo hacerlo manualmente.

Hola chicos, dos cosas. 1) ¡Bienvenido a los pandas! Sugiero usar más tipos nativos de Python como diccionarios. Deja de intentar convertir Python (o cualquier idioma) en SQL. 2) Técnicamente, esto no es un error. Es solo un efecto no deseado del código. Puede superarlo fácilmente fuera del contexto del paquete, y eso es lo que creo que es la respuesta correcta, a menos que alguien aquí se encargue de ello.

@ MikeTam1021 , explique cómo superar esto fuera del contexto del paquete. Gracias.

Estoy bastante seguro de que eso es exactamente lo que las personas en este hilo han estado discutiendo. Veo muchas buenas soluciones arriba que deberían funcionar.

@ MikeTam1021 No se trata de convertir pandas en SQL (¡Dios no lo quiera!), Pero no podría estar más de acuerdo con:

Nunca debería haber una clasificación automática no deseada. Si el usuario desea ordenar los nombres de las columnas, déjelo hacerlo manualmente.

Concatenar DataFrames debería tener el mismo efecto que "escribirlos uno al lado del otro", y ese tipo implícito definitivamente viola el principio del mínimo asombro.

Estoy de acuerdo. No debería. También asume un orden para las columnas, que es SQLish, y no pura informática. Realmente deberías saber dónde están tus datos.

Ya casi no uso pandas después de descubrir este y muchos otros problemas. Me ha convertido en un mejor programador.

+1 en esto

Esto funciona para mi:

cols = list(df1)+list(df2)
df1 = pd.concat([df1, df2])
df1 = df1.loc[:, cols]

Tengo que quejarme sobre cómo se implementa este parche. Ha cambiado simultáneamente la firma de la función de concat Y ha introducido una advertencia sobre el uso. Todo dentro del mismo compromiso.

El problema con eso es que usamos pandas en múltiples servidores y no podemos garantizar que todos los servidores tengan exactamente la misma versión de pandas en todo momento. Así que ahora tenemos usuarios menos técnicos que ven advertencias de programas que nunca antes habían visto y no están seguros de si la advertencia es una señal de un problema.

Puedo identificar fácilmente de DÓNDE viene la advertencia, pero no puedo agregar ninguna de las opciones sugeridas porque eso rompería el programa en cualquier servidor que ejecute una versión anterior de pandas.

Hubiera sido preferible poner la capacidad de clasificación en 0.23 y agregar la advertencia a alguna versión posterior. Sé que es un fastidio, pero es bastante desagradable asumir que los usuarios pueden actualizar inmediatamente todas las implementaciones al código más reciente.

Parece que puede establecer un filtro global para esta advertencia y luego
suelte eso cuando todos estén actualizados.

Funcionalmente, eso es lo mismo, ¿verdad?

El jueves 4 de octubre de 2018 a las 9:18 a. M., DavidEscott [email protected] escribió:

Tengo que quejarme sobre cómo se implementa este parche. Tienes
simultáneamente cambió la firma de la función de concat E introdujo un
advertencia sobre el uso. Todo dentro del mismo compromiso.

El problema con eso es que usamos pandas en varios servidores y no podemos
Garantizar que todos los servidores tengan exactamente la misma versión de pandas.
veces. Así que ahora tenemos usuarios menos técnicos que ven advertencias de programas.
nunca han visto antes, y no están seguros de si la advertencia es una señal de
un problema.

Puedo identificar fácilmente de DÓNDE viene la advertencia, pero no puedo agregar
cualquiera de las opciones sugeridas porque eso rompería el programa en cualquier
servidor que ejecuta una versión anterior de pandas.

Hubiera sido preferible si pusiera la capacidad de clasificación en
0.23, y agregó la advertencia a alguna versión posterior. Sé que es un dolor, pero
Es bastante desagradable suponer que los usuarios pueden actualizar inmediatamente todos
implementaciones al último código.

-
Recibe esto porque modificó el estado abierto / cerrado.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/pandas-dev/pandas/issues/4588#issuecomment-427036391 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/ABQHItEhYfv5kqB-R-pDX4zyIh45hF7kks5uhhiWgaJpZM4A6TeA
.

@TomAugspurger Hay muchas formas en que nosotros, de nuestro lado, podemos lidiar con esto. Ciertamente, filtrar las advertencias es una. No es genial porque la mecánica de los filtros de advertencias es un poco fea ...

  1. Tendría que agregar el filtro a múltiples programas.
  2. No es una buena manera de especificar una advertencia específica para filtrar:

    • Puedo filtrar por módulo y lineno, pero esa no es una referencia estable,

    • Puedo filtrar por módulo y FutureWarning pero luego no recibiría ninguna advertencia de los pandas y me sorprenderían otros cambios,

    • o puedo filtrar por su mensaje largo de varias líneas

  3. Y luego recuerde quitar ese filtro cuando todo esté actualizado y ya no importe.

En cualquier caso, las deficiencias en el módulo warnings ciertamente no son algo que pueda poner al pie del equipo de pandas.

Tampoco es culpa tuya que tengamos un servidor más antiguo que no podamos actualizar fácilmente, así que esa sería la otra cosa que puedo hacer (solo actualizar todas las malditas implementaciones). En última instancia, reconozco que tengo que hacer eso y que es mi responsabilidad tratar de mantener nuestras implementaciones juntas.

Simplemente me parece un poco extraño que estuvieras tan preocupado por un posible cambio en el comportamiento final visible del usuario que agregaste esta opción de clasificación a lo que anteriormente era una API subespecificada y, sin embargo, simultáneamente arrojaste una advertencia al programador ... ambos la advertencia y el cambio propuesto en el comportamiento de clasificación constituyen "comportamiento visible del usuario" en mi libro, solo que de diferente gravedad.

Respondí una pregunta relacionada sobre SO.

¿Fue útil esta página
0 / 5 - 0 calificaciones