首页 > 解决方案 > Numpy 向量化搞乱了数据类型 (2)

问题描述

我有不想要的行为np.vectorize,即它改变了进入原始函数的参数的数据类型。我最初的问题是关于一般情况的,我将使用这个新问题来询问更具体的情况。

(为什么是第二个问题?我创建了这个关于更具体案例的问题以说明问题 - 从具体到更一般总是更容易。我已经单独创建了这个问题,因为我认为它是对保留一般情况以及对它的一般答案(如果找到)很有用,而不是因为考虑解决任何特定问题而被“污染”。)

所以,一个具体的例子。我住的地方,星期三是彩票日。因此,让我们从一个pandas包含今年所有星期三的日期列的数据框开始:

df = pd.DataFrame({'date': pd.date_range('2020-01-01', freq='7D', periods=53)})

我想看看我会在哪些可能的日子里玩。每个月的月初和月底我都不会觉得特别幸运,有些月份我觉得特别不吉利。因此,我使用此函数来查看日期是否符合条件:

def qualifies(dt, excluded_months = []):
    #Date qualifies, if...
    #. it's on or after the 5th of the month; and
    #. at least 5 days remain till the end of the month (incl. date itself); and
    #. it's not in one of the months in excluded_months.
    if dt.day < 5:
        return False
    if (dt + pd.tseries.offsets.MonthBegin(1) - dt).days < 5:
        return False
    if dt.month in excluded_months:
        return False
    return True

我希望你意识到这个例子仍然有些做作;)但它更接近我想要做的事情。我尝试以两种方式应用此功能:

df['qualifies1'] = df['date'].apply(lambda x: qualifies(x, [3, 8]))
df['qualifies2'] = np.vectorize(qualifies, excluded=[1])(df['date'], [3, 8])

据我所知,两者都应该工作,我更喜欢后者,因为前者速度慢而且不受欢迎编辑:我了解到,第一个也不赞成大声笑。

但是,只有第一个成功,第二个失败并带有AttributeError: 'numpy.datetime64' object has no attribute 'day'. 所以我的问题是,如果有一种方法可以np.vectorize在这个函数上使用qualifies,它需要一个日期时间/时间戳作为参数。

非常感谢!

PS:对于感兴趣的,这是df

In [15]: df
Out[15]: 
         date  qualifies1
0  2020-01-01       False
1  2020-01-08        True
2  2020-01-15        True
3  2020-01-22        True
4  2020-01-29       False
5  2020-02-05        True
6  2020-02-12        True
7  2020-02-19        True
8  2020-02-26       False
9  2020-03-04       False
10 2020-03-11       False
11 2020-03-18       False
12 2020-03-25       False
13 2020-04-01       False
14 2020-04-08        True
15 2020-04-15        True
16 2020-04-22        True
17 2020-04-29       False
18 2020-05-06        True
19 2020-05-13        True
20 2020-05-20        True
21 2020-05-27        True
22 2020-06-03       False
23 2020-06-10        True
24 2020-06-17        True
25 2020-06-24        True
26 2020-07-01       False
27 2020-07-08        True
28 2020-07-15        True
29 2020-07-22        True
30 2020-07-29       False
31 2020-08-05       False
32 2020-08-12       False
33 2020-08-19       False
34 2020-08-26       False
35 2020-09-02       False
36 2020-09-09        True
37 2020-09-16        True
38 2020-09-23        True
39 2020-09-30       False
40 2020-10-07        True
41 2020-10-14        True
42 2020-10-21        True
43 2020-10-28       False
44 2020-11-04       False
45 2020-11-11        True
46 2020-11-18        True
47 2020-11-25        True
48 2020-12-02       False
49 2020-12-09        True
50 2020-12-16        True
51 2020-12-23        True
52 2020-12-30       False

标签: pythonpandasnumpydate

解决方案


我认为@rpanai 在原帖上的回答仍然是最好的。在这里分享我的测试:

def qualifies(dt, excluded_months = []):
    if dt.day < 5:
        return False
    if (dt + pd.tseries.offsets.MonthBegin(1) - dt).days < 5:
        return False
    if dt.month in excluded_months:
        return False
    return True

def new_qualifies(dt, excluded_months = []):
    dt = pd.Timestamp(dt)
    if dt.day < 5:
        return False
    if (dt + pd.tseries.offsets.MonthBegin(1) - dt).days < 5:
        return False
    if dt.month in excluded_months:
        return False
    return True

df = pd.DataFrame({'date': pd.date_range('2020-01-01', freq='7D', periods=12000)})

申请方法:

%%timeit
df['qualifies1'] = df['date'].apply(lambda x: qualifies(x, [3, 8]))

每个循环 385 毫秒 ± 21.6 毫秒(平均值 ± 标准偏差。7 次运行,每个循环 1 个)


转换方法:

%%timeit
df['qualifies1'] = df['date'].apply(lambda x: new_qualifies(x, [3, 8]))

每个循环 389 毫秒 ± 12.6 毫秒(平均值 ± 标准偏差。7 次运行,每个循环 1 个)


矢量化代码:

%%timeit
df['qualifies2'] =  np.logical_not((df['date'].dt.day<5).values | \
    ((df['date']+pd.tseries.offsets.MonthBegin(1)-df['date']).dt.days < 5).values |\
    (df['date'].dt.month.isin([3, 8])).values)

每个循环 4.83 毫秒 ± 117 微秒(平均值 ± 标准偏差。7 次运行,每次 100 次循环)


推荐阅读