les pandas créent une nouvelle colonne en fonction des valeurs des autres colonnes / appliquent une fonction de plusieurs colonnes, par ligne

316

Je veux appliquer ma fonction personnalisée (il utilise un if-else échelle) à ces six colonnes ( ERI_Hispanic, ERI_AmerInd_AKNatv, ERI_Asian, ERI_Black_Afr.Amer, ERI_HI_PacIsl, ERI_White) dans chaque rangée de mon dataframe.

J'ai essayé différentes méthodes à partir d'autres questions, mais je n'arrive toujours pas à trouver la bonne réponse à mon problème. L'élément critique est que si la personne est considérée comme hispanique, elle ne peut pas être considérée comme autre chose. Même s'ils ont un «1» dans une autre colonne ethnique, ils sont toujours considérés comme hispaniques et non comme deux races ou plus. De même, si la somme de toutes les colonnes ERI est supérieure à 1, elles sont comptées comme deux races ou plus et ne peuvent pas être comptées comme une ethnie unique (sauf pour les Hispaniques). J'espère que cela a du sens. Toute aide sera fortement appréciée.

C'est presque comme faire une boucle for à travers chaque ligne et si chaque enregistrement répond à un critère, ils sont ajoutés à une liste et éliminés de l'original.

À partir de la trame de données ci-dessous, je dois calculer une nouvelle colonne en fonction des spécifications suivantes dans SQL:

========================= CRITÈRES ========================= =======

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

Commentaire: Si le drapeau ERI pour Hispanique est Vrai (1), l'employé est classé comme «Hispanique»

Commentaire: Si plus d'un drapeau ERI non hispanique est vrai, retournez «Deux ou plus»

====================== 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
la source
Votre fonction particulière n'est qu'une longue échelle if-else où les valeurs de certaines variables ont priorité sur les autres. Il serait appelé un décodeur prioritaire dans le langage de l'ingénierie matérielle.
smci

Réponses:

408

OK, deux étapes pour cela - la première consiste à écrire une fonction qui fait la traduction que vous voulez - j'ai mis un exemple en fonction de votre pseudo-code:

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'

Vous voudrez peut-être passer en revue cela, mais cela semble faire l'affaire - notez que le paramètre entrant dans la fonction est considéré comme un objet Series étiqueté "ligne".

Ensuite, utilisez la fonction apply dans pandas pour appliquer la fonction - par exemple

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

Notez le spécificateur axis = 1, ce qui signifie que l'application se fait au niveau d'une ligne plutôt qu'au niveau d'une colonne. Les résultats sont ici:

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 vous êtes satisfait de ces résultats, exécutez-le à nouveau, en enregistrant les résultats dans une nouvelle colonne dans votre trame de données d'origine.

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

La trame de données résultante ressemble à ceci (faites défiler vers la droite pour voir la nouvelle colonne):

      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
la source
69
juste une note: si vous ne faites qu'alimenter la ligne dans votre fonction, vous pouvez simplement faire:df.apply(label_race, axis=1)
Paul H
1
Si je voulais faire quelque chose de similaire avec une autre ligne, pourrais-je utiliser la même fonction? Par exemple, à partir des résultats, si ['race_label'] == "Blanc" retourne "Blanc" et ainsi de suite. Mais si la ['race_label'] == 'Unknown' renvoie les valeurs de la colonne ['rno_defined']. Je suppose que la même fonction fonctionnerait, mais je n'arrive pas à comprendre comment obtenir les valeurs de l'autre colonne.
Dave
2
Vous pouvez écrire une nouvelle fonction, qui regarde le champ 'race_label', et envoyer les résultats dans un nouveau champ, ou - et je pense que cela pourrait être mieux dans ce cas, éditez la fonction d'origine, en changeant la dernière return 'Other'ligne à return row['rno_defined']laquelle devrait remplacez la valeur de cette colonne dans les cas où l'ensemble d'instructions if / then ne trouve pas de correspondance (c'est-à-dire où actuellement, vous voyez «Autre»).
Thomas Kimber
9
Vous pouvez simplifier: df.apply(lambda row: label_race (row),axis=1)todf.apply(label_race, axis=1)
user48956
5
Dans les versions plus récentes, si vous obtenez «SettingWithCopyWarning», vous devriez regarder la méthode «assign». Voir: stackoverflow.com/a/12555510/3015186
np8
218

