เลือกตามสตริงบางส่วนจาก DataFrame แพนด้า


448

ฉันมีDataFrameคอลัมน์ 4 คอลัมน์โดยที่ 2 มีค่าสตริง ฉันสงสัยว่าจะมีวิธีในการเลือกแถวตามการจับคู่สตริงบางส่วนกับคอลัมน์ใดคอลัมน์หนึ่งหรือไม่?

กล่าวอีกนัยหนึ่งฟังก์ชั่นหรือฟังก์ชั่นแลมบ์ดาที่จะทำอะไรเช่น

re.search(pattern, cell_in_question) 

ส่งคืนบูลีน ผมคุ้นเคยกับไวยากรณ์ของแต่ดูเหมือนจะไม่สามารถหาวิธีที่จะทำเช่นเดียวกันกับสตริงการแข่งขันพูดบางส่วนdf[df['A'] == "hello world"]'hello'

ใครบางคนจะสามารถชี้ให้ฉันไปในทิศทางที่ถูกต้อง?

คำตอบ:


786

จากปัญหา GitHub # 620ดูเหมือนว่าคุณจะสามารถทำสิ่งต่อไปนี้ได้ในไม่ช้า:

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

อัปเดต: วิธีสตริง vectorized (เช่น Series.str)มีให้บริการในแพนด้า 0.8.1 ขึ้นไป


1
เราจะไปเกี่ยวกับ "สวัสดี" และ "สหราชอาณาจักร" ได้อย่างไรถ้าฉันต้องการค้นหาพวกเขาด้วยเงื่อนไข "หรือ"
LonelySoul

56
เนื่องจากเมธอด str. * ถือว่ารูปแบบการป้อนข้อมูลเป็นนิพจน์ทั่วไปคุณสามารถใช้df[df['A'].str.contains("Hello|Britain")]
Garrett

7
เป็นไปได้ไหมที่.str.containsจะใช้.query()api ?
zyxue


3
df[df['value'].astype(str).str.contains('1234.+')]สำหรับการกรองคอลัมน์ที่ไม่ใช่สตริง
François Leblanc

213

ฉันลองวิธีแก้ปัญหาที่เสนอข้างต้น:

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

และได้รับข้อผิดพลาด:

ValueError: ไม่สามารถปิดบังด้วยอาร์เรย์ที่มีค่า NA / NaN

คุณสามารถแปลงค่า NA ให้เป็นFalseดังนี้:

df[df["A"].str.contains("Hello|Britain", na=False)]

54
หรือคุณสามารถทำได้: df [df ['A']. str.contain ("Hello | Britain", na = False)]
joshlk

2
df[df['A'].astype(str).str.contains("Hello|Britain")]ทำงานได้ดี
Nagabhushan SN

108

ฉันจะเลือกโดยสตริงบางส่วนจาก DataFrame แพนด้าได้อย่างไร

โพสต์นี้มีไว้สำหรับผู้อ่านที่ต้องการ

  • ค้นหาสตริงย่อยในคอลัมน์สตริง (กรณีที่ง่ายที่สุด)
  • ค้นหาสตริงย่อยหลายรายการ (คล้ายกับisin)
  • ตรงกับคำทั้งหมดจากข้อความ (เช่น "สีน้ำเงิน" ควรตรงกับ "ท้องฟ้าเป็นสีฟ้า" แต่ไม่ใช่ "bluejay")
  • ตรงกับคำทั้งหมด
  • ทำความเข้าใจถึงเหตุผลที่อยู่เบื้องหลัง "ValueError: ไม่สามารถสร้างดัชนีด้วยเวกเตอร์ที่มีค่า NA / NaN"

... และต้องการทราบข้อมูลเพิ่มเติมเกี่ยวกับวิธีการที่ควรใช้กับผู้อื่น

(PS: ฉันเคยเห็นคำถามมากมายในหัวข้อที่คล้ายกันฉันคิดว่ามันจะเป็นการดีที่จะออกจากที่นี่)


การค้นหาสตริงย่อยพื้นฐาน

# setup
df1 = pd.DataFrame({'col': ['foo', 'foobar', 'bar', 'baz']})
df1

      col
0     foo
1  foobar
2     bar
3     baz

str.containsสามารถใช้ในการทำการค้นหาสตริงย่อยหรือการค้นหาตาม regex การค้นหาเริ่มต้นที่ใช้ regex เว้นแต่คุณจะปิดการใช้งานอย่างชัดเจน

นี่คือตัวอย่างของการค้นหาจาก regex

