linux text analysis frequency word-frequency

linux - Determinación de la frecuencia de palabras de términos específicos



text analysis (7)

Soy un estudiante de ciencias no informático que realiza una tesis de historia que implica determinar la frecuencia de términos específicos en varios textos y luego trazar estas frecuencias a lo largo del tiempo para determinar cambios y tendencias. Mientras que he descubierto cómo determinar las frecuencias de las palabras para un archivo de texto dado, estoy tratando con un (relativamente, para mí) gran cantidad de archivos (> 100) y, por consistencia, me gustaría limitar las palabras incluidas en el recuento de frecuencias a un conjunto específico de términos (algo así como lo opuesto a una "lista de detención")

Esto debe mantenerse muy simple. Al final, todo lo que necesito es las frecuencias para las palabras específicas para cada archivo de texto que proceso, preferiblemente en formato de hoja de cálculo (pestaña de archivo delineado) para poder crear gráficos y visualizaciones usando esos datos.

Utilizo Linux día a día, me siento cómodo usando la línea de comandos y me encantaría una solución de código abierto (o algo que podría ejecutar con WINE). Sin embargo, eso no es un requisito:

Veo dos formas de resolver este problema:

  1. Encuentre una manera de eliminar todas las palabras en un archivo de texto EXCEPTO para la lista predefinida y luego haga el conteo de frecuencia desde allí, o:
  2. Encuentre una forma de hacer un recuento de frecuencia usando solo los términos de la lista predefinida.

¿Algunas ideas?


Hago este tipo de cosas con un script como el siguiente (en sintaxis bash):

for file in *.txt do sed -r ''s/([^ ]+) +//1/n/g'' "$file" / | grep -F -f ''go-words'' / | sort | uniq -c > "${file}.frq" done

Puede ajustar la expresión regular que usa para delimitar palabras individuales; en el ejemplo, simplemente trato el espacio en blanco como el delimitador. El argumento -f para grep es un archivo que contiene sus palabras de interés, una por línea.


