【问题标题】:Length of each string in a NumPy arrayNumPy 数组中每个字符串的长度
【发布时间】:2017-11-19 03:25:12
【问题描述】:

NumPy 中是否有返回数组中每个字符串长度的内置操作?

我认为NumPy string operations 中的任何一个都不会这样做,对吗?

我可以使用for 循环来完成,但也许有更有效的方法?

import numpy as np
arr = np.array(['Hello', 'foo', 'and', 'whatsoever'], dtype='S256')

sizes = []
for i in arr:
    sizes.append(len(i))

print(sizes)
[5, 3, 3, 10]

【问题讨论】:

  • 对于中等大小的数组,列表理解等效项很好:[len(i) for i in arr]np.char 函数也不是很快,因为它们仍然需要对每个元素应用 string 方法。不要

标签: python python-3.x numpy


【解决方案1】:

您可以使用vectorizenumpy。它要快得多。

mylen = np.vectorize(len)
print mylen(arr)

【讨论】:

  • 在我的时间里,mylen 明显比这个小示例数组的列表理解要慢,而对于 1000 倍大的数组来说几乎没有快。 vectorize 不承诺速度。它确实使迭代多维数组的所有元素更容易。
  • @hpaulj 我指的是 for 循环。它比正常情况要快,如果数据很大,它仍然比列表理解要快。数据很重要。 :)
  • 与 pandas 相比,这比 df['s'].str.len()
  • 在 Numpy 中这个标准操作没有单行吗? np.vectorize(len)(arr) 很奇怪。
  • np.vectorize 没有更快的原因是因为np.vectorize 只是运行 Python for 循环——它确实编译。事实上,这种缺陷就是 Numba 被编写的原因。 Travis Oliphant(NumPy 的作者)就是这么说的:youtu.be/QpaapVaL8Fw?t=1621
【解决方案2】:

06/20 更新:满足 u+0000 字符和非连续输入 - 感谢 @M1L0U

这是几种方法的比较。

观察:

  • 对于大于 1000 行的输入大小,viewcasting + argmax 始终如一,并且在很大程度上是最快的。
  • Python 解决方案受益于先将数组转换为列表。
  • map 击败列表理解
  • np.frompyfunc 和较小程度的 np.vectorize 比他们的声誉更好

.

 contiguous
method ↓↓                  size →→  |     10|    100|   1000|  10000| 100000|1000000
------------------------------------+-------+-------+-------+-------+-------+-------
np.char.str_len                     |  0.006|  0.037|  0.350|  3.566| 34.781|345.803
list comprehension                  |  0.005|  0.036|  0.312|  2.970| 28.783|293.715
list comprehension after .tolist()  |  0.002|  0.011|  0.117|  1.119| 12.863|133.886
map                                 |  0.002|  0.008|  0.080|  0.745|  9.374|103.749
np.frompyfunc                       |  0.004|  0.011|  0.089|  0.861|  8.824| 88.739
np.vectorize                        |  0.025|  0.032|  0.132|  1.046| 12.112|133.863
safe argmax                         |  0.026|  0.026|  0.056|  0.290|  2.827| 32.583

 non-contiguous
method ↓↓                  size →→  |     10|    100|   1000|  10000| 100000|1000000
------------------------------------+-------+-------+-------+-------+-------+-------
np.char.str_len                     |  0.006|  0.037|  0.349|  3.575| 34.525|344.859
list comprehension                  |  0.005|  0.032|  0.306|  2.963| 29.445|292.527
list comprehension after .tolist()  |  0.002|  0.011|  0.117|  1.043| 11.081|130.644
map                                 |  0.002|  0.008|  0.081|  0.731|  7.967| 99.848
np.frompyfunc                       |  0.005|  0.012|  0.099|  0.885|  9.221| 92.700
np.vectorize                        |  0.025|  0.033|  0.146|  1.063| 11.844|134.505
safe argmax                         |  0.026|  0.026|  0.057|  0.291|  2.997| 31.161

代码:

import numpy as np

flist = []
def timeme(name):
    def wrap_gen(f):
        flist.append((name, f))
        return(f)
    return wrap_gen

@timeme("np.char.str_len")
def np_char():
    return np.char.str_len(A)

