Cree una nueva columna basada en valores de otras columnas / aplique una función de múltiples columnas, por filas en Pandas
Quiero aplicar mi función personalizada (usa una escalera if-else) a estas seis columnas ( ,,,,, ) en cada fila de mi marco de datos ERI_Hispanic
.ERI_AmerInd_AKNatv
ERI_Asian
ERI_Black_Afr.Amer
ERI_HI_PacIsl
ERI_White
Probé diferentes métodos de otras preguntas, pero 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 ninguna otra cosa. Incluso si tienen un "1" en otra columna de origen étnico, todavía se les cuenta 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 etnia única (excepto los hispanos).
Es casi como hacer un bucle for en cada fila y si cada registro cumple con un criterio, se agrega a una lista y se elimina 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 el indicador ERI para hispano es Verdadero (1), el empleado se clasifica como “hispano”
Comentario: Si más de un indicador ERI no hispano es verdadero, 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
Bien, dos pasos para esto: el primero es escribir una función que haga la traducción que deseas. He creado un ejemplo basado en tu 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, utilice la función de aplicación en pandas para aplicar la función, por ejemplo
df.apply(label_race, axis=1)
Tenga en cuenta el axis=1
especificador, eso significa que la aplicación se realiza a nivel de fila, en lugar de a 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, ejecútelo nuevamente y guarde los resultados en una nueva columna en su marco de datos original.
df['race_label'] = df.apply(label_race, 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
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 consigues el SettingWithCopyWarning
puedes hacerlo de esta manera también:
col = df.apply(lambda row: row.a + row.b, axis=1)
df = df.assign(c=col.values) # assign values to column 'c'
Fuente: https://stackoverflow.com/a/12555510/243392
Y si el nombre de tu columna incluye espacios, puedes usar una sintaxis como esta:
df = df.assign(**{'some column name': col.values})
Y aquí está la documentación para aplicar y asignar .
Las respuestas anteriores son perfectamente válidas, pero existe una solución vectorizada, en forma de numpy.select
. Esto le permite definir condiciones y luego definir resultados para esas condiciones, de manera mucho más eficiente 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
¿ Por qué debería numpy.select
usarse más apply
? A continuación se muestran 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)
El uso numpy.select
nos brinda un rendimiento enormemente mejorado y la discrepancia solo aumentará a medida que crezcan los datos.
.apply()
toma una función como primer parámetro; pase la label_race
función así:
df['race_label'] = df.apply(label_race, axis=1)
No es necesario crear una función lambda para pasar una función.