为什么numpy中没有'is' ufunc?

Why is there no 'is' ufunc in numpy?

我一定可以

a[a == 0] = something

a 中等于零的每个条目设置为 something。等价地,我可以写

a[np.equal(a, 0)] = something

现在,假设 adtype=object 的数组。我不能写 a[a is None],因为 a 本身当然不是 None。意图很明确:我希望比较 is 像任何其他 ufunc 一样被广播。 list from the docs 列出的内容与 is-unfunc.

完全不同

为什么会有 none,而且对我来说更有趣的是:什么是高性能替代品?

这里有两件事在起作用。

第一个(也是更重要的)一个是 is 直接在 Python 解释器中实现,没有重定向到 dunder 方法的选项。与许多其他对象一样,Numpy 数组具有实现 == 操作的 __eq__ 方法。 a is None 大致被视为 id(a) == id(None),在任何情况下都无法追索元素实现。这就是 python 的工作原理。

第二个方面是 numpy 从根本上是为存储数字而设计的。对象数组是将对对象的引用存储为数字的特殊情况。这似乎与列表存储对象引用的方式相同,但仅在处理引用时才相似。例如,列表的元素始终是对对象的引用,即使列表包含齐次整数也是如此。 dtype int 的 numpy 数组 包含 python 个对象。数组的每个连续元素都是原始二进制整数,而不是对 python 对象包装器的引用。即使 python 允许您覆盖 is 运算符,按元素应用也是没有意义的。

所以如果你想比较对象,使用python列表:

mylist = [...]
mylist = [something if x is None else x for x in mylist]

如果您坚持使用 numpy 数组,要么 (a) 使用数字数组并用其他东西标记 None 元素,例如 np.nan,要么 (b) 将数组视为列表.您将必须将 idis 应用于每个元素,它们是 python 构造,因此此时没有“性能”方式来执行此操作,或者 (c) 仅使用==,会触发python级的相等比较,对于单例None.

相当于is

除了像 reshape 这样的操作和不依赖于 dtype 的索引(itemsize 除外),对象 dtype 数组上的操作以列表理解速度执行,迭代元素并对每个元素应用适当的方法。有时该方法不存在,例如在执行 np.sin.

为了说明,请考虑来自评论之一的数组:

In [132]: a = np.array([1, None, 0, np.nan, ''])
In [133]: a
Out[133]: array([1, None, 0, nan, ''], dtype=object)

对象数组测试:

In [134]: a==None
Out[134]: array([False,  True, False, False, False])
In [135]: timeit a==None
5.16 µs ± 73.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

一个等价的理解:

In [136]: [x is None for x in a]
Out[136]: [False, True, False, False, False]
In [137]: timeit [x is None for x in a]
1.52 µs ± 18.6 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

它更快,即使我们将结果转换回数组(这不是一个便宜的步骤):

In [138]: timeit np.array([x is None for x in a])
4.67 µs ± 95.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

数组列表版本的迭代速度更快:

In [139]: timeit np.array([x is None for x in a.tolist()])
2.52 µs ± 48.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

让我们看看完整的赋值动作:

In [141]: a[[x is None for x in a.tolist()]]
Out[141]: array([None], dtype=object)
In [142]: %%timeit a1=a.copy()
     ...: a1[[x is None for x in a1.tolist()]] = np.nan
     ...: 
     ...: 
4.03 µs ± 10 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [143]: %%timeit a1=a.copy()
     ...: a1[a1==None] = np.nan
     ...: 
     ...: 
6.18 µs ± 28.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

通常的注意事项可能会有所不同。