自然排序 Pandas DataFrame

34

我有一个带索引的pandas DataFrame想要进行自然排序。Natsort似乎不起作用。在构建DataFrame之前对索引进行排序似乎也没有帮助,因为我对DataFrame进行的操作似乎会破坏排序过程。您有什么想法可以自然地重新排序索引吗?

from natsort import natsorted
import pandas as pd

# An unsorted list of strings
a = ['0hr', '128hr', '72hr', '48hr', '96hr']
# Sorted incorrectly
b = sorted(a)
# Naturally Sorted 
c = natsorted(a)

# Use a as the index for a DataFrame
df = pd.DataFrame(index=a)
# Sorted Incorrectly
df2 = df.sort()
# Natsort doesn't seem to work
df3 = natsorted(df)

print(a)
print(b)
print(c)
print(df.index)
print(df2.index)
print(df3.index)

@sethMMorton 我本以为在对数据进行排序时,df3.index 应该与 c 相同,以使其与索引值保持一致。 - agf1997
如果pd.sort有一个key选项会很好,但它没有。这个答案提供了一个解决方法,可以让您传递从natsort_keygen生成的键。 - SethMMorton
1
我刚刚向 pandas 开发人员提交了一个正式请求,要求在此处的 sort 方法中添加 key:https://github.com/pydata/pandas/issues/9855 - SethMMorton
我的上述问题是一个重复的问题,活跃的问题是https://github.com/pydata/pandas/issues/3942。 - SethMMorton
现在pandas已经有了sort_valueskey参数,https://dev59.com/YV0b5IYBdhLWcg3wIOF9#63890954 现在应该成为被接受的答案。 - SethMMorton
3个回答

50

使用 sort_values 来排序 pandas >= 1.1.0

自从 pandas 1.1.0 以来,DataFrame.sort_values 新增了 key 参数,我们可以直接使用 natsort.natsort_keygen 对某一列进行排序,而不需要先将其设置为索引。

df = pd.DataFrame({
    "time": ['0hr', '128hr', '72hr', '48hr', '96hr'],
    "value": [10, 20, 30, 40, 50]
})

    time  value
0    0hr     10
1  128hr     20
2   72hr     30
3   48hr     40
4   96hr     50
from natsort import natsort_keygen

df.sort_values(
    by="time",
    key=natsort_keygen()
)

    time  value
0    0hr     10
3   48hr     40
2   72hr     30
4   96hr     50
1  128hr     20

7
这个提议的解决方法需要付出更多的努力才能实现,但是使用 key=natsort_keygen() 是否会更简单一些? - SethMMorton
同意,我已相应更新了我的答案。感谢你的提醒和编写出如此美妙的代码包 :) @SethMMorton - Erfan
如果我尝试对两列不同类型的数据进行排序,例如 df.sort_values(['Title', 'Copies'], ascending=[False, True], key=natsort_keygen()),我会得到这个错误 ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all()。我从 pd.read_csv 中获取了数据框,并提供了列名和类型。有什么办法可以解决吗? - Agostino

33

现在,pandassort_valuessort_index中均支持key,因此您现在应该参考这个答案并将所有的投票都发送到那里,因为它现在是正确的答案。

我将保留我的答案,以供那些卡在旧版pandas上的人使用,或者作为历史的好奇心。


被接受的答案回答了所提出的问题。 我想补充一下如何在DataFrame中对列使用natsort,因为这可能是下一个提出的问题。

In [1]: from pandas import DataFrame

In [2]: from natsort import natsorted, index_natsorted, order_by_index

In [3]: df = DataFrame({'a': ['a5', 'a1', 'a10', 'a2', 'a12'], 'b': ['b1', 'b1', 'b2', 'b2', 'b1']}, index=['0hr', '128hr', '72hr', '48hr', '96hr'])

In [4]: df
Out[4]: 
         a   b
0hr     a5  b1
128hr   a1  b1
72hr   a10  b2
48hr    a2  b2
96hr   a12  b1

正如被接受的回答所示,按索引排序是相当简单的:

In [5]: df.reindex(index=natsorted(df.index))
Out[5]: 
         a   b
0hr     a5  b1
48hr    a2  b2
72hr   a10  b2
96hr   a12  b1
128hr   a1  b1

如果您希望按同样的方式对某一列进行排序,那么您需要按照所需列重排索引。 natsort 提供了便利函数 index_natsortedorder_by_index 来完成这个操作。

In [6]: df.reindex(index=order_by_index(df.index, index_natsorted(df.a)))
Out[6]: 
         a   b
128hr   a1  b1
48hr    a2  b2
0hr     a5  b1
72hr   a10  b2
96hr   a12  b1

In [7]: df.reindex(index=order_by_index(df.index, index_natsorted(df.b)))
Out[7]: 
         a   b
0hr     a5  b1
128hr   a1  b1
96hr   a12  b1
72hr   a10  b2
48hr    a2  b2

