Comparez deux colonnes à l'aide de pandas

104

En utilisant ceci comme point de départ:

a = [['10', '1.2', '4.2'], ['15', '70', '0.03'], ['8', '5', '0']]
df = pd.DataFrame(a, columns=['one', 'two', 'three'])

Out[8]: 
  one  two three
0   10  1.2   4.2
1   15  70   0.03
2    8   5     0

Je veux utiliser quelque chose comme une ifdéclaration dans les pandas.

if df['one'] >= df['two'] and df['one'] <= df['three']:
    df['que'] = df['one']

En gros, vérifiez chaque ligne via l' ifinstruction, créez une nouvelle colonne.

La documentation dit à utiliser .allmais il n'y a pas d'exemple ...

Merlin
la source
Quelle devrait être la valeur si la ifdéclaration est False?
Alex Riley
3
@Merlin: Si vous avez des données numériques dans une colonne, il vaut mieux ne pas les mélanger avec des chaînes. Cela change le dtype de la colonne en object. Cela permet de stocker des objets Python arbitraires dans la colonne, mais cela se fait au prix d'un calcul numérique plus lent. Ainsi, si la colonne stocke des données numériques, l'utilisation de NaN pour not-a-numbers est préférable.
unutbu
1
Avoir des entiers sous forme de chaînes et d' essayer de faire la comparaison sur eux semble étrange: a = [['10', '1.2', '4.2'], ['15', '70', '0.03'], ['8', '5', '0']]. Cela crée des résultats confus avec du code "correct": df['que'] = df['one'][(df['one'] >= df['two']) & (df['one'] <= df['three'])] donne 10pour la première ligne, alors qu'il devrait produire NaNsi l'entrée aurait été des entiers.
Amorce du

Réponses:

147

Vous pouvez utiliser np.where . Si condest un tableau booléen, et Aet Bsont des tableaux, alors

C = np.where(cond, A, B)

définit C comme étant égal à Acondest True et Bcondest False.

import numpy as np
import pandas as pd

a = [['10', '1.2', '4.2'], ['15', '70', '0.03'], ['8', '5', '0']]
df = pd.DataFrame(a, columns=['one', 'two', 'three'])

df['que'] = np.where((df['one'] >= df['two']) & (df['one'] <= df['three'])
                     , df['one'], np.nan)

rendements

  one  two three  que
0  10  1.2   4.2   10
1  15   70  0.03  NaN
2   8    5     0  NaN

Si vous avez plus d'une condition, vous pouvez utiliser np.select à la place. Par exemple, si vous souhaitez df['que']égaler df['two']quand df['one'] < df['two'], alors

conditions = [
    (df['one'] >= df['two']) & (df['one'] <= df['three']), 
    df['one'] < df['two']]

choices = [df['one'], df['two']]

df['que'] = np.select(conditions, choices, default=np.nan)

rendements

  one  two three  que
0  10  1.2   4.2   10
1  15   70  0.03   70
2   8    5     0  NaN

Si nous pouvons supposer que df['one'] >= df['two']when df['one'] < df['two']est False, alors les conditions et les choix pourraient être simplifiés à

conditions = [
    df['one'] < df['two'],
    df['one'] <= df['three']]

choices = [df['two'], df['one']]

