如何在dtype=object的numpy数组上广播函数?

2 投票
2 回答
1702 浏览
提问于 2025-04-18 07:15

如果我有一个数字值的数组,因为数组的长度不一样,所以我必须用对象指针来代替直接使用值:

In [145]: import numpy as np

In [147]: a = np.array([[1,2],[3,4,5]])

In [148]: a
Out[148]: array([[1, 2], [3, 4, 5]], dtype=object)

In [150]: np.sin(a)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-150-58d97006f018> in <module>()
----> 1 np.sin(a)

In [152]: np.sin(a[0])
Out[152]: array([ 0.84147098,  0.90929743])

我该如何对这些实际的数字值应用一个函数,而不需要手动一个一个地遍历这个数组呢?

2 个回答

1

就像其他人提到的,最好避免使用数组 dtype=object

还有一种避免这个问题的方法,令人惊讶的是到目前为止没有人提到,那就是用 NaN 填充,以便让数组达到相同的形状。

a = np.array([[1,2],[3,4,5]])
maxlen = max(len(x) for x in a)
b = np.array([ x+[np.NaN]*(maxlen-len(x)) for x in a ])
b
=> array([[  1.,   2.,  nan], [  3.,   4.,   5.]])
b.shape
=> (2, 3)
np.sin(b) 
=> array([[ 0.84147098,  0.90929743,         nan],
          [ 0.14112001, -0.7568025 , -0.95892427]])

当然,处理包含 NaN 的数组时要小心,比如你可能想用 nanmax 代替 max,等等。

1

这里有几个不同的问题。首先,在numpy中对python对象进行广播并没有太大好处;在这种情况下,使用纯python可能会更有效。

>>> a = np.array([[1, 2, 3], [4, 5, 6]], dtype=object)
>>> b = np.arange(1, 7).reshape(2, 3)
>>> c = [[1, 2, 3], [4, 5, 6]]
>>> %timeit a * 5
100000 loops, best of 3: 4.28 µs per loop
>>> %timeit b * 5
100000 loops, best of 3: 2.08 µs per loop
>>> %timeit [[x * 5 for x in l] for l in c]
1000000 loops, best of 3: 998 ns per loop

这些速度的变化可能不太均匀,但你大概明白了。

其次,问题并不是直接和广播有关。numpy可以很高兴地对python列表进行广播,但结果可能和你预期的不一样:

>>> a = np.array([[1, 2, 3], [4, 5]], dtype=object)
>>> a * 5
    array([[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3],
       [4, 5, 4, 5, 4, 5, 4, 5, 4, 5]], dtype=object)

numpy允许数组中的对象定义它们自己的操作符或函数的版本。在这个例子中,python列表把*定义为重复!即使在混合类型的数组中也是如此;你可以试试这个:np.array([5, [1, 2]], dtype=object) * 5。之所以sin在这种情况下不能广播,是因为python列表根本没有定义sin

你可能会发现使用固定宽度的数组加上掩码会更好。

>>> np.ma.array([[1, 2, 3], [4, 5, 6]], mask=[[0, 0, 0], [0, 0, 1]])
    masked_array(data =
 [[1 2 3]
 [4 5 --]],
             mask =
 [[False False False]
 [False False  True]],
       fill_value = 999999)

如你所见,你可以通过这种方式“模拟”一个不规则数组,它的表现会和你预期的一样。

>>> a = np.ma.array([[1, 2, 3], [4, 5, 6]], mask=[[0, 0, 0], [0, 0, 1]])
>>> np.sin(a)
    masked_array(data =
 [[0.841470984808 0.909297426826 0.14112000806]
 [-0.756802495308 -0.958924274663 --]],
             mask =
 [[False False False]
 [False False  True]],
       fill_value = 1e+20)

值得提到的是,有几种方法可以创建掩码数组。在你的情况下,masked_invalid可能会很有用。

>>> np.ma.masked_invalid([[1, 2, 3], [4, 5, np.NaN]])
masked_array(data =
 [[1.0 2.0 3.0]
 [4.0 5.0 --]],
             mask =
 [[False False False]
 [False False  True]],
       fill_value = 1e+20)

你也可以通过条件来创建掩码数组:

>>> x = np.array([[1, 2, 3], [4, 5, 6]])
>>> np.ma.masked_where(x > 5, x)
masked_array(data =
 [[1 2 3]
 [4 5 --]],
             mask =
 [[False False False]
 [False False  True]],
       fill_value = 999999)

想要了解这些技术的完整列表,可以查看这里

撰写回答