python string pandas dataframe nltk

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:

  1. No está en minúscula sus datos.
  2. No te estás deshaciendo de los dígitos y la puntuación correctamente.
  3. No está devolviendo una cadena (debe unirse a la lista usando str.join y devolverla)
  4. 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.

  1. Su estructura de stopwords es una lista , y las comprobaciones en las listas son lentas . Lo primero que debe hacer es convertir eso en un set , haciendo que el tiempo not in control.

  2. Estás usando nltk.word_tokenize que es innecesariamente lento.

  3. 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.

  1. Minúscula con str.lower
  2. Eliminar el ruido utilizando str.replace
  3. Dividir palabras en celdas separadas usando str.split
  4. Aplique la eliminación de pd.DataFrame.isin pd.DataFrame.where usando pd.DataFrame.isin + pd.DataFrame.where
  5. 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".