如果您想按任意数量的列(或一列和索引)重新排序,可以使用zip(Python2上使用itertools.izip),以指定在多个列上进行排序。给定的第一列将是主要排序列,然后是次要排序列,然后是第三列等等...

In [8]: df.reindex(index=order_by_index(df.index, index_natsorted(zip(df.b, df.a))))
Out[8]: 
         a   b
128hr   a1  b1
0hr     a5  b1
96hr   a12  b1
48hr    a2  b2
72hr   a10  b2

In [9]: df.reindex(index=order_by_index(df.index, index_natsorted(zip(df.b, df.index))))
Out[9]: 
         a   b
0hr     a5  b1
96hr   a12  b1
128hr   a1  b1
48hr    a2  b2
72hr   a10  b2

这里是另一种使用 Categorical 对象的方法,这是由 pandas 开发人员告诉我的“适当”方法。需要 pandas >= 0.16.0 才能使用。目前,它只适用于列,但显然在 pandas >= 0.17.0 中,他们将添加 CategoricalIndex,允许在索引上使用此方法。
In [1]: from pandas import DataFrame

In [2]: from natsort import natsorted

In [3]: df = DataFrame({'a': ['a5', 'a1', 'a10', 'a2', 'a12'], 'b': ['b1', 'b1', 'b2', 'b2', 'b1']}, index=['0hr', '128hr', '72hr', '48hr', '96hr'])

In [4]: df.a = df.a.astype('category')

In [5]: df.a.cat.reorder_categories(natsorted(df.a), inplace=True, ordered=True)

In [6]: df.b = df.b.astype('category')

In [8]: df.b.cat.reorder_categories(natsorted(set(df.b)), inplace=True, ordered=True)

In [9]: df.sort('a')
Out[9]: 
         a   b
128hr   a1  b1
48hr    a2  b2
0hr     a5  b1
72hr   a10  b2
96hr   a12  b1

In [10]: df.sort('b')
Out[10]: 
         a   b
0hr     a5  b1
128hr   a1  b1
96hr   a12  b1
72hr   a10  b2
48hr    a2  b2

In [11]: df.sort(['b', 'a'])
Out[11]: 
         a   b
128hr   a1  b1
0hr     a5  b1
96hr   a12  b1
48hr    a2  b2
72hr   a10  b2
Categorical 对象允许您为 DataFrame 定义排序顺序。调用 reorder_categories 时提供的元素必须是唯一的,因此在列 "b" 上调用了 set
我留给用户决定这是否比使用 reindex 方法更好,因为它要求您在对 DataFrame 进行排序之前独立地对列数据进行排序(尽管我认为第二次排序相当高效)。
完全披露,我是 natsort 的作者。

15
如果您想对数据框进行排序,只需对索引或数据进行排序,并将其直接赋值给数据框的索引,而不是尝试将数据框作为参数传递,因为这会导致一个空列表:
In [7]:

df.index = natsorted(a)
df.index
Out[7]:
Index(['0hr', '48hr', '72hr', '96hr', '128hr'], dtype='object')

请注意,df.index = natsorted(df.index) 也可以起作用。

如果您将 df 作为参数传递,则会产生一个空列表,在这种情况下是因为 df 为空(没有列),否则它将返回已排序的列,这不是您想要的结果:

In [10]:

natsorted(df)
Out[10]:
[]

编辑

如果您想对索引进行排序,以便数据与索引一起重新排序,则可以使用reindex函数:

In [13]:

df=pd.DataFrame(index=a, data=np.arange(5))
df
Out[13]:
       0
0hr    0
128hr  1
72hr   2
48hr   3
96hr   4
In [14]:

df = df*2
df
Out[14]:
       0
0hr    0
128hr  2
72hr   4
48hr   6
96hr   8
In [15]:

df.reindex(index=natsorted(df.index))
Out[15]:
       0
0hr    0
48hr   6
72hr   4
96hr   8
128hr  2

请注意,您必须将reindex的结果分配给一个新的数据框或将其分配回原来的数据框本身,它不接受inplace参数。


我认为这样做没有抓住重点。我知道我可以自然排序a并将其用作索引,但是由于我对数据框执行的操作,我的实际代码会破坏数据框索引的排序。我需要在数据框中重新排序索引和相关数据。 - agf1997
3
那么你在这里询问的是什么,你想要在数据操作后对索引进行自然排序吗?你可以使用reindex函数,并在索引上调用natsorted函数,如df.reindex(index=natsorted(df.index)) - EdChum
@agf1997 使用 inplace=True - SethMMorton
1
@SethMMorton 抱歉,reindex 是少数几个不接受 inplace 参数的函数之一,所以是的,你必须将其分配给自身。 - EdChum
@agf1997 我认为Seth对我为什么得到了一个空列表感兴趣,我只是确认这种行为与sorted(df)一致,因此与你的问题无关。 - EdChum
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接