Clasificación personalizada en el marco de datos de pandas
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}
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_index
sobre 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 replace
mé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).
pandas >= 1.1
Pronto podrás utilizar sort_values
con key
argumento:
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 key
argumento 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.map
e Series.argsort
indexarla df
usando 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_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 ahora está obsoleto) o , pd.Categorical
pero debe especificarlo ordered=True
para 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_values
llamada 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 groupby
ordenar la salida.
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!
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