Настройка
В целях демонстрации давайте рассмотрим этот фрейм данных.
df = pd.DataFrame({'text':['a..b?!??', '%hgh&12','abc123!!!', '$$$1234']})
df
text
0 a..b?!??
1 %hgh&12
2 abc123!!!
3 $$$1234
Ниже я перечислю альтернативы, одну за другой, в порядке увеличения производительности
str.replace
Этот параметр включен, чтобы установить метод по умолчанию в качестве эталона для сравнения других, более производительных решений.
В нем используется встроенная в * pandas функция str.replace
, которая выполняет регулярное выражениезамена на основе.
df['text'] = df['text'].str.replace(r'[^\w\s]+', '')
df
text
0 ab
1 hgh12
2 abc123
3 1234
Это очень легко закодировать, и его можно легко прочитать, но медленно.
regex.sub
Это включает использование функции sub
из библиотеки re
.Предварительно скомпилируйте шаблон регулярного выражения для производительности и вызовите regex.sub
внутри списка.Преобразуйте df['text']
в список заранее, если вы можете сэкономить память, из этого вы получите небольшой прирост производительности.
import re
p = re.compile(r'[^\w\s]+')
df['text'] = [p.sub('', x) for x in df['text'].tolist()]
df
text
0 ab
1 hgh12
2 abc123
3 1234
Примечание: Если ваши данные имеют значения NaN, это (как и следующий метод ниже) не будет работать как есть.См. Раздел « Другие вопросы ».
str.translate
Функция Python str.translate
реализована на C и поэтому очень быстрая .
Как это работает:
- Сначала объедините все свои строки, чтобы сформировать одну огромную строку, используя один (или более) символ разделитель что вы выбираете.Вы должны использовать символ / подстроку, которая, как вы можете гарантировать, не будет входить в ваши данные.
- Выполнить
str.translate
для большой строки, удаляя пунктуацию (исключая разделитель из шага 1). - Разделить строку на разделитель, который использовался для объединения на шаге 1. Результирующий список должен иметь ту же длину, что и ваш исходный столбец.
ЗдесьВ этом примере мы рассмотрим разделитель труб |
.Если ваши данные содержат канал, то вы должны выбрать другой разделитель.
import string
punct = '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{}~' # `|` is not present here
transtab = str.maketrans(dict.fromkeys(punct, ''))
df['text'] = '|'.join(df['text'].tolist()).translate(transtab).split('|')
df
text
0 ab
1 hgh12
2 abc123
3 1234
Производительность
str.translate
покажет лучшие результаты.Обратите внимание, что на приведенном ниже графике представлен еще один вариант Series.str.translate
из ответа MaxU .
(Интересно, что я повторяю это во второй раз, и результаты немного отличаются от предыдущих. Во время второго запускакажется, re.sub
выиграл у str.translate
для действительно небольших объемов данных.) ![enter image description here](https://i.stack.imgur.com/Eb0XZ.png)
Существует риск, связанный с использованием translate
(в частности,проблема автоматизации процесса принятия решения о том, какой разделитель использовать, нетривиален), но компромиссы стоят риска.
Другие соображения
Обработка NaN с помощью методов понимания списка; Обратите внимание, что этот метод (и следующий) будет работать только до тех пор, пока ваши данные не имеют NaN.При обработке NaN вам нужно будет определить индексы ненулевых значений и заменить их только.Попробуйте что-то вроде этого:
df = pd.DataFrame({'text': [
'a..b?!??', np.nan, '%hgh&12','abc123!!!', '$$$1234', np.nan]})
idx = np.flatnonzero(df['text'].notna())
col_idx = df.columns.get_loc('text')
df.iloc[idx,col_idx] = [
p.sub('', x) for x in df.iloc[idx,col_idx].tolist()]
df
text
0 ab
1 NaN
2 hgh12
3 abc123
4 1234
5 NaN
Работа с фреймами данных; Если вы имеете дело с фреймами данных, где каждый столбец требует замены, процедура проста:
v = pd.Series(df.values.ravel())
df[:] = translate(v).values.reshape(df.shape)
Или,
v = df.stack()
v[:] = translate(v)
df = v.unstack()
Обратите внимание, что функция translate
определена ниже в коде сравнительного анализа.
Каждое решение имеет компромиссы, поэтому решите, какое решение лучшеваши потребности будут зависеть от того, чем вы готовы пожертвовать.Два очень распространенных соображения - это производительность (которую мы уже видели) и использование памяти.str.translate
- решение, требующее памяти, поэтому используйте его с осторожностью.
Еще одним соображением является сложность вашего регулярного выражения.Иногда вы можете удалить все, что не является буквенно-цифровым или пробельным символом.В других случаях вам нужно будет сохранить некоторые символы, такие как дефисы, двоеточия и терминаторы предложений [.!?]
.Их указание явно усложнит ваше регулярное выражение, что, в свою очередь, может повлиять на производительность этих решений.Обязательно протестируйте эти решения на своих данных, прежде чем решать, что использовать.
Наконец, с помощью этого решения будут удалены символы Юникода.Вы можете настроить свое регулярное выражение (если используете решение на основе регулярных выражений) или просто использовать str.translate
в противном случае.
Чтобы получить больше производительности (для большего N), взгляните на этот ответ Пол Панцер .
Приложение
Функции
def pd_replace(df):
return df.assign(text=df['text'].str.replace(r'[^\w\s]+', ''))
def re_sub(df):
p = re.compile(r'[^\w\s]+')
return df.assign(text=[p.sub('', x) for x in df['text'].tolist()])
def translate(df):
punct = string.punctuation.replace('|', '')
transtab = str.maketrans(dict.fromkeys(punct, ''))
return df.assign(
text='|'.join(df['text'].tolist()).translate(transtab).split('|')
)
# MaxU's version (https://stackoverflow.com/a/50444659/4909087)
def pd_translate(df):
punct = string.punctuation.replace('|', '')
transtab = str.maketrans(dict.fromkeys(punct, ''))
return df.assign(text=df['text'].str.translate(transtab))
Код оценки производительности
from timeit import timeit
import pandas as pd
import matplotlib.pyplot as plt
res = pd.DataFrame(
index=['pd_replace', 're_sub', 'translate', 'pd_translate'],
columns=[10, 50, 100, 500, 1000, 5000, 10000, 50000],
dtype=float
)
for f in res.index:
for c in res.columns:
l = ['a..b?!??', '%hgh&12','abc123!!!', '$$$1234'] * c
df = pd.DataFrame({'text' : l})
stmt = '{}(df)'.format(f)
setp = 'from __main__ import df, {}'.format(f)
res.at[f, c] = timeit(stmt, setp, number=30)
ax = res.div(res.min()).T.plot(loglog=True)
ax.set_xlabel("N");
ax.set_ylabel("time (relative)");
plt.show()