我出现了不想要的行为np.vectorize,即,它更改了进入原始函数的参数的数据类型。我最初的问题是关于一般情况的,我将使用这个新问题来提出更具体的情况。
np.vectorize
(为什么要问第二个问题?为了说明问题,我创建 了一个关于更具体案例 的问题-从具体问题到更一般的问题总是比较容易。而且我 单独 创建了这个问题,因为我认为这是有助于保持一般情况以及对一般情况的答复(应该找到),而不会因为考虑解决任何特定问题而受到“污染”。)
因此,一个具体的例子。我住的地方,星期三是彩票日。因此,让我们从一个pandas带有日期列的数据帧开始,该数据列包含今年所有的星期三:
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在此函数上使用qualifiesdatetime / timestamp作为参数。
AttributeError: 'numpy.datetime64' object has no attribute 'day'
qualifies
非常感谢!
PS:有兴趣的是df:
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
如果使用,np.vectorize则最好指定otypes。在这种情况下,错误是由未指定vectorize使用时间的试验计算引起的otypes。另一种方法是将Series作为对象类型数组传递。
otypes
vectorize
np.vectorize有性能免责声明。 np.frompyfunc可能更快,甚至可以理解列表。
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。(vectorizedocs说使用excluded参数会降低性能,所以我正在使用lambdawith所使用的apply):
excluded
lambda
apply
In [34]: np.vectorize(lambda x:foo(x,[3,8]))(df['date']) 2020-01-01T00:00:00.000000000 <class 'numpy.datetime64'> 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[34]: array([ True, True, True, True, True])
第一行是datetime64产生问题的。其他行是原始的熊猫对象。如果指定otypes,该问题将消失:
datetime64
In [35]: np.vectorize(lambda x:foo(x,[3,8]), otypes=['bool'])(df['date']) 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[35]: array([ True, True, True, True, True])
适用:
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
甲datetime64D型是通过在包裹的系列生产的np.array。
np.array
In [37]: np.array(df['date']) Out[37]: array(['2020-01-01T00:00:00.000000000', '2020-01-08T00:00:00.000000000', '2020-01-15T00:00:00.000000000', '2020-01-22T00:00:00.000000000', '2020-01-29T00:00:00.000000000'], dtype='datetime64[ns]')
显然np.vectorize,在执行初始试验计算时会进行这种包装,但在进行主迭代时则不会。指定otypes跳过该试用计算。尽管这是一个比较模糊的情况,但是该试用计算在其他SO中引起了问题。
在过去,当我进行测试时,np.vectorize它比更明确的迭代要慢。它确实有明确的性能免责声明。当函数需要多个输入并需要广播的好处时,这是最有价值的。仅使用一个参数很难证明。
np.frompyfuncunderlies vectorize,但返回对象dtype。它的速度通常比数组上的显式迭代快2倍,尽管其速度与列表上的迭代速度相似。在创建和使用numpy对象数组时,它似乎最有用。在这种情况下,我没有使它起作用。
该np.vectorize代码是在np.lib.function_base.py。
np.lib.function_base.py
如果otypes未指定,则代码执行以下操作:
args = [asarray(arg) for arg in args] inputs = [arg.flat[0] for arg in args] outputs = func(*inputs)
它将每个参数(此处只有一个)放入一个数组,并采用第一个元素。然后将其传递给func。如图Out[37]所示,这将是一个datetime64对象。
func
Out[37]
要使用frompyfunc,我需要转换的dtype df['date']:
frompyfunc
df['date']
In [68]: np.frompyfunc(lambda x:foo(x,[3,8]), 1,1)(df['date']) 1577836800000000000 <class 'int'> 1578441600000000000 <class 'int'> ...
没有它,它传递int给函数,并传递给熊猫时间对象:
int
In [69]: np.frompyfunc(lambda x:foo(x,[3,8]), 1,1)(df['date'].astype(object)) 2020-01-01 00:00:00 <class 'pandas._libs.tslibs.timestamps.Timestamp'> 2020-01-08 00:00:00 <class 'pandas._libs.tslibs.timestamps.Timestamp'> ...
因此,这种使用方式qualifies是:
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)
这就解释了为什么vectorize可以otypes工作-它frompyfunc与对象dtype输入一起使用。将其与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是确保将dtype对象传递给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']]