Spec-Zone.ru › pandas 2

Работа с текстовыми данными

Типы текстовых данных

Существует два способа хранения текстовых данных в pandas:

  1. object - массив NumPy.

  2. StringDtype расширенный тип.

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

До версии pandas 1.0, object тип был единственным вариантом. Это было неудобно по многим причинам:

  1. Вы можете случайно сохранить смесь строк и нестроковых значений в массиве типа object. Лучше иметь специализированный тип.

  2. object тип нарушает операции, специфичные для типа, такие как DataFrame.select_dtypes(). Нет ясного способа выбрать только текст, исключая нетекстовые, но все еще имеющие тип object столбцы.

  3. При чтении кода содержимое массива типа object менее понятно, чем содержимое массива типа 'string'.

В настоящее время производительность массивов object типа строк и arrays.StringArray примерно одинакова. Ожидается, что будущие улучшения значительно повысят производительность и снизят размер занимаемой памяти StringArray.

Предупреждение

StringArray в настоящее время считается экспериментальным. Реализация и части API могут измениться без предварительного уведомления.

Для обеспечения обратной совместимости, object тип остается по умолчанию при преобразовании списка строк.

In [1]: pd.Series(["a", "b", "c"])
Out[1]: 
0    a
1    b
2    c
dtype: object

Чтобы явно указать string тип, укажите dtype

In [2]: pd.Series(["a", "b", "c"], dtype="string")
Out[2]: 
0    a
1    b
2    c
dtype: string

In [3]: pd.Series(["a", "b", "c"], dtype=pd.StringDtype())
Out[3]: 
0    a
1    b
2    c
dtype: string

Или astype после того, как Series или DataFrame созданы.

In [4]: s = pd.Series(["a", "b", "c"])

In [5]: s
Out[5]: 
0    a
1    b
2    c
dtype: object

In [6]: s.astype("string")
Out[6]: 
0    a
1    b
2    c
dtype: string

Вы также можете использовать StringDtype/"string" в качестве типа для нестроковых данных, и он будет преобразован в string тип:

In [7]: s = pd.Series(["a", 2, np.nan], dtype="string")

In [8]: s
Out[8]: 
0       a
1       2
2    <NA>
dtype: string

In [9]: type(s[1])
Out[9]: str

или преобразовать из существующих данных pandas:

In [10]: s1 = pd.Series([1, 2, np.nan], dtype="Int64")

In [11]: s1
Out[11]: 
0       1
1       2
2    <NA>
dtype: Int64

In [12]: s2 = s1.astype("string")

In [13]: s2
Out[13]: 
0       1
1       2
2    <NA>
dtype: string

In [14]: type(s2[0])
Out[14]: str

Различия в поведении

Это места, где поведение объектов StringDtype отличается от object типа.

  1. Для StringDtype, методы доступа к строкам, возвращающие числовое значение, всегда будут возвращать целочисленный тип с возможностью отсутствия значений (nullable), а не целочисленный или плавающий тип, в зависимости от наличия значений NA. Методы, возвращающие булево значение, будут возвращать булевый тип с возможностью отсутствия значений.

    In [15]: s = pd.Series(["a", None, "b"], dtype="string")
    
    In [16]: s
    Out[16]: 
    0       a
    1    <NA>
    2       b
    dtype: string
    
    In [17]: s.str.count("a")
    Out[17]: 
    0       1
    1    <NA>
    2       0
    dtype: Int64
    
    In [18]: s.dropna().str.count("a")
    Out[18]: 
    0    1
    2    0
    dtype: Int64
    

    Оба результата имеют тип Int64. Сравните это с типом object.

    In [19]: s2 = pd.Series(["a", None, "b"], dtype="object")
    
    In [20]: s2.str.count("a")
    Out[20]: 
    0    1.0
    1    NaN
    2    0.0
    dtype: float64
    
    In [21]: s2.dropna().str.count("a")
    Out[21]: 
    0    1
    2    0
    dtype: int64
    

    При наличии значений NA тип результата — float64. То же самое относится к методам, возвращающим булевые значения.

    In [22]: s.str.isdigit()
    Out[22]: 
    0    False
    1     <NA>
    2    False
    dtype: boolean
    
    In [23]: s.str.match("a")
    Out[23]: 
    0     True
    1     <NA>
    2    False
    dtype: boolean
    
  1. Некоторые строковые методы, такие как Series.str.decode(), недоступны для StringArray, так как StringArray хранит только строки, а не байты.

  2. При сравнениях arrays.StringArray и Series на основе StringArray вернет объект с типом BooleanDtype, а не объектом типа bool . Пропущенные значения в StringArray будут передаваться в операциях сравнения, а не всегда сравнивать как неравные, как numpy.nan.

