Pilih dengan string parsial dari DataFrame panda

448

Saya punya DataFramedengan 4 kolom yang 2 berisi nilai string. Saya bertanya-tanya apakah ada cara untuk memilih baris berdasarkan kecocokan string parsial terhadap kolom tertentu?

Dengan kata lain, fungsi atau fungsi lambda yang akan melakukan sesuatu seperti

re.search(pattern, cell_in_question) 

mengembalikan boolean. Saya kenal dengan sintaks df[df['A'] == "hello world"]tetapi sepertinya tidak dapat menemukan cara untuk melakukan hal yang sama dengan kata string match parsial 'hello'.

Apakah seseorang dapat mengarahkan saya ke arah yang benar?

euforia
sumber

Jawaban:

786

Berdasarkan masalah github # 620 , sepertinya Anda akan segera dapat melakukan hal berikut:

df[df['A'].str.contains("hello")]

Pembaruan: metode string vektor (yaitu, Series.str) tersedia di panda 0.8.1 dan lebih tinggi.

Garrett
sumber
1
Bagaimana kita berbicara tentang "Halo" dan "Inggris" jika saya ingin menemukan mereka dengan kondisi "ATAU".
LonelySoul
56
Karena metode str. * Memperlakukan pola input sebagai ekspresi reguler, Anda dapat menggunakandf[df['A'].str.contains("Hello|Britain")]
Garrett
7
Apakah mungkin untuk mengkonversi .str.containsmenggunakan .query()api ?
zyxue
3
df[df['value'].astype(str).str.contains('1234.+')]untuk memfilter kolom non-string-type.
François Leblanc
213

Saya mencoba solusi yang diusulkan di atas:

df[df["A"].str.contains("Hello|Britain")]

dan mendapat kesalahan:

ValueError: tidak bisa menutupi dengan array yang berisi nilai NA / NaN

Anda dapat mengubah nilai NA menjadi False, seperti ini:

df[df["A"].str.contains("Hello|Britain", na=False)]
sharon
sumber
54
Atau Anda dapat melakukannya: df [df ['A']. Str.contains ("Hello | Britain", na = Salah)]
joshlk
2
df[df['A'].astype(str).str.contains("Hello|Britain")]bekerja juga
Nagabhushan SN
108

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.containsdapat 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=Falseuntuk 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 ValueErrors
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=Falseuntuk 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.escapeuntuk 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 pterlihat seperti ini,

p
# '\\b(?:foo|baz)\\b'

Alternatif Hebat: Gunakan Daftar Pemahaman !

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 inoperator 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.searchdi 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

Lebih Pilihan untuk Partial String Matching: np.char.find, np.vectorize, DataFrame.query.

Selain str.containsdan 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 strmetode 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 querydan evalkeluarga metode dapat ditemukan di Dinamis Ekspresi Evaluasi di panda menggunakan pd.eval () .


Prioritas Penggunaan yang Disarankan

  1. (Pertama) str.contains, karena kesederhanaannya dan kemudahan dalam menangani NaN dan data campuran
  2. Daftarkan pemahaman, untuk kinerjanya (terutama jika data Anda adalah murni string)
  3. np.vectorize
  4. (Terakhir) df.query
