【问题标题】:Sort a Pandas Dataframe by Multiple Columns Using Key Argument使用键参数按多列对 Pandas 数据框进行排序
【发布时间】:2022-04-03 03:38:40
【问题描述】:

我有一个带有以下列的 pandas 数据框的数据框:

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

我希望主要按“二”列排序,然后按“一”列排序。对于二级排序,我想使用自定义排序规则,该规则将按字母字符[A-Z] 对列“一”进行排序,然后是尾随数字[0-100]。所以,排序的结果是:

one two
 A1   1
 B1   1
 A2   1
 A1   2
 B1   2
 A2   2

在使用这样的排序规则之前,我已经对类似于列“一”的字符串列表进行了排序:

def custom_sort(value):
    return (value[0], int(value[1:]))

my_list.sort(key=custom_sort)

如果我尝试通过 pandas 排序应用此规则,我会遇到许多问题,包括:

  1. pandas DataFrame.sort_values() 函数像 sort() 函数一样接受用于排序的键,但键函数应该是矢量化的(根据 pandas 文档)。如果我尝试将排序键仅应用于“一”列,则会收到错误 "TypeError: cannot convert the series to "
  2. 当您使用 pandas DataFrame.sort_values() 方法时,它会将排序键应用于您传入的所有列。这将不起作用,因为我想首先使用本机数字排序按列“二”进行排序。

如上所述,我将如何对 DataFrame 进行排序?

【问题讨论】:

  • 找到解决办法了吗?
  • 接受的答案对我有用!

标签: python pandas dataframe sorting


【解决方案1】:

您可以将列 one 拆分为其组成部分,将它们作为列添加到数据框中,然后使用列 two 对它们进行排序。最后,删除临时列。

>>> (df.assign(lhs=df['one'].str[0], rhs=df['one'].str[1:].astype(int))
       .sort_values(['two', 'rhs', 'lhs'])
       .drop(columns=['lhs', 'rhs']))
  one  two
5  A1    1
1  B1    1
3  A2    1
2  A1    2
4  B1    2
0  A2    2

【讨论】:

  • 感谢您的帮助。这个答案有效,您只需要在 sort_values() 调用中切换 lhs 和 rhs 的顺序即可获得预期的排序顺序(看起来我在问题中的措辞有点偏离)。
  • 有没有办法使用“自然排序”对列进行排序(例如通过natsort 包)并避免创建临时列?
【解决方案2】:

使用 str.extract 创建一些基于 1) 字母 (a-zA-Z]+) 和 2) 数字 (\d+) 的临时列,然后删除它们:

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

df['one-letter'] = df['one'].str.extract('([a-zA-Z]+)')
df['one-number'] = df['one'].str.extract('(\d+)')
df = df.sort_values(['two', 'one-number', 'one-letter']).drop(['one-letter', 'one-number'], axis=1)
df
Out[38]: 
  one  two
5  A1    1
1  B1    1
3  A2    1
2  A1    2
4  B1    2

【讨论】:

    【解决方案3】:

    其中一个解决方案是使两列都 pd.Categorical 并将预期顺序作为参数“类别”传递。

    但是我有一些要求,我不能强制未知\意外的值,不幸的是,这就是 pd.Categorical 正在做的事情。此外 None 作为类别支持并自动强制。

    所以我的解决方案是使用一个键以自定义排序顺序对多个列进行排序:

    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)
    

    输出:

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

    请注意,此解决方案可能会很慢。

    【讨论】:

      猜你喜欢
      • 2020-02-29
      • 2010-11-20
      • 1970-01-01
      • 1970-01-01
      • 2021-03-27
      • 2017-12-26
      • 2020-04-23
      相关资源
      最近更新 更多