Domanda

Sono uno studente non informatico che sta facendo una tesi di storia che prevede la determinazione della frequenza di termini specifici in un numero di testi e quindi la stampa di queste frequenze nel tempo per determinare cambiamenti e tendenze. Mentre ho capito come determinare le frequenze delle parole per un dato file di testo, ho a che fare con un numero relativamente grande (per me) di file (> 100) e per motivi di coerenza vorrei limitare le parole incluse nella frequenza conta per un insieme specifico di termini (una specie di opposto di un "elenco di arresto")

Questo dovrebbe essere mantenuto molto semplice. Alla fine tutto ciò che devo avere sono le frequenze per le parole specifiche per ogni file di testo che elaboro, preferibilmente in formato foglio di calcolo (file delineato da tabulazione) in modo da poter creare grafici e visualizzazioni utilizzando tali dati.

Uso Linux quotidianamente, mi sento a mio agio a utilizzare la riga di comando e mi piacerebbe una soluzione open source (o qualcosa che potrei eseguire con WINE). Questo non è un requisito tuttavia:

Vedo due modi per risolvere questo problema:

  1. Trova un modo per eliminare tutte le parole in un file di testo EXCEPT per l'elenco predefinito e quindi fare il conteggio delle frequenze da lì, oppure:
  2. Trova un modo per fare un conteggio delle frequenze usando solo i termini dell'elenco predefinito.

Qualche idea?

È stato utile?

Soluzione

Vorrei andare con la seconda idea. Ecco un semplice programma Perl che leggerà un elenco di parole dal primo file fornito e stamperà un conteggio di ogni parola nell'elenco dal secondo file fornito in formato separato da tabulazioni. L'elenco di parole nel primo file deve essere fornito uno per riga.

#!/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(

Vorrei andare con la seconda idea. Ecco un semplice programma Perl che leggerà un elenco di parole dal primo file fornito e stamperà un conteggio di ogni parola nell'elenco dal secondo file fornito in formato separato da tabulazioni. L'elenco di parole nel primo file deve essere fornito uno per riga.

linux
frequencies
science
words

Se il file words.txt contiene:

perl analyze.pl words.txt text.txt

E il file text.txt contiene il testo del tuo post, il seguente comando:

frequencies     3
linux   1
science 1
words   3

stamperà:

s/-//g;

Nota che rompere i confini delle parole usando \ b potrebbe non funzionare come vuoi in tutti i casi, ad esempio, se i tuoi file di testo contengono parole che sono sillabate su più righe dovrai fare qualcosa di un po 'più intelligente per abbinarle . In questo caso è possibile verificare se l'ultimo carattere di una riga è un trattino e, in tal caso, è sufficiente rimuovere il trattino e leggere un'altra riga prima di dividere la riga in parole.

Modifica : versione aggiornata che gestisce le parole senza distinzione tra maiuscole e minuscole e gestisce le parole sillabate su più righe.

Nota che se ci sono parole sillabate, alcune delle quali sono spezzate su linee e altre no, questo non le troverà tutte perché ha rimosso solo i trattini alla fine di una riga. In questo caso potresti voler rimuovere tutti i trattini e abbinare le parole dopo che i trattini sono stati rimossi. Puoi farlo semplicemente aggiungendo la seguente riga subito prima della funzione split:

<*>)} = 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) ?

Vorrei andare con la seconda idea. Ecco un semplice programma Perl che leggerà un elenco di parole dal primo file fornito e stamperà un conteggio di ogni parola nell'elenco dal secondo file fornito in formato separato da tabulazioni. L'elenco di parole nel primo file deve essere fornito uno per riga.

<*>

Se il file words.txt contiene:

<*>

E il file text.txt contiene il testo del tuo post, il seguente comando:

<*>

stamperà:

<*>

Nota che rompere i confini delle parole usando \ b potrebbe non funzionare come vuoi in tutti i casi, ad esempio, se i tuoi file di testo contengono parole che sono sillabate su più righe dovrai fare qualcosa di un po 'più intelligente per abbinarle . In questo caso è possibile verificare se l'ultimo carattere di una riga è un trattino e, in tal caso, è sufficiente rimuovere il trattino e leggere un'altra riga prima di dividere la riga in parole.

Modifica : versione aggiornata che gestisce le parole senza distinzione tra maiuscole e minuscole e gestisce le parole sillabate su più righe.

