Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/321.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python Numpy矢量化会弄乱数据类型(2)_Python_Pandas_Numpy_Date - Fatal编程技术网

Python Numpy矢量化会弄乱数据类型(2)

Python Numpy矢量化会弄乱数据类型(2),python,pandas,numpy,date,Python,Pandas,Numpy,Date,我发现np.vectorize中出现了一些不必要的行为,也就是说,它将参数的数据类型更改为原始函数。我的天,我将用这个新问题问一个更具体的案例 (为什么要问第二个问题?为了说明问题,我创建了一个关于更具体案例的问题——从具体案例到更一般的案例总是比较容易。我单独创建了这个问题,因为我认为保留一般案例以及对其的一般答案(如果找到答案)是有用的),并且不会因为思考解决任何特定问题而受到“污染”。) 这是一个具体的例子。在我住的地方,星期三是彩票日。因此,让我们从一个带有日期列的pandas数据框开始

我发现
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
据我所知,两者都应该有效,我更喜欢后者,因为前者速度慢,效率高编辑:我知道第一个也是不赞成lol的

但是,只有第一个成功,第二个失败,原因是
AttributeError:'numpy.datetime64'对象没有属性'day'
。所以我的问题是,是否有一种方法可以在这个函数上使用
np.vectorize
限定了
,它以datetime/timestamp作为参数。

非常感谢

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

正如在原始问题中一样,我可以通过在函数的第一个
if
-语句之前添加
dt=pd.to_datetime(dt)
,将问题“解决”为
pandas
datetime对象


老实说,这感觉就像修补了一些坏了的东西,不应该使用。我将只使用
。应用
,然后接受性能冲击。任何觉得有更好的解决方案的人都非常欢迎分享:)

我认为@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)})
每个回路385 ms±21.6 ms(7次运行的平均值±标准偏差,每个回路1次)


换算方法:

%%timeit
df['qualifies1'] = df['date'].apply(lambda x: new_qualifies(x, [3, 8]))
每个回路389 ms±12.6 ms(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)
%%timeit
df['qualifies2']=np.logical_not((df['date'].dt.daysummmary
如果使用
np.vectorize
最好指定
otypes
。在这种情况下,错误是由
vectorize
在未指定
otypes
时使用的试算引起的。另一种方法是将序列作为对象类型数组传递

np.vectorize
有一个性能免责声明。
np.frompyfunc
可能更快,甚至可以理解列表

测试矢量化 让我们定义一个更简单的函数-一个显示参数类型的函数:

In [31]: def foo(dt, excluded_months=[]): 
    ...:     print(dt,type(dt)) 
    ...:     return True 
和更小的数据帧:

In [32]: df = pd.DataFrame({'date': pd.date_range('2020-01-01', freq='7D', perio
    ...: ds=5)})                                                                
In [33]: df                                                                     
Out[33]: 
        date
0 2020-01-01
1 2020-01-08
2 2020-01-15
3 2020-01-22
4 2020-01-29
测试
vectorize
vectorize
文档说使用
excluded
参数会降低性能,因此我使用
lambda
,就像使用
apply
一样):

适用于:

In [36]: df['date'].apply(lambda x: foo(x, [3, 8]))                             
2020-01-01 00:00:00 <class 'pandas._libs.tslibs.timestamps.Timestamp'>
2020-01-08 00:00:00 <class 'pandas._libs.tslibs.timestamps.Timestamp'>
2020-01-15 00:00:00 <class 'pandas._libs.tslibs.timestamps.Timestamp'>
2020-01-22 00:00:00 <class 'pandas._libs.tslibs.timestamps.Timestamp'>
2020-01-29 00:00:00 <class 'pandas._libs.tslibs.timestamps.Timestamp'>
Out[36]: 
0    True
1    True
2    True
3    True
4    True
Name: date, dtype: bool
显然,
np.vectorize
在执行初始试算时会进行这种包装,但在执行主要迭代时不会。指定
otypes
会跳过该试算。该试算会在其他SO中引发问题,尽管这是一种更为模糊的情况

在过去,当我测试
np.vectorize
时,它比更明确的迭代要慢。它确实有一个明确的性能声明。当函数需要多个输入时,它最有价值,并且需要广播的好处。当只使用一个参数时,很难证明它的合理性

np.frompyfunc
作为
vectorize
的基础,但返回一个对象数据类型。它通常比数组上的显式迭代快2倍,尽管速度与列表上的迭代相似。在创建和使用numpy对象数组时,它似乎最有用。在这种情况下,我没有让它工作

矢量化码
np.vectorize
代码位于
np.lib.function\u base.py

如果未指定
o类型
,则代码会:

        args = [asarray(arg) for arg in args]
        inputs = [arg.flat[0] for arg in args]
        outputs = func(*inputs)
它将每个参数(这里只有一个)放入一个数组,并获取第一个元素。然后将其传递给
func
。如
Out[37]
所示,这将是一个
datetime64
对象

frompyfunc 要使用Pyfunc的
,我需要转换
df['date']
的数据类型:

In [68]: np.frompyfunc(lambda x:foo(x,[3,8]), 1,1)(df['date'])                  
1577836800000000000 <class 'int'>
1578441600000000000 <class 'int'>
...
因此,
的这种用法符合以下条件:

In [71]: np.frompyfunc(lambda x:qualifies(x,[3,8]),1,1)(df['date'].astype(object))                                                                     
Out[71]: 
0    False
1     True
2     True
3     True
4    False
Name: date, dtype: object
对象数据类型 对于主迭代,
np.vectorize

      ufunc = frompyfunc(_func, len(args), nout)
      # Convert args to object arrays first
        inputs = [array(a, copy=False, subok=True, dtype=object)
                  for a in args]
        outputs = ufunc(*inputs)
这就解释了为什么使用
otypes
进行矢量化
有效-它将
frompyfunc
与对象数据类型输入一起使用。将此与
Out[37]
进行对比:

In [74]: np.array(df['date'], dtype=object)                                     
Out[74]: 
array([Timestamp('2020-01-01 00:00:00'), Timestamp('2020-01-08 00:00:00'),
       Timestamp('2020-01-15 00:00:00'), Timestamp('2020-01-22 00:00:00'),
       Timestamp('2020-01-29 00:00:00')], dtype=object)
指定
otypes
的另一种方法是确保将对象数据类型传递给
vectorize

In [75]: np.vectorize(qualifies, excluded=[1])(df['date'].astype(object), [3, 8])                                                                      
Out[75]: array([False,  True,  True,  True, False])
这似乎是最快的版本:

np.frompyfunc(lambda x: qualifies(x,[3,8]),1,1)(np.array(df['date'],object))    
或者更好的是,一个简单的Python迭代:

[qualifies(x,[3,8]) for x in df['date']] 

这种方法真的比
.apply
的性能快吗?在我的测试中,它实际上是很慢的。我刚刚尝试过,有趣的是,它不是。我尝试过短(53行)和长(5300行)数据帧,时间间隔最多只有几%。这很有趣,因为如果你看看我对原始问题的回答(链接在这个答案中),您可以看到,当要向量化的函数只使用一个参数时,速度是原来的两倍。我还通过添加
dt=pd.Timestamp(dt)
来解决这个问题,因为属性
day
来自
Timestamp
类。似乎
vectorize
访问
df[
In [71]: np.frompyfunc(lambda x:qualifies(x,[3,8]),1,1)(df['date'].astype(object))                                                                     
Out[71]: 
0    False
1     True
2     True
3     True
4    False
Name: date, dtype: object
      ufunc = frompyfunc(_func, len(args), nout)
      # Convert args to object arrays first
        inputs = [array(a, copy=False, subok=True, dtype=object)
                  for a in args]
        outputs = ufunc(*inputs)
In [74]: np.array(df['date'], dtype=object)                                     
Out[74]: 
array([Timestamp('2020-01-01 00:00:00'), Timestamp('2020-01-08 00:00:00'),
       Timestamp('2020-01-15 00:00:00'), Timestamp('2020-01-22 00:00:00'),
       Timestamp('2020-01-29 00:00:00')], dtype=object)
In [75]: np.vectorize(qualifies, excluded=[1])(df['date'].astype(object), [3, 8])                                                                      
Out[75]: array([False,  True,  True,  True, False])
np.frompyfunc(lambda x: qualifies(x,[3,8]),1,1)(np.array(df['date'],object))    
[qualifies(x,[3,8]) for x in df['date']]