# find rows in `df1` which contain "foo" followed by something
df1[df1['col'].str.contains(r'foo(?!$)')]

      col
1  foobar

บางครั้งไม่จำเป็นต้องค้นหา regex ดังนั้นให้ระบุregex=Falseเพื่อปิดการใช้งาน

#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

ประสิทธิภาพการทำงานที่ชาญฉลาดการค้นหา regex ช้ากว่าการค้นหาสตริงย่อย:

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)

หลีกเลี่ยงการใช้การค้นหาตาม regex หากคุณไม่ต้องการ

ที่อยู่ValueErrorของ
บางครั้งการทำการค้นหาและการกรองสตริงย่อยในผลลัพธ์จะส่งผลให้

ValueError: cannot index with vector containing NA / NaN values

ซึ่งมักเป็นเพราะข้อมูลแบบผสมหรือ NaNs ในคอลัมน์วัตถุของคุณ

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)

สิ่งที่ไม่ใช่สตริงไม่สามารถใช้วิธีการสตริงได้ดังนั้นผลลัพธ์คือ NaN (โดยธรรมชาติ) ในกรณีนี้ให้ระบุna=Falseเพื่อละเว้นข้อมูลที่ไม่ใช่สตริง

s.str.contains('foo|bar', na=False)

0     True
1     True
2    False
3     True
4    False
5    False
dtype: bool

การค้นหาสตริงย่อยหลายรายการ

สิ่งนี้สามารถทำได้ง่ายที่สุดผ่านการค้นหา regex โดยใช้ regex OR ไพพ์

# 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

คุณยังสามารถสร้างรายการคำศัพท์จากนั้นจึงเข้าร่วม:

terms = ['foo', 'baz']
df4[df4['col'].str.contains('|'.join(terms))]

          col
0     foo abc
1  foobar xyz
3      baz 45

บางครั้งก็ควรที่จะหลบหนีแง่ของคุณในกรณีที่พวกเขามีตัวอักษรที่สามารถตีความได้ว่าmetacharacters regex หากข้อกำหนดของคุณมีอักขระใด ๆ ต่อไปนี้ ...

. ^ $ * + ? { } [ ] \ | ( )

จากนั้นคุณจะต้องใช้re.escapeเพื่อหลบหนี :

import re
df4[df4['col'].str.contains('|'.join(map(re.escape, terms)))]

          col
0     foo abc
1  foobar xyz
3      baz 45

re.escape มีผลของการหลบหนีตัวละครพิเศษเพื่อให้พวกเขาได้รับการปฏิบัติอย่างแท้จริง

re.escape(r'.foo^')
# '\\.foo\\^'

การจับคู่คำทั้งหมด

ตามค่าเริ่มต้นการค้นหาย่อยจะค้นหาสตริงย่อย / รูปแบบที่ระบุโดยไม่คำนึงว่าเป็นคำเต็มหรือไม่ เพื่อให้ตรงกับคำเต็มเราจะต้องใช้ประโยชน์จากการแสดงออกปกติที่นี่ - โดยเฉพาะรูปแบบของเราจะต้องระบุขอบเขตของคำ ( \b)

ตัวอย่างเช่น,

df3 = pd.DataFrame({'col': ['the sky is blue', 'bluejay by the window']})
df3

                     col
0        the sky is blue
1  bluejay by the window

พิจารณาตอนนี้

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

ค้นหาคำทั้งหมด

คล้ายกับด้านบนยกเว้นเราเพิ่มขอบเขตคำ ( \b) ลงในรูปแบบที่รวม

p = r'\b(?:{})\b'.format('|'.join(map(re.escape, terms)))
df4[df4['col'].str.contains(p)]

       col
0  foo abc
3   baz 45

ที่pมีลักษณะเช่นนี้

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

ทางเลือกที่ยอดเยี่ยม: ใช้ความเข้าใจในรายการ !

เพราะว่าคุณสามารถ! และคุณควร! พวกมันมักจะเร็วกว่าเมธอดของสตริงเล็กน้อยเนื่องจากเมธอดของสตริงนั้นยากต่อการ vectorise และมักจะมีการใช้งานแบบวนรอบ

แทน,

df1[df1['col'].str.contains('foo', regex=False)]

ใช้inโอเปอเรเตอร์ด้านในรายการคอมพ์

df1[['foo' in x for x in df1['col']]]

       col
0  foo abc
1   foobar

แทน,

regex_pattern = r'foo(?!$)'
df1[df1['col'].str.contains(regex_pattern)]

