os pandas criam uma nova coluna com base nos valores de outras colunas / aplicam uma função de várias colunas, em linhas

316

Quero aplicar minha função personalizada (ele usa uma escada de if-else) para estes seis colunas ( ERI_Hispanic, ERI_AmerInd_AKNatv, ERI_Asian, ERI_Black_Afr.Amer, ERI_HI_PacIsl, ERI_White) em cada linha da minha trama de dados.

Tentei métodos diferentes de outras perguntas, mas ainda não consigo encontrar a resposta certa para o meu problema. A parte crítica disso é que, se a pessoa é contada como hispânica, ela não pode ser contada como qualquer outra coisa. Mesmo que eles tenham um "1" em outra coluna de etnia, eles ainda são contados como hispânicos, não como duas ou mais raças. Da mesma forma, se a soma de todas as colunas da ERI for maior que 1, elas serão contadas como duas ou mais raças e não poderão ser contadas como uma etnia única (exceto para hispânicas). Espero que isso faça sentido. Qualquer ajuda será muito apreciada.

É quase como fazer um loop for em cada linha e, se cada registro atender a um critério, eles serão adicionados a uma lista e eliminados do original.

No dataframe abaixo, preciso calcular uma nova coluna com base nas seguintes especificações no SQL:

========================= CRITÉRIOS ======================== =======

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

Comentário: se o sinalizador do ERI para hispânico for verdadeiro (1), o funcionário será classificado como "hispânico"

Comentário: se mais de 1 bandeira ERI não hispânica for verdadeira, retorne "Dois ou mais"

====================== 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
fonte
Sua função específica é apenas uma longa escada if-else, em que os valores de algumas variáveis ​​têm prioridade sobre outros. Seria chamado decodificador de prioridade na linguagem da engenharia de hardware.
smci 28/09/19

Respostas:

408

OK, duas etapas para isso - primeiro é escrever uma função que faça a tradução desejada - juntei um exemplo com base no seu pseudo-có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'

Você pode revisar isso, mas parece fazer o truque - observe que o parâmetro que entra na função é considerado um objeto da série chamado "linha".

Em seguida, use a função aplicar nos pandas para aplicar a função - por exemplo

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

Observe o especificador axis = 1, isso significa que o aplicativo é feito em uma linha, em vez de no nível da coluna. Os resultados estão aqui:

0           White
1        Hispanic
2           White
3           White
4           Other
5           White
6     Two Or More
7           White
8    Haw/Pac Isl.
9           White

Se você estiver satisfeito com esses resultados, execute-o novamente, salvando os resultados em uma nova coluna no quadro de dados original.

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

O quadro de dados resultante tem esta aparência (role para a direita para ver a nova coluna):

      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
fonte
69
apenas uma nota: se você está apenas alimentando a linha em sua função, você pode apenas fazer:df.apply(label_race, axis=1)
Paul H
1
Se eu quisesse fazer algo semelhante com outra linha, poderia usar a mesma função? Por exemplo, a partir dos resultados, se ['race_label'] == "White" retornar 'White' e assim por diante. Mas se o ['race_label'] == 'Unknown' retornar os valores da coluna ['rno_defined']. Presumo que a mesma função funcione, mas não consigo descobrir como obter os valores da outra coluna.
Dave
2
Você pode escrever uma nova função, que analise o campo 'race_label', e enviar os resultados para um novo campo, ou - e eu acho que isso pode ser melhor neste caso, edite a função original, alterando a return 'Other'linha final para a return row['rno_defined']qual deve substitua o valor dessa coluna nos casos em que o conjunto de instruções if / then não encontra uma correspondência (ou seja, onde atualmente, você vê 'Outros').
Thomas Kimber
9
Você pode simplificar: df.apply(lambda row: label_race (row),axis=1)paradf.apply(label_race, axis=1)
user48956
5
Nas versões mais recentes, se você obtiver 'SettingWithCopyWarning', deverá consultar o método 'assign'. Veja: stackoverflow.com/a/12555510/3015186
np8
218

Como este é o primeiro resultado do Google para 'pandas nova coluna de outros', aqui está um exemplo simples:

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

Se você receber, SettingWithCopyWarningpoderá fazê-lo desta maneira também:

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'

Fonte: https://stackoverflow.com/a/12555510/243392

E se o nome da sua coluna incluir espaços, você poderá usar a sintaxe como esta:

df = df.assign(**{'some column name': col.values})

E aqui está a documentação para aplicar e atribuir .

Brian Burns
fonte
1
Resposta curta, destilada até o essencial!
Frode Akselsen
1
Estou recebendo o SettingWithCopyWarningque faço. df['c'] = df.apply(lambda row: row.a + row.b, axis=1) Isso é um problema real aqui, ou não devo me preocupar com isso?
Nate
2
@ Nate Eu nunca recebi esse aviso - talvez dependa dos dados no quadro de dados? Mas eu ammended a resposta com base em outro resposta de 2017.
Brian Burns
57

As respostas acima são perfeitamente válidas, mas existe uma solução vetorizada, na forma de numpy.select. Isso permite definir condições e, em seguida, definir saídas para essas condições, com muito mais eficiência do que usar apply:


Primeiro, defina as condições:

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,
]

Agora, defina as saídas correspondentes:

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 que deveria numpy.selectser usado apply? Aqui estão algumas verificações de desempenho:

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)

O uso numpy.selectnos proporciona um desempenho amplamente aprimorado, e a discrepância só aumenta à medida que os dados aumentam.

user3483203
fonte
8
Esta solução é tão subestimada. Eu sabia que poderia fazer algo semelhante com a aplicação, mas estava procurando uma alternativa, pois tenho que fazer essa operação para milhares de arquivos. Estou tão feliz por ter encontrado o seu post.
MLX
Estou tendo problemas para criar algo semelhante. Recebo a mensagem de erro "o valor de verdade de uma série é ambíguo ...". Meu código é Kansas_City = ['ND', 'SD', 'NE', 'KS', 'MN', 'IA', 'MO'] condições = [df_merge ['state_alpha'] em Kansas_City] outputs = [' Kansas City '] df_merge [' Region '] = np.select (condições, saídas,' Outros ') Pode ajudar?
Shawn Schreier
3
Essa deve ser a resposta aceita. Os outros são bons, mas quando você trabalha com dados maiores, esse é o único que funciona e funciona incrivelmente rápido.
TheProletariat
29

.apply()assume uma função como o primeiro parâmetro; passe a label_racefunção da seguinte maneira:

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

Você não precisa criar uma função lambda para passar em uma função.

Gabrielle Simard-Moore
fonte
12

tente isso,

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 

use em .locvez de apply.

melhora a vetorização.

.loc funciona de maneira simples, mascara linhas com base na condição, aplica valores às linhas congeladas.

para obter mais detalhes, visite .loc docs

Métricas de desempenho:

Resposta Aceita:

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 loop (média ± desvio padrão de 7 corridas, 1 loop cada)

Minha resposta proposta:

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 loop (média ± desvio padrão de 7 corridas, 10 loops cada)

Mohamed Thasin ah
fonte