Überprüfen Sie, ob die Pandas-Spalte alle Elemente aus einer Liste enthält

20

Ich habe einen df wie diesen:

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

Und eine Liste von Gegenständen:

letters = ['a','c']

Mein Ziel ist es, alle Zeilen zu erhalten frame, die mindestens die 2 Elemente enthaltenletters

Ich habe mir diese Lösung ausgedacht:

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

Dies gibt mir, was ich will, aber es ist möglicherweise nicht die beste Lösung in Bezug auf Skalierbarkeit. Gibt es eine "vektorisierte" Lösung? Vielen Dank

Kauber
quelle
4
Sie erhalten nur Zeilen, die den letzten Buchstaben enthalten, da Sie den Unterrahmen in einer Iteration überschreiben
Tom Ron
@ TomRon Du hast recht, was für ein Fehler :)
Kauber

Antworten:

12

Ich würde eine Liste von Serien erstellen und dann eine vektorisierte anwenden np.all:

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

Es gibt wie erwartet:

       a
0  a,b,c
1  a,c,f
3  a,z,c
Serge Ballesta
quelle
3
Herzlichen Glückwunsch zu 100k!
Peter Haddad
14

Eine Möglichkeit besteht darin, die Spaltenwerte mithilfe von Listen in Listen aufzuteilen str.splitund zu überprüfen, ob set(letters)es sich um eine subsetder erhaltenen Listen handelt:

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


Benchmark:

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'
)

Geben Sie hier die Bildbeschreibung ein

Yatu
quelle
Ich bekomme, TypeError: unhashable type: 'set'wenn ich Ihren Code ausführe? lief es auf dem bereitgestellten Rahmen aboe
Datanovice
Welche Version? @Datanovice Doppelte Überprüfung und alles scheint in Ordnung
Yatu
Meine Pandas sind 1.0.3und Python ist 3.7wahrscheinlich nur ich
Datanovice
3
@Datanovice Ich denke, Sie brauchen Python 3.8 dafür :)
Anky
2
Danke, ich bekomme den gleichen Fehler wie @Datanovice und kann leider nicht zu Python 3.8 springen
Kauber
7

Sie können verwenden 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
quelle
7

Dies löst es auch:

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

Verwenden Sie 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
quelle
5

IIUC explodeund ein Boolescher Filter

Die Idee ist, eine einzelne Reihe zu erstellen, dann können wir anhand des Index die Anzahl der wahren Vorkommen Ihrer Liste anhand einer kumulativen Summe gruppieren

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
quelle
1
frame.iloc[[x for x in range(len(frame)) if set(letters).issubset(frame.iloc[x,0])]]

Ausgabe:

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

Ausgabe

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