Все остальное, что следует в остальной части документа, одинаково относится к string и object типу.

Строковые методы

Серии и индексы оснащены набором методов обработки строк, которые позволяют легко работать с каждым элементом массива. Возможно, самое важное, что эти методы автоматически исключают пропущенные/NA значения. Доступ к ним осуществляется через атрибут str и, как правило, имеют имена, соответствующие эквивалентным (скалярным) встроенным строковым методам:

In [24]: s = pd.Series(
   ....:     ["A", "B", "C", "Aaba", "Baca", np.nan, "CABA", "dog", "cat"], dtype="string"
   ....: )
   ....: 

In [25]: s.str.lower()
Out[25]: 
0       a
1       b
2       c
3    aaba
4    baca
5    <NA>
6    caba
7     dog
8     cat
dtype: string

In [26]: s.str.upper()
Out[26]: 
0       A
1       B
2       C
3    AABA
4    BACA
5    <NA>
6    CABA
7     DOG
8     CAT
dtype: string

In [27]: s.str.len()
Out[27]: 
0       1
1       1
2       1
3       4
4       4
5    <NA>
6       4
7       3
8       3
dtype: Int64
In [28]: idx = pd.Index([" jack", "jill ", " jesse ", "frank"])

In [29]: idx.str.strip()
Out[29]: Index(['jack', 'jill', 'jesse', 'frank'], dtype='object')

In [30]: idx.str.lstrip()
Out[30]: Index(['jack', 'jill ', 'jesse ', 'frank'], dtype='object')

In [31]: idx.str.rstrip()
Out[31]: Index([' jack', 'jill', ' jesse', 'frank'], dtype='object')

Строковые методы в индексах особенно полезны для очистки или преобразования столбцов DataFrame. Например, у вас могут быть столбцы с начальными или конечными пробелами:

In [32]: df = pd.DataFrame(
   ....:     np.random.randn(3, 2), columns=[" Column A ", " Column B "], index=range(3)
   ....: )
   ....: 

In [33]: df
Out[33]: 
   Column A   Column B 
0   0.469112  -0.282863
1  -1.509059  -1.135632
2   1.212112  -0.173215

Так как df.columns это объект Index, мы можем использовать атрибут .str

In [34]: df.columns.str.strip()
Out[34]: Index(['Column A', 'Column B'], dtype='object')

In [35]: df.columns.str.lower()
Out[35]: Index([' column a ', ' column b '], dtype='object')

Эти строковые методы могут быть использованы для очистки столбцов по мере необходимости. Здесь мы удаляем начальные и конечные пробелы, приводим все имена к нижнему регистру и заменяем оставшиеся пробелы на нижние подчеркивания:

In [36]: df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")

In [37]: df
Out[37]: 
   column_a  column_b
0  0.469112 -0.282863
1 -1.509059 -1.135632
2  1.212112 -0.173215

Примечание

Если у вас есть Series где много элементов повторяются (т. е. количество уникальных элементов в Series намного меньше длины Series), может быть быстрее преобразовать исходный Series в один из типов category и затем использовать .str.<method> или .dt.<property> на нём. Разница в производительности происходит от того, что для Series типа category, операции со строками выполняются над .categories , а не над каждым элементом Series.

Обратите внимание, что Series типа category со строковыми .categories имеет некоторые ограничения по сравнению со строковым Series (например, вы не можете добавлять строки друг к другу: s + " " + s не будет работать, если s является Series типа category). Также, методы .str которые работают с элементами типа list недоступны для такого Series.

Предупреждение

Тип Series определяется автоматически и допустимыми типами являются (т. е. строки).

В общем случае, атрибут .str предназначен для работы только со строками. В очень редких случаях другие использования не поддерживаются и могут быть отключены в будущем.

Разделение и замена строк

Методы, такие как split, возвращают Series списков:

In [38]: s2 = pd.Series(["a_b_c", "c_d_e", np.nan, "f_g_h"], dtype="string")

