Предположим, у меня есть фрейм данных, в котором я хочу заменить подстроку, не являющуюся регулярным выражением, состоящую только из символов (т. е. a-z, A-Z) и/или цифр (т. е. 0-9) через pd.Series.str.replace. В документации указано, что эта функция эквивалентна str.replace
или re.sub()
, в зависимости от аргумента regex
(по умолчанию False).
Помимо, скорее всего, излишеств, есть ли какие-либо недостатки, которые следует учитывать, если функция вызывалась с regex=True
для замен, не связанных с регулярными выражениями (например, производительность)? Если да, то какие? Конечно, я не предлагаю использовать функцию таким образом.
Пример: замените «Слон» в приведенном ниже фрейме данных.
import pandas as pd
data = {'Animal_Name': ['Elephant African', 'Elephant Asian', 'Elephant Indian', 'Elephant Borneo', 'Elephant Sumatran']}
df = pd.DataFrame(data)
df = df['Animal_Name'].str.replace('Elephant', 'Tiger', regex=True)
Функция Pandas str.replace()
использует re.sub()
под капотом, когда установлен флаг regex=True
. В противном случае для regex=False
используется базовая строковая функция Python replace()
.
Реализации re.sub
и replace
не одинаковы. В общем, мы ожидаем более высоких накладных расходов при замене подстроки с помощью re.sub
по сравнению с replace
. Одним из таких накладных расходов может быть то, что при использовании re.sub
(str.replace
с regex=True)
первый параметр сначала необходимо проанализировать как регулярное выражение, прежде чем его можно будет использовать.
В общем, вам следует избегать использования регулярных выражений, если они вам не нужны, поэтому использование regex=False
— лучший вариант для повышения производительности, когда вам не нужны регулярные выражения.
@mozway: Любопытно, какие еще факторы влияют на выбор функции?
@mozway Суть моего ответа заключалась в вызове механизма регулярных выражений вместо простой замены. Если под капотом Pandas становится умнее и игнорирует собственный флаг regex
, то это в любом случае выходит из-под нашего контроля.
@silence_of_the_lambdas передает флаги и использует вызываемый объект в качестве замены / @Tim да, вы можете рассматривать это как «умную» функцию. Series.replace
еще более «продвинутый».
Использование регулярных выражений с простыми словами, как правило, нормально (кроме проблем с эффективностью), однако могут возникнуть проблемы, если у вас есть специальные символы. Эту проблему часто упускают из виду, и я видел многих людей, которые не понимали, почему их str.replace
не удалось.
Панды даже изменили стандартное регулярное выражение=True на regex=False , и первоначальная причина этого ( #GH24804) заключалась в том, что str.replace('.', '')
удалит все символы, что ожидается, если вы знаете регулярное выражение, но не совсем, если ты нет.
Например, давайте попробуем заменить 1.5
на 2.3
, а валюту $
на £
:
df = pd.DataFrame({'input': ['This item costs $1.5.', 'We need 125 units.']})
df['out1'] = df['input'].str.replace('1.5', '2.3', regex=False)
df['out1_regex'] = df['input'].str.replace('1.5', '2.3', regex=True)
df['out2'] = df['input'].str.replace('$', '£', regex=False)
df['out2_regex'] = df['input'].str.replace('$', '£', regex=True)
Выход:
input out1 out1_regex \
0 This item costs $1.5. This item costs $2.3. This item costs $2.3.
1 We need 125 units. We need 125 units. We need 2.3 units.
out2 out2_regex
0 This item costs £1.5. This item costs $1.5.£
1 We need 125 units. We need 125 units.£
Поскольку .
и $
имеют особое значение в регулярном выражении, их нельзя использовать как есть, и их следует экранировать (1\.5
/\$
), что можно сделать программно с помощью re.escape.
чистая замена Python будет использоваться, если:
regex=False
pat
— это строка (передача скомпилированного регулярного выражения с regex=False
вызовет ValueError
)case
нет False
repl
не вызываетсяВо всех остальных случаях будет использоваться re.sub.
Код, который это делает: core/strings/object_array.py:
def _str_replace(
self,
pat: str | re.Pattern,
repl: str | Callable,
n: int = -1,
case: bool = True,
flags: int = 0,
regex: bool = True,
):
if case is False:
# add case flag, if provided
flags |= re.IGNORECASE
if regex or flags or callable(repl):
if not isinstance(pat, re.Pattern):
if regex is False:
pat = re.escape(pat)
pat = re.compile(pat, flags=flags)
n = n if n >= 0 else 0
f = lambda x: pat.sub(repl=repl, string=x, count=n)
else:
f = lambda x: x.replace(pat, repl, n)
Учитывая шаблон без специальных символов, regex=True
примерно в 6 раз медленнее, чем regex=False
в линейном режиме:
Конечно, это правда, поэтому я написал, что подстрока должна содержать только символы (a-z, A-Z) и цифры (0-9) :)
@silence_of_the_lambdas Я пропустил этот момент, но, по моему мнению, это проблема №1 с параметром regex
. Как я уже упоминал в комментарии к другому ответу, regex=False
не только определяет, будет ли использоваться регулярное выражение. Если вы используете regex=False, case=False
, будет использовано регулярное выражение.
@silence_of_the_lambdas, чтобы ответить на ваш другой комментарий, я добавил более подробную информацию о том, как осуществляется выбор использования механизма регулярных выражений.
re.sub
существенно медленнее, чем str.replace
, даже для таких простых строк. Вы можете проверить это с помощью модуля timeit:
from timeit import timeit
setup = "import pandas as pd; series = pd.Series(['Elephant']).repeat(10000)"
print(timeit(setup=setup, stmt = "series.str.replace('Elephant', 'Tiger', regex=False)",
number=10000))
print(timeit(setup=setup, stmt = "series.str.replace('Elephant', 'Tiger', regex=True)",
number=10000))
Для меня результаты были заметно другими.
11.037849086000051
18.286654022999983
Это не совсем так. Да,
str.replace
использует как Pythonstr.replace
, так иre.sub
, но выбор использования одного из них не делается исключительно на основе ключевого словаregex
. Например,pd.Series(['ABC']).str.replace('a', 'x', regex=False, case=False)
будет использоватьre.sub
под капотом.