使用Pandas Groupby和Apply函数时如何处理空值问题

3
我有一个在Pandas中的Dataframe,其中包含字母和两个日期作为列。我想使用shift()计算前一行的两个日期之间的工作日,只要Letter值相同(使用groupby())。我以前是用apply()来做这个的。这个方法在我传入某些数据时有效,但其中一个日期缺失。我将所有内容移动到一个函数中,并使用try/except子句处理缺失的值,但现在我的函数对所有值都返回NaN。似乎日期的None值会影响每次调用函数,而我认为它只会在groupby()中的Letter是A时才会这样做。
import pandas as pd
from datetime import datetime
import numpy as np

def business_days(x):
    try:
      return pd.DataFrame(np.busday_count(x['First Date'].tolist(), x['Last Date'].tolist())).shift().reset_index(drop=True)
    except ValueError:
        return None

df = pd.DataFrame(data=[['A', datetime(2016, 1, 7), None],
                        ['A', datetime(2016, 3, 1), datetime(2016, 3, 8)],
                        ['B', datetime(2016, 5, 1), datetime(2016, 5, 10)],
                        ['B', datetime(2016, 6, 5), datetime(2016, 6, 7)]],
                  columns=['Letter', 'First Date', 'Last Date'])

df['First Date'] = df['First Date'].apply(lambda x: x.to_datetime().date())
df['Last Date'] = df['Last Date'].apply(lambda x: x.to_datetime().date())

df['Gap'] = df.groupby('Letter').apply(business_days)

print df

实际输出:

  Letter  First Date   Last Date  Gap
0      A  2016-01-07         NaT  NaN
1      A  2016-03-01  2016-03-08  NaN
2      B  2016-05-01  2016-05-10  NaN
3      B  2016-06-05  2016-06-07  NaN

期望输出:

  Letter   First Day    Last Day   Gap
0      A  2016-01-07         NAT  NAN
1      A  2016-03-01  2016-03-08  NAN
2      B  2016-05-01  2016-05-10  NAN
3      B  2016-06-05  2016-06-07  7

在您当前的代码中,如果一个Letter组在单个行中有一个NaT,busday_count函数会引发ValueError并返回None。您希望整个Letter组的Gap值为NaN还是希望计算非NaT行的工作日? - unutbu
@unutbu 只针对非 NaT 行 - user2242044
1个回答

6
  • 暂时忽略NaT,请注意在应用groupby之前可以对整个df列执行np.busday_count计算。这将节省时间,因为它将多个对np.busday_count的调用(每个组一个)替换为对np.busday_count的单个调用。应用于大数组的一个函数调用通常比小数组上的多个函数调用更快。

  • 为了处理NaTs,您可以使用pd.notnull来识别具有NaTs的行,并屏蔽First DateLast Date,以便仅发送有效日期到np.busday_count。然后,您可以为那些日期包含NaTs的行填充NaN

  • 在我们计算所有工作日数之后,我们只需要按Letter进行分组并向下移位值。可以使用groupby/transform('shift')完成此操作。

import datetime as DT
import numpy as np
import pandas as pd

def business_days(start, end):
    mask = pd.notnull(start) & pd.notnull(end)
    start = start.values.astype('datetime64[D]')[mask]
    end = end.values.astype('datetime64[D]')[mask]
    result = np.empty(len(mask), dtype=float)
    result[mask] = np.busday_count(start, end)
    result[~mask] = np.nan
    return result

df = pd.DataFrame(data=[['A', DT.datetime(2016, 1, 7), None],
                        ['A', DT.datetime(2016, 3, 1), DT.datetime(2016, 3, 8)],
                        ['B', DT.datetime(2016, 5, 1), DT.datetime(2016, 5, 10)],
                        ['B', DT.datetime(2016, 6, 5), DT.datetime(2016, 6, 7)]],
                  columns=['Letter', 'First Date', 'Last Date'])

df['Gap'] = business_days(df['First Date'], df['Last Date'])
print(df)
#   Letter First Date  Last Date  Gap
# 0      A 2016-01-07        NaT  NaN
# 1      A 2016-03-01 2016-03-08  5.0
# 2      B 2016-05-01 2016-05-10  6.0
# 3      B 2016-06-05 2016-06-07  1.0

df['Gap'] = df.groupby('Letter')['Gap'].transform('shift')
print(df)

打印

  Letter First Date  Last Date  Gap
0      A 2016-01-07        NaT  NaN
1      A 2016-03-01 2016-03-08  NaN
2      B 2016-05-01 2016-05-10  NaN
3      B 2016-06-05 2016-06-07  6.0

1
我喜欢在一步中将计算应用于整个列。然而,我收到了“ValueError:无法使用NaT(非时间)日期计算工作日计数”的值错误。 - user2242044
嗯,我无法重现那个错误。你在运行我上面发布的代码时有遇到吗? - unutbu
如果您在上面发布的代码中遇到了ValueError错误,请问您使用的是哪个版本的Pandas和NumPy?如果您只在将business_days应用于自己的数据时(而不是在上面的代码中),遇到了该ValueError错误,请问传递给business_days的参数的数据类型是什么? - unutbu
我也遇到了与上面用户相同的错误。我正在使用Pandas 0.19.2和Numpy 1.11.3。<ipython-input-118-70ecbc042cd4> in business_days(start, end) 4 end = end.values.astype('datetime64[D]')[mask] 5 result = np.empty(len(mask), dtype=float) ----> 6 result[mask] = np.busday_count(start, end) 7 result[~mask] = np.nan 8 return result ValueError: 无法计算NaT(非时间)日期的工作日计数 - cyril
好的,升级到Numpy 1.12.1已经解决了这个问题。 - cyril

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