Vérifiez si la colonne pandas contient tous les éléments d'une liste

20

J'ai un df comme ça:

frame = pd.DataFrame({'a' : ['a,b,c', 'a,c,f', 'b,d,f','a,z,c']})

Et une liste d'articles:

letters = ['a','c']

Mon objectif est d’obtenir toutes les lignes framecontenant au moins les 2 élémentsletters

J'ai trouvé cette solution:

for i in letters:
    subframe = frame[frame['a'].str.contains(i)]

Cela me donne ce que je veux, mais ce n'est peut-être pas la meilleure solution en termes d'évolutivité. Existe-t-il une solution «vectorisée»? Merci

Kauber
la source
4
Il ne vous donnera que des lignes contenant la dernière lettre car vous remplacez le sous-cadre dans n'importe quelle itération
Tom Ron
@TomRon Vous avez raison, quelle gaffe :)
Kauber

Réponses:

12

Je construirais une liste de séries, puis appliquerais une vectorisation np.all:

contains = [frame['a'].str.contains(i) for i in letters]
resul = frame[np.all(contains, axis=0)]

Il donne comme prévu:

       a
0  a,b,c
1  a,c,f
3  a,z,c
Serge Ballesta
la source
3
congratz sur 100k!
Peter Haddad
14

Une façon consiste à diviser les valeurs des colonnes en listes à l'aide de str.splitet à vérifier s'il set(letters)s'agit d'une subsetdes listes obtenues:

letters_s = set(letters)
frame[frame.a.str.split(',').map(letters_s.issubset)]

     a
0  a,b,c
1  a,c,f
3  a,z,c

Référence:

def serge(frame):
    contains = [frame['a'].str.contains(i) for i in letters]
    return frame[np.all(contains, axis=0)]

def yatu(frame):
    letters_s = set(letters)
    return frame[frame.a.str.split(',').map(letters_s.issubset)]

def austin(frame):
    mask =  frame.a.apply(lambda x: np.intersect1d(x.split(','), letters).size > 0)
    return frame[mask]

def datanovice(frame):
    s = frame['a'].str.split(',').explode().isin(letters).groupby(level=0).cumsum()
    return frame.loc[s[s.ge(2)].index.unique()]

perfplot.show(
    setup=lambda n: pd.concat([frame]*n, axis=0).reset_index(drop=True), 

    kernels=[
        lambda df: serge(df),
        lambda df: yatu(df),
        lambda df: df[df['a'].apply(lambda x: np.all([*map(lambda l: l in x, letters)]))],
        lambda df: austin(df),
        lambda df: datanovice(df),
    ],

    labels=['serge', 'yatu', 'bruno','austin', 'datanovice'],
    n_range=[2**k for k in range(0, 18)],
    equality_check=lambda x, y: x.equals(y),
    xlabel='N'
)

entrez la description de l'image ici

yatu
la source
Je reçois TypeError: unhashable type: 'set'quand j'exécute votre code? l'a exécuté sur le cadre fourni aboe
Datanovice
Quelle version? @Datanovice Double vérification et tout semble aller bien
yatu
mes pandas sont 1.0.3et python est 3.7probablement juste moi
Datanovice
3
@Datanovice je pense que vous avez besoin de python 3.8 pour cela :)
anky
2
Merci, j'ai la même erreur que @Datanovice et je ne peux pas passer à python 3.8 malheureusement
Kauber
7

Vous pouvez utiliser np.intersect1d:

import pandas as pd
import numpy as np

frame = pd.DataFrame({'a' : ['a,b,c', 'a,c,f', 'b,d,f','a,z,c']})
letters = ['a','c']

mask =  frame.a.apply(lambda x: np.intersect1d(x.split(','), letters).size > 0)
print(frame[mask])

    a
0  a,b,c
1  a,c,f
3  a,z,c
Austin
la source
7

Cela le résout également:

frame[frame['a'].apply(lambda x: np.all([*map(lambda l: l in x, letters)]))]
Bruno Mello
la source
6

Utilisez set.issubset :

frame = pd.DataFrame({'a' : ['a,b,c', 'a,c,f', 'b,d,f','a,z,c','x,y']})
letters = ['a','c']

frame[frame['a'].apply(lambda x: set(letters).issubset(x))]

Out:

       a
0  a,b,c
1  a,c,f
3  a,z,c
ManojK
la source
5

IIUC, explodeet un filtre booléen

l'idée est de créer une seule série puis on peut regrouper par l'index le décompte des vraies occurrences de votre liste en utilisant une somme cumulée

s = frame['a'].str.split(',').explode().isin(letters).groupby(level=0).cumsum()

print(s)

0    1.0
0    1.0
0    2.0
1    1.0
1    2.0
1    2.0
2    0.0
2    0.0
2    0.0
3    1.0
3    1.0
3    2.0

frame.loc[s[s.ge(2)].index.unique()]

out:

       a
0  a,b,c
1  a,c,f
3  a,z,c
Datanovice
la source
1
frame.iloc[[x for x in range(len(frame)) if set(letters).issubset(frame.iloc[x,0])]]

production:

        a
 0  a,b,c
 1  a,c,f
 3  a,z,c

timeit

%%timeit
#hermes
frame.iloc[[x for x in range(len(frame)) if set(letters).issubset(frame.iloc[x,0])]]

production

300 µs ± 32.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Hermes Morales
la source