Crear nueva columna con valores incrementales eficientemente.

Estoy creando una columna con valores incrementales y luego agregando una cadena al comienzo de la columna. Cuando se usa en datos grandes, esto es muy lento. Por favor, sugiera una manera más rápida y eficiente para el mismo.

df['New_Column'] = np.arange(df[0])+1 df['New_Column'] = 'str' + df['New_Column'].astype(str) 

Entrada

 id Field Value 1 A 1 2 B 0 3 D 1 

Salida

 id Field Value New_Column 1 A 1 str_1 2 B 0 str_2 3 D 1 str_3 

Añadiré dos más en la mezcla

Numpy

 from numpy.core.defchararray import add df.assign(new=add('str_', np.arange(1, len(df) + 1).astype(str))) id Field Value new 0 1 A 1 str_1 1 2 B 0 str_2 2 3 D 1 str_3 

f-string en la comprensión

Python 3.6+

 df.assign(new=[f'str_{i}' for i in range(1, len(df) + 1)]) id Field Value new 0 1 A 1 str_1 1 2 B 0 str_2 2 3 D 1 str_3 

Prueba de tiempo

Conclusiones

La comprensión gana el día con un rendimiento relativo a la simplicidad. Eso sí, este era el método propuesto por csᴘᴇᴇᴅ. Aprecio los votos positivos (gracias) pero vamos a dar crédito a lo que se debe.

Citonizar la comprensión no parecía ayudar. Tampoco las cuerdas.
El numexp de Divakar aparece en la parte superior para el rendimiento sobre datos más grandes.

Funciones

 %load_ext Cython 

 %%cython def gen_list(l, h): return ['str_%s' % i for i in range(l, h)] 

 pir1 = lambda d: d.assign(new=[f'str_{i}' for i in range(1, len(d) + 1)]) pir2 = lambda d: d.assign(new=add('str_', np.arange(1, len(d) + 1).astype(str))) cld1 = lambda d: d.assign(new=['str_%s' % i for i in range(1, len(d) + 1)]) cld2 = lambda d: d.assign(new=gen_list(1, len(d) + 1)) jez1 = lambda d: d.assign(new='str_' + pd.Series(np.arange(1, len(d) + 1), d.index).astype(str)) div1 = lambda d: d.assign(new=create_inc_pattern(prefix_str='str_', start=1, stop=len(d) + 1)) div2 = lambda d: d.assign(new=create_inc_pattern_numexpr(prefix_str='str_', start=1, stop=len(d) + 1)) 

Pruebas

 res = pd.DataFrame( np.nan, [10, 30, 100, 300, 1000, 3000, 10000, 30000], 'pir1 pir2 cld1 cld2 jez1 div1 div2'.split() ) for i in res.index: d = pd.concat([df] * i) for j in res.columns: stmt = f'{j}(d)' setp = f'from __main__ import {j}, d' res.at[i, j] = timeit(stmt, setp, number=200) 

Resultados

 res.plot(loglog=True) 

introduzca la descripción de la imagen aquí

 res.div(res.min(1), 0) pir1 pir2 cld1 cld2 jez1 div1 div2 10 1.243998 1.137877 1.006501 1.000000 1.798684 1.277133 1.427025 30 1.009771 1.144892 1.012283 1.000000 2.144972 1.210803 1.283230 100 1.090170 1.567300 1.039085 1.000000 3.134154 1.281968 1.356706 300 1.061804 2.260091 1.072633 1.000000 4.792343 1.051886 1.305122 1000 1.135483 3.401408 1.120250 1.033484 7.678876 1.077430 1.000000 3000 1.310274 5.179131 1.359795 1.362273 13.006764 1.317411 1.000000 10000 2.110001 7.861251 1.942805 1.696498 17.905551 1.974627 1.000000 30000 2.188024 8.236724 2.100529 1.872661 18.416222 1.875299 1.000000 

Más funciones

 def create_inc_pattern(prefix_str, start, stop): N = stop - start # count of numbers W = int(np.ceil(np.log10(N+1))) # width of numeral part in string dl = len(prefix_str)+W # datatype length dt = np.uint8 # int datatype for string to-from conversion padv = np.full(W,48,dtype=np.uint8) a0 = np.r_[np.fromstring(prefix_str,dtype='uint8'), padv] r = np.arange(start, stop) addn = (r[:,None] // 10**np.arange(W-1,-1,-1))%10 a1 = np.repeat(a0[None],N,axis=0) a1[:,len(prefix_str):] += addn.astype(dt) a1.shape = (-1) a2 = np.zeros((len(a1),4),dtype=dt) a2[:,0] = a1 return np.frombuffer(a2.ravel(), dtype='U'+str(dl)) import numexpr as ne def create_inc_pattern_numexpr(prefix_str, start, stop): N = stop - start # count of numbers W = int(np.ceil(np.log10(N+1))) # width of numeral part in string dl = len(prefix_str)+W # datatype length dt = np.uint8 # int datatype for string to-from conversion padv = np.full(W,48,dtype=np.uint8) a0 = np.r_[np.fromstring(prefix_str,dtype='uint8'), padv] r = np.arange(start, stop) r2D = r[:,None] s = 10**np.arange(W-1,-1,-1) addn = ne.evaluate('(r2D/s)%10') a1 = np.repeat(a0[None],N,axis=0) a1[:,len(prefix_str):] += addn.astype(dt) a1.shape = (-1) a2 = np.zeros((len(a1),4),dtype=dt) a2[:,0] = a1 return np.frombuffer(a2.ravel(), dtype='U'+str(dl)) 