Haría un "grep" en los archivos para encontrar todas las líneas que contienen tus palabras clave. (Grep -f se puede usar para especificar un archivo de entrada de palabras para buscar (canalizar la salida de grep a un archivo). Esto le dará una lista de líneas que contienen instancias de sus palabras. Luego, haga un "sed" para reemplace sus separadores de palabras (espacios más probables) con líneas nuevas, para darle un archivo de palabras separadas (una palabra por línea). Ahora ejecute grep nuevamente, con su misma lista de palabras, excepto que esta vez especifique -c (para obtener un conteo de las líneas con las palabras especificadas, es decir, un recuento de las ocurrencias de la palabra en el archivo original).

El método de dos pasos simplemente hace la vida más fácil para "sed"; el primer grep debería eliminar muchas líneas.

Puede hacer esto en comandos básicos de línea de comandos de Linux. Una vez que se sienta cómodo con el proceso, puede ponerlo todo en el script de shell con bastante facilidad.


Primero, familiarícese con el análisis léxico y cómo escribir una especificación de generador de escáner. Lea las presentaciones para usar herramientas como YACC, Lex, Bison o mi favorito personal, JFlex. Aquí defines lo que constituye un token. Aquí es donde aprendes acerca de cómo crear un tokenizador.

Luego tienes lo que se llama una lista de semillas. El opuesto de la lista de detención generalmente se conoce como la lista de inicio o el léxico limitado. Lexicon también sería una buena cosa para aprender. Parte de la aplicación necesita cargar la lista de inicio en la memoria para poder consultarla rápidamente. La forma típica de almacenar es un archivo con una palabra por línea, luego lea esto al inicio de la aplicación, una vez, en algo así como un mapa. Es posible que desee aprender sobre el concepto de hash.

Desde aquí, debe pensar en el algoritmo básico y las estructuras de datos necesarias para almacenar el resultado. Una distribución se representa fácilmente como una matriz dispersa bidimensional. Aprende los conceptos básicos de una matriz dispersa. No necesitas 6 meses de álgebra lineal para entender lo que hace.

Debido a que está trabajando con archivos más grandes, recomendaría un enfoque basado en flujos. No lea todo el archivo en la memoria. Léelo como una secuencia en el tokenizer que produce una secuencia de tokens.

En la siguiente parte del algoritmo, piense cómo transformar la lista de tokens en una lista que contenga solo las palabras que desee. Si lo piensas bien, la lista está en la memoria y puede ser muy grande, por lo que es mejor filtrar las palabras que no comienzan al principio. Entonces, en el punto crítico donde obtienes un token nuevo del tokenizer y antes de agregarlo a la lista de tokens, haz una búsqueda en la lista de palabras in-memory-start para ver si la palabra es una palabra de inicio. De ser así, guárdelo en la lista de tokens de salida. De lo contrario, ignórelo y pase al siguiente token hasta que se lea todo el archivo.

Ahora tiene una lista de tokens solo de interés. La cuestión es que no está mirando otras métricas de indexación como posición, caso y contexto. Por lo tanto, realmente no necesita una lista de todos los tokens. Realmente solo quieres una matriz dispersa de tokens distintos con conteos asociados.

Entonces, primero crea una matriz dispersa vacía. Luego, piense en la inserción del token recién encontrado durante el análisis. Cuando ocurra, incremente su conteo si está en la lista o inserte un nuevo token con un conteo de 1. Esta vez, al final del análisis del archivo, tiene una lista de tokens distintos, cada uno con una frecuencia de al menos 1.

Esa lista ahora está en-mem y puedes hacer lo que quieras. Volcarlo en un archivo CSV sería un proceso trivial de iterar sobre las entradas y escribir cada entrada por línea con su recuento.

Para el caso, eche un vistazo al producto no comercial llamado "GATE" o un producto comercial como TextAnalyst o productos listados en http://textanalysis.info


Supongo que los nuevos archivos se presentan con el tiempo, y así es como cambian las cosas.

Creo que su mejor opción sería ir con algo como su opción 2. No tiene mucho sentido preprocesar los archivos, si todo lo que quiere hacer es contar las apariciones de palabras clave. Revisaría cada archivo una vez, contando cada vez que aparezca una palabra en tu lista. Personalmente lo haría en Ruby, pero un lenguaje como Perl o Python también haría esta tarea bastante sencilla. Por ejemplo, podría usar una matriz asociativa con las palabras clave como claves, y un recuento de las ocurrencias como valores. (Pero esto podría ser demasiado simplista si necesita almacenar más información sobre las ocurrencias).

No estoy seguro de si desea almacenar información por archivo, o acerca de todo el conjunto de datos? Supongo que no sería muy difícil de incorporar.

No estoy seguro de qué hacer con los datos una vez que los tenga, exportarlos a una hoja de cálculo estaría bien, si eso le proporciona lo que necesita. O tal vez le resulte más fácil a largo plazo escribir un código extra que muestre los datos muy bien para usted. Depende de lo que quieras hacer con los datos (por ejemplo, si quieres producir solo unos gráficos al final del ejercicio y ponerlos en un informe, entonces exportar a CSV probablemente tendría más sentido, mientras que si quieres generar un nuevo conjunto de datos todos los días durante un año; luego, construir la herramienta para hacerlo automáticamente es casi seguramente la mejor idea.

Editar: Acabo de descubrir que, dado que estás estudiando historia, lo más probable es que tus documentos no cambien con el tiempo, sino que reflejen un conjunto de cambios que ya ocurrieron. Perdón por malentendido. De todos modos, creo que casi todo lo que dije antes todavía se aplica, pero supongo que te inclinarás por exportar a CSV o por lo que prefieres, en lugar de una pantalla automatizada.

Suena como un proyecto divertido: ¡buena suerte!

Ben


Me gustaría ir con la segunda idea. Aquí hay un programa simple de Perl que leerá una lista de palabras del primer archivo proporcionado e imprimirá un conteo de cada palabra en la lista del segundo archivo provisto en formato separado por tabuladores. La lista de palabras en el primer archivo debe proporcionarse una por línea.

#!/usr/bin/perl use strict; use warnings; my $word_list_file = shift; my $process_file = shift; my %word_counts; # Open the word list file, read a line at a time, remove the newline, # add it to the hash of words to track, initialize the count to zero open(WORDS, $word_list_file) or die "Failed to open list file: $!/n"; while (<WORDS>) { chomp; # Store words in lowercase for case-insensitive match $word_counts{lc($_)} = 0; } close(WORDS); # Read the text file one line at a time, break the text up into words # based on word boundaries (/b), iterate through each word incrementing # the word count in the word hash if the word is in the hash open(FILE, $process_file) or die "Failed to open process file: $!/n"; while (<FILE>) { chomp; while ( /-$/ ) { # If the line ends in a hyphen, remove the hyphen and # continue reading lines until we find one that doesn''t chop; my $next_line = <FILE>; defined($next_line) ? $_ .= $next_line : last; } my @words = split //b/, lc; # Split the lower-cased version of the string foreach my $word (@words) { $word_counts{$word}++ if exists $word_counts{$word}; } } close(FILE); # Print each word in the hash in alphabetical order along with the # number of time encountered, delimited by tabs (/t) foreach my $word (sort keys %word_counts) { print "$word/t$word_counts{$word}/n" }

Si el archivo words.txt contiene:

linux frequencies science words

Y el archivo text.txt contiene el texto de su publicación, el siguiente comando:

perl analyze.pl words.txt text.txt

se imprimirá:

frequencies 3 linux 1 science 1 words 3

Tenga en cuenta que romper los límites de las palabras con / b puede no funcionar de la manera que desee en todos los casos, por ejemplo, si sus archivos de texto contienen palabras con guiones en líneas, necesitará hacer algo un poco más inteligente para hacerlas coincidir. En este caso, podría verificar si el último carácter de una línea es un guión y, si lo es, simplemente elimine el guión y lea otra línea antes de dividir la línea en palabras.

Editar : versión actualizada que maneja las palabras sin distinción de mayúsculas y minúsculas y maneja las palabras con guiones en las líneas.

Tenga en cuenta que si hay palabras con guiones, algunas de las cuales están rotas en líneas y otras que no, estas no las encontrarán todas porque solo eliminaron guiones al final de una línea. En este caso, es posible que desee eliminar todos los guiones y hacer coincidir las palabras una vez que se eliminen los guiones. Puede hacer esto simplemente agregando la siguiente línea justo antes de la función de división:

s/-//g;


Otro intento de Perl:

#!/usr/bin/perl -w use strict; use File::Slurp; use Tie::File; # Usage: # # $ perl WordCount.pl <Files> # # Example: # # $ perl WordCount.pl *.text # # Counts words in all files given as arguments. # The words are taken from the file "WordList". # The output is appended to the file "WordCount.out" in the format implied in the # following example: # # File,Word1,Word2,Word3,... # File1,0,5,3,... # File2,6,3,4,... # . # . # . # ### Configuration my $CaseSensitive = 1; # 0 or 1 my $OutputSeparator = ","; # another option might be "/t" (TAB) my $RemoveHyphenation = 0; # 0 or 1. Careful, may be too greedy. ### my @WordList = read_file("WordList"); chomp @WordList; tie (my @Output, ''Tie::File'', "WordCount.out"); push (@Output, join ($OutputSeparator, "File", @WordList)); for my $InFile (@ARGV) { my $Text = read_file($InFile); if ($RemoveHyphenation) { $Text =~ s/-/n//g; }; my %Count; for my $Word (@WordList) { if ($CaseSensitive) { $Count{$Word} = ($Text =~ s/(/b$Word/b)/$1/g); } else { $Count{$Word} = ($Text =~ s/(/b$Word/b)/$1/gi); }; }; my $OutputLine = "$InFile"; for my $Word (@WordList) { if ($Count{$Word}) { $OutputLine .= $OutputSeparator . $Count{$Word}; } else { $OutputLine .= $OutputSeparator . "0"; }; }; push (@Output, $OutputLine); }; untie @Output;

Cuando coloco su pregunta en el archivo wc-test y la respuesta de Robert Gamble en wc-ans-test , el archivo de salida se ve así:

File,linux,frequencies,science,words wc-ans-test,2,2,2,12 wc-test,1,3,1,3

Este es un archivo de valores separados por comas (csv) (pero puede cambiar el separador en el script). Debe ser legible para cualquier aplicación de hoja de cálculo. Para graficar gráficos, recomendaría gnuplot , que es totalmente programable, para que pueda modificar su salida independientemente de los datos de entrada.


Al diablo con grandes guiones. Si estás dispuesto a captar todas las palabras, prueba este shell fu:

cat *.txt | tr A-Z a-z | tr -cs a-z ''/n'' | sort | uniq -c | sort -rn | sed ''/[0-9] /&, /''

Eso (probado) le dará una lista de todas las palabras ordenadas por frecuencia en formato CSV, fácilmente importadas por su hoja de cálculo favorita. Si debe tener las palabras de parada, intente insertar grep -w -F -f stopwords.txt en la tubería (no probado).