Puisqu'il s'agit du premier résultat Google pour «pandas new column from others», voici un exemple 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 vous obtenez le, SettingWithCopyWarningvous pouvez également le faire de cette façon:

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'

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

Et si le nom de votre colonne comprend des espaces, vous pouvez utiliser une syntaxe comme celle-ci:

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

Et voici la documentation pour appliquer et attribuer .

Brian Burns
la source
1
Réponse courte, distillée jusqu'à l'essentiel!
Frode Akselsen
1
J'obtiens SettingWithCopyWarningquand je le fais df['c'] = df.apply(lambda row: row.a + row.b, axis=1) Est-ce un vrai problème ici, ou ne devrais-je pas m'en inquiéter?
Nate
2
@Nate Je n'ai jamais reçu cet avertissement - cela dépend peut-être des données dans le dataframe? Mais j'ai modifié la réponse sur la base d'une autre réponse de 2017.
Brian Burns
57

Les réponses ci-dessus sont parfaitement valables, mais une solution vectorisée existe, sous la forme de numpy.select. Cela vous permet de définir des conditions, puis de définir des sorties pour ces conditions, beaucoup plus efficacement qu'en utilisant apply:


Définissez d'abord les conditions:

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

Maintenant, définissez les sorties correspondantes:

outputs = [
    'Hispanic', 'Two Or More', 'A/I AK Native', 'Asian', 'Black/AA', 'Haw/Pac Isl.', 'White'
]

Enfin, en utilisant 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

Pourquoi devrait numpy.selectêtre utilisé plus apply? Voici quelques vérifications de performances:

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)

L'utilisation numpy.selectnous donne des performances considérablement améliorées, et l'écart ne fera qu'augmenter à mesure que les données augmentent.

user3483203
la source
8
Cette solution est tellement sous-estimée. Je savais que je pouvais faire quelque chose de similaire avec apply mais je cherchais une alternative car je dois faire cette opération pour des milliers de fichiers. Je suis tellement content d'avoir trouvé votre message.
mlx
J'ai du mal à créer quelque chose de similaire. J'obtiens "la valeur de vérité d'une série est ambiguë ..." message d'erreur. Mon code est Kansas_City = ['ND', 'SD', 'NE', 'KS', 'MN', 'IA', 'MO'] conditions = [df_merge ['state_alpha'] dans Kansas_City] sorties = [' Kansas City '] df_merge [' Region '] = np.select (conditions, sorties,' Autre ') Pouvez-vous m'aider?
Shawn Schreier
3
Ce devrait être la réponse acceptée. Les autres sont très bien, mais une fois que vous travaillez dans des données plus volumineuses, celle-ci est la seule qui fonctionne, et elle fonctionne incroyablement rapidement.
TheProletariat
29

.apply()prend une fonction comme premier paramètre; passez la label_racefonction comme suit:

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

Vous n'avez pas besoin de créer une fonction lambda pour transmettre une fonction.

Gabrielle Simard-Moore
la source
12

essaye ça,

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 

utiliser .locau lieu de apply.

il améliore la vectorisation.

.loc fonctionne de manière simple, masque les lignes en fonction de la condition, applique des valeurs aux lignes figées.

pour plus de détails visitez, .loc docs

Indicateurs de performance:

Réponse acceptée:

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 par boucle (moyenne ± écart standard de 7 passages, 1 boucle chacun)

Ma réponse proposée:

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 par boucle (écart moyen ± standard de 7 passages, 10 boucles chacun)

Mohamed Thasin ah
la source