ใช้re.compile(เพื่อแคช regex ของคุณ) + Pattern.searchในรายการคอมพ์

p = re.compile(regex_pattern, flags=re.IGNORECASE)
df1[[bool(p.search(x)) for x in df1['col']]]

      col
1  foobar

หาก "col" มี NaN แสดงว่าไม่ใช่

df1[df1['col'].str.contains(regex_pattern, na=False)]

ใช้,

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

np.char.findตัวเลือกเพิ่มเติมสำหรับการจับคู่สายบางส่วน: np.vectorize, DataFrame.query,

นอกเหนือจากstr.containsและรายการความเข้าใจคุณยังสามารถใช้ทางเลือกต่อไปนี้

np.char.find
รองรับการค้นหาซับสตริง (อ่าน: ไม่มี regex) เท่านั้น

df4[np.char.find(df4['col'].values.astype(str), 'foo') > -1]

          col
0     foo abc
1  foobar xyz

np.vectorize
นี่คือเสื้อคลุมรอบวง แต่มีค่าใช้จ่ายน้อยกว่าstrวิธีการแพนด้าส่วนใหญ่

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

สามารถใช้โซลูชัน Regex ได้:

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
รองรับเมธอดสตริงผ่านเอ็นจิน python สิ่งนี้ไม่มีประโยชน์ด้านประสิทธิภาพที่มองเห็นได้ แต่ก็มีประโยชน์หากคุณต้องการสร้างคิวรีของคุณแบบไดนามิก

df1.query('col.str.contains("foo")', engine='python')

      col
0     foo
1  foobar

ข้อมูลเพิ่มเติมเกี่ยวกับqueryและevalครอบครัวของวิธีการที่สามารถพบได้แบบไดนามิกการแสดงออกในการประเมินผลโดยใช้หมีแพนด้า pd.eval ()


การใช้งานที่แนะนำ

  1. (ขั้นแรก) str.containsเพื่อความง่ายและความสะดวกในการจัดการ NaNs และข้อมูลแบบผสม
  2. แสดงรายการความเข้าใจสำหรับประสิทธิภาพ (โดยเฉพาะถ้าข้อมูลของคุณเป็นสตริงล้วนๆ)
  3. np.vectorize
  4. (ล่าสุด) df.query