Cuando todo lo demás falla, usa una lista de comprensión :

 df['NewColumn'] = ['str_%s' %i for i in range(1, len(df) + 1)] 

Más aceleraciones son posibles si usted cythonize su función:

 %load_ext Cython %%cython def gen_list(l, h): return ['str_%s' %i for i in range(l, h)] 

Tenga en cuenta que este código se ejecuta en Python3.6.0 (IPython6.2.1). Solución mejorada gracias a @hpaulj en los comentarios.


 # @jezrael's fastest solution %%timeit df['NewColumn'] = np.arange(len(df['a'])) + 1 df['NewColumn'] = 'str_' + df['New_Column'].map(str) 547 ms ± 13.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) 

 # in this post - no cython %timeit df['NewColumn'] = ['str_%s'%i for i in range(n)] 409 ms ± 9.36 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) 

 # cythonized list comp %timeit df['NewColumn'] = gen_list(1, len(df) + 1) 370 ms ± 9.23 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) 

Enfoque propuesto

Después de juguetear un poco con la cadena y los dtypes numéricos y aprovechar la fácil interoperabilidad entre ellos, aquí hay algo que terminé para obtener cadenas rellenadas con ceros, ya que NumPy lo hace bien y permite operaciones vectorizadas de esa manera:

 def create_inc_pattern(prefix_str, start, stop): N = stop - start # count of numbers W = int(np.ceil(np.log10(stop+1))) # width of numeral part in string padv = np.full(W,48,dtype=np.uint8) a0 = np.r_[np.fromstring(prefix_str,dtype='uint8'), padv] a1 = np.repeat(a0[None],N,axis=0) r = np.arange(start, stop) addn = (r[:,None] // 10**np.arange(W-1,-1,-1))%10 a1[:,len(prefix_str):] += addn.astype(a1.dtype) return a1.view('S'+str(a1.shape[1])).ravel() 

Bring en numexpr para una transmisión más rápida + operación de módulo –

 import numexpr as ne def create_inc_pattern_numexpr(prefix_str, start, stop): N = stop - start # count of numbers W = int(np.ceil(np.log10(stop+1))) # width of numeral part in string padv = np.full(W,48,dtype=np.uint8) a0 = np.r_[np.fromstring(prefix_str,dtype='uint8'), padv] a1 = np.repeat(a0[None],N,axis=0) r = np.arange(start, stop) r2D = r[:,None] s = 10**np.arange(W-1,-1,-1) addn = ne.evaluate('(r2D/s)%10') a1[:,len(prefix_str):] += addn.astype(a1.dtype) return a1.view('S'+str(a1.shape[1])).ravel() 

Por lo tanto, para utilizar como la nueva columna:

 df['New_Column'] = create_inc_pattern(prefix_str='str_', start=1, stop=len(df)+1) 

Ejecuciones de muestra

 In [334]: create_inc_pattern_numexpr(prefix_str='str_', start=1, stop=14) Out[334]: array(['str_01', 'str_02', 'str_03', 'str_04', 'str_05', 'str_06', 'str_07', 'str_08', 'str_09', 'str_10', 'str_11', 'str_12', 'str_13'], dtype='|S6') In [338]: create_inc_pattern(prefix_str='str_', start=1, stop=124) Out[338]: array(['str_001', 'str_002', 'str_003', 'str_004', 'str_005', 'str_006', 'str_007', 'str_008', 'str_009', 'str_010', 'str_011', 'str_012',.. 'str_115', 'str_116', 'str_117', 'str_118', 'str_119', 'str_120', 'str_121', 'str_122', 'str_123'], dtype='|S7') 

Explicación

Idea básica y explicación con una ejecución de muestra paso a paso

La idea básica es crear la matriz numérica equivalente ASCII, que se puede ver o convertir por conversión de dtype en una cadena uno. Para ser más específicos, crearíamos números de tipo uint8. Por lo tanto, cada cadena estaría representada por una matriz de números 1D. Para la lista de cadenas que se traducirían en una matriz 2D de números con cada fila (matriz 1D) que representa una cadena única.

1) Entradas:

 In [22]: prefix_str='str_' ...: start=15 ...: stop=24 

2) Parámetros:

 In [23]: N = stop - start # count of numbers ...: W = int(np.ceil(np.log10(stop+1))) # width of numeral part in string In [24]: N,W Out[24]: (9, 2) 

