Bagaimana cara memilih string parsial dari panda DataFrame?
Posting ini dimaksudkan untuk pembaca yang ingin
- mencari substring di kolom string (kasus paling sederhana)
- mencari beberapa substring (mirip dengan
isin
)
- cocokkan seluruh kata dari teks (misalnya, "biru" harus cocok dengan "langit berwarna biru" tetapi tidak "bluejay")
- cocokkan beberapa kata utuh
- Memahami alasan di balik "ValueError: tidak dapat mengindeks dengan vektor yang berisi nilai NA / NaN"
... dan ingin tahu lebih banyak tentang metode apa yang harus disukai daripada yang lain.
(PS: Saya sudah melihat banyak pertanyaan tentang topik yang sama, saya pikir akan baik untuk meninggalkan ini di sini.)
Pencarian Substring Dasar
# setup
df1 = pd.DataFrame({'col': ['foo', 'foobar', 'bar', 'baz']})
df1
col
0 foo
1 foobar
2 bar
3 baz
str.contains
dapat digunakan untuk melakukan pencarian substring atau pencarian berbasis regex. Pencarian default untuk berbasis regex kecuali Anda menonaktifkannya secara eksplisit.
Berikut adalah contoh pencarian berbasis regex,
# find rows in `df1` which contain "foo" followed by something
df1[df1['col'].str.contains(r'foo(?!$)')]
col
1 foobar
Terkadang pencarian regex tidak diperlukan, jadi tentukan regex=False
untuk menonaktifkannya.
#select all rows containing "foo"
df1[df1['col'].str.contains('foo', regex=False)]
# same as df1[df1['col'].str.contains('foo')] but faster.
col
0 foo
1 foobar
Dari segi kinerja, pencarian regex lebih lambat daripada pencarian substring:
df2 = pd.concat([df1] * 1000, ignore_index=True)
%timeit df2[df2['col'].str.contains('foo')]
%timeit df2[df2['col'].str.contains('foo', regex=False)]
6.31 ms ± 126 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.8 ms ± 241 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Hindari menggunakan pencarian berbasis regex jika Anda tidak membutuhkannya.
Mengatasi ValueError
s
Kadang-kadang, melakukan pencarian substring dan memfilter pada hasil akan menghasilkan
ValueError: cannot index with vector containing NA / NaN values
Ini biasanya karena data campuran atau NaN di kolom objek Anda,
s = pd.Series(['foo', 'foobar', np.nan, 'bar', 'baz', 123])
s.str.contains('foo|bar')
0 True
1 True
2 NaN
3 True
4 False
5 NaN
dtype: object
s[s.str.contains('foo|bar')]
# ---------------------------------------------------------------------------
# ValueError Traceback (most recent call last)
Apa pun yang bukan string tidak dapat menerapkan metode string di dalamnya, sehingga hasilnya adalah NaN (secara alami). Dalam hal ini, tentukan na=False
untuk mengabaikan data non-string,
s.str.contains('foo|bar', na=False)
0 True
1 True
2 False
3 True
4 False
5 False
dtype: bool
Beberapa Pencarian Substring
Ini paling mudah dicapai melalui pencarian regex menggunakan regex ATAU pipa.
# Slightly modified example.
df4 = pd.DataFrame({'col': ['foo abc', 'foobar xyz', 'bar32', 'baz 45']})
df4
col
0 foo abc
1 foobar xyz
2 bar32
3 baz 45
df4[df4['col'].str.contains(r'foo|baz')]
col
0 foo abc
1 foobar xyz
3 baz 45
Anda juga dapat membuat daftar istilah, lalu bergabung dengan mereka:
terms = ['foo', 'baz']
df4[df4['col'].str.contains('|'.join(terms))]
col
0 foo abc
1 foobar xyz
3 baz 45
Terkadang, adalah bijaksana untuk melarikan diri dari istilah Anda jika mereka memiliki karakter yang dapat diartikan sebagai regach metacharacters . Jika istilah Anda mengandung karakter berikut ...
. ^ $ * + ? { } [ ] \ | ( )
Kemudian, Anda harus menggunakan re.escape
untuk melarikan diri dari mereka:
import re
df4[df4['col'].str.contains('|'.join(map(re.escape, terms)))]
col
0 foo abc
1 foobar xyz
3 baz 45
re.escape
memiliki efek melarikan diri dari karakter khusus sehingga mereka diperlakukan secara harfiah.
re.escape(r'.foo^')
# '\\.foo\\^'
Mencocokkan Seluruh Kata
Secara default, pencarian substring mencari substring / pola yang ditentukan terlepas dari apakah itu kata penuh atau tidak. Untuk hanya mencocokkan kata-kata penuh, kita perlu menggunakan ekspresi reguler di sini — khususnya, pola kita perlu menentukan batas kata ( \b
).
Sebagai contoh,
df3 = pd.DataFrame({'col': ['the sky is blue', 'bluejay by the window']})
df3
col
0 the sky is blue
1 bluejay by the window
Sekarang pertimbangkan,
df3[df3['col'].str.contains('blue')]
col
0 the sky is blue
1 bluejay by the window
v / s
df3[df3['col'].str.contains(r'\bblue\b')]
col
0 the sky is blue
Pencarian Banyak Kata Utuh
Mirip dengan di atas, kecuali kami menambahkan batas kata ( \b
) ke pola bergabung.
p = r'\b(?:{})\b'.format('|'.join(map(re.escape, terms)))
df4[df4['col'].str.contains(p)]
col
0 foo abc
3 baz 45
Di mana p
terlihat seperti ini,
p
# '\\b(?:foo|baz)\\b'
Karena kamu bisa! Dan kamu harus! Mereka biasanya sedikit lebih cepat daripada metode string, karena metode string sulit untuk vectorise dan biasanya memiliki implementasi gila.
Dari pada,
df1[df1['col'].str.contains('foo', regex=False)]
Gunakan in
operator di dalam daftar comp,
df1[['foo' in x for x in df1['col']]]
col
0 foo abc
1 foobar
Dari pada,
regex_pattern = r'foo(?!$)'
df1[df1['col'].str.contains(regex_pattern)]
Gunakan re.compile
(untuk me-cache regex Anda) + Pattern.search
di dalam daftar comp,
p = re.compile(regex_pattern, flags=re.IGNORECASE)
df1[[bool(p.search(x)) for x in df1['col']]]
col
1 foobar
Jika "col" memiliki NaN, maka alih-alih
df1[df1['col'].str.contains(regex_pattern, na=False)]
Menggunakan,
def try_search(p, x):
try:
return bool(p.search(x))
except TypeError:
return False
p = re.compile(regex_pattern)
df1[[try_search(p, x) for x in df1['col']]]
col
1 foobar
Selain str.contains
dan daftar pemahaman, Anda juga dapat menggunakan alternatif berikut.
np.char.find
Mendukung pencarian substring (baca: tidak ada regex) saja.
df4[np.char.find(df4['col'].values.astype(str), 'foo') > -1]
col
0 foo abc
1 foobar xyz
np.vectorize
Ini adalah pembungkus di sekitar lingkaran, tetapi dengan overhead yang lebih rendah daripada kebanyakan str
metode panda .
f = np.vectorize(lambda haystack, needle: needle in haystack)
f(df1['col'], 'foo')
# array([ True, True, False, False])
df1[f(df1['col'], 'foo')]
col
0 foo abc
1 foobar
Solusi Regex mungkin:
regex_pattern = r'foo(?!$)'
p = re.compile(regex_pattern)
f = np.vectorize(lambda x: pd.notna(x) and bool(p.search(x)))
df1[f(df1['col'])]
col
1 foobar
DataFrame.query
Mendukung metode string melalui mesin python. Ini tidak menawarkan manfaat kinerja yang terlihat, tetapi tetap berguna untuk mengetahui apakah Anda perlu secara dinamis menghasilkan pertanyaan Anda.
df1.query('col.str.contains("foo")', engine='python')
col
0 foo
1 foobar
Informasi lebih lanjut tentang query
dan eval
keluarga metode dapat ditemukan di Dinamis Ekspresi Evaluasi di panda menggunakan pd.eval () .
Prioritas Penggunaan yang Disarankan
- (Pertama)
str.contains
, karena kesederhanaannya dan kemudahan dalam menangani NaN dan data campuran
- Daftarkan pemahaman, untuk kinerjanya (terutama jika data Anda adalah murni string)
np.vectorize
- (Terakhir)
df.query