In [39]: s2.str.split("_")
Out[39]: 
0    [a, b, c]
1    [c, d, e]
2         <NA>
3    [f, g, h]
dtype: object

Элементы в списках разделения можно получить, используя обозначение get или []:

In [40]: s2.str.split("_").str.get(1)
Out[40]: 
0       b
1       d
2    <NA>
3       g
dtype: object

In [41]: s2.str.split("_").str[1]
Out[41]: 
0       b
1       d
2    <NA>
3       g
dtype: object

Легко расширить это, чтобы возвращать DataFrame, используя expand.

In [42]: s2.str.split("_", expand=True)
Out[42]: 
      0     1     2
0     a     b     c
1     c     d     e
2  <NA>  <NA>  <NA>
3     f     g     h

Когда исходный Series имеет StringDtype, выходные столбцы также будут StringDtype.

Также можно ограничить количество разделений:

In [43]: s2.str.split("_", expand=True, n=1)
Out[43]: 
      0     1
0     a   b_c
1     c   d_e
2  <NA>  <NA>
3     f   g_h

rsplit похож на split, за исключением того, что он работает в обратном направлении, т.е. от конца строки к началу строки:

In [44]: s2.str.rsplit("_", expand=True, n=1)
Out[44]: 
      0     1
0   a_b     c
1   c_d     e
2  <NA>  <NA>
3   f_g     h

replace необязательно использует регулярные выражения:

In [45]: s3 = pd.Series(
   ....:     ["A", "B", "C", "Aaba", "Baca", "", np.nan, "CABA", "dog", "cat"],
   ....:     dtype="string",
   ....: )
   ....: 

In [46]: s3
Out[46]: 
0       A
1       B
2       C
3    Aaba
4    Baca
5        
6    <NA>
7    CABA
8     dog
9     cat
dtype: string

In [47]: s3.str.replace("^.a|dog", "XX-XX ", case=False, regex=True)
Out[47]: 
0           A
1           B
2           C
3    XX-XX ba
4    XX-XX ca
5            
6        <NA>
7    XX-XX BA
8      XX-XX 
9     XX-XX t
dtype: string

Изменено в версии 2.0.

Шаблон одного символа с regex=True также будет обрабатываться как регулярное выражение:

In [48]: s4 = pd.Series(["a.b", ".", "b", np.nan, ""], dtype="string")

In [49]: s4
Out[49]: 
0     a.b
1       .
2       b
3    <NA>
4        
dtype: string

In [50]: s4.str.replace(".", "a", regex=True)
Out[50]: 
0     aaa
1       a
2       a
3    <NA>
4        
dtype: string

Если вы хотите выполнить буквальную замену строки (эквивалент str.replace()), можно установить необязательный параметр regex в False, а не экранировать каждый символ. В этом случае как pat, так и repl должны быть строками:

In [51]: dollars = pd.Series(["12", "-$10", "$10,000"], dtype="string")

# These lines are equivalent
In [52]: dollars.str.replace(r"-\$", "-", regex=True)
Out[52]: 
0         12
1        -10
2    $10,000
dtype: string

In [53]: dollars.str.replace("-$", "-", regex=False)
Out[53]: 
0         12
1        -10
2    $10,000
dtype: string

Метод replace также может принимать вызываемую функцию в качестве замены. Она вызывается для каждого pat с использованием re.sub(). Вызываемая функция должна принимать один позиционный аргумент (объект регулярного выражения) и возвращать строку.

# Reverse every lowercase alphabetic word
In [54]: pat = r"[a-z]+"

In [55]: def repl(m):
   ....:     return m.group(0)[::-1]
   ....: 

In [56]: pd.Series(["foo 123", "bar baz", np.nan], dtype="string").str.replace(
   ....:     pat, repl, regex=True
   ....: )
   ....: 
Out[56]: 
0    oof 123
1    rab zab
2       <NA>
dtype: string

# Using regex groups
In [57]: pat = r"(?P<one>\w+) (?P<two>\w+) (?P<three>\w+)"

In [58]: def repl(m):
   ....:     return m.group("two").swapcase()
   ....: 

In [59]: pd.Series(["Foo Bar Baz", np.nan], dtype="string").str.replace(
   ....:     pat, repl, regex=True
   ....: )
   ....: 
Out[59]: 
0     bAR
1    <NA>
dtype: string

