La puntuación y los valores en minúscula y minúscula 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 –
str.join
y devolverla) 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 de parada.
Su estructura de stopwords
es una lista , y in
verificaciones las listas son lentas . Lo primero que se debe hacer es convertir eso en un set
, haciendo que el tiempo de not in
verificación sea constante.
Estás usando nltk.word_tokenize
que es innecesariamente lento.
Por último, no siempre debe confiar en la 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, crea tus 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 multilínea. 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 clave, o minúsculas) –
def preprocess(x): x = re.sub('[^az\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 aplicaría entonces 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.
str.lower
str.replace
str.split
palabras en celdas separadas usando str.split
pd.DataFrame.isin
pd.DataFrame.where
utilizando pd.DataFrame.isin
+ pd.DataFrame.where
agg
. v = v.str.lower().str.replace('[^az\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('[^az\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 descabellada para ti:
for _c in c: df[_c] = preprocess2(df[_c])
Veamos la diferencia entre nuestra versión no loopy y la original –
s = pd.concat([s] * 100000, ignore_index=True) s.size 300000
Primero, un chequeo de cordura.
preprocess2(s).eq(s.apply(preprocess)).all() True
Ahora vienen los tiempos.
%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, ya que la apply
rara vez es más rápida que una solución no loca. Pero esto tiene sentido en este caso porque hemos optimizado el preprocess
bastante, y las operaciones de cadena en los pandas rara vez se vectorizan (por lo general lo son, pero la ganancia de rendimiento no es tanto como se esperaría).
Veamos si podemos hacerlo mejor, omitiendo la apply
, usando np.vectorize
preprocess3 = np.vectorize(preprocess) %timeit preprocess3(s) 1 loop, best of 3: 9.65 s per loop
Lo que es idéntico de apply
pero resulta ser un poco más rápido debido a la reducción de la sobrecarga alrededor del bucle “oculto”.