Pandas création conditionnelle d'une colonne série / trame de données


314

J'ai une trame de données dans le sens de ce qui suit:

    Type       Set
1    A          Z
2    B          Z           
3    B          X
4    C          Y

Je veux ajouter une autre colonne à la trame de données (ou générer une série) de la même longueur que la trame de données (= nombre égal d'enregistrements / lignes) qui définit une couleur verte si Set = 'Z' et 'rouge' si Set = sinon .

Quelle est la meilleure façon de procéder?

Réponses:


713

Si vous n'avez que deux choix:

df['color'] = np.where(df['Set']=='Z', 'green', 'red')

Par exemple,

import pandas as pd
import numpy as np

df = pd.DataFrame({'Type':list('ABBC'), 'Set':list('ZZXY')})
df['color'] = np.where(df['Set']=='Z', 'green', 'red')
print(df)

les rendements

  Set Type  color
0   Z    A  green
1   Z    B  green
2   X    B    red
3   Y    C    red

Si vous avez plus de deux conditions, utiliseznp.select . Par exemple, si vous voulez colorêtre

  • yellow quand (df['Set'] == 'Z') & (df['Type'] == 'A')
  • sinon bluequand(df['Set'] == 'Z') & (df['Type'] == 'B')
  • sinon purplequand(df['Type'] == 'B')
  • sinon black,

puis utilisez

df = pd.DataFrame({'Type':list('ABBC'), 'Set':list('ZZXY')})
conditions = [
    (df['Set'] == 'Z') & (df['Type'] == 'A'),
    (df['Set'] == 'Z') & (df['Type'] == 'B'),
    (df['Type'] == 'B')]
choices = ['yellow', 'blue', 'purple']
df['color'] = np.select(conditions, choices, default='black')
print(df)

qui donne

  Set Type   color
0   Z    A  yellow
1   Z    B    blue
2   X    B  purple
3   Y    C   black

1
ne fonctionne pas si je mets deux conditions à l'intérieur de la clause where avec et
Amol Sharma

2
df ['color'] = list (np.where (df ['Set'] == 'Z', 'green', 'red')) supprimera l'avertissement pandas: Une valeur tente d'être définie sur une copie d'une tranche à partir d'un DataFrame. Essayez d'utiliser .loc [row_indexer, col_indexer] = value à la place
denson

3
«vert» et «rouge» peuvent également être remplacés par l'arithmétique des colonnes. par exemple ,df['foo'] = np.where(df['Set']=='Z', df['Set'], df['Type'].shift(1))
Alejandro

np.where crée-t-il une nouvelle colonne? J'ai utilisé ce code et quand je fais df.color.head () j'obtiens: l'objet 'numpy.ndarray' n'a pas d'attribut 'head'
vvv

3
C'est dommage que je ne puisse pas voter à plusieurs reprises. Un vote positif ne semble pas suffisant.
Harper

120

La compréhension des listes est une autre façon de créer une autre colonne de manière conditionnelle. Si vous travaillez avec des dtypes d'objet dans des colonnes, comme dans votre exemple, les compréhensions de liste surpassent généralement la plupart des autres méthodes.

Exemple de compréhension de liste:

df['color'] = ['red' if x == 'Z' else 'green' for x in df['Set']]

% tests timeit:

import pandas as pd
import numpy as np

df = pd.DataFrame({'Type':list('ABBC'), 'Set':list('ZZXY')})
%timeit df['color'] = ['red' if x == 'Z' else 'green' for x in df['Set']]
%timeit df['color'] = np.where(df['Set']=='Z', 'green', 'red')
%timeit df['color'] = df.Set.map( lambda x: 'red' if x == 'Z' else 'green')

1000 loops, best of 3: 239 µs per loop
1000 loops, best of 3: 523 µs per loop
1000 loops, best of 3: 263 µs per loop

4
Notez que, avec des cadres de données beaucoup plus grands (pensez- pd.DataFrame({'Type':list('ABBC')*100000, 'Set':list('ZZXY')*100000})taille), numpy.wheredépasse map, mais la compréhension de la liste est roi (environ 50% plus rapide que numpy.where).
blacksite

3
La méthode de compréhension de liste peut-elle être utilisée si la condition a besoin d'informations provenant de plusieurs colonnes? Je cherche quelque chose comme ça (cela ne fonctionne pas):df['color'] = ['red' if (x['Set'] == 'Z') & (x['Type'] == 'B') else 'green' for x in df]
Mappi

2
Ajoutez des lignes à la trame de données, puis vous pouvez accéder à plusieurs colonnes via la ligne: ['rouge' si (ligne ['Set'] == 'Z') & (ligne ['Type'] == 'B') else 'vert 'pour l'index, ligne dans df.iterrows ()]
cheekybastard

1
Notez que cette belle solution ne fonctionnera pas si vous devez prendre des valeurs de remplacement d'une autre série dans la trame de données, telles quedf['color_type'] = np.where(df['Set']=='Z', 'green', df['Type'])
Paul Rougieux

@cheekybastard Ou pas, car il .iterrows()est notoirement lent et le DataFrame ne doit pas être modifié pendant l'itération.
AMC

21

Un autre moyen d’y parvenir est de

df['color'] = df.Set.map( lambda x: 'red' if x == 'Z' else 'green')

Bonne approche, cela peut être mémorisé pour une efficacité plus rapide (dans des ensembles de données plus importants), mais nécessiterait une étape supplémentaire.
Yaakov Bressler

21

Voici encore une autre façon d'habiller ce chat, en utilisant un dictionnaire pour mapper de nouvelles valeurs sur les clés de la liste:

def map_values(row, values_dict):
    return values_dict[row]

values_dict = {'A': 1, 'B': 2, 'C': 3, 'D': 4}

df = pd.DataFrame({'INDICATOR': ['A', 'B', 'C', 'D'], 'VALUE': [10, 9, 8, 7]})

df['NEW_VALUE'] = df['INDICATOR'].apply(map_values, args = (values_dict,))

À quoi ça ressemble:

df
Out[2]: 
  INDICATOR  VALUE  NEW_VALUE
0         A     10          1
1         B      9          2
2         C      8          3
3         D      7          4

Cette approche peut être très puissante lorsque vous avez de nombreuses ifelseinstructions de type à effectuer (c'est-à-dire de nombreuses valeurs uniques à remplacer).

Et bien sûr, vous pouvez toujours faire ceci:

df['NEW_VALUE'] = df['INDICATOR'].map(values_dict)

Mais cette approche est plus de trois fois plus lente que l' applyapproche d'en haut, sur ma machine.

Et vous pouvez également le faire en utilisant dict.get:

df['NEW_VALUE'] = [values_dict.get(v, None) for v in df['INDICATOR']]

J'aime cette réponse car elle montre comment faire plusieurs remplacements de valeurs
Monica Heddneck

Mais cette approche est plus de trois fois plus lente que l'approche appliquée d'en haut, sur ma machine. Comment les avez-vous évalués? D'après mes mesures rapides, la .map()solution est ~ 10 fois plus rapide que .apply().
AMC

Mise à jour: sur 100 000 000 lignes, 52 valeurs de chaîne, .apply()prend 47 secondes, contre seulement 5,91 secondes pour .map().
AMC

19

Ce qui suit est plus lent que les approches chronométrées ici , mais nous pouvons calculer la colonne supplémentaire en fonction du contenu de plus d'une colonne, et plus de deux valeurs peuvent être calculées pour la colonne supplémentaire.

Exemple simple utilisant uniquement la colonne "Set":

def set_color(row):
    if row["Set"] == "Z":
        return "red"
    else:
        return "green"

df = df.assign(color=df.apply(set_color, axis=1))

print(df)
  Set Type  color
0   Z    A    red
1   Z    B    red
2   X    B  green
3   Y    C  green

Exemple avec plus de couleurs et plus de colonnes prises en compte:

def set_color(row):
    if row["Set"] == "Z":
        return "red"
    elif row["Type"] == "C":
        return "blue"
    else:
        return "green"

df = df.assign(color=df.apply(set_color, axis=1))

print(df)
  Set Type  color
0   Z    A    red
1   Z    B    red
2   X    B  green
3   Y    C   blue

Edit (21/06/2019): Utilisation de plydata

Il est également possible d'utiliser plydata pour faire ce genre de choses (cela semble encore plus lent que d'utiliser assignet apply, cependant).

from plydata import define, if_else

Simple if_else:

df = define(df, color=if_else('Set=="Z"', '"red"', '"green"'))

print(df)
  Set Type  color
0   Z    A    red
1   Z    B    red
2   X    B  green
3   Y    C  green

Imbriqué if_else:

df = define(df, color=if_else(
    'Set=="Z"',
    '"red"',
    if_else('Type=="C"', '"green"', '"blue"')))

print(df)                            
  Set Type  color
0   Z    A    red
1   Z    B    red
2   X    B   blue
3   Y    C  green

10

Peut-être que cela a été possible avec les nouvelles mises à jour de Pandas, mais je pense que ce qui suit est la réponse la plus courte et peut-être la meilleure pour l'instant. Vous pouvez utiliser le.loc méthode et utiliser une ou plusieurs conditions selon vos besoins.

Résumé du code:

df=pd.DataFrame(dict(Type='A B B C'.split(), Set='Z Z X Y'.split()))
df['Color'] = "red"
df.loc[(df['Set']=="Z"), 'Color'] = "green"

#practice!
df.loc[(df['Set']=="Z")&(df['Type']=="B")|(df['Type']=="C"), 'Color'] = "purple"

Explication:

df=pd.DataFrame(dict(Type='A B B C'.split(), Set='Z Z X Y'.split()))

# df so far: 
  Type Set  
0    A   Z 
1    B   Z 
2    B   X 
3    C   Y

ajouter une colonne «couleur» et définir toutes les valeurs sur «rouge»

df['Color'] = "red"

Appliquez votre seule condition:

df.loc[(df['Set']=="Z"), 'Color'] = "green"


# df: 
  Type Set  Color
0    A   Z  green
1    B   Z  green
2    B   X    red
3    C   Y    red

ou plusieurs conditions si vous le souhaitez:

df.loc[(df['Set']=="Z")&(df['Type']=="B")|(df['Type']=="C"), 'Color'] = "purple"

Vous pouvez lire sur les opérateurs logiques Pandas et la sélection conditionnelle ici: Opérateurs logiques pour l'indexation booléenne dans Pandas


2
Le meilleur jusqu'ici. Vous pourriez probablement ajouter pour plus de conditions ce serait le codedf.loc[(df['Set']=="Z") & (df['Type']=="A"), 'Color'] = "green"
Salvador Vigo

2
Ce devrait être la réponse acceptée. En fait idiomatique et extensible.
AMC

1

Un liner avec .apply()méthode suit:

df['color'] = df['Set'].apply(lambda set_: 'green' if set_=='Z' else 'red')

Après cela, dfle bloc de données ressemble à ceci:

>>> print(df)
  Type Set  color
0    A   Z  green
1    B   Z  green
2    B   X    red
3    C   Y    red

0

Si vous travaillez avec des données massives, une approche mémorisée serait la meilleure:

# First create a dictionary of manually stored values
color_dict = {'Z':'red'}

# Second, build a dictionary of "other" values
color_dict_other = {x:'green' for x in df['Set'].unique() if x not in color_dict.keys()}

# Next, merge the two
color_dict.update(color_dict_other)

# Finally, map it to your column
df['color'] = df['Set'].map(color_dict)

Cette approche sera plus rapide lorsque vous avez plusieurs valeurs répétées. Ma règle générale est de mémoriser quand: data_size> 10**4& n_distinct<data_size/4

Ex Memoize dans un cas 10 000 lignes avec 2 500 ou moins de valeurs distinctes.


D'accord, donc avec seulement 2 valeurs distinctes à mapper, 100 000 000 lignes, il faut 6,67 secondes pour s'exécuter sans "mémorisation" et 9,86 secondes avec.
AMC

100 000 000 lignes, 52 valeurs distinctes, dont 1 correspond à la première valeur de sortie et les 51 autres correspondent toutes à l'autre: 7,99 secondes sans mémorisation, 11,1 secondes avec.
AMC

Vos valeurs sont-elles dans un ordre aléatoire? Ou sont-ils dos à dos? La vitesse élevée des pandas pourrait être due à la mise en cache @AMC
Yaakov Bressler

1
Vos valeurs sont-elles dans un ordre aléatoire? Ou sont-ils dos à dos? Les valeurs sont aléatoires, sélectionnées à l'aide de random.choices().
AMC
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.