Выделение частичной строкой в ​​панде DataFrame


451

У меня есть DataFrameс 4 столбцами, из которых 2 содержат строковые значения. Мне было интересно, если есть способ выбрать строки на основе частичного совпадения строк с конкретным столбцом?

Другими словами, функция или лямбда-функция, которая будет делать что-то вроде

re.search(pattern, cell_in_question) 

возвращая логическое значение. Я знаком с синтаксисом, df[df['A'] == "hello world"]но не могу найти способ сделать то же самое с частичным совпадением строк, скажем 'hello'.

Кто-нибудь сможет указать мне правильное направление?

Ответы:


788

Судя по проблеме github № 620 , похоже, вы скоро сможете сделать следующее:

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

Обновление: векторизованные строковые методы (например, 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.+')]для фильтрации столбцов нестрокового типа.
Франсуа Лебланк

214

Я попробовал предлагаемое решение выше:

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.contains ("Hello | Britain", na = False)]
joshlk

2
df[df['A'].astype(str).str.contains("Hello|Britain")]работал так же
Нагабхушан С.Н.

108

Как выбрать частичную строку в панде 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)

Избегайте использования поиска на основе регулярных выражений, если он вам не нужен.

Адресация ValueErrors
Иногда выполнение поиска и фильтрации подстроки по результату приводит к

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

Поиск нескольких подстрок

Этого легче всего достичь с помощью поиска регулярных выражений с использованием регулярного выражения 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

Иногда разумно избегать ваших терминов, если в них есть символы, которые можно интерпретировать как метасимволы регулярных выражений . Если ваши термины содержат любой из следующих символов ...

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

Затем вам нужно использовать, 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

Множественный поиск по всему слову

Аналогично приведенному выше, за исключением того, что мы добавляем слово 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оператор внутри списка comp,

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внутри списка comp,

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

      col
1  foobar

Если у «col» есть NaNs, то вместо

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

Дополнительные параметры для частичного Струнный Matching: np.char.find, np.vectorize, DataFrame.query.

В дополнение к 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 () .


Рекомендуемое приоритетность использования

  1. (Во-первых) str.contains, за его простоту и удобство обработки NaN и смешанных данных
  2. Список понимания, для его производительности (особенно, если ваши данные являются чисто строками)
  3. np.vectorize
  4. (Прошлой) df.query

Не могли бы вы отредактировать правильный метод для использования при поиске строки в двух или более столбцах? В основном: any(needle in haystack for needling in ['foo', 'bar'] and haystack in (df['col'], df['col2']))и варианты я попробовал все удушье (это жалуется any()и справедливо так ... Но документ блаженно неясно, как сделать такой запрос.
Дени де Бернарди

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

@ cs95 Извлечение строк с подстрокой, содержащей пробел после + в pandas df Ответ получен в ближайшее время, но, возможно, вы захотите взглянуть на него.
Анки

@ankiiiiiii Похоже, вы пропустили ту часть моего ответа, где я упомянул метасимволы регулярных выражений: «Иногда разумно избегать использования ваших терминов, если они содержат символы, которые можно интерпретировать как метасимволы регулярных выражений».
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')
Тед Петру

это должен быть собственный вопрос + ответ, его уже искали 50 человек ...
PV8

1
@ Вопрос PV8 уже существует: stackoverflow.com/questions/31551412/… . Но когда я ищу в Google «pandas Выбрать столбец по частичной строке», эта тема появляется первой
Philipp Schwarz

28

Краткое примечание: если вы хотите сделать выбор на основе частичной строки, содержащейся в индексе, попробуйте следующее:

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

5
Вы можете просто df [df.index.to_series (). Str.contains ('LLChit')]
Юрий Байда

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
Должно быть в 2–3 раза быстрее, если вы скомпилируете регулярное выражение перед циклом: regex = re.compile (regex), а затем, если regex.search (record)
MarkokraM

1
@MarkokraM docs.python.org/3.6/library/re.html#re.compile говорит, что самые последние регулярные выражения кэшируются для вас, поэтому вам не нужно компилировать себя.
Teepeemm

Не используйте iteritems для перебора DataFrame. Он занимает последнее место с точки зрения пандорабельности и производительности
cs95

5

Использование содержимого не помогло моей строке со специальными символами. Найти работал, хотя.

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

3

Если вам нужно сделать нечувствительны к регистру поиск строки в панд dataframe столбцов:

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

2

Перед этим есть ответы, которые выполняют заданную функцию, в любом случае я хотел бы показать наиболее общий способ:

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

Таким образом, вы получите столбец, который ищите, каким бы образом он ни был написан.

(Очевидно, вы должны написать правильное выражение регулярного выражения для каждого случая)


1
Это фильтрует заголовки столбцов . Это не общее, это неверно.
cs95

@MicheldeRuiter, который все еще неверен, вместо этого будет фильтроваться по индексным меткам!
cs95

Не отвечает на вопрос. Но я кое-чему научился. :)
Мишель де Рюйтер

2

Может быть, вы хотите найти какой-то текст во всех столбцах кадра данных Pandas, а не только в их подмножестве. В этом случае поможет следующий код.

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

Предупреждение. Этот метод относительно медленный, хотя и удобный.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.