按照外部顺序,将Pandas的行按日期时间月份排序

4

我在对数据框进行排序时遇到了一些问题,我尝试使用这个问题来解决,但是并未成功。我有一个名为nudf的数据框,如下所示:

                     date  level_1      0 
0     2016-10-01 00:00:00      0.0  74.00    
1     2016-10-01 00:30:00      0.5     72    
2     2016-10-01 01:00:00      1.0     70    
3     2016-10-01 01:30:00      1.5     64    
4     2016-10-01 02:00:00      2.0     63    
5     2016-10-01 02:30:00      2.5     60    
...                   ...      ...    ...   
19003 2017-09-31 21:30:00     21.5    129    
19004 2017-09-31 22:00:00     22.0    118    
19005 2017-09-31 22:30:00     22.5    106  
19006 2017-09-31 23:00:00     23.0     84    
19007 2017-09-31 23:30:00     23.5     76    

我想要做的是按照外部月份顺序排序行:

[4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3]

这是最近12个月,从上个月开始算起。我想忽略年份,并按照上述顺序对每个月的行块进行排序。

例如,给出以下行:

0     2016-02-01 00:00:00      0.0  74.00    
1     2016-02-01 00:30:00      0.5     72    
2     2016-03-01 01:00:00      1.0     70    
3     2016-03-01 01:30:00      1.5     64    
4     2017-04-01 02:00:00      2.0     63    
5     2017-04-01 02:30:00      2.5     60  

结果应该是:
4     2017-04-01 02:00:00      2.0     63    
5     2017-04-01 02:30:00      2.5     60
0     2016-02-01 00:00:00      0.0  74.00    
1     2016-02-01 00:30:00      0.5     72    
2     2016-03-01 01:00:00      1.0     70    
3     2016-03-01 01:30:00      1.5     64      

我尝试过:

nudf['month'] = nudf.apply(lambda row: row.date.month, axis=1)
nudf.month = nudf.month.astype("category")
nudf.month.cat.set_categories([x.month for x in reversed(_get_last_x_months(12))], inplace=True)

nudf.sort_values(["month"], inplace=True)

但日期和时间顺序并未保持。
3个回答

3
你可以使用单独的 categorical, argsortiloc
另外,注意我使用了 kind='mergesort',因为 mergesort 是一种“稳定”的排序算法,能够保持等值行的相对顺序。
mcats = [4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3]
nudf.iloc[pd.Categorical(nudf.date.dt.month, mcats, True).argsort(kind='mergesort')]

                 date  level_1     0
4 2017-04-01 02:00:00      2.0  63.0
5 2017-04-01 02:30:00      2.5  60.0
0 2016-02-01 00:00:00      0.0  74.0
1 2016-02-01 00:30:00      0.5  72.0
2 2016-03-01 01:00:00      1.0  70.0
3 2016-03-01 01:30:00      1.5  64.0

您也可以添加该列

mcats = [4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3]
nudf = nudf.assign(month=pd.Categorical(nudf.date.dt.month, mcats, True))
nudf.sort_values('month', kind='mergesort')

                 date  level_1     0 month
4 2017-04-01 02:00:00      2.0  63.0     4
5 2017-04-01 02:30:00      2.5  60.0     4
0 2016-02-01 00:00:00      0.0  74.0     2
1 2016-02-01 00:30:00      0.5  72.0     2
2 2016-03-01 01:00:00      1.0  70.0     3
3 2016-03-01 01:30:00      1.5  64.0     3

如果我们尝试按月份排序,然后按日期排序,就不需要指定稳定排序,只需按两列排序即可。
mcats = [4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3]
nudf = nudf.assign(month=pd.Categorical(nudf.date.dt.month, mcats, True))
nudf.sort_values(['month', 'date'])

或者我们可以使用 np.lexsort 而不是 argsort 来基于多个数组返回一个排序置换。

mcats = [4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3]
nudf.iloc[np.lexsort(
    [nudf.date, pd.Categorical(nudf.date.dt.month, mcats, True)]
)]

又是那些方便的pandas工具之一,我以前从未了解过。非常有趣的答案!我一直在想这个顺序是如何工作的...我会进行测试。 - MattR
正如您可以在这里看到的那样,对于我拥有的较大文件,顺序不正确。 - Darkstarone
我修改了我的帖子,以指定一个稳定的排序算法。看看这是否有帮助。 - piRSquared
与归并排序的添加完美配合。非常感谢。 - Darkstarone

1
您可以通过使用%来进行排序。但是为了获得您想要的输出,首先按日期时间排序。
nudf.sort_values(by='date', inplace=True)

mcats = [x.month for x in reversed(_get_last_x_months(12))]
#[4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3]
nudf['m_fake'] = (nudf.date.dt.month+(12-mcats[0]))%12
nudf.sort_values(by='m_fake')

#                 date  val1  val2  m_fake
#4 2017-04-01 02:00:00   2.0    63       0
#5 2017-04-01 02:30:00   2.5    60       0
#0 2016-02-01 00:00:00   0.0    74       10
#1 2016-02-01 00:30:00   0.5    72       10
#2 2016-03-01 01:00:00   1.0    70       11
#3 2016-03-01 01:30:00   1.5    64       11

排序是由[x.month for x in reversed(_get_last_x_months(12))]生成的,所以我希望最好使用它。 - Darkstarone
好的,那么你只需要执行12-order[0],就再也不用担心它了。 - ALollz
可以在这里看到,在更大的数据集上,有些日期是无序的。 - Darkstarone
这是因为在大文件中它们没有按日期排序,而在示例中它们已经排序了。我刚刚更新了先按日期排序,然后应用月份排序的方法。应该可以了。 - ALollz
是的,那是我的错误,你的答案似乎也可以工作 - 感谢你的帮助。另一个答案先到了,但这也是一个好的解决方案。 - Darkstarone

1
你可以使用map来改变值并重新排序列。
# creates an int value based on the date using .dt.month (must be a date type)
df['month_value'] = df['date'].dt.month

# creates a dictionary that will remap the values
new_order = {4:1, 5:2, 6:3, 7:4, 8:5, 9:6, 10:7, 11:8, 12:9, 1:10, 2:11, 3:12}

# creates a new column based on the mapping
df['new_value'] = df['month_value'].map(new_order)

# sorts the values based on the new column
df.sort_values(by='new_value')

           date  month_value  new_value
4 2017-04-01            4          1
5 2017-04-01            4          1
0 2016-02-01            2         11
1 2016-02-01            2         11
2 2016-03-01            3         12
3 2016-03-01            3         12

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