ฉันจะเลือกโดยสตริงบางส่วนจาก 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
นอกเหนือจาก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 ()
การใช้งานที่แนะนำ
- (ขั้นแรก)
str.contains
เพื่อความง่ายและความสะดวกในการจัดการ NaNs และข้อมูลแบบผสม
- แสดงรายการความเข้าใจสำหรับประสิทธิภาพ (โดยเฉพาะถ้าข้อมูลของคุณเป็นสตริงล้วนๆ)
np.vectorize
- (ล่าสุด)
df.query