Supprimer des lignes d'un DataFrame pandas basé sur une expression conditionnelle impliquant len ​​(chaîne) donnant KeyError


303

J'ai un pandas DataFrame et je veux en supprimer des lignes où la longueur de la chaîne dans une colonne particulière est supérieure à 2.

Je m'attends à pouvoir le faire (par cette réponse ):

df[(len(df['column name']) < 2)]

mais je reçois juste l'erreur:

KeyError: u'no item named False'

Qu'est-ce que je fais mal?

(Remarque: je sais que je peux utiliser df.dropna()pour supprimer les lignes qui en contiennent NaN, mais je n'ai pas vu comment supprimer les lignes basées sur une expression conditionnelle.)

Réponses:


169

Lorsque vous le faites, len(df['column name'])vous obtenez simplement un numéro, à savoir le nombre de lignes dans le DataFrame (c'est-à-dire la longueur de la colonne elle-même). Si vous souhaitez appliquer lenà chaque élément de la colonne, utilisez df['column name'].map(len). Alors essayez

df[df['column name'].map(len) < 2]

3
J'ai trouvé un moyen d'utiliser une compréhension de liste: df[[(len(x) < 2) for x in df['column name']]]mais la vôtre est beaucoup plus agréable. Merci de votre aide!
sjs

13
Si quelqu'un a besoin d'une comparaison plus complexe, un lambda peut toujours être utilisé. df[df['column name'].map(lambda x: str(x)!=".")]
4lberto

1
Pour une raison quelconque, aucune des autres options n'a fonctionné pour moi, à l'exception de celle publiée par @ 4lberto. Je suis sur pandas 0.23.4Python 3.6
Goelakash

1
J'ajouterais un .copy()à la fin, au cas où vous souhaiteriez éditer ultérieurement ce cadre de données (par exemple, l'attribution de nouvelles colonnes déclencherait l'avertissement "Une valeur tente d'être définie sur une copie d'une tranche à partir d'un DataFrame".
PlasmaBinturong

807

Pour répondre directement au titre original de cette question "Comment supprimer des lignes d'un DataFrame pandas basé sur une expression conditionnelle" (ce qui, je comprends, n'est pas nécessairement le problème de l'OP mais pourrait aider d'autres utilisateurs à rencontrer cette question) une façon de le faire est d'utiliser la méthode drop :

df = df.drop(some labels)

df = df.drop(df[<some boolean condition>].index)

Exemple

Pour supprimer toutes les lignes où la colonne «score» est <50:

df = df.drop(df[df.score < 50].index)

Version en place (comme indiqué dans les commentaires)

df.drop(df[df.score < 50].index, inplace=True)

Conditions multiples

(voir Indexation booléenne )

Les opérateurs sont: |pour or, &pour andet ~pournot . Ceux-ci doivent être regroupés à l'aide de parenthèses.

Pour supprimer toutes les lignes où le «score» de la colonne est <50 et> 20

df = df.drop(df[(df.score < 50) & (df.score > 20)].index)


32
Je veux juste remarquer que la fonction drop prend en charge le remplacement sur place. C'est à dire,. votre solution est la même que df.drop (df [df.score <50] .index, inplace = True). Néanmoins, ne connaissait pas l'astuce "index".
M'a

9
Je veux juste souligner qu'avant d'utiliser cette astuce d'index, vous devez vous assurer que vos valeurs d'index sont uniques (ou appel reset_index()). J'ai trouvé cela difficile quand beaucoup de lignes ont été supprimées de mon dataframe.
Jay

3
comment puis-je supprimer toutes les lignes où le type de colonne est str? Je souhaite conserver uniquement les types de colonne de liste. J'ai essayé test = df.drop(df[df['col1'].dtype == str].index)mais j'obtiens l'erreur KeyError: False J'ai aussi essayé df.drop(df[df.col1.dtype == str].index)et df.drop(df[type(df.cleaned_norm_email) == str].index)mais rien ne semble fonctionner? Quelqu'un peut-il me conseiller? Merci! @User
PyRsquared

1
C'est une vieille question, mais ... @ les poissons à problèmes aquatiques sont beaucoup plus rapides que celui-ci. Notez que vous calculez df[(df.score < 50) & (df.score > 20)]dans le cadre de votre réponse. Si vous inversiez cela, df = df[(df.score >= 50) | (df.score <= 20)]vous obtiendrez votre réponse beaucoup plus rapidement.
Roobie Nuby

1
@RoobieNuby - ce n'est pas la même condition.
Nguai al

106

Vous pouvez affecter le DataFrameà une version filtrée de lui-même:

df = df[df.score > 50]

C'est plus rapide que drop:

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test = test[test.x < 0]
# 54.5 ms ± 2.02 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test.drop(test[test.x > 0].index, inplace=True)
# 201 ms ± 17.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test = test.drop(test[test.x > 0].index)
# 194 ms ± 7.03 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Comment puis-je vérifier l'utilisation de plusieurs colonnes ou la condition?
Piyush S. Wanare


9

Je développerai la solution générique de @ User pour fournir un drop alternative gratuite. Ceci est pour les personnes dirigées ici en fonction du titre de la question (pas le problème d'OP)

Supposons que vous souhaitiez supprimer toutes les lignes contenant des valeurs négatives. Une solution de revêtement est: -

df = df[(df > 0).all(axis=1)]

Explication étape par étape: -

Générons une trame de données de distribution normale aléatoire 5x5

np.random.seed(0)
df = pd.DataFrame(np.random.randn(5,5), columns=list('ABCDE'))
      A         B         C         D         E
0  1.764052  0.400157  0.978738  2.240893  1.867558
1 -0.977278  0.950088 -0.151357 -0.103219  0.410599
2  0.144044  1.454274  0.761038  0.121675  0.443863
3  0.333674  1.494079 -0.205158  0.313068 -0.854096
4 -2.552990  0.653619  0.864436 -0.742165  2.269755

Que la condition supprime les négatifs. Un booléen df satisfaisant la condition: -

df > 0
      A     B      C      D      E
0   True  True   True   True   True
1  False  True  False  False   True
2   True  True   True   True   True
3   True  True  False   True  False
4  False  True   True  False   True

Une série booléenne pour toutes les lignes remplissant la condition Remarque si un élément de la ligne échoue, la ligne est marquée comme fausse

(df > 0).all(axis=1)
0     True
1    False
2     True
3    False
4    False
dtype: bool

Enfin, filtrez les lignes du bloc de données en fonction de la condition

df[(df > 0).all(axis=1)]
      A         B         C         D         E
0  1.764052  0.400157  0.978738  2.240893  1.867558
2  0.144044  1.454274  0.761038  0.121675  0.443863

Vous pouvez attribuer revenir à df de réellement supprimer vs filtre ing fait ci - dessus
df = df[(df > 0).all(axis=1)]

Cela peut facilement être étendu pour filtrer les lignes contenant des NaN (entrées non numériques): -
df = df[(~df.isnull()).all(axis=1)]

Cela peut également être simplifié pour des cas comme: Supprimer toutes les lignes où la colonne E est négative

df = df[(df.E>0)]

Je voudrais terminer avec quelques statistiques de profilage expliquant pourquoi la dropsolution @ User est plus lente que la filtration basée sur des colonnes brutes: -

%timeit df_new = df[(df.E>0)]
345 µs ± 10.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit dft.drop(dft[dft.E < 0].index, inplace=True)
890 µs ± 94.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Une colonne est essentiellement un à- Seriesdire un NumPytableau, il peut être indexé sans frais. Pour les personnes intéressées par la façon dont l'organisation de la mémoire sous-jacente joue dans la vitesse d'exécution, voici un excellent lien sur l'accélération des pandas :


6

Dans les pandas, vous pouvez faire str.lenavec votre frontière et utiliser le résultat booléen pour la filtrer.

df[df['column name'].str.len().lt(2)]

3

Si vous souhaitez supprimer des lignes de trame de données sur la base d'une condition compliquée sur la valeur de la colonne, l'écriture de la manière indiquée ci-dessus peut être compliquée. J'ai la solution plus simple suivante qui fonctionne toujours. Supposons que vous souhaitiez supprimer la colonne avec «en-tête», alors commencez par la répertorier.

text_data = df['name'].tolist()

maintenant, appliquez une fonction sur chaque élément de la liste et mettez-la dans une série panda:

text_length = pd.Series([func(t) for t in text_data])

dans mon cas, j'essayais juste d'obtenir le nombre de jetons:

text_length = pd.Series([len(t.split()) for t in text_data])

ajoutez maintenant une colonne supplémentaire avec la série ci-dessus dans le bloc de données:

df = df.assign(text_length = text_length .values)

maintenant, nous pouvons appliquer des conditions sur la nouvelle colonne telles que:

df = df[df.text_length  >  10]
def pass_filter(df, label, length, pass_type):

    text_data = df[label].tolist()

    text_length = pd.Series([len(t.split()) for t in text_data])

    df = df.assign(text_length = text_length .values)

    if pass_type == 'high':
        df = df[df.text_length  >  length]

    if pass_type == 'low':
        df = df[df.text_length  <  length]

    df = df.drop(columns=['text_length'])

    return df
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.