如何切片 numpy 字符串数组的每个元素?
How can I slice each element of a numpy array of strings?
Numpy 有一些非常有用的 string operations,可以向量化通常的 Python 字符串操作。
与这些操作和pandas.str
相比,numpy strings 模块似乎缺少一个非常重要的功能:对数组中的每个字符串进行切片的能力。例如,
a = numpy.array(['hello', 'how', 'are', 'you'])
numpy.char.sliceStr(a, slice(1, 3))
>>> numpy.array(['el', 'ow', 're' 'ou'])
我是否在具有此功能的模块中遗漏了一些明显的方法?否则,有没有快速矢量化的方法来实现这个?
有趣的遗漏......我想你总是可以自己写:
import numpy as np
def slicer(start=None, stop=None, step=1):
return np.vectorize(lambda x: x[start:stop:step], otypes=[str])
a = np.array(['hello', 'how', 'are', 'you'])
print(slicer(1, 3)(a)) # => ['el' 'ow' 're' 'ou']
编辑:这里有一些使用 James Joyce 的 Ulysses 文本的基准。 @hpaulj 的最后一个策略似乎是明显的赢家。 @Divakar 改进了@hpaulj 的最后一个策略。
import numpy as np
import requests
ulysses = requests.get('http://www.gutenberg.org/files/4300/4300-0.txt').text
a = np.array(ulysses.split())
# Ufunc
def slicer(start=None, stop=None, step=1):
return np.vectorize(lambda x: x[start:stop:step], otypes=[str])
%timeit slicer(1, 3)(a)
# => 1 loop, best of 3: 221 ms per loop
# Non-mutating loop
def loop1(a):
out = np.empty(len(a), dtype=object)
for i, word in enumerate(a):
out[i] = word[1:3]
%timeit loop1(a)
# => 1 loop, best of 3: 262 ms per loop
# Mutating loop
def loop2(a):
for i in range(len(a)):
a[i] = a[i][1:3]
b = a.copy()
%timeit -n 1 -r 1 loop2(b)
# 1 loop, best of 1: 285 ms per loop
# From @hpaulj's answer
%timeit np.frompyfunc(lambda x:x[1:3],1,1)(a)
# => 10 loops, best of 3: 141 ms per loop
%timeit np.frompyfunc(lambda x:x[1:3],1,1)(a).astype('U2')
# => 1 loop, best of 3: 170 ms per loop
%timeit a.view('U1').reshape(len(a),-1)[:,1:3].astype(object).sum(axis=1)
# => 10 loops, best of 3: 60.7 ms per loop
def slicer_vectorized(a,start,end):
b = a.view('S1').reshape(len(a),-1)[:,start:end]
return np.fromstring(b.tostring(),dtype='S'+str(end-start))
%timeit slicer_vectorized(a,1,3)
# => The slowest run took 5.34 times longer than the fastest.
# This could mean that an intermediate result is being cached.
# 10 loops, best of 3: 16.8 ms per loop
大多数(如果不是全部)np.char
中的函数将现有的 str
方法应用于数组的每个元素。它比直接迭代(或 vectorize
)快一点,但不是那么快。
没有字符串切片器;至少不是那种名字。最近的是用切片索引:
In [274]: 'astring'[1:3]
Out[274]: 'st'
In [275]: 'astring'.__getitem__
Out[275]: <method-wrapper '__getitem__' of str object at 0xb3866c20>
In [276]: 'astring'.__getitem__(slice(1,4))
Out[276]: 'str'
迭代方法可以使用 frompyfunc
(vectorize
也使用):
In [277]: a = numpy.array(['hello', 'how', 'are', 'you'])
In [278]: np.frompyfunc(lambda x:x[1:3],1,1)(a)
Out[278]: array(['el', 'ow', 're', 'ou'], dtype=object)
In [279]: np.frompyfunc(lambda x:x[1:3],1,1)(a).astype('U2')
Out[279]:
array(['el', 'ow', 're', 'ou'],
dtype='<U2')
我可以将其视为单个字符数组,然后将其切片
In [289]: a.view('U1').reshape(4,-1)[:,1:3]
Out[289]:
array([['e', 'l'],
['o', 'w'],
['r', 'e'],
['o', 'u']],
dtype='<U1')
我仍然需要弄清楚如何将它转换回 'U2'。
In [290]: a.view('U1').reshape(4,-1)[:,1:3].copy().view('U2')
Out[290]:
array([['el'],
['ow'],
['re'],
['ou']],
dtype='<U2')
初始查看步骤将数据缓冲区显示为 Py3 字符(在 S
或 Py2 字符串的情况下这些将是字节):
In [284]: a.view('U1')
Out[284]:
array(['h', 'e', 'l', 'l', 'o', 'h', 'o', 'w', '', '', 'a', 'r', 'e', '',
'', 'y', 'o', 'u', '', ''],
dtype='<U1')
选择 1:3 列相当于选择 a.view('U1')[[1,2,6,7,11,12,16,17]]
,然后重塑和查看。无需详细说明,我对它需要一份副本并不感到惊讶。
这是一个向量化的方法 -
def slicer_vectorized(a,start,end):
b = a.view((str,1)).reshape(len(a),-1)[:,start:end]
return np.fromstring(b.tostring(),dtype=(str,end-start))
样本运行-
In [68]: a = np.array(['hello', 'how', 'are', 'you'])
In [69]: slicer_vectorized(a,1,3)
Out[69]:
array(['el', 'ow', 're', 'ou'],
dtype='|S2')
In [70]: slicer_vectorized(a,0,3)
Out[70]:
array(['hel', 'how', 'are', 'you'],
dtype='|S3')
运行时测试 -
测试我可以 运行 由其他作者 post 编写的所有方法,还包括本文前面 post 中的矢量化方法。
这是时间安排 -
In [53]: # Setup input array
...: a = np.array(['hello', 'how', 'are', 'you'])
...: a = np.repeat(a,10000)
...:
# @Alberto Garcia-Raboso's answer
In [54]: %timeit slicer(1, 3)(a)
10 loops, best of 3: 23.5 ms per loop
# @hapaulj's answer
In [55]: %timeit np.frompyfunc(lambda x:x[1:3],1,1)(a)
100 loops, best of 3: 11.6 ms per loop
# Using loop-comprehension
In [56]: %timeit np.array([i[1:3] for i in a])
100 loops, best of 3: 12.1 ms per loop
# From this post
In [57]: %timeit slicer_vectorized(a,1,3)
1000 loops, best of 3: 787 µs per loop
为了解决这个问题,到目前为止,我一直在将 numpy array
转换为 pandas Series
并返回。这不是一个很好的解决方案,但它确实有效,而且速度相对较快。
a = numpy.array(['hello', 'how', 'are', 'you'])
pandas.Series(a).str[1:3].values
array(['el', 'ow', 're', 'ou'], dtype=object)
我完全同意这是一个疏漏,这就是我打开PR #20694的原因。如果被接受,您将能够完全按照您的建议进行操作,但使用更传统的名称 np.char.slice_
:
>>> a = np.array(['hello', 'how', 'are', 'you'])
>>> np.char.slice_(a, 1, 3)
array(['el', 'ow', 're' 'ou'])
PR 中的代码功能齐全,因此您可以制作它的工作副本,但它使用了一些 hack 来绕过一些限制。
对于这种简单的情况,可以使用简单的切片。从 numpy 1.23.0 开始,您可以查看不同大小 (PR #20722) 的 dtype 下的非连续数组。这意味着你可以做到
>>> a[:, None].view('U1')[:, 1:3].view('U2').squeeze()
array(['el', 'ow', 're' 'ou'])
Numpy 有一些非常有用的 string operations,可以向量化通常的 Python 字符串操作。
与这些操作和pandas.str
相比,numpy strings 模块似乎缺少一个非常重要的功能:对数组中的每个字符串进行切片的能力。例如,
a = numpy.array(['hello', 'how', 'are', 'you'])
numpy.char.sliceStr(a, slice(1, 3))
>>> numpy.array(['el', 'ow', 're' 'ou'])
我是否在具有此功能的模块中遗漏了一些明显的方法?否则,有没有快速矢量化的方法来实现这个?
有趣的遗漏......我想你总是可以自己写:
import numpy as np
def slicer(start=None, stop=None, step=1):
return np.vectorize(lambda x: x[start:stop:step], otypes=[str])
a = np.array(['hello', 'how', 'are', 'you'])
print(slicer(1, 3)(a)) # => ['el' 'ow' 're' 'ou']
编辑:这里有一些使用 James Joyce 的 Ulysses 文本的基准。 @hpaulj 的最后一个策略似乎是明显的赢家。 @Divakar 改进了@hpaulj 的最后一个策略。
import numpy as np
import requests
ulysses = requests.get('http://www.gutenberg.org/files/4300/4300-0.txt').text
a = np.array(ulysses.split())
# Ufunc
def slicer(start=None, stop=None, step=1):
return np.vectorize(lambda x: x[start:stop:step], otypes=[str])
%timeit slicer(1, 3)(a)
# => 1 loop, best of 3: 221 ms per loop
# Non-mutating loop
def loop1(a):
out = np.empty(len(a), dtype=object)
for i, word in enumerate(a):
out[i] = word[1:3]
%timeit loop1(a)
# => 1 loop, best of 3: 262 ms per loop
# Mutating loop
def loop2(a):
for i in range(len(a)):
a[i] = a[i][1:3]
b = a.copy()
%timeit -n 1 -r 1 loop2(b)
# 1 loop, best of 1: 285 ms per loop
# From @hpaulj's answer
%timeit np.frompyfunc(lambda x:x[1:3],1,1)(a)
# => 10 loops, best of 3: 141 ms per loop
%timeit np.frompyfunc(lambda x:x[1:3],1,1)(a).astype('U2')
# => 1 loop, best of 3: 170 ms per loop
%timeit a.view('U1').reshape(len(a),-1)[:,1:3].astype(object).sum(axis=1)
# => 10 loops, best of 3: 60.7 ms per loop
def slicer_vectorized(a,start,end):
b = a.view('S1').reshape(len(a),-1)[:,start:end]
return np.fromstring(b.tostring(),dtype='S'+str(end-start))
%timeit slicer_vectorized(a,1,3)
# => The slowest run took 5.34 times longer than the fastest.
# This could mean that an intermediate result is being cached.
# 10 loops, best of 3: 16.8 ms per loop
大多数(如果不是全部)np.char
中的函数将现有的 str
方法应用于数组的每个元素。它比直接迭代(或 vectorize
)快一点,但不是那么快。
没有字符串切片器;至少不是那种名字。最近的是用切片索引:
In [274]: 'astring'[1:3]
Out[274]: 'st'
In [275]: 'astring'.__getitem__
Out[275]: <method-wrapper '__getitem__' of str object at 0xb3866c20>
In [276]: 'astring'.__getitem__(slice(1,4))
Out[276]: 'str'
迭代方法可以使用 frompyfunc
(vectorize
也使用):
In [277]: a = numpy.array(['hello', 'how', 'are', 'you'])
In [278]: np.frompyfunc(lambda x:x[1:3],1,1)(a)
Out[278]: array(['el', 'ow', 're', 'ou'], dtype=object)
In [279]: np.frompyfunc(lambda x:x[1:3],1,1)(a).astype('U2')
Out[279]:
array(['el', 'ow', 're', 'ou'],
dtype='<U2')
我可以将其视为单个字符数组,然后将其切片
In [289]: a.view('U1').reshape(4,-1)[:,1:3]
Out[289]:
array([['e', 'l'],
['o', 'w'],
['r', 'e'],
['o', 'u']],
dtype='<U1')
我仍然需要弄清楚如何将它转换回 'U2'。
In [290]: a.view('U1').reshape(4,-1)[:,1:3].copy().view('U2')
Out[290]:
array([['el'],
['ow'],
['re'],
['ou']],
dtype='<U2')
初始查看步骤将数据缓冲区显示为 Py3 字符(在 S
或 Py2 字符串的情况下这些将是字节):
In [284]: a.view('U1')
Out[284]:
array(['h', 'e', 'l', 'l', 'o', 'h', 'o', 'w', '', '', 'a', 'r', 'e', '',
'', 'y', 'o', 'u', '', ''],
dtype='<U1')
选择 1:3 列相当于选择 a.view('U1')[[1,2,6,7,11,12,16,17]]
,然后重塑和查看。无需详细说明,我对它需要一份副本并不感到惊讶。
这是一个向量化的方法 -
def slicer_vectorized(a,start,end):
b = a.view((str,1)).reshape(len(a),-1)[:,start:end]
return np.fromstring(b.tostring(),dtype=(str,end-start))
样本运行-
In [68]: a = np.array(['hello', 'how', 'are', 'you'])
In [69]: slicer_vectorized(a,1,3)
Out[69]:
array(['el', 'ow', 're', 'ou'],
dtype='|S2')
In [70]: slicer_vectorized(a,0,3)
Out[70]:
array(['hel', 'how', 'are', 'you'],
dtype='|S3')
运行时测试 -
测试我可以 运行 由其他作者 post 编写的所有方法,还包括本文前面 post 中的矢量化方法。
这是时间安排 -
In [53]: # Setup input array
...: a = np.array(['hello', 'how', 'are', 'you'])
...: a = np.repeat(a,10000)
...:
# @Alberto Garcia-Raboso's answer
In [54]: %timeit slicer(1, 3)(a)
10 loops, best of 3: 23.5 ms per loop
# @hapaulj's answer
In [55]: %timeit np.frompyfunc(lambda x:x[1:3],1,1)(a)
100 loops, best of 3: 11.6 ms per loop
# Using loop-comprehension
In [56]: %timeit np.array([i[1:3] for i in a])
100 loops, best of 3: 12.1 ms per loop
# From this post
In [57]: %timeit slicer_vectorized(a,1,3)
1000 loops, best of 3: 787 µs per loop
为了解决这个问题,到目前为止,我一直在将 numpy array
转换为 pandas Series
并返回。这不是一个很好的解决方案,但它确实有效,而且速度相对较快。
a = numpy.array(['hello', 'how', 'are', 'you'])
pandas.Series(a).str[1:3].values
array(['el', 'ow', 're', 'ou'], dtype=object)
我完全同意这是一个疏漏,这就是我打开PR #20694的原因。如果被接受,您将能够完全按照您的建议进行操作,但使用更传统的名称 np.char.slice_
:
>>> a = np.array(['hello', 'how', 'are', 'you'])
>>> np.char.slice_(a, 1, 3)
array(['el', 'ow', 're' 'ou'])
PR 中的代码功能齐全,因此您可以制作它的工作副本,但它使用了一些 hack 来绕过一些限制。
对于这种简单的情况,可以使用简单的切片。从 numpy 1.23.0 开始,您可以查看不同大小 (PR #20722) 的 dtype 下的非连续数组。这意味着你可以做到
>>> a[:, None].view('U1')[:, 1:3].view('U2').squeeze()
array(['el', 'ow', 're' 'ou'])