3) Crea una matriz 1D de números que representan la cadena de inicio:

 In [25]: padv = np.full(W,48,dtype=np.uint8) ...: a0 = np.r_[np.fromstring(prefix_str,dtype='uint8'), padv] In [27]: a0 Out[27]: array([115, 116, 114, 95, 48, 48], dtype=uint8) 

4) Amplíe para cubrir el rango de cadenas como matriz 2D:

 In [33]: a1 = np.repeat(a0[None],N,axis=0) ...: r = np.arange(start, stop) ...: addn = (r[:,None] // 10**np.arange(W-1,-1,-1))%10 ...: a1[:,len(prefix_str):] += addn.astype(a1.dtype) In [34]: a1 Out[34]: array([[115, 116, 114, 95, 49, 53], [115, 116, 114, 95, 49, 54], [115, 116, 114, 95, 49, 55], [115, 116, 114, 95, 49, 56], [115, 116, 114, 95, 49, 57], [115, 116, 114, 95, 50, 48], [115, 116, 114, 95, 50, 49], [115, 116, 114, 95, 50, 50], [115, 116, 114, 95, 50, 51]], dtype=uint8) 

5) Por lo tanto, cada fila representa un ascii equivalente a una cadena, cada una fuera de la salida deseada. Vamos a conseguirlo con el paso final:

 In [35]: a1.view('S'+str(a1.shape[1])).ravel() Out[35]: array(['str_15', 'str_16', 'str_17', 'str_18', 'str_19', 'str_20', 'str_21', 'str_22', 'str_23'], dtype='|S6') 

Tiempos

Aquí hay una rápida prueba de tiempos contra la versión de comprensión de lista que parece estar funcionando de la mejor manera en los tiempos de otras publicaciones.

 In [339]: N = 10000 In [340]: %timeit ['str_%s'%i for i in range(N)] 1000 loops, best of 3: 1.12 ms per loop In [341]: %timeit create_inc_pattern_numexpr(prefix_str='str_', start=1, stop=N) 1000 loops, best of 3: 490 µs per loop In [342]: N = 100000 In [343]: %timeit ['str_%s'%i for i in range(N)] 100 loops, best of 3: 14 ms per loop In [344]: %timeit create_inc_pattern_numexpr(prefix_str='str_', start=1, stop=N) 100 loops, best of 3: 4 ms per loop 

Códigos Python-3

En Python-3, para obtener la cadena dtype array, se nos pedía que rellenásemos con algunos ceros más en la matriz intermedia int dtype. Entonces, el equivalente sin y con las versiones numexpr para Python-3 terminó convirtiéndose en algo así:

Método # 1 (No numexpr):

 def create_inc_pattern(prefix_str, start, stop): N = stop - start # count of numbers W = int(np.ceil(np.log10(stop+1))) # width of numeral part in string dl = len(prefix_str)+W # datatype length dt = np.uint8 # int datatype for string to-from conversion padv = np.full(W,48,dtype=np.uint8) a0 = np.r_[np.fromstring(prefix_str,dtype='uint8'), padv] r = np.arange(start, stop) addn = (r[:,None] // 10**np.arange(W-1,-1,-1))%10 a1 = np.repeat(a0[None],N,axis=0) a1[:,len(prefix_str):] += addn.astype(dt) a1.shape = (-1) a2 = np.zeros((len(a1),4),dtype=dt) a2[:,0] = a1 return np.frombuffer(a2.ravel(), dtype='U'+str(dl)) 

Método # 2 (con numexpr):

 import numexpr as ne def create_inc_pattern_numexpr(prefix_str, start, stop): N = stop - start # count of numbers W = int(np.ceil(np.log10(stop+1))) # width of numeral part in string dl = len(prefix_str)+W # datatype length dt = np.uint8 # int datatype for string to-from conversion padv = np.full(W,48,dtype=np.uint8) a0 = np.r_[np.fromstring(prefix_str,dtype='uint8'), padv] r = np.arange(start, stop) r2D = r[:,None] s = 10**np.arange(W-1,-1,-1) addn = ne.evaluate('(r2D/s)%10') a1 = np.repeat(a0[None],N,axis=0) a1[:,len(prefix_str):] += addn.astype(dt) a1.shape = (-1) a2 = np.zeros((len(a1),4),dtype=dt) a2[:,0] = a1 return np.frombuffer(a2.ravel(), dtype='U'+str(dl)) 

Tiempos –

 In [8]: N = 100000 In [9]: %timeit ['str_%s'%i for i in range(N)] 100 loops, best of 3: 18.5 ms per loop In [10]: %timeit create_inc_pattern_numexpr(prefix_str='str_', start=1, stop=N) 100 loops, best of 3: 6.06 ms per loop 

Una posible solución es convertir los valores a la string s por map :

 df['New_Column'] = np.arange(len(df['a']))+1 df['New_Column'] = 'str_' + df['New_Column'].map(str)