คุณสามารถแก้ไขในวิธีที่ถูกต้องที่จะใช้เมื่อค้นหาสตริงในคอลัมน์สองคอลัมน์ขึ้นไปได้หรือไม่? โดยทั่วไป: any(needle in haystack for needling in ['foo', 'bar'] and haystack in (df['col'], df['col2']))และรูปแบบที่ฉันพยายามทำให้หายใจไม่ออก (มันบ่นเกี่ยวกับany()และถูกต้องดังนั้น ... แต่หมอไม่ชัดเจนว่าวิธีการทำแบบสอบถามดังกล่าวมีความสุข
เดนิสเดอ Bernardy

@DenisdeBernardydf[['col1', 'col2']].apply(lambda x: x.str.contains('foo|bar')).any(axis=1)
cs95

@ cs95 การแยกแถวที่มีซับสตริงที่มีช่องว่างหลังจาก + ใน pandas dfมันได้รับคำตอบในเร็ว ๆ นี้ แต่คุณอาจต้องการดูมัน
ankii

@ankiiiiiii ดูเหมือนว่าคุณพลาดส่วนหนึ่งของคำตอบของฉันที่ฉันพูดถึงตัวละครใน regex: "บางครั้งมันก็ควรที่จะหลีกเลี่ยงคำศัพท์ของคุณในกรณีที่พวกเขามีตัวละครที่สามารถตีความได้ว่าเป็น metach ตัวละคร regex"
cs95

1
@ 00schneider r ในกรณีนี้ใช้เพื่อระบุตัวอักษรสตริงดิบ สิ่งเหล่านี้ทำให้การเขียนสตริงการแสดงออกปกติง่ายขึ้น stackoverflow.com/q/2081640
cs95

53

หากใครสงสัยวิธีการแก้ปัญหาที่เกี่ยวข้อง: "เลือกคอลัมน์ด้วยสตริงบางส่วน"

ใช้:

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

และหากต้องการเลือกแถวด้วยการจับคู่สตริงบางส่วนให้ส่งผ่านaxis=0ไปยังตัวกรอง:

# selects rows which contain the word hello in their index label
df.filter(like='hello', axis=0)  

6
สิ่งนี้สามารถกลั่นเป็น:df.loc[:, df.columns.str.contains('a')]
elPastor

18
ซึ่งสามารถกลั่นเพิ่มเติมได้ที่df.filter(like='a')
Ted Petrou

นี่ควรเป็นคำถาม + คำตอบของตัวเองแล้วมีคนค้นหาแล้ว 50 คน ...
PV8

1
มี @ คำถาม PV8 แล้ว: stackoverflow.com/questions/31551412/… . แต่เมื่อฉันค้นหาคำว่า "pandas Select ด้วยสตริงบางส่วน" ใน google ข้อความนี้จะปรากฏขึ้นก่อน
Philipp Schwarz

28

บันทึกย่อแบบด่วน: หากคุณต้องการทำการเลือกตามสตริงบางส่วนที่มีอยู่ในดัชนีลองทำสิ่งต่อไปนี้:

df['stridx']=df.index
df[df['stridx'].str.contains("Hello|Britain")]

5
คุณสามารถ df [df.index.to_series (). str.contain ('LLChit')]
Yury Bayda

21

สมมติว่าคุณมีดังต่อไปนี้DataFrame:

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

คุณสามารถใช้inโอเปอเรเตอร์ในนิพจน์แลมบ์ดาเพื่อสร้างตัวกรองของคุณ

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

เคล็ดลับที่นี่คือการใช้axis=1ตัวเลือกในการapplyส่งผ่านองค์ประกอบไปยังฟังก์ชั่นแลมบ์ดาทีละแถวเมื่อเทียบกับคอลัมน์โดยคอลัมน์


ฉันจะแก้ไขข้างต้นอย่างไรเพื่อบอกว่า x ['a'] มีอยู่ในตอนต้นของ x ['b'] เท่านั้น
ComplexData

1
ใช้เป็นความคิดที่ดีที่นี่ในแง่ของประสิทธิภาพและหน่วยความจำ ดูคำตอบนี้
cs95

8

นี่คือสิ่งที่ฉันทำลงเอยด้วยการจับคู่สตริงบางส่วน หากใครมีวิธีการที่มีประสิทธิภาพมากขึ้นโปรดแจ้งให้เราทราบ

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

3
ควรจะเร็วกว่า 2x ถึง 3x ถ้าคุณรวบรวม regex ก่อนวน: regex = re.compile (regex) และถ้า regex.search (บันทึก)
MarkokraM

1
@MarkokraM docs.python.org/3.6/library/re.html#re.compileบอกว่า regexs ล่าสุดถูกแคชสำหรับคุณดังนั้นคุณไม่จำเป็นต้องรวบรวมตัวเอง
Teepeemm

อย่าใช้ iteritems เพื่อวนซ้ำ DataFrame มันอันดับสุดท้ายในแง่ของความน่าเชื่อถือและประสิทธิภาพ
cs95

5

การใช้การบรรจุไม่ได้ผลกับสตริงของฉันที่มีอักขระพิเศษ หางานทำ

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

2

มีคำตอบก่อนหน้านี้ที่ทำให้คุณสมบัติที่ถามสำเร็จอย่างไรก็ตามฉันต้องการแสดงวิธีการทั่วไปมากที่สุด:

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

ด้วยวิธีนี้คุณจะได้คอลัมน์ที่คุณมองหาวิธีเขียน

(อย่างชัดเจนคุณต้องเขียนนิพจน์ regex ที่เหมาะสมสำหรับแต่ละกรณี)


1
ฟิลเตอร์ในคอลัมน์นี้ส่วนหัว มันไม่ธรรมดามันไม่ถูกต้อง
cs95

@MicheldeRuiter ที่ยังคงไม่ถูกต้องนั่นจะกรองตามป้ายกำกับดัชนีแทน!
cs95

ไม่ตอบคำถาม แต่ฉันเรียนรู้บางสิ่ง :)
Michel de Ruiter

2

บางทีคุณอาจต้องการค้นหาข้อความในคอลัมน์ทั้งหมดของ Pandas dataframe และไม่ใช่แค่ในส่วนย่อยของข้อความเหล่านั้น ในกรณีนี้รหัสต่อไปนี้จะช่วย

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

คำเตือน. วิธีนี้ค่อนข้างช้า แต่ก็สะดวก


2

คุณควรจะทำการค้นหาแบบตัวเล็กและตัวเล็กสำหรับสตริงในคอลัมน์ดาต้าดาต้า:

df[df['A'].str.contains("hello", case=False)]
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.