Clasificación personalizada en el marco de datos de pandas

Resuelto Kathirmani Sukumar asked hace 11 años • 5 respuestas

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

¿Cómo puedo hacer una clasificación personalizada usando un diccionario, por ejemplo?

custom_dict = {'March':0, 'April':1, 'Dec':3}  
Kathirmani Sukumar avatar Dec 12 '12 18:12 Kathirmani Sukumar
Aceptado

Pandas 0.15 introdujo Categorical Series , 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 ordenes 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 anterior para aquellos interesados ​​...

Podrías 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 ligera diferencia es que esto no aumentará si hay un valor fuera del diccionario (permanecerá igual).

Andy Hayden avatar Dec 12 '2012 11:12 Andy Hayden

pandas >= 1.1

Pronto podrás utilizar 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 está ordenada 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 Ordenar con claves .


pandas <= 1.0.X

Un método simple es usar la salida Series.mape Series.argsortindexarla dfusando DataFrame.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 el mapeo.

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 sólo funciona en elementos numéricos. De lo contrario, necesitarás solucionar este problema usando sort_valuesy 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 ahora está obsoleto) o , pd.Categoricalpero debe especificarlo 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, una simple sort_valuesllamada será suficiente:

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á al groupbyordenar la salida.

cs95 avatar Jan 22 '2019 04:01 cs95

Actualizar

¡ Usa la respuesta seleccionada ! es más nuevo que esta publicación y no es solo la forma oficial de mantener datos ordenados en pandas, es mejor en todos los aspectos, incluidas las características/rendimiento, etc. No uses mi método hacky que describo a continuación.

Solo escribo esta actualización porque la gente sigue votando mi respuesta, pero definitivamente es peor que la aceptada :)

Publicación original

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

Utilizo el df.iloc[index]método, que hace referencia a una fila en una serie/marco de datos por posición (en comparación con df.loc, que hace referencia por valor). Usando esto, sólo 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 utilizar 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 multiíndice:

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 utiliza 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 volverse lento 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 para acelerar el código!

Michael Delgado avatar Nov 19 '2014 05:11 Michael Delgado
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 avatar Dec 12 '2012 11:12 eumiro