(L'hypothèse peut ne pas être vraie si df['one']ou df['two']contiennent des NaN.)


Notez que

a = [['10', '1.2', '4.2'], ['15', '70', '0.03'], ['8', '5', '0']]
df = pd.DataFrame(a, columns=['one', 'two', 'three'])

définit un DataFrame avec des valeurs de chaîne. Puisqu'elles ont l'air numériques, vous feriez peut-être mieux de convertir ces chaînes en flottants:

df2 = df.astype(float)

Cela modifie les résultats, cependant, car les chaînes se comparent caractère par caractère, tandis que les flottants sont comparés numériquement.

In [61]: '10' <= '4.2'
Out[61]: True

In [62]: 10 <= 4.2
Out[62]: False
unutbu
la source
73

Vous pouvez utiliser .equalspour des colonnes ou des dataframes entiers.

df['col1'].equals(df['col2'])

S'ils sont égaux, cette déclaration sera renvoyée True, sinon False.

ccook5760
la source
22
Remarque: cela compare uniquement la colonne entière à une autre. Cela ne compare pas l'élément de colonne sage
guerda
1
Que diriez-vous si vous voulez voir si une colonne a toujours la valeur «supérieur à» ou «inférieur» aux autres colonnes?
rrlamichhane le
28

Vous pouvez utiliser apply () et faire quelque chose comme ça

df['que'] = df.apply(lambda x : x['one'] if x['one'] >= x['two'] and x['one'] <= x['three'] else "", axis=1)

ou si vous préférez ne pas utiliser de lambda

def que(x):
    if x['one'] >= x['two'] and x['one'] <= x['three']:
        return x['one']
    return ''
df['que'] = df.apply(que, axis=1)
Bob Haffner
la source
2
Je soupçonne que c'est probablement un peu plus lent que les autres approches publiées, car cela ne tire pas parti des opérations vectorisées que les pandas permettent.
Marius
@BobHaffner: les lambda ne sont pas lisibles lors de l'utilisation d'instructions if / then / else complexes.
Merlin
@Merlin vous pourriez ajouter un elseif et je serais d'accord avec vous sur les lambdas et les conditions multiples
Bob Haffner
existe-t-il un moyen de généraliser la fonction non lambda de sorte que vous puissiez passer des colonnes de dataframe et ne pas changer le nom?
AZhao
@AZhao vous pouvez généraliser avec iloc comme ceci df ['que'] = df.apply (lambda x: x.iloc [0] if x.iloc [0]> = x.iloc [1] et x.iloc [0 ] <= x.iloc [2] else "", axis = 1) C'est ce que vous voulez dire? Évidemment. l'ordre de vos colonnes compte
Bob Haffner
9

Une méthode consiste à utiliser une série booléenne pour indexer la colonne df['one']. Cela vous donne une nouvelle colonne où les Trueentrées ont la même valeur que la même ligne df['one']et les Falsevaleurs sont NaN.

La série booléenne est juste donnée par votre ifinstruction (bien qu'il soit nécessaire d'utiliser à la &place de and):

>>> df['que'] = df['one'][(df['one'] >= df['two']) & (df['one'] <= df['three'])]
>>> df
    one two three   que
0   10  1.2 4.2      10
1   15  70  0.03    NaN
2   8   5   0       NaN

Si vous souhaitez que les NaNvaleurs soient remplacées par d'autres valeurs, vous pouvez utiliser la fillnaméthode sur la nouvelle colonne que. J'ai utilisé à la 0place de la chaîne vide ici:

>>> df['que'] = df['que'].fillna(0)
>>> df
    one two three   que
0   10  1.2   4.2    10
1   15   70  0.03     0
2    8    5     0     0
Alex Riley
la source
4

Mettez chaque condition individuelle entre parenthèses, puis utilisez l' &opérateur pour combiner les conditions:

df.loc[(df['one'] >= df['two']) & (df['one'] <= df['three']), 'que'] = df['one']

Vous pouvez remplir les lignes non correspondantes en utilisant simplement ~(l'opérateur "non") pour inverser la correspondance:

df.loc[~ ((df['one'] >= df['two']) & (df['one'] <= df['three'])), 'que'] = ''

Vous devez utiliser &et ~plutôt que andet notcar les opérateurs &et ~fonctionnent élément par élément.

Le résultat final:

df
Out[8]: 
  one  two three que
0  10  1.2   4.2  10
1  15   70  0.03    
2   8    5     0  
Marius
la source
1

À utiliser np.selectsi vous avez plusieurs conditions à vérifier à partir de la trame de données et à afficher un choix spécifique dans une colonne différente

conditions=[(condition1),(condition2)]
choices=["choice1","chocie2"]

df["new column"]=np.select=(condtion,choice,default=)

Remarque: le nombre de conditions et le nombre de choix doivent correspondre, répétez le texte dans le choix si pour deux conditions différentes vous avez les mêmes choix

psn1997
la source
0

Je pense que le plus proche de l'intuition de l'OP est une instruction if en ligne:

df['que'] = (df['one'] if ((df['one'] >= df['two']) and (df['one'] <= df['three'])) 
Nic Scozzaro
la source
Votre code me donne une erreurdf['que'] = (df['one'] if ((df['one'] >= df['two']) and (df['one'] <= df['three'])) ^ SyntaxError: unexpected EOF while parsing
vasili111