Работа с текстовыми данными
Серии и индексы оснащены набором методов обработки строк, которые облегчают работу с каждым элементом массива. Возможно, самое важное, эти методы автоматически исключают пропущенные/NA значения. К ним можно получить доступ через атрибут str и, как правило, они имеют названия, соответствующие эквивалентным (скалярным) встроенным методам строк:
In [1]: s = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca', np.nan, 'CABA', 'dog', 'cat']) In [2]: s.str.lower() Out[2]: 0 a 1 b 2 c 3 aaba 4 baca 5 NaN 6 caba 7 dog 8 cat dtype: object In [3]: s.str.upper() Out[3]: 0 A 1 B 2 C 3 AABA 4 BACA 5 NaN 6 CABA 7 DOG 8 CAT dtype: object In [4]: s.str.len() Out[4]: 0 1.0 1 1.0 2 1.0 3 4.0 4 4.0 5 NaN 6 4.0 7 3.0 8 3.0 dtype: float64
In [5]: idx = pd.Index([' jack', 'jill ', ' jesse ', 'frank']) In [6]: idx.str.strip() Out[6]: Index([u'jack', u'jill', u'jesse', u'frank'], dtype='object') In [7]: idx.str.lstrip() Out[7]: Index([u'jack', u'jill ', u'jesse ', u'frank'], dtype='object') In [8]: idx.str.rstrip() Out[8]: Index([u' jack', u'jill', u' jesse', u'frank'], dtype='object')
Методы строк в индексе особенно полезны для очистки или преобразования столбцов DataFrame. Например, у вас могут быть столбцы с ведущими или хвостовыми пробелами:
In [9]: df = pd.DataFrame(randn(3, 2), columns=[' Column A ', ' Column B '],
...: index=range(3))
...:
In [10]: df
Out[10]:
Column A Column B
0 0.017428 0.039049
1 -2.240248 0.847859
2 -1.342107 0.368828
Поскольку df.columns является объектом Index, мы можем использовать аксессор .str
In [11]: df.columns.str.strip() Out[11]: Index([u'Column A', u'Column B'], dtype='object') In [12]: df.columns.str.lower() Out[12]: Index([u' column a ', u' column b '], dtype='object')
Затем эти методы строк можно использовать для очистки столбцов по мере необходимости. Здесь мы удаляем ведущие и хвостовые пробелы, приводя все имена к нижнему регистру и заменяя оставшиеся пробелы подчёркиваниями:
In [13]: df.columns = df.columns.str.strip().str.lower().str.replace(' ', '_')
In [14]: df
Out[14]:
column_a column_b
0 0.017428 0.039049
1 -2.240248 0.847859
2 -1.342107 0.368828
Примечание
Если у вас есть 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.
Разделение и замена строк
Методы, такие как split возвращают серию списков:
In [15]: s2 = pd.Series(['a_b_c', 'c_d_e', np.nan, 'f_g_h'])
In [16]: s2.str.split('_')
Out[16]:
0 [a, b, c]
1 [c, d, e]
2 NaN
3 [f, g, h]
dtype: object
Элементы в разбитых списках можно получить, используя обозначение get или [].
In [17]: s2.str.split('_').str.get(1)
Out[17]:
0 b
1 d
2 NaN
3 g
dtype: object
In [18]: s2.str.split('_').str[1]
Out[18]:
0 b
1 d
2 NaN
3 g
dtype: object
Легко расширить это для возвращения DataFrame, используя expand.
In [19]: s2.str.split('_', expand=True)
Out[19]:
0 1 2
0 a b c
1 c d e
2 NaN None None
3 f g h
Также можно ограничить количество разделений:
In [20]: s2.str.split('_', expand=True, n=1)
Out[20]:
0 1
0 a b_c
1 c d_e
2 NaN None
3 f g_h
rsplit аналогичен split, за исключением того, что он работает в обратном направлении, т. е. от конца строки к началу строки:
In [21]: s2.str.rsplit('_', expand=True, n=1)
Out[21]:
0 1
0 a_b c
1 c_d e
2 NaN None
3 f_g h
Методы, такие как replace и findall также принимают регулярные выражения:
In [22]: s3 = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca',
....: '', np.nan, 'CABA', 'dog', 'cat'])
....:
In [23]: s3
Out[23]:
0 A
1 B
2 C
3 Aaba
4 Baca
5
6 NaN
7 CABA
8 dog
9 cat
dtype: object
In [24]: s3.str.replace('^.a|dog', 'XX-XX ', case=False)
Out[24]:
0 A
1 B
2 C
3 XX-XX ba
4 XX-XX ca
5
6 NaN
7 XX-XX BA
8 XX-XX
9 XX-XX t
dtype: object
Необходимо соблюдать осторожность, чтобы учитывать регулярные выражения! Например, следующий код вызовет проблемы из-за значения регулярного выражения $:
# Consider the following badly formatted financial data
In [25]: dollars = pd.Series(['12', '-$10', '$10,000'])
# This does what you'd naively expect:
In [26]: dollars.str.replace('$', '')
Out[26]:
0 12
1 -10
2 10,000
dtype: object
# But this doesn't:
In [27]: dollars.str.replace('-$', '-')
Out[27]:
0 12
1 -$10
2 $10,000
dtype: object
# We need to escape the special character (for >1 len patterns)
In [28]: dollars.str.replace(r'-\$', '-')
Out[28]:
0 12
1 -10
2 $10,000
dtype: object
Индексирование с помощью .str
Вы можете использовать обозначение [] для прямого индексирования по позициям. Если вы индексируете за пределами строки, результат будет NaN.
In [29]: s = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca', np.nan, ....: 'CABA', 'dog', 'cat']) ....: In [30]: s.str[0] Out[30]: 0 A 1 B 2 C 3 A 4 B 5 NaN 6 C 7 d 8 c dtype: object In [31]: s.str[1] Out[31]: 0 NaN 1 NaN 2 NaN 3 a 4 a 5 NaN 6 A 7 o 8 a dtype: object
Извлечение подстрок
Извлечение первого совпадения в каждом объекте (extract)
Новое в версии 0.13.0.
Предупреждение
В версии 0.18.0, extract получил аргумент expand. Когда expand=False, он возвращает Series, Index или DataFrame, в зависимости от объекта и шаблона регулярного выражения (то же поведение, что и до 0.18.0). Когда expand=True, он всегда возвращает DataFrame, что более согласуется и менее запутанно с точки зрения пользователя.
Метод extract принимает регулярное выражение с по крайней мере одной группой захвата.
Извлечение регулярного выражения с более чем одной группой возвращает DataFrame с одним столбцом на каждую группу.
In [32]: pd.Series(['a1', 'b2', 'c3']).str.extract('([ab])(\d)', expand=False)
Out[32]:
0 1
0 a 1
1 b 2
2 NaN NaN
Элементы, которые не совпадают, возвращают строку, заполненную NaN. Таким образом, серия необработанных строк может быть «преобразована» в серию или DataFrame с таким же индексом и очищенными или более полезными строками без необходимости get() для доступа к кортежам или re.match объектам. Тип данных результата всегда объект, даже если совпадение не найдено и результат содержит только NaN.
Имена групп, такие как
In [33]: pd.Series(['a1', 'b2', 'c3']).str.extract('(?P<letter>[ab])(?P<digit>\d)', expand=False)
Out[33]:
letter digit
0 a 1
1 b 2
2 NaN NaN
и необязательные группы, такие как
In [34]: pd.Series(['a1', 'b2', '3']).str.extract('([ab])?(\d)', expand=False)
Out[34]:
0 1
0 a 1
1 b 2
2 NaN 3
также могут быть использованы. Обратите внимание, что любые имена групп захвата в регулярном выражении будут использованы в качестве имен столбцов; в противном случае будут использованы номера групп захвата.
Извлечение регулярного выражения с одной группой возвращает DataFrame с одним столбцом, если expand=True.
In [35]: pd.Series(['a1', 'b2', 'c3']).str.extract('[ab](\d)', expand=True)
Out[35]:
0
0 1
1 2
2 NaN
Он возвращает Series, если expand=False.
In [36]: pd.Series(['a1', 'b2', 'c3']).str.extract('[ab](\d)', expand=False)
Out[36]:
0 1
1 2
2 NaN
dtype: object
Вызов Index с регулярным выражением ровно с одной группой захвата возвращает DataFrame с одним столбцом, если expand=True.
In [37]: s = pd.Series(["a1", "b2", "c3"], ["A11", "B22", "C33"])
In [38]: s
Out[38]:
A11 a1
B22 b2
C33 c3
dtype: object
In [39]: s.index.str.extract("(?P<letter>[a-zA-Z])", expand=True)
Out[39]:
letter
0 A
1 B
2 C
Он возвращает Series, если expand=False.
In [40]: s.index.str.extract("(?P<letter>[a-zA-Z])", expand=False)
Out[40]: Index([u'A', u'B', u'C'], dtype='object', name=u'letter')
Вызов Index с регулярным выражением более чем с одной группой захвата возвращает DataFrame, если expand=True.
In [41]: s.index.str.extract("(?P<letter>[a-zA-Z])([0-9]+)", expand=True)
Out[41]:
letter 1
0 A 11
1 B 22
2 C 33
Он вызывает ValueError если expand=False.
>>> s.index.str.extract("(?P<letter>[a-zA-Z])([0-9]+)", expand=False)
ValueError: only one regex group is supported with Index
В таблице ниже обобщено поведение extract(expand=False) (входной объект в первом столбце, количество групп в регулярном выражении в первой строке)
| 1 группа | >1 группа | |
| Индекс | Индекс | ValueError |
| Серия | Серия | DataFrame |
Извлечение всех совпадений в каждом объекте (extractall)
Новое в версии 0.18.0.
В отличие от extract (возвращает только первое совпадение),
In [42]: s = pd.Series(["a1a2", "b1", "c1"], index=["A", "B", "C"]) In [43]: s Out[43]: A a1a2 B b1 C c1 dtype: object In [44]: two_groups = '(?P<letter>[a-z])(?P<digit>[0-9])' In [45]: s.str.extract(two_groups, expand=True) Out[45]: letter digit A a 1 B b 1 C c 1
метод extractall возвращает каждое совпадение. Результат extractall всегда является DataFrame с MultiIndex по строкам. Последний уровень MultiIndex называется match и указывает порядок в исходной строке.
In [46]: s.str.extractall(two_groups)
Out[46]:
letter digit
match
A 0 a 1
1 a 2
B 0 b 1
C 0 c 1
Когда каждая строка в серии имеет ровно одно совпадение,
In [47]: s = pd.Series(['a3', 'b3', 'c2']) In [48]: s Out[48]: 0 a3 1 b3 2 c2 dtype: object
тогда extractall(pat).xs(0, level='match') дает тот же результат, что и extract(pat).
In [49]: extract_result = s.str.extract(two_groups, expand=True)
In [50]: extract_result
Out[50]:
letter digit
0 a 3
1 b 3
2 c 2
In [51]: extractall_result = s.str.extractall(two_groups)
In [52]: extractall_result
Out[52]:
letter digit
match
0 0 a 3
1 0 b 3
2 0 c 2
In [53]: extractall_result.xs(0, level="match")
Out[53]:
letter digit
0 a 3
1 b 3
2 c 2
Index также поддерживает .str.extractall. Он возвращает DataFrame, который имеет тот же результат, что и DataFrame с индексом по умолчанию (начинается с 0).
Новое в версии 0.19.0.
In [54]: pd.Index(["a1a2", "b1", "c1"]).str.extractall(two_groups)
Out[54]:
letter digit
match
0 0 a 1
1 a 2
1 0 b 1
2 0 c 1
In [55]: pd.Series(["a1a2", "b1", "c1"]).str.extractall(two_groups)
Out[55]:
letter digit
match
0 0 a 1
1 a 2
1 0 b 1
2 0 c 1
Проверка строк, которые соответствуют или содержат шаблон
Вы можете проверить, содержат ли элементы шаблон:
In [56]: pattern = r'[a-z][0-9]' In [57]: pd.Series(['1', '2', '3a', '3b', '03c']).str.contains(pattern) Out[57]: 0 False 1 False 2 False 3 False 4 False dtype: bool
или соответствуют шаблону:
In [58]: pd.Series(['1', '2', '3a', '3b', '03c']).str.match(pattern, as_indexer=True) Out[58]: 0 False 1 False 2 False 3 False 4 False dtype: bool
Различие между match и contains заключается в строгости: match опирается на строгую re.match, а contains опирается на re.search.
Предупреждение
В предыдущих версиях match предназначался для извлечения групп, возвращая не очень удобную серию кортежей. Новый метод extract (описанный в предыдущем разделе) теперь предпочтительнее.
Это старое, устаревшее поведение match всё ещё является значением по умолчанию. Как показано выше, воспользуйтесь новым поведением, установив as_indexer=True. В этом режиме, match аналогичен contains, возвращая булеву серию. Новое поведение станет поведением по умолчанию в будущей версии.
-
Methods like match, contains, startswith, and endswith take - дополнительный аргумент
na, чтобы пропущенные значения можно было рассматривать как True или False:
In [59]: s4 = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca', np.nan, 'CABA', 'dog', 'cat'])
In [60]: s4.str.contains('A', na=False)
Out[60]:
0 True
1 False
2 False
3 True
4 False
5 False
6 True
7 False
8 False
dtype: bool
Создание индикаторных переменных
Вы можете извлечь фиктивные переменные из столбцов строк. Например, если они разделены '|':
In [61]: s = pd.Series(['a', 'a|b', np.nan, 'a|c']) In [62]: s.str.get_dummies(sep='|') Out[62]: a b c 0 1 0 0 1 1 1 0 2 0 0 0 3 1 0 1
Строки Index также поддерживают get_dummies , которое возвращает MultiIndex.
Новое в версии 0.18.1.
In [63]: idx = pd.Index(['a', 'a|b', np.nan, 'a|c'])
In [64]: idx.str.get_dummies(sep='|')
Out[64]:
MultiIndex(levels=[[0, 1], [0, 1], [0, 1]],
labels=[[1, 1, 0, 1], [0, 1, 0, 0], [0, 0, 0, 1]],
names=[u'a', u'b', u'c'])
См. также get_dummies().
Краткое описание методов
| Метод | Описание |
|---|---|
cat() | Конкатенация строк |
split() | Разделение строк по разделителю |
rsplit() | Разделение строк по разделителю, начиная с конца строки |
get() | Индексирование каждого элемента (получение i-го элемента) |
join() | Объединение строк в каждом элементе Series с заданным разделителем |
get_dummies() | Разделение строк по разделителю с возвратом DataFrame фиктивных переменных |
contains() | Возвращает булевый массив, если каждая строка содержит шаблон/регулярное выражение |
replace() | Замена вхождений шаблона/регулярного выражения другой строкой |
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
|
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–2012, 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/0.19.2/text.html