Метод replace также принимает скомпилированный объект регулярного выражения из re.compile() в качестве шаблона. Все флаги должны быть включены в скомпилированный объект регулярного выражения.

In [60]: import re

In [61]: regex_pat = re.compile(r"^.a|dog", flags=re.IGNORECASE)

In [62]: s3.str.replace(regex_pat, "XX-XX ", regex=True)
Out[62]: 
0           A
1           B
2           C
3    XX-XX ba
4    XX-XX ca
5            
6        <NA>
7    XX-XX BA
8      XX-XX 
9     XX-XX t
dtype: string

Включение аргумента flags при вызове replace с помощью скомпилированного объекта регулярного выражения вызовет ValueError.

In [63]: s3.str.replace(regex_pat, 'XX-XX ', flags=re.IGNORECASE)
---------------------------------------------------------------------------
ValueError: case and flags cannot be set when pat is a compiled regex

removeprefix и removesuffix имеют тот же эффект, что и str.removeprefix и str.removesuffix, добавленные в Python 3.9 <https://docs.python.org/3/library/stdtypes.html#str.removeprefix>`__:

Новое в версии 1.4.0.

In [64]: s = pd.Series(["str_foo", "str_bar", "no_prefix"])

In [65]: s.str.removeprefix("str_")
Out[65]: 
0          foo
1          bar
2    no_prefix
dtype: object

In [66]: s = pd.Series(["foo_str", "bar_str", "no_suffix"])

In [67]: s.str.removesuffix("_str")
Out[67]: 
0          foo
1          bar
2    no_suffix
dtype: object

Конкатенация

Существует несколько способов конкатенировать Series или Index, либо с самим собой, либо с другими, все основанные на cat(), соответственно Index.str.cat.

Конкатенация одного Series в строку

Содержимое Series (или Index ) можно конкатенировать:

In [68]: s = pd.Series(["a", "b", "c", "d"], dtype="string")

In [69]: s.str.cat(sep=",")
Out[69]: 'a,b,c,d'

Если не указано, ключевое слово sep по умолчанию для разделителя — пустая строка, sep='':

In [70]: s.str.cat()
Out[70]: 'abcd'

По умолчанию пропущенные значения игнорируются. Используя na_rep, им можно присвоить представление:

In [71]: t = pd.Series(["a", "b", np.nan, "d"], dtype="string")

In [72]: t.str.cat(sep=",")
Out[72]: 'a,b,d'

In [73]: t.str.cat(sep=",", na_rep="-")
Out[73]: 'a,b,-,d'

Конкатенация Series и некоторого списка в Series

Первый аргумент cat() может быть объектом-списком, при условии, что он соответствует длине вызываемого Series (или Index).

In [74]: s.str.cat(["A", "B", "C", "D"])
Out[74]: 
0    aA
1    bB
2    cC
3    dD
dtype: string

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

In [75]: s.str.cat(t)
Out[75]: 
0      aa
1      bb
2    <NA>
3      dd
dtype: string

In [76]: s.str.cat(t, na_rep="-")
Out[76]: 
0    aa
1    bb
2    c-
3    dd
dtype: string

Конкатенация Series и некоторого массива в Series

Параметр others также может быть двумерным. В этом случае количество строк должно соответствовать длинам вызываемого Series (или Index).

In [77]: d = pd.concat([t, s], axis=1)

In [78]: s
Out[78]: 
0    a
1    b
2    c
3    d
dtype: string

In [79]: d
Out[79]: 
      0  1
0     a  a
1     b  b
2  <NA>  c
3     d  d

In [80]: s.str.cat(d, na_rep="-")
Out[80]: 
0    aaa
1    bbb
2    c-c
3    ddd
dtype: string

Конкатенация Series и индексированного объекта в Series с выравниванием

Для конкатенации с Series или DataFrame, можно выровнять индексы перед конкатенацией, установив ключевое слово join.

In [81]: u = pd.Series(["b", "d", "a", "c"], index=[1, 3, 0, 2], dtype="string")

In [82]: s
Out[82]: 
0    a
1    b
2    c
3    d
dtype: string

In [83]: u
Out[83]: 
1    b
3    d
0    a
2    c
dtype: string

In [84]: s.str.cat(u)
Out[84]: 
0    aa
1    bb
2    cc
3    dd
dtype: string

