¿Cómo puedo dividir cada elemento de una matriz numpy de cadenas?

Numpy tiene algunas operaciones de cadena muy útiles, que vectorizan las operaciones de cadena de Python habituales.

En comparación con estas operaciones y con pandas.str , al módulo de cadenas de números parece faltarle uno muy importante: la capacidad de dividir cada cadena en la matriz. Por ejemplo,

 a = numpy.array(['hello', 'how', 'are', 'you']) numpy.char.sliceStr(a, slice(1, 3)) >>> numpy.array(['el', 'ow', 're' 'ou']) 

¿Me falta algún método obvio en el módulo con esta funcionalidad? De lo contrario, ¿hay una forma vectorizada rápida para lograr esto?

Aquí hay un enfoque vectorizado –

 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)) 

Ejecución de la muestra

 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') 

Prueba de tiempo de ejecución –

Probar todos los enfoques publicados por otros autores que podría ejecutar en mi final y también incluir el enfoque vectorizado de esta publicación.

Aquí están los tiempos –

 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 

La mayoría, si no todas las funciones en np.char aplican los métodos str existentes a cada elemento de la matriz. Es un poco más rápido que la iteración directa (o vectorize ), pero no de manera drástica.

No hay un cortador de cuerdas; Al menos no por ese tipo de nombre. Lo más cercano es la indexación con una porción:

 In [274]: 'astring'[1:3] Out[274]: 'st' In [275]: 'astring'.__getitem__ Out[275]:  In [276]: 'astring'.__getitem__(slice(1,4)) Out[276]: 'str' 

Un enfoque iterativo puede ser con frompyfunc (que también se usa mediante 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=' 

Podría verlo como una matriz de un solo carácter, y cortar ese

 In [289]: a.view('U1').reshape(4,-1)[:,1:3] Out[289]: array([['e', 'l'], ['o', 'w'], ['r', 'e'], ['o', 'u']], dtype=' 

Todavía tengo que averiguar cómo convertirlo de nuevo a 'U2'.

 In [290]: a.view('U1').reshape(4,-1)[:,1:3].copy().view('U2') Out[290]: array([['el'], ['ow'], ['re'], ['ou']], dtype=' 

El paso de la vista inicial muestra el databuffer como caracteres Py3 (estos serían bytes en un caso de cadena S o Py2):

 In [284]: a.view('U1') Out[284]: array(['h', 'e', 'l', 'l', 'o', 'h', 'o', 'w', '', '', 'a', 'r', 'e', '', '', 'y', 'o', 'u', '', ''], dtype=' 

Elegir las columnas 1: 3 equivale a seleccionar a.view('U1')[[1,2,6,7,11,12,16,17]] y luego remodelar y ver. Sin entrar en detalles, no me sorprende que requiera una copia.

Omisión interesante … Supongo que siempre puedes escribir el tuyo:

 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'] 

EDITAR: Aquí hay algunos puntos de referencia utilizando el texto de Ulysses por James Joyce. Parece que el ganador claro es la última estrategia de @ hpaulj. @Divakar se mete en la carrera mejorando la última estrategia de @ 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 

Para resolver esto, hasta ahora he estado transformando la array numpy en una Series pandas y viceversa. No es una solución bonita, pero funciona y funciona relativamente rápido.

 a = numpy.array(['hello', 'how', 'are', 'you']) pandas.Series(a).str[1:3].values array(['el', 'ow', 're', 'ou'], dtype=object)