Clasificación personalizada en el marco de datos de pandas

89

Tengo un marco de datos de Python Pandas, en el que una columna contiene el nombre del mes.

¿Cómo puedo hacer una ordenación personalizada usando un diccionario, por ejemplo:

custom_dict = {'March':0, 'April':1, 'Dec':3}  
Kathirmani Sukumar
fuente
1
¿Una columna contiene el nombre del mes significa que hay una columna que contiene los nombres de los meses (como mi respuesta), o muchas columnas con los nombres de las columnas como nombres de los meses (como los de eumiro)?
Andy Hayden
1
La respuesta aceptada está desactualizada y también es técnicamente incorrecta, ya pd.Categoricalque no interpreta las categorías como ordenadas por defecto. Vea esta respuesta .
cs95

Respuestas:

141

Pandas 0.15 introdujo la Serie categórica , que permite una forma mucho más clara de hacer esto:

Primero haga que la columna del mes sea categórica y especifique el orden a utilizar.

In [21]: df['m'] = pd.Categorical(df['m'], ["March", "April", "Dec"])

In [22]: df  # looks the same!
Out[22]:
   a  b      m
0  1  2  March
1  5  6    Dec
2  3  4  April

Ahora, cuando clasifique la columna del mes, se ordenará con respecto a esa lista:

In [23]: df.sort_values("m")
Out[23]:
   a  b      m
0  1  2  March
2  3  4  April
1  5  6    Dec

Nota: si un valor no está en la lista, se convertirá a NaN.


Una respuesta más antigua para los interesados ​​...

Podría crear una serie intermedia, y set_indexsobre eso:

df = pd.DataFrame([[1, 2, 'March'],[5, 6, 'Dec'],[3, 4, 'April']], columns=['a','b','m'])
s = df['m'].apply(lambda x: {'March':0, 'April':1, 'Dec':3}[x])
s.sort_values()

In [4]: df.set_index(s.index).sort()
Out[4]: 
   a  b      m
0  1  2  March
1  3  4  April
2  5  6    Dec

Como se comentó, en los pandas más nuevos, Series tiene un replacemétodo para hacer esto de manera más elegante:

s = df['m'].replace({'March':0, 'April':1, 'Dec':3})

La pequeña diferencia es que esto no aumentará si hay un valor fuera del diccionario (simplemente permanecerá igual).

Andy Hayden
fuente
s = df['m'].replace({'March':0, 'April':1, 'Dec':3})también funciona para la línea 2, solo por el bien de cualquiera que esté aprendiendo pandas como yo
kdauria
@kdauria buen lugar! ( .apply({'March':0, 'April':1, 'Dec':3}.get)¡Ha pasado un tiempo desde que escribí esto!) Reemplazar definitivamente la mejor opción, otra es usar :) En 0.15 tendremos Series / columnas categóricas, así que la mejor manera será usar eso y luego ordenar simplemente funcionará.
Andy Hayden
@AndyHayden Me he tomado la libertad de reemplazar la segunda línea con el método 'reemplazar'. Espero que esté bien.
Faheem Mitha
@AndyHayden edit rechazada, pero sigo pensando que es un cambio razonable.
Faheem Mitha
7
Solo asegúrese de usar df.sort_values("m")en pandas más nuevos (en lugar de df.sort("m")), de lo contrario obtendrá un AttributeError: 'DataFrame' object has no attribute 'sort';)
lluvia de ideas
17

pandas> = 1.1

Pronto podrás usar sort_valuescon keyargumento:

pd.__version__
# '1.1.0.dev0+2004.g8d10bfb6f'

custom_dict = {'March': 0, 'April': 1, 'Dec': 3} 
df

   a  b      m
0  1  2  March
1  5  6    Dec
2  3  4  April

df.sort_values(by=['m'], key=lambda x: x.map(custom_dict))

   a  b      m
0  1  2  March
2  3  4  April
1  5  6    Dec

El keyargumento toma como entrada una Serie y devuelve una Serie. Esta serie se ordena internamente y los índices ordenados se utilizan para reordenar el DataFrame de entrada. Si hay varias columnas para ordenar, la función clave se aplicará a cada una de ellas. Consulte Clasificación con claves .


pandas <= 1.0.X

Un método simple es usar la salida Series.mape Series.argsortindexar dfusandoDataFrame.iloc (ya que argsort produce posiciones enteras ordenadas); ya que tienes un diccionario; esto se vuelve fácil.

df.iloc[df['m'].map(custom_dict).argsort()]

   a  b      m
0  1  2  March
2  3  4  April
1  5  6    Dec

Si necesita ordenar en orden descendente , invierta la asignación.

df.iloc[(-df['m'].map(custom_dict)).argsort()]

   a  b      m
1  5  6    Dec
2  3  4  April
0  1  2  March