Nota che se ci sono parole sillabate, alcune delle quali sono spezzate su linee e altre no, questo non le troverà tutte perché ha rimosso solo i trattini alla fine di una riga. In questo caso potresti voler rimuovere tutti i trattini e abbinare le parole dopo che i trattini sono stati rimossi. Puoi farlo semplicemente aggiungendo la seguente riga subito prima della funzione split:

<*> .= $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" }

Se il file words.txt contiene:

<*>

E il file text.txt contiene il testo del tuo post, il seguente comando:

<*>

stamperà:

<*>

Nota che rompere i confini delle parole usando \ b potrebbe non funzionare come vuoi in tutti i casi, ad esempio, se i tuoi file di testo contengono parole che sono sillabate su più righe dovrai fare qualcosa di un po 'più intelligente per abbinarle . In questo caso è possibile verificare se l'ultimo carattere di una riga è un trattino e, in tal caso, è sufficiente rimuovere il trattino e leggere un'altra riga prima di dividere la riga in parole.

Modifica : versione aggiornata che gestisce le parole senza distinzione tra maiuscole e minuscole e gestisce le parole sillabate su più righe.

Nota che se ci sono parole sillabate, alcune delle quali sono spezzate su linee e altre no, questo non le troverà tutte perché ha rimosso solo i trattini alla fine di una riga. In questo caso potresti voler rimuovere tutti i trattini e abbinare le parole dopo che i trattini sono stati rimossi. Puoi farlo semplicemente aggiungendo la seguente riga subito prima della funzione split:

<*>

Altri suggerimenti

Faccio questo genere di cose con uno script come il seguente (nella sintassi bash):

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

Puoi modificare la regex che usi per delimitare singole parole; nell'esempio considero solo gli spazi bianchi come delimitatore. L'argomento -f per grep è un file che contiene le tue parole di interesse, una per riga.

Prima familiarizza con l'analisi lessicale e come scrivere una specifica del generatore di scanner. Leggi le introduzioni all'uso di strumenti come YACC, Lex, Bison o il mio preferito, JFlex. Qui puoi definire cosa costituisce un token. Qui è dove apprendi come creare un tokenizer.

Successivamente hai quella che viene chiamata una seed list. L'opposto dell'elenco di arresto viene di solito indicato come elenco di avvio o lessico limitato. Anche il Lexicon sarebbe una buona cosa da imparare. Parte dell'app deve caricare l'elenco di avvio in memoria per poter essere rapidamente interrogato. Il modo tipico di archiviare è un file con una parola per riga, quindi leggerlo all'inizio dell'app, una volta, in qualcosa come una mappa. Potresti voler conoscere il concetto di hashing.

Da qui si desidera pensare all'algoritmo di base e alle strutture di dati necessarie per memorizzare il risultato. Una distribuzione è facilmente rappresentata come una matrice sparsa bidimensionale. Impara le basi di una matrice sparsa. Non hai bisogno di 6 mesi di algebra lineare per capire cosa fa.

Dato che stai lavorando con file più grandi, raccomanderei un approccio basato sul flusso. Non leggere l'intero file in memoria. Leggilo come stream nel tokenizer che produce un flusso di token.

Nella parte successiva dell'algoritmo pensa a come trasformare l'elenco di token in un elenco contenente solo le parole che desideri. Se ci pensate, l'elenco è in memoria e può essere molto grande, quindi è meglio filtrare le parole non iniziali all'inizio. Quindi, nel punto critico in cui si ottiene un nuovo token dal tokenizer e prima di aggiungerlo all'elenco token, eseguire una ricerca nell'elenco start-words-list in memoria per vedere se la parola è una parola iniziale. In tal caso, tenerlo nell'elenco dei token di output. Altrimenti ignoralo e passa al token successivo fino a quando non viene letto l'intero file.

Ora hai un elenco di token solo di interesse. Il fatto è che non stai osservando altre metriche di indicizzazione come posizione, caso e contesto. Pertanto, in realtà non è necessario un elenco di tutti i token. Vuoi davvero solo una matrice sparsa di token distinti con conteggi associati.

Quindi, prima crea una matrice sparsa vuota. Quindi pensa all'inserimento del token appena trovato durante l'analisi. Quando si verifica, incrementa il conteggio se è nell'elenco oppure inserisci un nuovo token con un conteggio di 1. Questa volta, al termine dell'analisi del file, hai un elenco di token distinti, ognuno con una frequenza di almeno 1.

