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)