In [85]: s.str.cat(u, join="left")
Out[85]: 
0    aa
1    bb
2    cc
3    dd
dtype: string

Обычно доступны варианты для join (один из 'left', 'outer', 'inner', 'right'). В частности, выравнивание также означает, что разные длины больше не должны совпадать.

In [86]: v = pd.Series(["z", "a", "b", "d", "e"], index=[-1, 0, 1, 3, 4], dtype="string")

In [87]: s
Out[87]: 
0    a
1    b
2    c
3    d
dtype: string

In [88]: v
Out[88]: 
-1    z
 0    a
 1    b
 3    d
 4    e
dtype: string

In [89]: s.str.cat(v, join="left", na_rep="-")
Out[89]: 
0    aa
1    bb
2    c-
3    dd
dtype: string

In [90]: s.str.cat(v, join="outer", na_rep="-")
Out[90]: 
-1    -z
 0    aa
 1    bb
 2    c-
 3    dd
 4    -e
dtype: string

То же выравнивание можно использовать, когда others является DataFrame:

In [91]: f = d.loc[[3, 2, 1, 0], :]

In [92]: s
Out[92]: 
0    a
1    b
2    c
3    d
dtype: string

In [93]: f
Out[93]: 
      0  1
3     d  d
2  <NA>  c
1     b  b
0     a  a

In [94]: s.str.cat(f, join="left", na_rep="-")
Out[94]: 
0    aaa
1    bbb
2    c-c
3    ddd
dtype: string

Конкатенация Series и многих объектов в Series

Несколько объектов типа массива (в частности: Series, Index, и одномерные варианты np.ndarray) можно объединить в контейнер типа список (включая итераторы, dict-представления и т.д.).

In [95]: s
Out[95]: 
0    a
1    b
2    c
3    d
dtype: string

In [96]: u
Out[96]: 
1    b
3    d
0    a
2    c
dtype: string

In [97]: s.str.cat([u, u.to_numpy()], join="left")
Out[97]: 
0    aab
1    bbd
2    cca
3    ddc
dtype: string

Все элементы без индекса (например, np.ndarray) в переданном списке должны иметь длину, соответствующую вызываемому Series (или Index ), но Series и Index могут иметь произвольную длину (пока выравнивание не отключено с помощью join=None):

In [98]: v
Out[98]: 
-1    z
 0    a
 1    b
 3    d
 4    e
dtype: string

In [99]: s.str.cat([v, u, u.to_numpy()], join="outer", na_rep="-")
Out[99]: 
-1    -z--
0     aaab
1     bbbd
2     c-ca
3     dddc
4     -e--
dtype: string

Если использовать join='right' на списке others , содержащем разные индексы, в качестве основы для окончательной конкатенации будет использовано объединение этих индексов:

In [100]: u.loc[[3]]
Out[100]: 
3    d
dtype: string

In [101]: v.loc[[-1, 0]]
Out[101]: 
-1    z
 0    a
dtype: string

In [102]: s.str.cat([u.loc[[3]], v.loc[[-1, 0]]], join="right", na_rep="-")
Out[102]: 
 3    dd-
-1    --z
 0    a-a
dtype: string

Индексация с помощью .str

Вы можете использовать обозначение [] для прямого индексирования по позиционным расположениям. Если вы индексируете за пределы строки, результат будет NaN.

In [103]: s = pd.Series(
   .....:     ["A", "B", "C", "Aaba", "Baca", np.nan, "CABA", "dog", "cat"], dtype="string"
   .....: )
   .....: 

In [104]: s.str[0]
Out[104]: 
0       A
1       B
2       C
3       A
4       B
5    <NA>
6       C
7       d
8       c
dtype: string

In [105]: s.str[1]
Out[105]: 
0    <NA>
1    <NA>
2    <NA>
3       a
4       a
5    <NA>
6       A
7       o
8       a
dtype: string
END_OF_DOCUMENT_MARKER

Извлечение подстрок

Извлечение первого совпадения в каждом объекте (extract)

Метод extract принимает регулярное выражение регулярное выражение с хотя бы одной группой захвата.

Извлечение регулярного выражения с более чем одной группой возвращает DataFrame с одним столбцом на каждую группу.

In [106]: pd.Series(
   .....:     ["a1", "b2", "c3"],
   .....:     dtype="string",
   .....: ).str.extract(r"([ab])(\d)", expand=False)
   .....: 
