【问题标题】:Pandas find changes in column with datetime condition熊猫在日期时间条件下发现列中的变化
【发布时间】:2019-07-25 19:15:30
【问题描述】:

我有包含员工信息的大型 DataFrame(1000000+ 行)。

它包含有关员工 ID、记录日期和流动状态的信息。如果营业额不等于 1,则表示员工当前正在工作。

此处示例:

test_df =\
pd.DataFrame({'empl_id': [1,2,3,1,2,3,1,2,1,2,1,2,3], 
              'rec_date':pd.to_datetime(['20080131','20080131','20080131', 
                                         '20080229', '20080229', '20080229', 
                                         '20080331', '20080331', 
                                         '20080430', '20080430',
                                         '20080531', '20080531', '20080531'], 
                                        format='%Y%m%d'), 
              'turnover':[0,0,0,0,0,1,0,0,0,0,1,0,0]})




+----+-----------+---------------------+------------+
|    |   empl_id | rec_date            |   turnover |
+====+===========+=====================+============+
|  0 |         1 | 2008-01-31 00:00:00 |          0 |
+----+-----------+---------------------+------------+
|  1 |         2 | 2008-01-31 00:00:00 |          0 |
+----+-----------+---------------------+------------+
|  2 |         3 | 2008-01-31 00:00:00 |          0 |
+----+-----------+---------------------+------------+
|  3 |         1 | 2008-02-29 00:00:00 |          0 |
+----+-----------+---------------------+------------+
|  4 |         2 | 2008-02-29 00:00:00 |          0 |
+----+-----------+---------------------+------------+
|  5 |         3 | 2008-02-29 00:00:00 |          1 |
+----+-----------+---------------------+------------+
|  6 |         1 | 2008-03-31 00:00:00 |          0 |
+----+-----------+---------------------+------------+
|  7 |         2 | 2008-03-31 00:00:00 |          0 |
+----+-----------+---------------------+------------+
|  8 |         1 | 2008-04-30 00:00:00 |          0 |
+----+-----------+---------------------+------------+
|  9 |         2 | 2008-04-30 00:00:00 |          0 |
+----+-----------+---------------------+------------+
| 10 |         1 | 2008-05-31 00:00:00 |          1 |
+----+-----------+---------------------+------------+
| 11 |         2 | 2008-05-31 00:00:00 |          0 |
+----+-----------+---------------------+------------+
| 12 |         3 | 2008-05-31 00:00:00 |          0 |
+----+-----------+---------------------+------------+

我需要显示员工是否在相对于记录中指定时间的 2 个月后离开公司

我找到了解决方案,但处理速度太慢。对于这样大小的 DataFrame,需要超过 54 小时!

这是我的脚本:

    from datetime import datetime, date, timedelta
    import calendar
    import pandas as pd
import numpy as np

    # look only in employees with turnover
    res = test_df.groupby('empl_id')['turnover'].sum()
    keys_with_turn = res[res>0].index

    # function for add months
    def add_months(sourcedate,months):
        month = sourcedate.month - 1 + months
        year = sourcedate.year + month // 12
        month = month % 12 + 1
        day = min(sourcedate.day, calendar.monthrange(year,month)[1])
        return date(year,month,day)

    # add 2 months and convert to timestamp
    test_df['rec_date_plus_2'] = test_df['rec_date'].apply(lambda x: add_months(x, 2))
    test_df['rec_date_plus_2'] = pd.to_datetime(test_df['rec_date_plus_2'])



    test_df['turn_nxt_2'] = np.nan

    for i in range(len(keys_with_turn)): # loop over employees ids
        for index, row in test_df[test_df['empl_id']==keys_with_turn[i]].iterrows(): # loop over all recs with employee
            a = row['rec_date']
            b = row['rec_date_plus_2']

            turn_coef = test_df[(test_df['empl_id']==keys_with_turn[i]) & 
                                ((test_df['rec_date']>=a) & (test_df['rec_date']<=b))]['turnover'].sum()

            test_df.loc[(test_df['rec_date']==a) & 
                        (test_df['empl_id']==keys_with_turn[i]), 'turn_nxt_2'] = 0 if turn_coef == 0 else 1     

    test_df['turn_nxt_2'].fillna(0, inplace=True)

我正在寻找的结果:

+----+-----------+---------------------+------------+--------------+
|    |   empl_id | rec_date            |   turnover |   turn_nxt_2 |
+====+===========+=====================+============+==============+
|  0 |         1 | 2008-01-31 00:00:00 |          0 |            0 |
+----+-----------+---------------------+------------+--------------+
|  1 |         2 | 2008-01-31 00:00:00 |          0 |            0 |
+----+-----------+---------------------+------------+--------------+
|  2 |         3 | 2008-01-31 00:00:00 |          0 |            1 |
+----+-----------+---------------------+------------+--------------+
|  3 |         1 | 2008-02-29 00:00:00 |          0 |            0 |
+----+-----------+---------------------+------------+--------------+
|  4 |         2 | 2008-02-29 00:00:00 |          0 |            0 |
+----+-----------+---------------------+------------+--------------+
|  5 |         3 | 2008-02-29 00:00:00 |          1 |            1 |
+----+-----------+---------------------+------------+--------------+
|  6 |         1 | 2008-03-31 00:00:00 |          0 |            1 |
+----+-----------+---------------------+------------+--------------+
|  7 |         2 | 2008-03-31 00:00:00 |          0 |            0 |
+----+-----------+---------------------+------------+--------------+
|  8 |         1 | 2008-04-30 00:00:00 |          0 |            1 |
+----+-----------+---------------------+------------+--------------+
|  9 |         2 | 2008-04-30 00:00:00 |          0 |            0 |
+----+-----------+---------------------+------------+--------------+
| 10 |         1 | 2008-05-31 00:00:00 |          1 |            1 |
+----+-----------+---------------------+------------+--------------+
| 11 |         2 | 2008-05-31 00:00:00 |          0 |            0 |
+----+-----------+---------------------+------------+--------------+
| 12 |         3 | 2008-05-31 00:00:00 |          0 |            0 |
+----+-----------+---------------------+------------+--------------+

如何做的更快更多的熊猫方式?

【问题讨论】:

  • 如何使用groupby.min().max() 来获取数据框中第一个月和最后一个月的每个empl_id。然后你可以计算差异。如果您想要性能,请避免使用.iterrows()。 (仍然 54 小时似乎太长了)
  • @niels-henkens,很好的建议,但员工可以在数据集期间多次雇用和退出
  • 啊,这就更复杂了
  • ^也许更新您的示例数据以显示此警告?
  • @user3471881 最后添加了这种情况

标签: python pandas performance datetime


【解决方案1】:

一种更简单的方法是制作一个重复的数据框并在适当的键上合并。

我做了一个简单的代码来演示,虽然还可以改进,这里是:

从您的原始数据集开始,我们导入一个新库并转换日期类型,以便稍后对其执行操作:

import pandas as pd
from dateutil.relativedelta import relativedelta

DF_1 = pd.DataFrame({'empl_id': [1,2,3,1,2,3,1,2,1,2,1,2], 
              'rec_date':pd.to_datetime(['20080131','20080131','20080131', 
                                         '20080229', '20080229', '20080229', 
                                         '20080331', '20080331', 
                                         '20080430', '20080430',
                                         '20080531', '20080531'], 
                                        format='%Y%m%d'), 
              'turnover':[0,0,0,0,0,1,0,0,0,0,1,0]})

print (type(DF_1.rec_date[0]))
DF_1.rec_date = DF_1.rec_date.map(lambda X: X.date())
print (type(DF_1.rec_date[0]))

现在我们创建一个重复的数据框,其中包含一个引用每个条目所需合并日期的合并列

DF_2 = DF_1.copy()
DF_2['merge_value'] = DF_2.rec_date - relativedelta(months=2)

我们还在原始数据框上创建了一个合并列,以便在 pd.merge 中更容易引用

DF_1['merge_value'] = DF_1.rec_date.values

现在我们要做的就是合并!

DF_1.merge(DF_2, on=['empl_id','merge_value'])

另一个建议是先在较小的样本上尝试,如果您认为主键不是主键,则合并有时会出现问题! (在这种情况下,如果 ['empl_id','merge_value'] 的相同组合有多个条目)

希望对你有帮助!

【讨论】:

  • 谢谢,我试试看。 Crossjoins 在大型数据集上会占用大量 RAM,但时间可能是可以接受的。
  • 输出看起来不像我想做的事情(:
  • 输出可以通过删除和重命名列进行格式化,如果键不是主要的(相同 ID 和日期的多个条目),它也可能导致与您预期的不同
猜你喜欢
  • 1970-01-01
  • 2018-11-15
  • 2017-09-24
  • 2021-12-21
  • 1970-01-01
  • 2022-06-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多