L'elenco è ora in-mem e puoi fare quello che vuoi. Scaricare in un file CSV sarebbe un processo banale di scorrere le voci e scrivere ogni voce per riga con il suo conteggio.

In tal caso, dai un'occhiata al prodotto non commerciale chiamato " GATE " o un prodotto commerciale come TextAnalyst o prodotti elencati in http://textanalysis.info

Suppongo che i nuovi file vengano introdotti nel tempo, ed è così che cambiano le cose?

Suppongo che la tua scommessa migliore sarebbe quella di scegliere qualcosa come la tua opzione 2. Non c'è molto punto pre-elaborazione dei file, se tutto ciò che vuoi fare è contare le occorrenze delle parole chiave. Esaminerei ogni singolo file una volta, contando ogni volta che appare una parola nel tuo elenco. Personalmente lo farei in Ruby, ma un linguaggio come il perl o il pitone renderebbe questo compito abbastanza semplice. Ad esempio, è possibile utilizzare un array associativo con le parole chiave come chiavi e un conteggio delle occorrenze come valori. (Ma questo potrebbe essere troppo semplicistico se è necessario memorizzare ulteriori informazioni sugli eventi).

Non sono sicuro se si desidera archiviare informazioni per file o sull'intero set di dati? Immagino che non sarebbe troppo difficile da integrare.

Non sono sicuro di cosa fare con i dati una volta ottenuti: esportarli in un foglio di calcolo andrebbe bene, se ciò ti dà ciò di cui hai bisogno. Oppure potresti trovare più facile a lungo termine solo scrivere un po 'di codice extra che mostri i dati per te. Dipende da cosa vuoi fare con i dati (ad esempio se vuoi produrre solo alcuni grafici alla fine dell'esercizio e inserirli in un rapporto, quindi l'esportazione in CSV avrebbe probabilmente più senso, mentre se vuoi generare una nuova serie di dati ogni giorno per un anno, quindi la creazione di uno strumento per farlo automaticamente è quasi sicuramente la migliore idea.

Modifica: ho appena capito che, poiché stai studiando la storia, è probabile che i tuoi documenti non cambino nel tempo, ma piuttosto riflettano una serie di cambiamenti già avvenuti. Ci scusiamo per l'incomprensione. Ad ogni modo, penso che quasi tutto ciò che ho detto sopra sia ancora valido, ma immagino che ti appoggerai ad esportare in CSV o cosa hai invece di un display automatico.

Sembra un progetto divertente - buona fortuna!

Ben

Farei un " grep " sui file per trovare tutte le righe che contengono le parole chiave. (Grep -f può essere usato per specificare un file di input di parole da cercare (pipe l'output di grep in un file). Questo ti darà un elenco di righe che contengono istanze delle tue parole. Quindi fai un " sed " per sostituire i separatori di parole (spazi più probabili) con le nuove righe, per darti un file di parole separate (una parola per riga). Ora ripassa grep, con il tuo stesso elenco di parole, tranne che questa volta specifica -c (per ottenere un conteggio delle righe con le parole specificate, ovvero conteggio delle occorrenze della parola nel file originale).

Il metodo a due passaggi semplifica semplicemente la vita di "sed"; il primo grep dovrebbe eliminare molte righe.

Puoi farlo tutto nei comandi di base della riga di comando di Linux. Una volta che hai dimestichezza con il processo, puoi inserire tutto facilmente nello script di shell.

Un altro tentativo di 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;

Quando inserisco la tua domanda nel file wc-test e la risposta di Robert Gamble in wc-ans-test , il file di output è simile al seguente:

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

Questo è un file con valori separati da virgola (csv) (ma è possibile modificare il separatore nello script). Dovrebbe essere leggibile per qualsiasi applicazione di foglio di calcolo. Per la stampa di grafici, consiglierei gnuplot , che è completamente scriptabile, in modo da poter modificare l'output indipendentemente dai dati di input.

All'inferno con grandi script. Se sei disposto a prendere tutte parole, prova questa shell fu:

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

Questo (testato) ti fornirà un elenco di tutte le parole ordinate per frequenza in formato CSV, facilmente importabili dal tuo foglio di calcolo preferito. Se è necessario disporre delle parole di arresto, provare a inserire grep -w -F -f stopwords.txt nella pipeline (non testato).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top