python - Procesamiento de texto basado en NLTK con pandas
string dataframe (1)
La puntuación y los números, minúsculas, no funcionan mientras se usa nltk.
Mi código
stopwords=nltk.corpus.stopwords.words(''english'')+ list(string.punctuation)
user_defined_stop_words=[''st'',''rd'',''hong'',''kong'']
new_stop_words=stopwords+user_defined_stop_words
def preprocess(text):
return [word for word in word_tokenize(text) if word.lower() not in new_stop_words and not word.isdigit()]
miss_data[''Clean_addr''] = miss_data[''Adj_Addr''].apply(preprocess)
Entrada de muestra
23FLOOR 9 DES VOEUX RD WEST HONG KONG
PAG CONSULTING FLAT 15 AIA CENTRAL 1 CONNAUGHT RD CENTRAL
C/O CITY LOST STUDIOS AND FLAT 4F 13-15 HILLIER ST SHEUNG HONG KONG
Rendimiento esperado
floor des voeux west
pag consulting flat aia central connaught central
co city lost studios flat f hillier sheung
Su función es lenta y está incompleta. Primero, con los problemas:
- No está en minúscula sus datos.
- No te estás deshaciendo de los dígitos y la puntuación correctamente.
-
No está devolviendo una cadena (debe unirse a la lista usando
str.join
y devolverla) -
Además, una comprensión de la lista con el procesamiento de texto es una forma excelente de introducir problemas de legibilidad, sin mencionar posibles redundancias (puede llamar a una función varias veces, para cada condición
if
aparece).
A continuación, hay un par de ineficiencias evidentes con su función, especialmente con el código de eliminación de palabras vacías.
-
Su estructura de
stopwords
es una lista , y las comprobaciones en las listas son lentas . Lo primero que debe hacer es convertir eso en unset
, haciendo que el tiemponot in
control. -
Estás usando
nltk.word_tokenize
que es innecesariamente lento. -
Por último, no siempre debe confiar en
apply
, incluso si está trabajando con NLTK donde rara vez hay una solución vectorizada disponible. Casi siempre hay otras formas de hacer exactamente lo mismo. A menudo, incluso un bucle de Python es más rápido. Pero esto no está escrito en piedra.
Primero, cree sus
stopwords
mejoradas como un
conjunto
:
user_defined_stop_words = [''st'',''rd'',''hong'',''kong'']
i = nltk.corpus.stopwords.words(''english'')
j = list(string.punctuation) + user_defined_stop_words
stopwords = set(i).union(j)
La siguiente solución es deshacerse de la comprensión de la lista y convertirla en una función de varias líneas. Esto hace que las cosas sean mucho más fáciles de trabajar. Cada línea de su función debe estar dedicada a resolver una tarea en particular (por ejemplo, deshacerse de los dígitos / puntuación, o deshacerse de las palabras vacías, o minúsculas):
def preprocess(x):
x = re.sub(''[^a-z/s]'', '''', x.lower()) # get rid of noise
x = [w for w in x.split() if w not in set(stopwords)] # remove stopwords
return '' ''.join(x) # join the list
Como ejemplo.
Esto se
apply
a su columna:
df[''Clean_addr''] = df[''Adj_Addr''].apply(preprocess)
Como alternativa, aquí hay un enfoque que no se basa en
apply
.
Esto debería funcionar bien para oraciones pequeñas.
Cargue sus datos en una serie:
v = miss_data[''Adj_Addr'']
v
0 23FLOOR 9 DES VOEUX RD WEST HONG KONG
1 PAG CONSULTING FLAT 15 AIA CENTRAL 1 CONNAUGHT...
2 C/O CITY LOST STUDIOS AND FLAT 4F 13-15 HILLIE...
Name: Adj_Addr, dtype: object
Ahora viene el trabajo pesado.
-
Minúscula con
str.lower
-
Eliminar el ruido utilizando
str.replace
-
Dividir palabras en celdas separadas usando
str.split
-
Aplique la eliminación de
pd.DataFrame.isin
pd.DataFrame.where
usandopd.DataFrame.isin
+pd.DataFrame.where
-
Finalmente, únete al marco de datos usando
agg
.
v = v.str.lower().str.replace(''[^a-z/s]'', '''').str.split(expand=True)
v.where(~v.isin(stopwords) & v.notnull(), '''')/
.agg('' ''.join, axis=1)/
.str.replace(''/s+'', '' '')/
.str.strip()
0 floor des voeux west
1 pag consulting flat aia central connaught central
2 co city lost studios flat f hillier sheung
dtype: object
Para usar esto en varias columnas, coloque este código en una función
preprocess2
y llame a
apply
-
def preprocess2(v):
v = v.str.lower().str.replace(''[^a-z/s]'', '''').str.split(expand=True)
return v.where(~v.isin(stopwords) & v.notnull(), '''')/
.agg('' ''.join, axis=1)/
.str.replace(''/s+'', '' '')/
.str.strip()
c = [''Col1'', ''Col2'', ...] # columns to operate
df[c] = df[c].apply(preprocess2, axis=0)
Aún necesitará una llamada de
apply
, pero con un pequeño número de columnas, no debería escalar demasiado.
Si no te gusta
apply
, entonces aquí hay una variante para ti:
for _c in c:
df[_c] = preprocess2(df[_c])
Veamos la diferencia entre nuestra versión sin bucles y la original.
s = pd.concat([s] * 100000, ignore_index=True)
s.size
300000
Primero, un control de cordura -
preprocess2(s).eq(s.apply(preprocess)).all()
True
Ahora vienen los horarios.
%timeit preprocess2(s)
1 loop, best of 3: 13.8 s per loop
%timeit s.apply(preprocess)
1 loop, best of 3: 9.72 s per loop
Esto es sorprendente, porque la
apply
rara vez es más rápida que una solución no en bucle.
Pero esto tiene sentido en este caso porque hemos optimizado bastante el
preprocess
, y las operaciones de cadena en pandas rara vez se vectorizan (generalmente lo son, pero la ganancia de rendimiento no es tanto como cabría esperar).
Veamos si podemos hacerlo mejor, evitando la
apply
, usando
np.vectorize
preprocess3 = np.vectorize(preprocess)
%timeit preprocess3(s)
1 loop, best of 3: 9.65 s per loop
Lo cual es idéntico a
apply
pero resulta ser un poco más rápido debido a la sobrecarga reducida alrededor del bucle "oculto".