Out[106]: 
      0     1
0     a     1
1     b     2
2  <NA>  <NA>

Элементы, которые не соответствуют шаблону, возвращают строку, заполненную NaN. Таким образом, ряд необработанных строк можно «преобразовать» в подобный индексированный ряд или DataFrame очищенных или более полезных строк без необходимости get() для доступа к кортежам или re.match объектам. Тип данных результата всегда object, даже если совпадение не найдено и результат содержит только NaN.

Именованные группы, такие как

In [107]: pd.Series(["a1", "b2", "c3"], dtype="string").str.extract(
   .....:     r"(?P<letter>[ab])(?P<digit>\d)", expand=False
   .....: )
   .....: 
Out[107]: 
  letter digit
0      a     1
1      b     2
2   <NA>  <NA>

и необязательные группы, такие как

In [108]: pd.Series(
   .....:     ["a1", "b2", "3"],
   .....:     dtype="string",
   .....: ).str.extract(r"([ab])?(\d)", expand=False)
   .....: 
Out[108]: 
      0  1
0     a  1
1     b  2
2  <NA>  3

также могут быть использованы. Обратите внимание, что любые имена групп захвата в регулярном выражении будут использованы в качестве имён столбцов; в противном случае будут использованы номера групп захвата.

Извлечение регулярного выражения с одной группой возвращает DataFrame с одним столбцом, если expand=True.

In [109]: pd.Series(["a1", "b2", "c3"], dtype="string").str.extract(r"[ab](\d)", expand=True)
Out[109]: 
      0
0     1
1     2
2  <NA>

Он возвращает ряд, если expand=False.

In [110]: pd.Series(["a1", "b2", "c3"], dtype="string").str.extract(r"[ab](\d)", expand=False)
Out[110]: 
0       1
1       2
2    <NA>
dtype: string

Вызов Index с регулярным выражением ровно с одной группой захвата возвращает DataFrame с одним столбцом, если expand=True.

In [111]: s = pd.Series(["a1", "b2", "c3"], ["A11", "B22", "C33"], dtype="string")

In [112]: s
Out[112]: 
A11    a1
B22    b2
C33    c3
dtype: string

In [113]: s.index.str.extract("(?P<letter>[a-zA-Z])", expand=True)
Out[113]: 
  letter
0      A
1      B
2      C

Он возвращает Index, если expand=False.

In [114]: s.index.str.extract("(?P<letter>[a-zA-Z])", expand=False)
Out[114]: Index(['A', 'B', 'C'], dtype='object', name='letter')

Вызов Index с регулярным выражением более чем с одной группой захвата возвращает DataFrame если expand=True.

In [115]: s.index.str.extract("(?P<letter>[a-zA-Z])([0-9]+)", expand=True)
Out[115]: 
  letter   1
0      A  11
1      B  22
2      C  33

Возникает ValueError, если expand=False.

In [116]: s.index.str.extract("(?P<letter>[a-zA-Z])([0-9]+)", expand=False)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[116], line 1
----> 1 s.index.str.extract("(?P<letter>[a-zA-Z])([0-9]+)", expand=False)

File ~/work/pandas/pandas/pandas/core/strings/accessor.py:137, in forbid_nonstring_types.<locals>._forbid_nonstring_types.<locals>.wrapper(self, *args, **kwargs)
    132     msg = (
    133         f"Cannot use .str.{func_name} with values of "
    134         f"inferred dtype '{self._inferred_dtype}'."
    135     )
    136     raise TypeError(msg)
--> 137 return func(self, *args, **kwargs)

File ~/work/pandas/pandas/pandas/core/strings/accessor.py:2743, in StringMethods.extract(self, pat, flags, expand)
   2740     raise ValueError("pattern contains no capture groups")
   2742 if not expand and regex.groups > 1 and isinstance(self._data, ABCIndex):
-> 2743     raise ValueError("only one regex group is supported with Index")
   2745 obj = self._data
   2746 result_dtype = _result_dtype(obj)

ValueError: only one regex group is supported with Index

Ниже приведена сводная таблица поведения extract(expand=False) (входной строкой в первом столбце, количеством групп в регулярном выражении в первой строке):

1 группа

>1 группа

Индекс

Индекс

ValueError

Ряд

Ряд