@timeme("list comprehension")
def lst_cmp():
    return [len(a) for a in A]

@timeme("list comprehension after .tolist()")
def lst_cmp_opt():
    return [len(a) for a in A.tolist()]

@timeme("map")
def map_():
    return list(map(len, A.tolist()))

@timeme("np.frompyfunc")
def np_fpf():
    return np.frompyfunc(len, 1, 1)(A)

@timeme("np.vectorize")
def np_vect():
    return np.vectorize(len)(A)
    
@timeme("safe argmax")
def np_safe():
    assert A.dtype.kind=="U"
    # work around numpy's refusal to viewcast non contiguous arrays
    v = np.lib.stride_tricks.as_strided(
        A[0,None].view("u4"),(A.size,A.itemsize>>2),(A.strides[0],4))
    v = v[:,::-1].astype(bool)
    l = v.argmax(1)
    empty = (~(v[:,0]|l.astype(bool))).nonzero()
    l = v.shape[1]-l
    l[empty] = 0
    return l
    
A = np.random.choice(
    "Blind\x00text do not use the quick brown fox jumps over the lazy dog "
    .split(" "),1000000)[::2]

for _, f in flist[:-1]:
    assert (f()==flist[-1][1]()).all()

from timeit import timeit

for j,tag in [(1,"contiguous"),(2,"non-contiguous")]:
    print('\n',tag)
    L = ['|+' + len(flist)*'|',
         [f"{'method ↓↓                  size →→':36s}", 36*'-']
         + [f"{name:36s}" for name, f in flist]]
    for N in (10, 100, 1000, 10000, 100000, 1000000):
        A = np.random.choice("Blind\x00text do not use the quick brown fox"
                             " jumps over the lazy dog ".split(" "),j*N)[::j]
        L.append([f"{N:>7d}", 7*'-']
                 + [f"{timeit(f, number=10)*100:7.3f}" for name, f in flist])
    for sep, *line in zip(*L):
        print(*line, sep=sep)

【讨论】:

  • 很好的答案! argmin 的技巧很好,但它有一个极端情况:它找到第一个 '\x00',不一定是字符串的结尾。在大多数情况下,尤其是 unicode 字符串,唯一的 '\x00' 位于末尾,但某些字符串(如字节)可能在任何位置包含一些字符串。为了找到字符串的大小,我们需要从右边开始数有多少'\x00'。例如,这可以由(numpy.cumsum((v != 0)[:, ::-1],axis=1) > 0).sum(axis=1) 完成
  • 嗨@M1L0U 我写这个答案已经有一段时间了,但我认为它是正确的。请注意,我不是转换为字节,而是转换为 uint32,这与 numpy U... dtype 是 4 字节固定宽度编码这一事实相匹配。我不认为四个对齐的零字节在任何地方都是可能的,但作为结束标记。还是他们?
  • 技术上是可行的,但是很奇怪。除了“错误地”将原始字节转换为 unicode 字符串之外,我没有看到其他用例。从numpy 的角度来看,A = numpy.array([ 'ABC\x00DEF', 'A\x00BC']) 是一个完全有效的U7 数组
  • @M1L0U 圣牛,你是对的。显然, u+0000 是有效的 unicode。我会更新帖子。
  • 因为我喜欢边缘情况,所以这里有另一个 ^^(可能对谷歌搜索的人有用)。如果数组不是 C 连续的,那么 view(np.uint32) 将失败。这种事情通常发生在使用step!=1 切片之后。例如做A[::-1]。要解决此问题,您可以在 .view 之前添加 A = numpy.asarray(A, order='C')。请注意,它仅在需要时复制数据
【解决方案3】:

使用来自Numpystr_len

sizes = np.char.str_len(arr)

str_len 文档:https://numpy.org/devdocs/reference/generated/numpy.char.str_len.html

【讨论】:

  • 这是迄今为止该问题的最佳答案。请点赞!它必须在顶部!
【解决方案4】:

对我来说,这将是要走的路:

sizes = [len(i) for i in arr]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-21
    • 2010-12-07
    • 2021-11-14
    • 2012-04-27
    • 2018-08-04
    • 1970-01-01
    相关资源
    最近更新 更多