Як вибрати частинний рядок з панд DataFrame?
Цей пост призначений для читачів, які хочуть
- пошук підрядка в стовпці рядка (найпростіший випадок)
- пошук декількох підрядів (подібних до
isin
)
- відповідати цілому слову з тексту (наприклад, "блакитний" повинен відповідати "синє небо", але не "синій")
- збігаються кілька цілих слів
- Зрозумійте причину "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
може використовуватися для пошуку в підрядках або пошуку на основі регулярних виразів. Значення пошуку за замовчуванням на основі регулярних виразів, якщо ви явно не відключите його.
Ось приклад пошуку на основі регулярних виразів,
# find rows in `df1` which contain "foo" followed by something
df1[df1['col'].str.contains(r'foo(?!$)')]
col
1 foobar
Іноді пошук регулярного вибору не потрібен, тому вкажіть, 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
Пошук продуктивності, пошук у регулярних виразках повільніше, ніж пошук у підрядках:
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)
Не використовуйте пошук на основі регулярних виразів, якщо він вам не потрібен.
Адресація ValueError
s
Іноді пошук пошуку підрядків та фільтрування результату призведе до
ValueError: cannot index with vector containing NA / NaN values
Зазвичай це через змішані дані або NaN в стовпці об'єкта,
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
Пошук у декількох підрядках
Це найлегше досягти шляхом пошуку за допомогою регулярного вибору за допомогою каналу регулярного виводу АБО.
# 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
Іноді, розумно уникати своїх термінів, якщо вони мають символи, які можуть бути інтерпретовані як метасимволи регулярних виразів . Якщо ваші умови містять будь-який із наведених нижче символів ...
. ^ $ * + ? { } [ ] \ | ( )
Потім вам потрібно буде скористатися 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
в / с
df3[df3['col'].str.contains(r'\bblue\b')]
col
0 the sky is blue
Пошук у кількох словах
Аналогічно вище, за винятком того, що ми додамо слово border ( \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'
Чудова альтернатива: Використовуйте розуміння списку !
Бо ти можеш! І ти повинен! Зазвичай вони трохи швидші, ніж рядкові методи, тому що рядкові методи важко векторизувати і зазвичай мають петельні реалізації.
Замість,
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
(для кешування регексу) + 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
Підтримується пошук підрядкових рядків (читання: без регулярного вираження).
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
, для його простоти та зручності в роботі з NaN та змішаними даними
- Перелічіть розуміння для його ефективності (особливо якщо ваші дані суто рядки)
np.vectorize
- (Останній)
df.query