DataFrame

Извлечение всех совпадений в каждом объекте (extractall)

В отличие от extract (которая возвращает только первое совпадение),

In [117]: s = pd.Series(["a1a2", "b1", "c1"], index=["A", "B", "C"], dtype="string")

In [118]: s
Out[118]: 
A    a1a2
B      b1
C      c1
dtype: string

In [119]: two_groups = "(?P<letter>[a-z])(?P<digit>[0-9])"

In [120]: s.str.extract(two_groups, expand=True)
Out[120]: 
  letter digit
A      a     1
B      b     1
C      c     1

метод extractall возвращает каждое совпадение. Результат extractall всегда DataFrame с MultiIndex по своим строкам. Последний уровень MultiIndex называется match и указывает на порядок в объекте.

In [121]: s.str.extractall(two_groups)
Out[121]: 
        letter digit
  match             
A 0          a     1
  1          a     2
B 0          b     1
C 0          c     1

Когда у каждой строковой переменной в ряду ровно одно совпадение,

In [122]: s = pd.Series(["a3", "b3", "c2"], dtype="string")

In [123]: s
Out[123]: 
0    a3
1    b3
2    c2
dtype: string

тогда extractall(pat).xs(0, level='match') даёт такой же результат, как extract(pat).

In [124]: extract_result = s.str.extract(two_groups, expand=True)

In [125]: extract_result
Out[125]: 
  letter digit
0      a     3
1      b     3
2      c     2

In [126]: extractall_result = s.str.extractall(two_groups)

In [127]: extractall_result
Out[127]: 
        letter digit
  match             
0 0          a     3
1 0          b     3
2 0          c     2

In [128]: extractall_result.xs(0, level="match")
Out[128]: 
  letter digit
0      a     3
1      b     3
2      c     2

Index также поддерживает .str.extractall. Он возвращает DataFrame, который даёт тот же результат, что и Series.str.extractall с индексом по умолчанию (начинается с 0).

In [129]: pd.Index(["a1a2", "b1", "c1"]).str.extractall(two_groups)
Out[129]: 
        letter digit
  match             
0 0          a     1
  1          a     2
1 0          b     1
2 0          c     1

In [130]: pd.Series(["a1a2", "b1", "c1"], dtype="string").str.extractall(two_groups)
Out[130]: 
        letter digit
  match             
0 0          a     1
  1          a     2
1 0          b     1
2 0          c     1

Проверка строк на соответствие или включение шаблона

Можно проверить, содержат ли элементы шаблон:

In [131]: pattern = r"[0-9][a-z]"

In [132]: pd.Series(
   .....:     ["1", "2", "3a", "3b", "03c", "4dx"],
   .....:     dtype="string",
   .....: ).str.contains(pattern)
   .....: 
Out[132]: 
0    False
1    False
2     True
3     True
4     True
5     True
dtype: boolean

Или же элементы соответствуют шаблону:

In [133]: pd.Series(
   .....:     ["1", "2", "3a", "3b", "03c", "4dx"],
   .....:     dtype="string",
   .....: ).str.match(pattern)
   .....: 
Out[133]: 
0    False
1    False
2     True
3     True
4    False
5     True
dtype: boolean
In [134]: pd.Series(
   .....:     ["1", "2", "3a", "3b", "03c", "4dx"],
   .....:     dtype="string",
   .....: ).str.fullmatch(pattern)
   .....: 
Out[134]: 
0    False
1    False
2     True
3     True
4    False
5    False
dtype: boolean

Примечание

Различие между match, fullmatch, и contains заключается в строгости: fullmatch проверяет, соответствует ли вся строка регулярному выражению; match проверяет, есть ли совпадение регулярного выражения, которое начинается с первого символа строки; и contains проверяет, есть ли совпадение регулярного выражения в любой позиции внутри строки.

Соответствующие функции в пакете re для этих трёх режимов совпадения — re.fullmatch, re.match и re.search соответственно.

Методы, такие как match, fullmatch, contains, startswith, и endswith принимают дополнительный аргумент na, чтобы пропущенные значения можно было считать истинными или ложными:

In [135]: s4 = pd.Series(
   .....:     ["A", "B", "C", "Aaba", "Baca", np.nan, "CABA", "dog", "cat"], dtype="string"
   .....: )
   .....: 

