Jak sprawdzić, czy ciąg zawiera jeden z podciągów na liście w pandach?

119

Czy jest jakaś funkcja, która byłaby odpowiednikiem kombinacji df.isin()i df[col].str.contains()?

Na przykład, powiedzmy, że mam serię s = pd.Series(['cat','hat','dog','fog','pet'])i chcę znaleźć wszystkie miejsca, w których szawiera którekolwiek z ['og', 'at'], chciałbym uzyskać wszystko oprócz „zwierzaka”.

Mam rozwiązanie, ale jest raczej nieeleganckie:

searchfor = ['og', 'at']
found = [s.str.contains(x) for x in searchfor]
result = pd.DataFrame[found]
result.any()

Czy jest lepszy sposób na zrobienie tego?

ari
źródło
Uwaga : istnieje rozwiązanie opisane przez @unutbu, które jest bardziej wydajne niż użycie pd.Series.str.contains. Jeśli problemem jest wydajność, warto to zbadać.
jpp
Zdecydowanie zalecamy sprawdzenie tej odpowiedzi w celu wyszukiwania częściowego ciągu znaków przy użyciu wielu słów kluczowych / wyrażeń regularnych (przewiń w dół do podtytułu „ Wyszukiwanie wielu podłańcuchów ”).
cs95

Odpowiedzi:

219

Jedną z opcji jest po prostu użycie |znaku regex, aby spróbować dopasować każdy z podciągów w słowach w twojej serii s(nadal używasz str.contains).

Można skonstruować regex poprzez połączenie słów searchforz |:

>>> searchfor = ['og', 'at']
>>> s[s.str.contains('|'.join(searchfor))]
0    cat
1    hat
2    dog
3    fog
dtype: object

Jak @AndyHayden zauważył w komentarzach poniżej, uważaj, czy twoje podciągi mają znaki specjalne, takie jak $i, ^które chcesz dopasować dosłownie. Znaki te mają określone znaczenie w kontekście wyrażeń regularnych i będą miały wpływ na dopasowanie.

Możesz zwiększyć bezpieczeństwo listy podciągów, poprzedzając znaki niealfanumeryczne za pomocą re.escape:

>>> import re
>>> matches = ['$money', 'x^y']
>>> safe_matches = [re.escape(m) for m in matches]
>>> safe_matches
['\\$money', 'x\\^y']

Łańcuchy z na tej nowej liście będą dopasowywać każdy znak dosłownie, gdy są używane z str.contains.

Alex Riley
źródło
4
może dobrze jest dodać ten link pandas.pydata.org/pandas-docs/stable/ ... też. Począwszy od pandy 0.15, operacje na strunach są jeszcze łatwiejsze
goofd
6
jedną rzeczą, na którą musisz uważać, jest to, czy ciąg w searchfor ma specjalne znaki regex (możesz mapować za pomocą re.escape ).
Andy Hayden
@AndyHayden Dziękuję, poprawiłem swoją odpowiedź, aby uwzględnić tę komplikację.
Alex Riley
Nie wiem, dlaczego twoja metoda nie działa z „str.startswith ('|' .join (searchfor))”
Doo Hyun Shin
48

Możesz używać str.containssamodzielnie ze wzorcem wyrażenia regularnego, używając OR (|):

s[s.str.contains('og|at')]

Lub możesz dodać serię do, a dataframenastępnie użyć str.contains:

df = pd.DataFrame(s)
df[s.str.contains('og|at')] 

Wynik:

0 cat
1 hat
2 dog
3 fog 
l'l'l
źródło
jak to zrobić dla AND?
JacoSolari
1
@JacoSolari sprawdź tę odpowiedź stackoverflow.com/questions/37011734/…
James
1
@James tak, dzięki. Do uzupełnienia tutaj jest najbardziej pozytywny oneliner w tej odpowiedzi. df.col.str.contains(r'(?=.*apple)(?=.*banana)',regex=True)
JacoSolari
1

Oto jednowierszowa lambda, która również działa:

df["TrueFalse"] = df['col1'].apply(lambda x: 1 if any(i in x for i in searchfor) else 0)

Wejście:

searchfor = ['og', 'at']

df = pd.DataFrame([('cat', 1000.0), ('hat', 2000000.0), ('dog', 1000.0), ('fog', 330000.0),('pet', 330000.0)], columns=['col1', 'col2'])

   col1  col2
0   cat 1000.0
1   hat 2000000.0
2   dog 1000.0
3   fog 330000.0
4   pet 330000.0

Zastosuj Lambda:

df["TrueFalse"] = df['col1'].apply(lambda x: 1 if any(i in x for i in searchfor) else 0)

Wynik:

    col1    col2        TrueFalse
0   cat     1000.0      1
1   hat     2000000.0   1
2   dog     1000.0      1
3   fog     330000.0    1
4   pet     330000.0    0
Grant Shannon
źródło