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

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

Series и Index оснащены набором методов обработки строк, позволяющих легко выполнять операции над каждым элементом массива. Важнее всего, пожалуй, то, что эти методы автоматически исключают пропущенные значения/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')

Строковые методы для Index особенно полезны для очистки или преобразования столбцов 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

С помощью expand это легко преобразовать в DataFrame.

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

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

Извлечение первого совпадения в каждом элементе (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. Таким образом, Series с неструктурированными строками можно «преобразовать» в Series или 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, возвращается Series.

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

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

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

Если задан expand=False, возникает исключение ValueError.

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:140, in forbid_nonstring_types.<locals>._forbid_nonstring_types.<locals>.wrapper(self, *args, **kwargs)
    135     msg = (
    136         f"Cannot use .str.{func_name} with values of "
    137         f"inferred dtype '{self._inferred_dtype}'."
    138     )
    139     raise TypeError(msg)
--> 140 return func(self, *args, **kwargs)

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

ValueError: only one regex group is supported with Index

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

1 группа

>1 группа

Index

Index

ValueError

Series

Series

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

Если в каждой строке Series найдено ровно одно совпадение,

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, чтобы пропущенные значения можно было считать True или False:

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

Вернуть нормализованную форму Unicode. Эквивалентно 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–2011, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
© 2011–2025, Open source contributors
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/2.3.0/user_guide/text.html

Spec-Zone.ru

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