Tenga en cuenta que esto solo funciona con elementos numéricos. De lo contrario, deberá solucionar este problema utilizandosort_values y accediendo al índice:

df.loc[df['m'].map(custom_dict).sort_values(ascending=False).index]

   a  b      m
1  5  6    Dec
2  3  4  April
0  1  2  March

Hay más opciones disponibles con astype(esto está obsoleto ahora), o pd.Categorical, pero debe especificar ordered=Truepara que funcione correctamente .

# Older version,
# df['m'].astype('category', 
#                categories=sorted(custom_dict, key=custom_dict.get), 
#                ordered=True)
df['m'] = pd.Categorical(df['m'], 
                         categories=sorted(custom_dict, key=custom_dict.get), 
                         ordered=True)

Ahora, un simple sort_values llamada hará el truco:

df.sort_values('m')
 
   a  b      m
0  1  2  March
2  3  4  April
1  5  6    Dec

El orden categórico también se respetará cuando se groupbyclasifique la salida.

cs95
fuente
2
Ya lo ha enfatizado, pero me gustaría reiterar en caso de que alguien más lo pase por alto y lo pierda: Pandas Categorical establece ordered=Nonepor defecto. Si no se establece, el pedido será incorrecto o se interrumpirá en V23. La función Max en particular da un TypeError (Categórico no está ordenado para la operación max).
Dave Liu
16

Un poco tarde para el juego, pero aquí hay una manera de crear una función que clasifique los objetos Pandas Series, DataFrame y DataFrame multiindex usando funciones arbitrarias.

Hago uso del df.iloc[index]método, que hace referencia a una fila en un Series / DataFrame por posición (en comparación con df.loc, que hace referencia por valor). Usando esto, solo tenemos que tener una función que devuelva una serie de argumentos posicionales:

def sort_pd(key=None,reverse=False,cmp=None):
    def sorter(series):
        series_list = list(series)
        return [series_list.index(i) 
           for i in sorted(series_list,key=key,reverse=reverse,cmp=cmp)]
    return sorter

Puede usar esto para crear funciones de clasificación personalizadas. Esto funciona en el marco de datos utilizado en la respuesta de Andy Hayden:

df = pd.DataFrame([
    [1, 2, 'March'],
    [5, 6, 'Dec'],
    [3, 4, 'April']], 
  columns=['a','b','m'])

custom_dict = {'March':0, 'April':1, 'Dec':3}
sort_by_custom_dict = sort_pd(key=custom_dict.get)

In [6]: df.iloc[sort_by_custom_dict(df['m'])]
Out[6]:
   a  b  m
0  1  2  March
2  3  4  April
1  5  6  Dec

Esto también funciona en objetos DataFrames y Series de varios índices:

months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']

df = pd.DataFrame([
    ['New York','Mar',12714],
    ['New York','Apr',89238],
    ['Atlanta','Jan',8161],
    ['Atlanta','Sep',5885],
  ],columns=['location','month','sales']).set_index(['location','month'])

sort_by_month = sort_pd(key=months.index)

In [10]: df.iloc[sort_by_month(df.index.get_level_values('month'))]
Out[10]:
                 sales
location  month  
Atlanta   Jan    8161
New York  Mar    12714
          Apr    89238
Atlanta   Sep    5885

sort_by_last_digit = sort_pd(key=lambda x: x%10)

In [12]: pd.Series(list(df['sales'])).iloc[sort_by_last_digit(df['sales'])]
Out[12]:
2    8161
0   12714
3    5885
1   89238

Para mí, esto se siente limpio, pero usa mucho las operaciones de Python en lugar de depender de las operaciones optimizadas de pandas. No he realizado ninguna prueba de estrés, pero me imagino que esto podría ralentizarse en DataFrames muy grandes. No estoy seguro de cómo se compara el rendimiento con agregar, ordenar y luego eliminar una columna. ¡Se agradecería cualquier consejo sobre cómo acelerar el código!

Miguel Delgado
fuente
¿Funcionaría esto para ordenar múltiples columnas / índices?
ConanG
sí, pero la respuesta seleccionada es una forma mucho mejor de hacerlo. Si tiene varios índices, simplemente organícelos de acuerdo con el orden de clasificación que prefiera y luego utilícelos df.sort_index()para ordenar todos los niveles de índice.
Michael Delgado
9
import pandas as pd
custom_dict = {'March':0,'April':1,'Dec':3}

df = pd.DataFrame(...) # with columns April, March, Dec (probably alphabetically)

df = pd.DataFrame(df, columns=sorted(custom_dict, key=custom_dict.get))

devuelve un DataFrame con columnas marzo, abril, diciembre

eumiro
fuente
¿Esto ordena las columnas reales, en lugar de ordenar las filas según el predicado personalizado de la columna?
cs95