120 votes

Tri personnalisé dans un cadre de données pandas

J'ai un dataframe python pandas, dans lequel une colonne contient le nom du mois.

Comment puis-je faire un tri personnalisé en utilisant un dictionnaire, par exemple :

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

193voto

Andy Hayden Points 38010

Introduction de Pandas 0.15 Séries catégoriques qui permet de le faire de manière beaucoup plus claire :

Tout d'abord, faites de la colonne du mois une colonne catégorielle et spécifiez l'ordre à utiliser.

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

Maintenant, lorsque vous trierez la colonne du mois, elle sera triée par rapport à cette liste :

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

Remarque : si une valeur ne figure pas dans la liste, elle sera convertie en NaN.


Une réponse plus ancienne pour ceux qui sont intéressés...

Vous pourriez créer une série intermédiaire, et set_index sur ce point :

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

Comme indiqué, dans les pandas les plus récents, Series possède un replace pour faire cela de manière plus élégante :

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

La légère différence réside dans le fait qu'il n'y aura pas de message s'il existe une valeur en dehors du dictionnaire (la valeur restera la même).

50voto

coldspeed Points 111053

Pandas >= 1.1

Vous serez bientôt en mesure d'utiliser sort_values avec key argument :

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 prend en entrée une série et renvoie une série. Cette série est triée en interne et les indices triés sont utilisés pour réorganiser le DataFrame en entrée. S'il y a plusieurs colonnes à trier, la fonction clé sera appliquée à chacune d'elles à tour de rôle. Voir Triage avec des clés .


pandas <= 1.0.X

Une méthode simple consiste à utiliser la sortie Series.map y Series.argsort pour indexer dans df en utilisant DataFrame.iloc (puisque argsort produit des positions entières triées) ; puisque vous avez un dictionnaire, cela devient facile.

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

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

Si vous avez besoin de trier dans ordre décroissant inverser la cartographie.

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

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

Notez que cela ne fonctionne que pour les éléments numériques. Dans le cas contraire, vous devrez contourner ce problème en utilisant la commande sort_values et d'accéder à l'index :

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

Plus d'options sont disponibles avec astype (ceci est maintenant déprécié), ou pd.Categorical mais vous devez spécifier ordered=True pour qu'il fonctionne correctement .

# 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)

Maintenant, un simple sort_values fera l'affaire :

df.sort_values('m')

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

L'ordre catégorique sera également respecté lorsque groupby trie la sortie.

19voto

delgadom Points 560

Mise à jour

utiliser le réponse choisie ! il est plus récent que cet article et n'est pas seulement la façon officielle de maintenir des données ordonnées dans pandas, il est meilleur à tous les égards, y compris les fonctionnalités/performances, etc. N'utilisez pas la méthode bidon que je décris ci-dessous.

J'écris cette mise à jour uniquement parce que les gens n'arrêtent pas d'upvoter ma réponse, mais elle est définitivement pire que celle qui a été acceptée :)

Poste original

Un peu tard, mais voici un moyen de créer une fonction qui trie les objets pandas Series, DataFrame et DataFrame multi-index en utilisant des fonctions arbitraires.

Je me sers de la df.iloc[index] qui fait référence à une ligne d'une série ou d'un cadre de données par sa position (par rapport à la méthode de l'utilisateur). df.loc qui fait référence par valeur). En utilisant cela, il suffit d'avoir une fonction qui renvoie une série d'arguments positionnels :

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

Vous pouvez l'utiliser pour créer des fonctions de tri personnalisées. Cela fonctionne sur le dataframe utilisé dans la réponse d'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

Cela fonctionne également sur les DataFrames et les objets Series multi-index :

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

Pour moi, cela semble propre, mais il utilise fortement les opérations python plutôt que de s'appuyer sur des opérations pandas optimisées. Je n'ai pas fait de test de stress mais j'imagine que cela peut devenir lent sur des DataFrames de très grande taille. Je ne sais pas si les performances sont comparables à celles de l'ajout, du tri et de la suppression d'une colonne. Tout conseil pour accélérer le code serait apprécié !

10voto

eumiro Points 56644
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))

renvoie un DataFrame avec des colonnes Mars, Avril, Déc.

0voto

Evgeny Points 1291

J'avais la même tâche mais avec un ajout pour trier sur plusieurs colonnes.

Une des solutions consiste à faire en sorte que les deux colonnes pd.catégorique et passer l'ordre attendu comme argument "catégories".

Mais j'avais des exigences où je ne pouvais pas contraindre un inconnu. \unexpected et c'est malheureusement ce que fait pd.Categorical. De plus, None n'est pas soutenu comme une catégorie et contraint automatiquement.

Ma solution a donc consisté à utiliser une clé pour trier sur plusieurs colonnes avec un ordre de tri personnalisé :

import pandas as pd

df = pd.DataFrame([
    [A2, 2],
    [B1, 1],
    [A1, 2],
    [A2, 1],
    [B1, 2],
    [A1, 1]], 
  columns=['one','two'])

def custom_sorting(col: pd.Series) -> pd.Series:
    """Series is input and ordered series is expected as output"""
    to_ret = col
    # apply custom sorting only to column one:
    if col.name == "one":
        custom_dict = {}
        # for example ensure that A2 is first, pass items in sorted order here:
        def custom_sort(value):
            return (value[0], int(value[1:]))

        ordered_items = list(col.unique())
        ordered_items.sort(key=custom_sort)
        # apply custom order first:
        for index, item in enumerate(ordered_items):
            custom_dict[item] = index
        to_ret = col.map(custom_dict)
    # default text sorting is about to be applied
    return to_ret

# pass two columns to be sorted
df.sort_values(
    by=["two", "one"],
    ascending=True,
    inplace=True,
    key=custom_sorting,
)

print(df)

Sortie :

5  A1    1
3  A2    1
1  B1    1
2  A1    2
0  A2    2
4  B1    2

Sachez que cette solution peut être lente.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X