los pandas crean una nueva columna basada en valores de otras columnas / aplican una función de varias columnas, en fila

316

Quiero aplicar mi función personalizada (que utiliza una escalera si-else) a estos seis columnas ( ERI_Hispanic, ERI_AmerInd_AKNatv, ERI_Asian, ERI_Black_Afr.Amer, ERI_HI_PacIsl, ERI_White) en cada fila de mi trama 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 etnia, todavía se cuentan como hispanos, no dos o más razas. Del mismo modo, 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 para los hispanos). Esperemos que esto tenga sentido. Cualquier ayuda será apreciada.

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.

Del siguiente marco de datos 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 hispanos 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"

====================== DATAFRAME ===========================

     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
Dave
fuente
Su función particular es solo una larga escalera if-else donde los valores de algunas variables tienen prioridad sobre otras. Se llamaría un decodificador prioritario en lenguaje de ingeniería de hardware.
smci

Respuestas:

408

OK, dos pasos para esto, primero es escribir una función que haga la traducción que deseas, he reunido 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 ser el truco: tenga en cuenta que el parámetro que entra en la función se considera un objeto Serie etiquetado como "fila".

Luego, use la función de aplicar en pandas para aplicar la función, por ejemplo

df.apply (lambda row: label_race(row), axis=1)

Tenga en cuenta el eje = 1 especificador, eso 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, ejecútelos nuevamente, guardando 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 tiene este aspecto (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
Thomas Kimber
fuente
69
solo una nota: si solo está introduciendo la fila en su función, puede hacer lo siguiente:df.apply(label_race, axis=1)
Paul H
1
Si quisiera hacer algo similar con otra fila, ¿podría usar la misma función? Por ejemplo, a partir de los resultados, si ['race_label'] == "White" devuelve 'White' y así sucesivamente. Pero si el ['race_label'] == 'Desconocido' devuelve los valores de la columna ['rno_defined']. Supongo que la misma función funcionaría, pero parece que no puedo entender cómo obtener los valores de la otra columna.
Dave
2
Podría escribir una nueva función, que observe el campo 'race_label', y enviar los resultados a un nuevo campo, o, y creo que esto podría ser mejor en este caso, edite la función original, cambiando la return 'Other'línea final a la return row['rno_defined']que debería sustituya el valor de esa columna en aquellos casos en que el conjunto de sentencias if / then no encuentre una coincidencia (es decir, donde actualmente, ve 'Otro')
Thomas Kimber
99
Puede simplificar: df.apply(lambda row: label_race (row),axis=1)todf.apply(label_race, axis=1)
user48956
55
En las versiones más nuevas, si obtiene 'SettingWithCopyWarning', debe mirar el método 'asignar'. Ver: stackoverflow.com/a/12555510/3015186
np8
218

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 obtiene el SettingWithCopyWarningpuede 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})

Y aquí está la documentación para aplicar y asignar .

Brian Burns
fuente
1
Respuesta corta, destilada a lo esencial!
Frode Akselsen
1
Me sale SettingWithCopyWarningcuando lo hago df['c'] = df.apply(lambda row: row.a + row.b, axis=1) ¿Es un problema real aquí, o no debería preocuparme?
Nate
2
@Nate Nunca recibí esa advertencia, ¿tal vez depende de los datos en el marco de datos? Pero modifiqué la respuesta basada en otra respuesta de 2017.
Brian Burns
57

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

¿Por qué debería numpy.selectser usado 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)

El uso numpy.selectnos proporciona un rendimiento enormemente mejorado, y la discrepancia solo aumentará a medida que crezcan los datos.

usuario3483203
fuente
8
Esta solución está muy subestimada. Sabía que podía hacer algo similar con la aplicación, pero estaba buscando una alternativa, ya que tengo que hacer esa operación para miles de archivos. Me alegro mucho de haber encontrado tu publicación.
mlx
Tengo problemas para crear algo similar. Recibo el mensaje de error "el valor de verdad de una serie es ambiguo ...". Mi código es Kansas_City = ['ND', 'SD', 'NE', 'KS', 'MN', 'IA', 'MO'] condiciones = [df_merge ['state_alpha'] en Kansas_City] salidas = [' Kansas City '] df_merge [' Región '] = np.select (condiciones, salidas,' Otro ') ¿Puede ayudarme?
Shawn Schreier
3
Esta debería ser la respuesta aceptada. Los otros están bien, pero una vez que está trabajando en datos más grandes, este es el único que funciona, y funciona increíblemente rápido.
Proletariado
29

.apply()toma una función como primer parámetro; Pase la label_racefunción de la siguiente manera:

df['race_label'] = df.apply(label_race, axis=1)

No necesita hacer una función lambda para pasar una función.

Gabrielle Simard-Moore
fuente
12

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 en .loclugar de apply.

Mejora la vectorización.

.loc funciona de manera simple, enmascarar filas según la condición, aplicar valores a las filas congeladas.

para más detalles visite, .loc docs

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 ciclo (media ± estándar de desarrollo de 7 carreras, 1 ciclo 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)

Mohamed Thasin ah
fuente