dave
Quiero aplicar mi función personalizada (usa una escalera if-else) a estas seis columnas (ERI_Hispanic
, ERI_AmerInd_AKNatv
, ERI_Asian
, ERI_Black_Afr.Amer
, ERI_HI_PacIsl
, ERI_White
) en cada fila de mi marco de datos.
He probado diferentes métodos de otras preguntas, pero parece que todavía no puedo encontrar la respuesta correcta para mi problema. La parte crítica de esto es que si la persona se cuenta como hispana, no se puede contar como otra cosa. Incluso si tienen un “1” en otra columna de etnicidad, todavía se cuentan como hispanos, no como dos o más razas. De manera similar, si la suma de todas las columnas ERI es mayor que 1, se cuentan como dos o más razas y no se pueden contar como una única etnia (excepto para los hispanos).
Es casi como hacer un ciclo for a través de cada fila y si cada registro cumple con un criterio, se agregan a una lista y se eliminan del original.
Desde el marco de datos a continuación, necesito calcular una nueva columna basada en la siguiente especificación en SQL:
CRITERIOS
IF [ERI_Hispanic] = 1 THEN RETURN “Hispanic”
ELSE IF SUM([ERI_AmerInd_AKNatv] + [ERI_Asian] + [ERI_Black_Afr.Amer] + [ERI_HI_PacIsl] + [ERI_White]) > 1 THEN RETURN “Two or More”
ELSE IF [ERI_AmerInd_AKNatv] = 1 THEN RETURN “A/I AK Native”
ELSE IF [ERI_Asian] = 1 THEN RETURN “Asian”
ELSE IF [ERI_Black_Afr.Amer] = 1 THEN RETURN “Black/AA”
ELSE IF [ERI_HI_PacIsl] = 1 THEN RETURN “Haw/Pac Isl.”
ELSE IF [ERI_White] = 1 THEN RETURN “White”
Comentario: Si la Bandera ERI para Hispano es Verdadera (1), el empleado se clasifica como “Hispano”
Comentario: si más de 1 bandera ERI no hispana es verdadera, devuelva “Dos o más”
MARCO DE DATOS
lname fname rno_cd eri_afr_amer eri_asian eri_hawaiian eri_hispanic eri_nat_amer eri_white rno_defined
0 MOST JEFF E 0 0 0 0 0 1 White
1 CRUISE TOM E 0 0 0 1 0 0 White
2 DEPP JOHNNY 0 0 0 0 0 1 Unknown
3 DICAP LEO 0 0 0 0 0 1 Unknown
4 BRANDO MARLON E 0 0 0 0 0 0 White
5 HANKS TOM 0 0 0 0 0 1 Unknown
6 DENIRO ROBERT E 0 1 0 0 0 1 White
7 PACINO AL E 0 0 0 0 0 1 White
8 WILLIAMS ROBIN E 0 0 1 0 0 0 White
9 EASTWOOD CLINT E 0 0 0 0 0 1 White
Tomas Kimber
De acuerdo, dos pasos para esto: primero es escribir una función que haga la traducción que desea. He reunido un ejemplo basado en su pseudocódigo:
def label_race (row):
if row['eri_hispanic'] == 1 :
return 'Hispanic'
if row['eri_afr_amer'] + row['eri_asian'] + row['eri_hawaiian'] + row['eri_nat_amer'] + row['eri_white'] > 1 :
return 'Two Or More'
if row['eri_nat_amer'] == 1 :
return 'A/I AK Native'
if row['eri_asian'] == 1:
return 'Asian'
if row['eri_afr_amer'] == 1:
return 'Black/AA'
if row['eri_hawaiian'] == 1:
return 'Haw/Pac Isl.'
if row['eri_white'] == 1:
return 'White'
return 'Other'
Es posible que desee repasar esto, pero parece funcionar: observe que el parámetro que ingresa a la función se considera un objeto Serie etiquetado como “fila”.
A continuación, use la función de aplicación en pandas para aplicar la función, por ejemplo
df.apply (lambda row: label_race(row), axis=1)
Tenga en cuenta el especificador axis=1, lo que significa que la aplicación se realiza en una fila, en lugar de un nivel de columna. Los resultados están aquí:
0 White
1 Hispanic
2 White
3 White
4 Other
5 White
6 Two Or More
7 White
8 Haw/Pac Isl.
9 White
Si está satisfecho con esos resultados, vuelva a ejecutarlo y guarde los resultados en una nueva columna en su marco de datos original.
df['race_label'] = df.apply (lambda row: label_race(row), axis=1)
El marco de datos resultante se ve así (desplácese hacia la derecha para ver la nueva columna):
lname fname rno_cd eri_afr_amer eri_asian eri_hawaiian eri_hispanic eri_nat_amer eri_white rno_defined race_label
0 MOST JEFF E 0 0 0 0 0 1 White White
1 CRUISE TOM E 0 0 0 1 0 0 White Hispanic
2 DEPP JOHNNY NaN 0 0 0 0 0 1 Unknown White
3 DICAP LEO NaN 0 0 0 0 0 1 Unknown White
4 BRANDO MARLON E 0 0 0 0 0 0 White Other
5 HANKS TOM NaN 0 0 0 0 0 1 Unknown White
6 DENIRO ROBERT E 0 1 0 0 0 1 White Two Or More
7 PACINO AL E 0 0 0 0 0 1 White White
8 WILLIAMS ROBIN E 0 0 1 0 0 0 White Haw/Pac Isl.
9 EASTWOOD CLINT E 0 0 0 0 0 1 White White
Brian quema
Dado que este es el primer resultado de Google para ‘pandas nueva columna de otros’, aquí hay un ejemplo simple:
import pandas as pd
# make a simple dataframe
df = pd.DataFrame({'a':[1,2], 'b':[3,4]})
df
# a b
# 0 1 3
# 1 2 4
# create an unattached column with an index
df.apply(lambda row: row.a + row.b, axis=1)
# 0 4
# 1 6
# do same but attach it to the dataframe
df['c'] = df.apply(lambda row: row.a + row.b, axis=1)
df
# a b c
# 0 1 3 4
# 1 2 4 6
si obtienes el SettingWithCopyWarning
puedes hacerlo de esta manera también:
fn = lambda row: row.a + row.b # define a function for the new column
col = df.apply(fn, axis=1) # get column data with an index
df = df.assign(c=col.values) # assign values to column 'c'
Fuente: https://stackoverflow.com/a/12555510/243392
Y si el nombre de su columna incluye espacios, puede usar una sintaxis como esta:
df = df.assign(**{'some column name': col.values})
-
Respuesta muy interesante. Una pregunta, ¿cómo podemos hacer
df.apply(lambda row: row.a + row.b, axis=1)
pero también involucrando la siguiente fila? Tengo una pregunta sobre esto aquí stackoverflow.com/questions/74792731/…– Robot Kansai
14 de diciembre a las 2:45
Las respuestas anteriores son perfectamente válidas, pero existe una solución vectorizada, en forma de numpy.select
. Esto le permite definir condiciones, luego definir salidas para esas condiciones, mucho más eficientemente que usar apply
:
Primero, defina las condiciones:
conditions = [
df['eri_hispanic'] == 1,
df[['eri_afr_amer', 'eri_asian', 'eri_hawaiian', 'eri_nat_amer', 'eri_white']].sum(1).gt(1),
df['eri_nat_amer'] == 1,
df['eri_asian'] == 1,
df['eri_afr_amer'] == 1,
df['eri_hawaiian'] == 1,
df['eri_white'] == 1,
]
Ahora, defina las salidas correspondientes:
outputs = [
'Hispanic', 'Two Or More', 'A/I AK Native', 'Asian', 'Black/AA', 'Haw/Pac Isl.', 'White'
]
Finalmente, usando numpy.select
:
res = np.select(conditions, outputs, 'Other')
pd.Series(res)
0 White
1 Hispanic
2 White
3 White
4 Other
5 White
6 Two Or More
7 White
8 Haw/Pac Isl.
9 White
dtype: object
Porque deberia numpy.select
ser usado sobre apply
? Aquí hay algunas comprobaciones de rendimiento:
df = pd.concat([df]*1000)
In [42]: %timeit df.apply(lambda row: label_race(row), axis=1)
1.07 s ± 4.16 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [44]: %%timeit
...: conditions = [
...: df['eri_hispanic'] == 1,
...: df[['eri_afr_amer', 'eri_asian', 'eri_hawaiian', 'eri_nat_amer', 'eri_white']].sum(1).gt(1),
...: df['eri_nat_amer'] == 1,
...: df['eri_asian'] == 1,
...: df['eri_afr_amer'] == 1,
...: df['eri_hawaiian'] == 1,
...: df['eri_white'] == 1,
...: ]
...:
...: outputs = [
...: 'Hispanic', 'Two Or More', 'A/I AK Native', 'Asian', 'Black/AA', 'Haw/Pac Isl.', 'White'
...: ]
...:
...: np.select(conditions, outputs, 'Other')
...:
...:
3.09 ms ± 17 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Usando numpy.select
Nos da vastamente rendimiento mejorado, y la discrepancia solo aumentará a medida que crezcan los datos.
Gabrielle Simard-Moore
.apply()
toma una función como primer parámetro; pasar en el label_race
funcionar como tal:
df['race_label'] = df.apply(label_race, axis=1)
No necesita hacer una función lambda para pasar una función.
Mohamed Thasin ah
prueba esto,
df.loc[df['eri_white']==1,'race_label'] = 'White'
df.loc[df['eri_hawaiian']==1,'race_label'] = 'Haw/Pac Isl.'
df.loc[df['eri_afr_amer']==1,'race_label'] = 'Black/AA'
df.loc[df['eri_asian']==1,'race_label'] = 'Asian'
df.loc[df['eri_nat_amer']==1,'race_label'] = 'A/I AK Native'
df.loc[(df['eri_afr_amer'] + df['eri_asian'] + df['eri_hawaiian'] + df['eri_nat_amer'] + df['eri_white']) > 1,'race_label'] = 'Two Or More'
df.loc[df['eri_hispanic']==1,'race_label'] = 'Hispanic'
df['race_label'].fillna('Other', inplace=True)
O/P:
lname fname rno_cd eri_afr_amer eri_asian eri_hawaiian \
0 MOST JEFF E 0 0 0
1 CRUISE TOM E 0 0 0
2 DEPP JOHNNY NaN 0 0 0
3 DICAP LEO NaN 0 0 0
4 BRANDO MARLON E 0 0 0
5 HANKS TOM NaN 0 0 0
6 DENIRO ROBERT E 0 1 0
7 PACINO AL E 0 0 0
8 WILLIAMS ROBIN E 0 0 1
9 EASTWOOD CLINT E 0 0 0
eri_hispanic eri_nat_amer eri_white rno_defined race_label
0 0 0 1 White White
1 1 0 0 White Hispanic
2 0 0 1 Unknown White
3 0 0 1 Unknown White
4 0 0 0 White Other
5 0 0 1 Unknown White
6 0 0 1 White Two Or More
7 0 0 1 White White
8 0 0 0 White Haw/Pac Isl.
9 0 0 1 White White
usar .loc
en vez de apply
.
mejora la vectorización.
.loc
funciona de manera simple, enmascara filas según la condición, aplica valores a las filas congeladas.
para más detalles visita, documentos .loc
Métricas de rendimiento:
Respuesta aceptada:
def label_race (row):
if row['eri_hispanic'] == 1 :
return 'Hispanic'
if row['eri_afr_amer'] + row['eri_asian'] + row['eri_hawaiian'] + row['eri_nat_amer'] + row['eri_white'] > 1 :
return 'Two Or More'
if row['eri_nat_amer'] == 1 :
return 'A/I AK Native'
if row['eri_asian'] == 1:
return 'Asian'
if row['eri_afr_amer'] == 1:
return 'Black/AA'
if row['eri_hawaiian'] == 1:
return 'Haw/Pac Isl.'
if row['eri_white'] == 1:
return 'White'
return 'Other'
df=pd.read_csv('dataser.csv')
df = pd.concat([df]*1000)
%timeit df.apply(lambda row: label_race(row), axis=1)
1,15 s ± 46,5 ms por bucle (media ± desviación estándar de 7 ejecuciones, 1 bucle cada una)
Mi respuesta propuesta:
def label_race(df):
df.loc[df['eri_white']==1,'race_label'] = 'White'
df.loc[df['eri_hawaiian']==1,'race_label'] = 'Haw/Pac Isl.'
df.loc[df['eri_afr_amer']==1,'race_label'] = 'Black/AA'
df.loc[df['eri_asian']==1,'race_label'] = 'Asian'
df.loc[df['eri_nat_amer']==1,'race_label'] = 'A/I AK Native'
df.loc[(df['eri_afr_amer'] + df['eri_asian'] + df['eri_hawaiian'] + df['eri_nat_amer'] + df['eri_white']) > 1,'race_label'] = 'Two Or More'
df.loc[df['eri_hispanic']==1,'race_label'] = 'Hispanic'
df['race_label'].fillna('Other', inplace=True)
df=pd.read_csv('s22.csv')
df = pd.concat([df]*1000)
%timeit label_race(df)
24,7 ms ± 1,7 ms por bucle (media ± desviación estándar de 7 ejecuciones, 10 bucles cada una)
conejo
Si inspeccionamos su código fuente, apply()
es un azúcar sintáctico para un bucle for de Python (a través de la apply_series_generator()
metodo de la FrameApply
clase). Debido a que tiene los pandas por encima, por lo general es Más lento que un bucle de Python.
Utilice métodos optimizados (vectorizados) siempre que sea posible. Si tiene que usar un bucle, use @numba.jit
decorador.
1. No use apply()
para una escalera if-else
df.apply()
es casi la forma más lenta de hacer esto en pandas. Como se muestra en las respuestas de user3483203 y Mohamed Thasin ah, según el tamaño del marco de datos, np.select()
y df.loc
puede ser 50-300 veces más rápido que df.apply()
para producir la misma salida.
Da la casualidad de que una implementación de bucle (no muy diferente apply()
) con el @jit
decorador de numba
módulo es (alrededor de 50-60%) más rápido que df.loc
y np.select
.1
Numba funciona en matrices numpy, así que antes de usar el jit
decorador, debe convertir el marco de datos en una matriz numpy. Luego complete los valores en una matriz vacía preinicializada al verificar las condiciones en un bucle. Dado que las matrices numpy no tienen nombres de columna, debe acceder a las columnas por su índice en el bucle. La parte más inconveniente de la escalera if-else en la función jitted sobre la de apply()
está accediendo a las columnas por sus índices. De lo contrario, es casi la misma implementación.
import numpy as np
import numba as nb
@nb.jit(nopython=True)
def conditional_assignment(arr, res):
length = len(arr)
for i in range(length):
if arr[i][3] == 1:
res[i] = 'Hispanic'
elif arr[i][0] + arr[i][1] + arr[i][2] + arr[i][4] + arr[i][5] > 1:
res[i] = 'Two Or More'
elif arr[i][0] == 1:
res[i] = 'Black/AA'
elif arr[i][1] == 1:
res[i] = 'Asian'
elif arr[i][2] == 1:
res[i] = 'Haw/Pac Isl.'
elif arr[i][4] == 1:
res[i] = 'A/I AK Native'
elif arr[i][5] == 1:
res[i] = 'White'
else:
res[i] = 'Other'
return res
# the columns with the boolean data
cols = [c for c in df.columns if c.startswith('eri_')]
# initialize an empty array to be filled in a loop
# for string dtype arrays, we need to know the length of the longest string
# and use it to set the dtype
res = np.empty(len(df), dtype=f"<U{len('A/I AK Native')}")
# pass the underlying numpy array of `df[cols]` into the jitted function
df['rno_defined'] = conditional_assignment(df[cols].values, res)
2. No use apply()
para operaciones numéricas
Si necesita agregar una nueva fila agregando dos columnas, su primer instinto puede ser escribir
df['c'] = df.apply(lambda row: row['a'] + row['b'], axis=1)
Pero en lugar de esto, agrega filas usando sum(axis=1)
método (o +
operador si solo hay un par de columnas):
df['c'] = df[['a','b']].sum(axis=1)
# equivalently
df['c'] = df['a'] + df['b']
Dependiendo del tamaño del marco de datos, sum(1)
puede ser cientos de veces más rápido que apply()
.
De hecho, casi nunca necesitará apply()
para operaciones numéricas en un marco de datos de pandas porque tiene métodos optimizados para la mayoría de las operaciones: adición (sum(1)
), resta (sub()
o diff()
), multiplicación (prod(1)
), división (div()
o /
), poder (pow()
), >
, >=
, ==
, %
, //
, &
, |
etc. todo se puede realizar en todo el marco de datos sin apply()
.
Por ejemplo, supongamos que desea crear una nueva columna utilizando la siguiente regla:
IF [colC] > 0 THEN RETURN [colA] * [colB]
ELSE RETURN [colA] / [colB]
Usando los métodos optimizados de pandas, esto se puede escribir como
df['new'] = df[['colA','colB']].prod(1).where(df['colC']>0, df['colA'] / df['colB'])
el equivalente apply()
la solución es:
df['new'] = df.apply(lambda row: row.colA * row.colB if row.colC > 0 else row.colA / row.colB, axis=1)
El enfoque que utiliza los métodos optimizados es 250 veces más rápido que el equivalente apply()
enfoque para marcos de datos con 20k filas. Esta brecha solo aumenta a medida que aumenta el tamaño de los datos (para un marco de datos con 1 mil filas, es 365 veces más rápido) y la diferencia de tiempo será cada vez más notable.2
1: En el siguiente resultado, muestro el rendimiento de los tres enfoques utilizando un marco de datos con 24 mil filas (este es el marco más grande que puedo construir en mi máquina). Para marcos más pequeños, la función numba-jitted también se ejecuta consistentemente al menos un 50% más rápido que los otros dos (puede comprobarlo usted mismo).
def pd_loc(df):
df['rno_defined'] = 'Other'
df.loc[df['eri_nat_amer'] == 1, 'rno_defined'] = 'A/I AK Native'
df.loc[df['eri_asian'] == 1, 'rno_defined'] = 'Asian'
df.loc[df['eri_afr_amer'] == 1, 'rno_defined'] = 'Black/AA'
df.loc[df['eri_hawaiian'] == 1, 'rno_defined'] = 'Haw/Pac Isl.'
df.loc[df['eri_white'] == 1, 'rno_defined'] = 'White'
df.loc[df[['eri_afr_amer', 'eri_asian', 'eri_hawaiian', 'eri_nat_amer', 'eri_white']].sum(1) > 1, 'rno_defined'] = 'Two Or More'
df.loc[df['eri_hispanic'] == 1, 'rno_defined'] = 'Hispanic'
return df
def np_select(df):
conditions = [df['eri_hispanic'] == 1,
df[['eri_afr_amer', 'eri_asian', 'eri_hawaiian', 'eri_nat_amer', 'eri_white']].sum(1).gt(1),
df['eri_nat_amer'] == 1,
df['eri_asian'] == 1,
df['eri_afr_amer'] == 1,
df['eri_hawaiian'] == 1,
df['eri_white'] == 1]
outputs = ['Hispanic', 'Two Or More', 'A/I AK Native', 'Asian', 'Black/AA', 'Haw/Pac Isl.', 'White']
df['rno_defined'] = np.select(conditions, outputs, 'Other')
return df
@nb.jit(nopython=True)
def conditional_assignment(arr, res):
length = len(arr)
for i in range(length):
if arr[i][3] == 1 :
res[i] = 'Hispanic'
elif arr[i][0] + arr[i][1] + arr[i][2] + arr[i][4] + arr[i][5] > 1 :
res[i] = 'Two Or More'
elif arr[i][0] == 1:
res[i] = 'Black/AA'
elif arr[i][1] == 1:
res[i] = 'Asian'
elif arr[i][2] == 1:
res[i] = 'Haw/Pac Isl.'
elif arr[i][4] == 1 :
res[i] = 'A/I AK Native'
elif arr[i][5] == 1:
res[i] = 'White'
else:
res[i] = 'Other'
return res
def nb_loop(df):
cols = [c for c in df.columns if c.startswith('eri_')]
res = np.empty(len(df), dtype=f"<U{len('A/I AK Native')}")
df['rno_defined'] = conditional_assignment(df[cols].values, res)
return df
# df with 24mil rows
n = 4_000_000
df = pd.DataFrame({
'eri_afr_amer': [0, 0, 0, 0, 0, 0]*n,
'eri_asian': [1, 0, 0, 0, 0, 0]*n,
'eri_hawaiian': [0, 0, 0, 1, 0, 0]*n,
'eri_hispanic': [0, 1, 0, 0, 1, 0]*n,
'eri_nat_amer': [0, 0, 0, 0, 1, 0]*n,
'eri_white': [0, 0, 1, 1, 0, 0]*n
}, dtype="int8")
df.insert(0, 'name', ['MOST', 'CRUISE', 'DEPP', 'DICAP', 'BRANDO', 'HANKS']*n)
%timeit nb_loop(df)
# 5.23 s ± 45.2 ms per loop (mean ± std. dev. of 10 runs, 10 loops each)
%timeit pd_loc(df)
# 7.97 s ± 28.8 ms per loop (mean ± std. dev. of 10 runs, 10 loops each)
%timeit np_select(df)
# 8.5 s ± 39.6 ms per loop (mean ± std. dev. of 10 runs, 10 loops each)
2: En el resultado a continuación, muestro el rendimiento de los dos enfoques utilizando un marco de datos con filas de 20k y nuevamente con filas de 1 mil. Para marcos más pequeños, la brecha es menor porque el enfoque optimizado tiene una sobrecarga mientras que apply()
es un bucle. A medida que aumenta el tamaño del marco, el costo general de la vectorización disminuye en comparación con el tiempo de ejecución general del código, mientras que apply()
sigue siendo un bucle sobre el marco.
n = 20_000 # 1_000_000
df = pd.DataFrame(np.random.rand(n,3)-0.5, columns=['colA','colB','colC'])
%timeit df[['colA','colB']].prod(1).where(df['colC']>0, df['colA'] / df['colB'])
# n = 20000: 2.69 ms ± 23.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
# n = 1000000: 86.2 ms ± 441 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit df.apply(lambda row: row.colA * row.colB if row.colC > 0 else row.colA / row.colB, axis=1)
# n = 20000: 679 ms ± 33.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
# n = 1000000: 31.5 s ± 587 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Sai Pardhu
Como señaló @ user3483203, numpy.select es el mejor enfoque
Almacene sus declaraciones condicionales y las acciones correspondientes en dos listas
conds = [(df['eri_hispanic'] == 1),(df[['eri_afr_amer', 'eri_asian', 'eri_hawaiian', 'eri_nat_amer', 'eri_white']].sum(1).gt(1)),(df['eri_nat_amer'] == 1),(df['eri_asian'] == 1),(df['eri_afr_amer'] == 1),(df['eri_hawaiian'] == 1),(df['eri_white'] == 1,])
actions = ['Hispanic', 'Two Or More', 'A/I AK Native', 'Asian', 'Black/AA', 'Haw/Pac Isl.', 'White']
Ahora puede usar np.select usando estas listas como sus argumentos
df['label_race'] = np.select(conds,actions,default="Other")
Referencia: https://numpy.org/doc/stable/reference/generated/numpy.select.html
Para esta tarea,
apply
parece obvio pero no úselo porque es solo un bucle sobre las filas. Hay mejores maneras de hacerlo. Ejemplos: aquí, aquí, aquí.– rabo de algodón
17 de noviembre a las 21:37