Виберіть за допомогою часткового рядка з панди DataFrame


448

У мене є DataFrame4 колонки, з яких 2 містять рядкові значення. Мені було цікаво, чи існує спосіб вибору рядків на основі часткового збігу рядків проти певного стовпця?

Іншими словами, функція або лямбда-функція, яка б робила щось подібне

re.search(pattern, cell_in_question) 

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

Хтось міг би вказати мені в правильному напрямку?

Відповіді:


786

На основі випуску 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.+')]для фільтрації стовпців типу "рядок".
Франсуа Леблан

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.contains ("Привіт, Британія", na = хибне)]
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

Пошук у декількох підрядках

Це найлегше досягти шляхом пошуку за допомогою регулярного вибору за допомогою каналу регулярного виводу АБО.

# 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

Розширені можливості пошуку для часткового Струнний 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 Вилучення рядків з підрядками, що містять пробіл після + у пандах df. На нього відповіли незабаром, але ви, можливо, захочете його переглянути.
ankii

@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')]
перегнати

18
який можна додатково перегнати доdf.filter(like='a')
Тед Петру

це має бути власне питання + відповідь, вже 50 людей шукали його ...
PV8

1
@ Вже існує PV8 питання: stackoverflow.com/questions/31551412 / ... . Але коли я шукаю в google на "pandas Select column by part string", ця тема з’являється першою
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 (запис)
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]

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)]

Увага. Цей метод відносно повільний, хоч і зручний.


2

Якщо вам потрібно буде здійснити нечутливий до регістру пошук рядка в стовпці фрейму даних pandas:

df[df['A'].str.contains("hello", case=False)]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.