Tengo un marco de datos de pandas (esto es solo una pequeña parte)
>>> d1
y norm test y norm train len(y_train) len(y_test) \
0 64.904368 116.151232 1645 549
1 70.852681 112.639876 1645 549
SVR RBF \
0 (35.652207342877873, 22.95533537448393)
1 (39.563683797747622, 27.382483096332511)
LCV \
0 (19.365430594452338, 13.880062435173587)
1 (19.099614489458364, 14.018867136617146)
RIDGE CV \
0 (4.2907610988480362, 12.416745648065584)
1 (4.18864306788194, 12.980833914392477)
RF \
0 (9.9484841581029428, 16.46902345373697)
1 (10.139848213735391, 16.282141345406522)
GB \
0 (0.012816232716538605, 15.950164822266007)
1 (0.012814519804493328, 15.305745202851712)
ET DATA
0 (0.00034337162272515505, 16.284800366214057) j2m
1 (0.00024811554516431878, 15.556506191784194) j2m
>>>
Quiero dividir todas las columnas que contienen tuplas. Por ejemplo, quiero reemplazar la columna LCV
con las columnas LCV-a
y LCV-b
.
¿Cómo puedo hacer eso?
En conjuntos de datos mucho más grandes, encontré que
.apply()
son pocos pedidos más lentos quepd.DataFrame(df['b'].values.tolist(), index=df.index)
Este problema de rendimiento se cerró en GitHub, aunque no estoy de acuerdo con esta decisión:
https://github.com/pandas-dev/pandas/issues/11615
EDITAR: basado en esta respuesta: https://stackoverflow.com/a/44196843/2230844
fuente
pd.DataFrame(df['b'].tolist())
sin el.values
parece funcionar bien también. (Y gracias, su solución es mucho más rápida que.apply()
)El
str
descriptor de acceso que está disponible para lospandas.Series
objetos dedtype == object
es en realidad un iterable.Suponga un
pandas.DataFrame
df
:df = pd.DataFrame(dict(col=[*zip('abcdefghij', range(10, 101, 10))])) df col 0 (a, 10) 1 (b, 20) 2 (c, 30) 3 (d, 40) 4 (e, 50) 5 (f, 60) 6 (g, 70) 7 (h, 80) 8 (i, 90) 9 (j, 100)
Podemos probar si es iterable
from collections import Iterable isinstance(df.col.str, Iterable) True
Luego podemos asignar a partir de él como hacemos con otros iterables:
var0, var1 = 'xy' print(var0, var1) x y
La solución más sencilla
Entonces en una línea podemos asignar ambas columnas
df['a'], df['b'] = df.col.str df col a b 0 (a, 10) a 10 1 (b, 20) b 20 2 (c, 30) c 30 3 (d, 40) d 40 4 (e, 50) e 50 5 (f, 60) f 60 6 (g, 70) g 70 7 (h, 80) h 80 8 (i, 90) i 90 9 (j, 100) j 100
Solución más rápida
Solo un poco más complicado, podemos usar
zip
para crear un iterable similardf['c'], df['d'] = zip(*df.col) df col a b c d 0 (a, 10) a 10 a 10 1 (b, 20) b 20 b 20 2 (c, 30) c 30 c 30 3 (d, 40) d 40 d 40 4 (e, 50) e 50 e 50 5 (f, 60) f 60 f 60 6 (g, 70) g 70 g 70 7 (h, 80) h 80 h 80 8 (i, 90) i 90 i 90 9 (j, 100) j 100 j 100
En línea
Es decir, no mute los existentes.
df
Esto funciona porque
assign
toma argumentos de palabras clave donde las palabras clave son los nombres de columna nuevos (o existentes) y los valores serán los valores de la nueva columna. Puede usar un diccionario y descomprimirlo**
y hacer que actúe como argumentos de palabras clave. Así que esta es una forma inteligente de asignar una nueva columna llamada'g'
que es el primer elemento deldf.col.str
iterable y'h'
que es el segundo elemento deldf.col.str
iterable.df.assign(**dict(zip('gh', df.col.str))) col g h 0 (a, 10) a 10 1 (b, 20) b 20 2 (c, 30) c 30 3 (d, 40) d 40 4 (e, 50) e 50 5 (f, 60) f 60 6 (g, 70) g 70 7 (h, 80) h 80 8 (i, 90) i 90 9 (j, 100) j 100
Mi versión del
list
enfoqueCon comprensión de listas moderna y desempaquetado de variables.
Nota: también en línea usando
join
df.join(pd.DataFrame([*df.col], df.index, [*'ef'])) col g h 0 (a, 10) a 10 1 (b, 20) b 20 2 (c, 30) c 30 3 (d, 40) d 40 4 (e, 50) e 50 5 (f, 60) f 60 6 (g, 70) g 70 7 (h, 80) h 80 8 (i, 90) i 90 9 (j, 100) j 100
La versión mutante sería
df[['e', 'f']] = pd.DataFrame([*df.col], df.index)
Prueba de ingenuo
DataFrame cortoUtilice uno definido anteriormente
DataFrame largo%timeit df.assign(**dict(zip('gh', df.col.str))) %timeit df.assign(**dict(zip('gh', zip(*df.col)))) %timeit df.join(pd.DataFrame([*df.col], df.index, [*'gh'])) 1.16 ms ± 21.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) 635 µs ± 18.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) 795 µs ± 42.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
10 ^ 3 veces más grande
df = pd.concat([df] * 1000, ignore_index=True) %timeit df.assign(**dict(zip('gh', df.col.str))) %timeit df.assign(**dict(zip('gh', zip(*df.col)))) %timeit df.join(pd.DataFrame([*df.col], df.index, [*'gh'])) 11.4 ms ± 1.53 ms per loop (mean ± std. dev. of 7 runs, 100 loops each) 2.1 ms ± 41.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) 2.33 ms ± 35.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
fuente
df['a'], df['b'] = df.col.str
:)Creo que una forma más sencilla es:
>>> import pandas as pd >>> df = pd.DataFrame({'a':[1,2], 'b':[(1,2), (3,4)]}) >>> df a b 0 1 (1, 2) 1 2 (3, 4) >>> df['b_a']=df['b'].str[0] >>> df['b_b']=df['b'].str[1] >>> df a b b_a b_b 0 1 (1, 2) 1 2 1 2 (3, 4) 3 4
fuente
str
representación de unpd.Series
objeto. ¿Puedes explicar cómo funciona esto?Sé que esto es de hace un tiempo, pero una advertencia de la segunda solución:
pd.DataFrame(df['b'].values.tolist())
es que descartará explícitamente el índice y agregará un índice secuencial predeterminado, mientras que la respuesta aceptada
no lo hará, ya que el resultado de aplicar conservará el índice de fila. Si bien el orden se retiene inicialmente de la matriz original, los pandas intentarán hacer coincidir los índices de los dos marcos de datos.
Esto puede ser muy importante si está intentando establecer las filas en una matriz indexada numéricamente, y los pandas intentarán hacer coincidir automáticamente el índice de la nueva matriz con el anterior, y causarán cierta distorsión en el orden.
Una mejor solución híbrida sería establecer el índice del marco de datos original en el nuevo, es decir
pd.DataFrame(df['b'].values.tolist(), index=df.index)
Lo que mantendrá la velocidad de usar el segundo método mientras asegura que el orden y la indexación se conservan en el resultado.
fuente