cs95
sumber
Bisakah Anda mengedit dengan metode yang benar untuk digunakan saat mencari string di dua atau lebih kolom? Pada dasarnya: any(needle in haystack for needling in ['foo', 'bar'] and haystack in (df['col'], df['col2']))dan variasi saya mencoba semua tersedak (itu mengeluh tentang any()dan memang begitu ... Tapi dokter itu tidak jelas bagaimana cara melakukan permintaan seperti itu.
Denis de Bernardy
@DenisdeBernardydf[['col1', 'col2']].apply(lambda x: x.str.contains('foo|bar')).any(axis=1)
cs95
@ cs95 Ekstraksi baris dengan substring yang berisi spasi putih setelah + di panda df Itu dijawab segera, tetapi Anda mungkin ingin melihatnya.
ankii
@ankiiiiiii Sepertinya Anda melewatkan bagian dari jawaban saya di mana saya menyebutkan regex metacharacters: "Terkadang, bijaksana untuk melarikan diri dari istilah Anda jika mereka memiliki karakter yang dapat diartikan sebagai regex metacharacters".
cs95
1
@ 00schneider r dalam hal ini digunakan untuk menunjukkan literal string mentah. Ini membuatnya lebih mudah untuk menulis string ekspresi reguler. stackoverflow.com/q/2081640
cs95
53

Jika ada yang bertanya-tanya bagaimana melakukan masalah yang terkait: "Pilih kolom dengan string parsial"

Menggunakan:

df.filter(like='hello')  # select columns which contain the word hello

Dan untuk memilih baris menurut pencocokan string parsial, berikan axis=0filter:

# selects rows which contain the word hello in their index label
df.filter(like='hello', axis=0)  
Philipp Schwarz
sumber
6
Ini dapat disaring ke:df.loc[:, df.columns.str.contains('a')]
elPastor
18
yang dapat disuling lebih lanjut kedf.filter(like='a')
Ted Petrou
ini seharusnya menjadi pertanyaan + jawaban sendiri, sudah 50 orang mencarinya ...
PV8
1
@ PV8 pertanyaan sudah ada: stackoverflow.com/questions/31551412/… . Tetapi ketika saya mencari "panda Select kolom dengan string parsial" di google, utas ini muncul lebih dulu
Philipp Schwarz
28

Catatan cepat: jika Anda ingin melakukan seleksi berdasarkan string parsial yang terdapat dalam indeks, coba yang berikut ini:

df['stridx']=df.index
df[df['stridx'].str.contains("Hello|Britain")]
Kristen
sumber
5
Anda bisa saja df [df.index.to_series (). Str.contains ('LLChit')]
Yury Bayda
21

Katakanlah Anda memiliki yang berikut ini DataFrame:

>>> df = pd.DataFrame([['hello', 'hello world'], ['abcd', 'defg']], columns=['a','b'])
>>> df
       a            b
0  hello  hello world
1   abcd         defg

Anda selalu dapat menggunakan inoperator dalam ekspresi lambda untuk membuat filter Anda.

>>> df.apply(lambda x: x['a'] in x['b'], axis=1)
0     True
1    False
dtype: bool

Kuncinya di sini adalah dengan menggunakan axis=1opsi dalam applyuntuk meneruskan elemen ke fungsi lambda, baris demi baris, berlawanan dengan kolom demi kolom.

Mike
sumber
Bagaimana cara saya memodifikasi di atas untuk mengatakan bahwa x ['a'] hanya ada di awal x ['b']?
ComplexData
1
melamar adalah ide yang buruk di sini dalam hal kinerja dan memori. Lihat jawaban ini .
cs95
8

Inilah yang akhirnya saya lakukan untuk pencocokan string parsial. Jika ada yang memiliki cara yang lebih efisien untuk melakukan ini, beri tahu saya.

def stringSearchColumn_DataFrame(df, colName, regex):
    newdf = DataFrame()
    for idx, record in df[colName].iteritems():

        if re.search(regex, record):
            newdf = concat([df[df[colName] == record], newdf], ignore_index=True)

    return newdf
euforia
sumber
3
Seharusnya 2x hingga 3x lebih cepat jika Anda mengkompilasi regex sebelum loop: regex = re.compile (regex) dan kemudian jika regex.search (record)
MarkokraM
1
@MarkokraM docs.python.org/3.6/library/re.html#re.compile mengatakan bahwa regex terbaru di-cache untuk Anda, jadi Anda tidak perlu mengkompilasi sendiri.
Teepeemm
Jangan gunakan iteritem untuk beralih ke DataFrame. Ini peringkat terakhir dalam hal kemandirian dan kinerja
cs95
5

Menggunakan berisi tidak berfungsi dengan baik untuk string saya dengan karakter khusus. Cari bekerja.

df[df['A'].str.find("hello") != -1]
Katu
sumber
2

Ada jawaban sebelum ini yang memenuhi fitur yang diminta, toh saya ingin menunjukkan cara yang paling umum:

df.filter(regex=".*STRING_YOU_LOOK_FOR.*")

Dengan cara ini, mari Anda mendapatkan kolom yang Anda cari apa pun caranya.

(Jelas, Anda harus menulis ekspresi regex yang tepat untuk setiap kasus)

xpeiro
sumber
1
Ini memfilter pada tajuk kolom . Itu tidak umum, itu salah.
cs95
@MicheldeRuiter yang masih salah, itu akan memfilter label indeks saja!
cs95
Tidak menjawab pertanyaan. Tetapi saya belajar sesuatu. :)
Michel de Ruiter
2

Mungkin Anda ingin mencari beberapa teks di semua kolom dari kerangka data Pandas, dan tidak hanya di subset dari mereka. Dalam hal ini, kode berikut akan membantu.

df[df.apply(lambda row: row.astype(str).str.contains('String To Find').any(), axis=1)]

Peringatan. Metode ini relatif lambat, meskipun nyaman.

Serhii Kushchenko
sumber
2

Jika Anda perlu melakukan pencarian case untuk string dalam kolom dataframe panda:

df[df['A'].str.contains("hello", case=False)]
kapulaga
sumber