2008-11-24 9 views
14

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 palabras para un archivo de texto dado, estoy tratando con un (relativamente, para mí) gran cantidad de archivos (> 100) y por consistencia, quisiera limitar las palabras incluidas en el conteo de frecuencia a un conjunto específico de términos (algo así como lo opuesto a una "lista de detención")Determinar la frecuencia de palabra de términos específicos

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.

Uso 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 pueda ejecutar con WINE). Eso no es un requisito, sin embargo:

Veo dos maneras de resolver este problema:

  1. encontrar una manera de tira a cabo todas las palabras en un archivo de texto a excepción de la lista predefinida y luego hacer lo recuento de frecuencia desde allí, o:
  2. Encuentra la forma de hacer un recuento de frecuencia usando solo los términos de la lista predefinida.

¿Alguna idea?

+0

Tengo curiosidad, ¿cuál es la lista de palabras? (y el tipo de textos) –

+0

Artículos. La lista de términos son palabras clave del campo. – fdsayre

Respuesta

7

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 contiene words.txt:

linux 
frequencies 
science 
words 

Y el text.txt archivo contiene el texto de su mensaje, el siguiente comando:

perl analyze.pl words.txt text.txt 

imprimirá:

frequencies  3 
linux 1 
science 1 
words 3 

Tenga en cuenta que la ruptura en los límites de 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 que coincidan. 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á todas porque solo eliminó 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 añadiendo la siguiente línea justo antes de la función de división:

s/-//g; 
+0

Gracias por la edición. Creo que primero tendré que limpiar el texto, quitar la separación silábica, las mayúsculas, etc. para que los datos sean más consistentes antes de hacer el recuento de frecuencias. Una última pregunta: ¿hay alguna forma de enviar el comando a un archivo delineado por pestañas? si no es fácil de cortar y pegar. GRACIAS. – fdsayre

+0

Editaré el programa para manejar palabras que no distinguen entre mayúsculas y minúsculas y manejar correctamente las palabras con guiones. Puede redirigir la salida a un archivo como este: analyze.pl file1 file2> file3. –

+0

pregunta final: ¿Hay alguna manera simple de tratar con términos de dos palabras? ES DECIR. Si el archivo de definiciones de términos contiene la frase "curva normal", se contará. Estoy muy impresionado de que este script devuelva un 0 cuando el término no está presente en el texto, ya que esto facilitará la coherencia de los datos. – fdsayre

1

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

Creo que su mejor opción sería elegir algo como su opción 2. No tiene mucho sentido preprocesar los archivos, si todo lo que desea hacer es contar las apariciones de las 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 sobre 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 y luego construir una herramienta para hacer eso automáticamente es casi seguro la mejor idea.

Editar: Acabo de descubrir que, como usted está estudiando historia, es probable que sus documentos no sean cambian con el tiempo, pero reflejan un conjunto de cambios que ya ocurrieron. Perdón por malentendido. De todos modos, creo que prácticamente todo lo que dije arriba todavía se aplica, pero supongo que se inclinarán a exportar a CSV o lo que sea en lugar de una pantalla automatizada.

Suena como una proyecto divertido - ¡buena suerte!

Ben

2

En primer lugar familiarizarse con el análisis léxico y cómo escribir una especificación 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.

A continuación tiene lo que se llama una lista de semillas. El opuesto de la lista de detención generalmente se conoce como lista de inicio o 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.

A partir de 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.

Como está trabajando con archivos más grandes, recomendaría un enfoque basado en secuencias. 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.

Por lo tanto, primero cree 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 la memoria y puede hacer lo que quiera. 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.

Por lo demás, echar un vistazo al producto no comercial llamado "puerta" o un producto comercial como TextAnalyst o los productos incluidos en http://textanalysis.info

+0

Olvidó información vital sobre la descomposición canónica de caracteres Unicode. :pag – erickson

1

lo haría un "grep" en los archivos para encontrar todos 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 debe 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 sh ell script bastante fácil.

4

que hacen este tipo de cosas con un script como el siguiente (en la 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 

que se pueden manipular la expresión regular se utiliza para delimitar las 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.

1

Otro intento 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 puse su pregunta en el archivo wc-test y la respuesta de Robert Gamble en wc-ans-test, el archivo de salida tiene el siguiente aspecto:

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

Esta es una separada por comas archivo de valor (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, por lo que puede ajustar su salida independientemente de los datos de entrada.

1

Al diablo con grandes guiones.Si usted está dispuesto a tomar todas las palabras, probar este fu shell:

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

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

Cuestiones relacionadas