In [136]: s4.str.contains("A", na=False)
Out[136]: 
0     True
1    False
2    False
3     True
4    False
5    False
6     True
7    False
8    False
dtype: boolean

Создание индикаторных переменных

Можно извлечь фиктивные переменные из столбцов строк. Например, если они разделены '|':

In [137]: s = pd.Series(["a", "a|b", np.nan, "a|c"], dtype="string")

In [138]: s.str.get_dummies(sep="|")
Out[138]: 
   a  b  c
0  1  0  0
1  1  1  0
2  0  0  0
3  1  0  1

Строковый Index также поддерживает get_dummies, который возвращает MultiIndex.

In [139]: idx = pd.Index(["a", "a|b", np.nan, "a|c"])

In [140]: idx.str.get_dummies(sep="|")
Out[140]: 
MultiIndex([(1, 0, 0),
            (1, 1, 0),
            (0, 0, 0),
            (1, 0, 1)],
           names=['a', 'b', 'c'])

См. также get_dummies().

Краткое описание методов

Метод

Описание

cat()

Конкатенация строк

split()

Разделение строк по разделителю

rsplit()

Разделение строк по разделителю, начиная с конца строки

get()

Индексирование каждого элемента (получение i-го элемента)

join()

Объединение строк в каждом элементе Series с указанным разделителем

get_dummies()

Разделение строк по разделителю с возвращением DataFrame фиктивных переменных

contains()

Возвращение булевого массива, если каждая строка содержит шаблон/регулярное выражение

replace()

Замена вхождений шаблона/регулярного выражения/строки другой строкой или возвращаемым значением вызываемого объекта, заданного для вхождения

removeprefix()

Удаление префикса из строки, т.е. удаление только в случае, если строка начинается с префикса.

removesuffix()

Удаление суффикса из строки, т.е. удаление только в случае, если строка заканчивается суффиксом.

repeat()

Дублирование значений (s.str.repeat(3) эквивалентно x * 3)

pad()

Добавление пробелов слева, справа или с обеих сторон строк

center()

Эквивалентно str.center

ljust()

Эквивалентно str.ljust

rjust()

Эквивалентно str.rjust

zfill()

Эквивалентно str.zfill

wrap()

Разделение длинных строк на строки с длиной меньше заданной ширины

slice()

Вырезание каждой строки в Series

slice_replace()

Замена среза в каждой строке заданным значением

count()

Подсчёт вхождений шаблона

startswith()

Эквивалентно str.startswith(pat) для каждого элемента

endswith()

Эквивалентно str.endswith(pat) для каждого элемента

findall()

Вычисление списка всех вхождений шаблона/регулярного выражения для каждой строки

match()

Вызов re.match для каждого элемента, возвращая совпадающие группы как список

extract()

Вызов re.search для каждого элемента, возвращая DataFrame с одной строкой для каждого элемента и одним столбцом для каждой группы захвата регулярного выражения

extractall()

Вызов re.findall для каждого элемента, возвращая DataFrame с одной строкой для каждого совпадения и одним столбцом для каждой группы захвата регулярного выражения

len()

Вычисление длин строк

strip()

Эквивалентно str.strip

rstrip()

Эквивалентно str.rstrip

lstrip()

Эквивалентно str.lstrip

partition()

Эквивалентно str.partition

rpartition()

Эквивалентно str.rpartition

lower()

Эквивалентно str.lower

casefold()

Эквивалентно str.casefold

upper()

Эквивалентно str.upper

find()

Эквивалентно str.find

rfind()

Эквивалентно str.rfind

index()

Эквивалентно str.index

rindex()

Эквивалентно str.rindex

capitalize()

Эквивалентно str.capitalize

swapcase()

Эквивалентно str.swapcase

normalize()

Возвращает нормальную форму Юникода. Эквивалентно unicodedata.normalize

translate()

Эквивалентно str.translate

isalnum()

Эквивалентно str.isalnum

isalpha()

Эквивалентно str.isalpha

isdigit()

Эквивалентно str.isdigit

isspace()

Эквивалентно str.isspace

islower()

Эквивалентно str.islower

isupper()

Эквивалентно str.isupper

istitle()

Эквивалентно str.istitle

isnumeric()

Эквивалентно str.isnumeric

isdecimal()

Эквивалентно str.isdecimal

© 2008–2022, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/2.2.2/user_guide/text.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API