Capitolo 3 Documenti e corpora
Testi e dati degli esempi (cartella Proton Drive, in aggiornamento). Gli script che seguono presuppongono che testi e dati siano in una sottocartella “dati”.
3.1 Introduzione
L’insieme dei testi oggetto di analisi viene definito corpus (pl. corpora), espressione che, negli studi linguistici, indica «la riunione di più opere, raccolte e pubblicate con lo scopo di fornire serie ordinate e complete degli scritti di uno o più autori o intorno a determinate materie» (Vocabolario Treccani).
Per corpus intenderemo qui, in primo luogo, i testi importati e organizzati ai fini dell’analisi.
Corpus è però anche una classe di oggetti di R, che:
- conservano i testi nella loro forma originaria;
- le informazioni relative ai testi (metadati dei documenti);
- e quelle sull’insieme dei documenti (metadati del corpus, come data, fonte, ecc.).
Il corpus consente «l’interazione fra il livello paradigmatico di studio del lessico e quello sintagmatico di analisi del discorso» (Bolasco 2013, 83): esso è dunque uno strumento che serve non solo ad organizzare i testi, ma anche a facilitare lo studio delle relazioni fra parole, testi ed eventualmente frammenti di testi.
| Quanteda | tidytext | tm | |
|---|---|---|---|
| corpus | sì | no | sì |
| metadati del corpus | sì | — | sì |
| metadati dei documenti (variabili) | sì | sì | sì |
| segmentazione | sì | — | no |
Un esempio di possibile uso dei corpora per la gestione e la trasformazione dei testi è illustrato nel § 3.3.5. Nel caso di testi molto brevi, come i messaggi di Twitter, invece, questo tipo di organizzazione potrebbe non essere necessaria (vedi § ??).
Di conseguenza, dei pacchetti che stiamo qui considerando:
tm lavora esclusivamente con oggetti corpus;
Quanteda li prevede, ma consente di lavorare anche direttamente con i token;
tidytext usa esclusivamente dataframe di token, costruiti a partire da dataframe di testi (3.2);
koRpus, nonostante il nome, non usa questa classe di oggetti, in quanto è dedicato all’analisi di singoli testi nella prospettiva della linguistica dei corpora (ma può essere integrato in tm: cfr. § 4.7.3).
Gli oggetti corpus di Quanteda e tm sono largamente compatibili fra di loro, benché sia necessario procedere ad una “conversione” per passare dall’uno all’altro.
Figura 15: Organizzazione dei documenti
3.2 L’importazione dei testi
L’organizzazione dei documenti ai fini del progetto di analisi inizia con le scelte relative all’importazione, ovvero quelle relative al formato dei dati — da cui dipendono i pacchetti e le funzioni che si potranno utilizzare.
Con il pacchetto readtext (Benoit e Obeng 2024), i documenti vengono importati in un dataframe (funzione readtext()), e in un formato compatibile con tutti i pacchetti qui considerati.
In particolare, di default, vengono attribuiti i seguenti nomi alle colonne:
doc_idper l’identificativo dei documenti;textper il campo del testo da analizzare.
La funzione permette di importare file in diversi formati: txt, ma anche doc, docx, pdf, html, csv, tab, tsv, e altri.
3.2.1 Importare un solo testo
Importiamo il testo de I Sepolcri (file “sepolcri.txt”) dalla sottocartella “dati”:
Nello spazio di lavoro, abbiamo ora un dataframe di classe “readtext”, composto di una riga (il testo), e le due variabili doc_id e text:
readtext object consisting of 1 document and 0 docvars.
# A data frame: 1 × 2
doc_id text
<chr> <chr>
1 sepolcri.txt "\"All'ombra \"..."
Importiamo anche il file “ansa_2020-02-05.txt”:
readtext object consisting of 1 document and 0 docvars.
# A data frame: 1 × 2
doc_id text
<chr> <chr>
1 ansa_2020-02-05.txt "\"##TITOLO S\"..."
3.2.2 Importare più testi organizzati in una cartella
Per importare un insieme di documenti contenuti in una cartella, è sufficiente indicare il percorso, e usare l’asterisco * come carattere jolly:
readtext object consisting of 2 documents and 0 docvars.
# A data frame: 2 × 2
doc_id text
<chr> <chr>
1 01_Achille Lauro.txt "\"Achille La\"..."
2 02_Alberto Urso.txt "\"Alberto Ur\"..."
In questo caso, il dataframe sarà composto di tante righe quanti sono i documenti contenuti nella cartella (nel nostro esempio, 2), e verrà assegnato come identificativo dei testi, nel campo doc_id, il nome del file (“01_Achille Lauro.txt”; “02_Alberto Urso.txt”).
3.2.3 Metadati dei documenti (variabili)
Le eventuali variabili relative ai singoli documenti (data, autore, intervistato, luogo, ecc.) possono essere importate da un file csv che le contiene (con un campo identificativo comune), o essere estrapolate dal nome o dal percorso del file7.
Ad esempio, possiamo ricavare le variabili del documento da nomi di file strutturati, ad esempio:
EU_euro_2004_de_PSE.txt
In questo caso, specificheremo che le variabili vanno estrapolate dal nome del file (docvarsfrom), e indicheremo il nome delle variabili (docvarnames):
readtext('dir',
docvarsfrom = "filenames",
docvarnames = c("unit", "context", "year", "language", "party"))Dove “unit” sarà il campo che conterrà il valore “EU”, “context” quello per “euro”, e così via.
Tornando all’esempio delle canzoni:
canzoni <- readtext('dati/canzoni/*.txt',
docvarsfrom = "filenames",
docvarnames = c("ID",
"cantante"))
canzonireadtext object consisting of 2 documents and 2 docvars.
# A data frame: 2 × 4
doc_id text ID cantante
<chr> <chr> <int> <chr>
1 01_Achille Lauro.txt "\"Achille La\"..." 1 Achille Lauro
2 02_Alberto Urso.txt "\"Alberto Ur\"..." 2 Alberto Urso
3.2.4 La gestione dei documenti
Il fatto che i testi siano organizzati in un dataframe permette di usare le funzioni per la gestione dei dati, più note agli utilizzatori di R, dopo l’importazione.
Ad esempio, possiamo cambiare il nome dell’identificativo con rename():
readtext object consisting of 2 documents and 2 docvars.
# A data frame: 2 × 4
file text ID cantante
<chr> <chr> <int> <chr>
1 01_Achille Lauro.txt "\"Achille La\"..." 1 Achille Lauro
2 02_Alberto Urso.txt "\"Alberto Ur\"..." 2 Alberto Urso
Oppure, tornando all’esempio dell’articolo di giornale, una volta effettuata l’importazione e strutturate le variabili dei documenti, trasformiamo la variabile “data” nel più appropriato formato Date:
articolo <- readtext('dati/ansa_2020-02-05.txt',
docvarsfrom = "filenames",
docvarnames = c("fonte",
"data"))
articolo$data <- as.Date(articolo$data)
articoloreadtext object consisting of 1 document and 2 docvars.
# A data frame: 1 × 4
doc_id text fonte data
<chr> <chr> <chr> <date>
1 ansa_2020-02-05.txt "\"##TITOLO S\"..." ansa 2020-02-05
3.2.5 Importare i testi da un dataset
Nel caso di testi brevi o molto brevi, come le risposte aperte a un questionario, o i messaggi di Twitter, i documenti sono generalmente raccolti e organizzati in un dataframe. In questo caso, potremmo voler usare le funzioni del pacchetto readr (o importare il dataset anche dal menu di RStudio).
Importiamo il dataset delle canzoni di Sanremo 2021 (“sanremo2021.csv”), presente, in formato tab-delimited (tsv, anche se con estensione .csv), nella cartella dati:
# A tibble: 6 × 3
cantante titolo testo
<chr> <chr> <chr>
1 Aiello Ora "Ora ora ora ora\nMi parli c…
2 Annalisa Scarrone Dieci "Cos'è che ti ho promesso\nN…
3 Arisa Potevi Fare Di Più "Lasciarsi adesso non fa più…
4 Bugo E Invece Sì "Le metropolitane vanno molt…
5 COLAPESCEDIMARTINO Musica Leggerissima "Se fosse un'orchestra a par…
6 Coma Cose Fiamme Negli Occhi "Quando ti sto vicino sento\…
Nota. Nel seguito verrà spesso usata la funzione head() (di solito nella forma %>% head()) al solo scopo di ridurre la lunghezza dell’output stampato. La funzione consente di stampare infatti solo le prime righe degli oggetti, e non svolge alcun altro ruolo nell’analisi.
Dal momento che vorremo usare questo dataset con i pacchetti per l’analisi testuale, trasformiamo i campi secondo gli standard visti sopra, ovvero:
costruiamo una variabile
doc_idcon il titolo (mutate("doc_id" = str_replace_all(titolo, " ", "_")))8;rinominiamo la colonna che contiene i testi delle canzoni, attribuendole il nome
text.
sanremo21 <- sanremo21 %>%
# titolo come doc_id
mutate("doc_id" = str_replace_all(titolo, " ", "_")) %>%
# rinominiamo il campo "testo"
select(doc_id, "text" = testo,
cantante, titolo)
head(sanremo21)# A tibble: 6 × 4
doc_id text cantante titolo
<chr> <chr> <chr> <chr>
1 Ora "Ora ora ora ora\nMi parli come… Aiello Ora
2 Dieci "Cos'è che ti ho promesso\nNon … Annalis… Dieci
3 Potevi_Fare_Di_Più "Lasciarsi adesso non fa più ma… Arisa Potev…
4 E_Invece_Sì "Le metropolitane vanno molto v… Bugo E Inv…
5 Musica_Leggerissima "Se fosse un'orchestra a parlar… COLAPES… Music…
6 Fiamme_Negli_Occhi "Quando ti sto vicino sento\nCh… Coma Co… Fiamm…
Nota. Per aggiungere o modificare una variabile, usiamo qui mutate(). Vedi anche https://www.agnesevardanega.eu/wiki/r/tidyverse/dplyr_mutate:
Per rinominare la variabile e nello stesso tempo riorganizzare l’ordine delle variabili, usiamo la funzione select(), che serve a scegliere alcune variabili di un dataset, eventualmente rinominandole. Vedi anche: https://www.agnesevardanega.eu/wiki/r/tidyverse/dplyr_select
3.3 I corpora in Quanteda
3.3.1 La funzione corpus
Per costruire il corpus di Quanteda a partire dal dataframe dei testi importati, usiamo la funzione corpus():
library(quanteda)
# impostiamo l'italiano per il parsing dei testi
quanteda_options(language_stemmer ="italian") Volendo intervenire in questa fase sugli apostrofi, non riconosciuti come confini di parole (vedi § 4.2.1), inseriamo uno spazio dopo gli apostrofi non seguiti da uno spazio:
[1] "corpus" "character"
[1] "character"
| oggetti importati | metadati |
|---|---|
| dataframe | doc_id, e altre variabili |
| vettore carattere | nomi degli elementi del vettore |
| corpus, tm corpus | metadati presenti |
3.3.3 I metadati
I metadati — del corpus e dei documenti — sono conservati come attributi dell’oggetto: names, docvars e meta.
'corpus' Named chr [1:2] "Achille Lauro\n\nMe Ne Frego\n\nSì\nNoi
sì\nNoi che qui\nSiamo soli qui\nNoi sì\nSoli qui\nFai di me quel
che v"| __truncated__ ...
- attr(*, "names")= chr [1:2] "01_Achille Lauro.txt" "02_Alberto Urso.txt"
- attr(*, "docvars")='data.frame': 2 obs. of 5 variables:
..$ docname_: chr [1:2] "01_Achille Lauro.txt" "02_Alberto Urso.txt"
...
- attr(*, "meta")=List of 3
..$ system:List of 6
...
..$ object:List of 2
...
..$ user : list()
Metadati dei documenti
L’identificativo del documento (doc_id) diventa l’attributo names degli elementi contenuti nell’oggetto (come per i named vector, le liste e i dataframe).
'corpus' Named chr [1:2] "Achille Lauro\n\nMe Ne Frego\n\nSì\nNoi
sì\nNoi che qui\nSiamo soli qui\nNoi sì\nSoli qui\nFai di me quel
che v"| __truncated__ ...
- attr(*, "names")= chr [1:2] "01_Achille Lauro.txt" "02_Alberto Urso.txt"
...
Con la funzione docnames() — oppure con la funzione di base names() — possiamo accedere ed eventualmente modificare i nomi dei documenti che compongono il corpus:
[1] "01_Achille Lauro.txt" "02_Alberto Urso.txt"
Le altre variabili relative ai documenti costituiscono i veri e propri metadati di documento, e, qualora siano presenti nel dataframe dei testi, vengono importate come dataframe (attributo docvars).
'corpus' Named chr [1:2] "Achille Lauro\n\nMe Ne Frego\n\nSì\nNoi
sì\nNoi che qui\nSiamo soli qui\nNoi sì\nSoli qui\nFai di me quel
che v"| __truncated__ ...
...
- attr(*, "docvars")='data.frame': 2 obs. of 5 variables:
..$ docname_: chr [1:2] "01_Achille Lauro.txt" "02_Alberto Urso.txt"
...
Costruiamo, ad esempio, il corpus delle canzoni di Sanremo 2021 (sempre correggendo i confini delle parole):
La funzione docvars() permette di accedere a questo dataframe, e dunque alle variabili, eventualmente modificandole:
cantante titolo
1 Aiello Ora
2 Annalisa Scarrone Dieci
3 Arisa Potevi Fare Di Più
4 Bugo E Invece Sì
5 COLAPESCEDIMARTINO Musica Leggerissima
6 Coma Cose Fiamme Negli Occhi
Questo significa che, se le variabili dei documenti sono contenute in un dataset diverso da quello che contiene i testi, esse possono essere aggiunte o unite successivamente, purché naturalmente abbiano il campo “doc_id” o un’altra variabile in comune (si veda l’esempio nel §3.3.5).
Per accedere ad una singola variabile, possiamo scrivere:
[1] "Aiello" "Annalisa Scarrone" "Arisa"
[4] "Bugo" "COLAPESCEDIMARTINO" "Coma Cose"
O anche, direttamente:
Metadati del corpus
La funzione per accedere e modificare i metadati del corpus è meta(), la cui sintassi è.
meta(x, field = NULL, type = c("user", "object", "system", "all"))
Possiamo accedere a tutti i metadati (meta(sanremo_21, type = "all")), oppure, separatamente, alle informazioni di sistema:
$`package-version`
[1] ‘3.2.0’
$`r-version`
[1] ‘4.1.1’
$system
sysname machine user
"Windows" "x86-64" "..."
$directory
[1] "C:/Users/..."
$created
[1] "2022-01-11"
$source
[1] "data.frame"
e a quelle relative all’oggetto:
$unit
[1] "documents"
$summary
$summary$hash
character(0)
$summary$data
NULL
Possiamo aggiungere altri metadati (che saranno di tipo “user”) dopo l’importazione. Registriamo ad esempio la fonte dei testi.
$fonte
[1] "angolotesti.it"
Ma possiamo anche impostarli in fase di importazione, come lista in cui definiamo il nome del campo (ad esempio “fonte”), e il valore del campo (“angolotesti.it”) (named list)9:
$fonte
[1] "angolotesti.it"
equivale a
$fonte
[1] "angolotesti.it"
3.3.4 Informazioni sul corpus
La generica funzione summary(), applicata al corpus, restituisce — come abbiamo visto — una veloce descrizione del corpus, compresi il numero di token e di type:
Corpus consisting of 2 documents, showing 2 documents:
Text Types Tokens Sentences ID cantante
01_Achille Lauro.txt 151 367 1 1 Achille Lauro
02_Alberto Urso.txt 95 207 1 2 Alberto Urso
Altra funzione utile per descrivere il corpus è ndoc(), con la quale otteniamo il numero dei documenti:
[1] 2
Con nsentence(), infine, possiamo conoscere il numero di frasi (in questo caso, i testi delle canzoni sono privi di punteggiatura):
01_Achille Lauro.txt 02_Alberto Urso.txt
1 1
corpus_reshape() |
Segmentare i documenti (reversibile) |
corpus_segment() |
Segmentare i documenti |
corpus_subset() |
Subset del corpus |
corpus_group() |
Raggruppare documenti in base a una variabile |
corpus_sample() |
Selezionare un campione di documenti all’interno del corpus |
corpus_trim() |
Rimuovere elementi del corpus (frasi, paragrafi, documenti) in base alla lunghezza. |
3.3.5 Esempio: unire testi e metadati da file diversi
Vogliamo importare le trascrizioni di alcune interviste in profondità in Word, contenute in una sottocartella della cartella “dati”, e associarle alle informazioni sulle persone intervistate, contenute in un foglio di calcolo di Google (google sheet).
A quel punto si importeranno le interviste e il foglio di lavoro, e li si unirà.
- Importiamo le trascrizioni (vedi § 3.2.2):
library(tidyverse)
library(readtext)
interviste <- readtext("dati/interviste/*") %>%
mutate(doc_id = str_remove_all(doc_id, ".docx"))
intervistereadtext object consisting of 10 documents and 0 docvars.
# Description: df [10 x 2]
doc_id text
<chr> <chr>
1 DCMSP0 "\"#RIC Buon \"..."
2 DSA0P0 "\"#RIC Buong\"..."
3 DSMSP1 "\"#RIC Allor\"..."
...
Il nome del file è importato come identificativo dell’intervista, ma con l’estensione del file. Questa è stata rimossa (mutate(doc_id = str_remove_all(doc_id, ".docx")).
Eliminiamo tutto il testo contenuto fra parentesi quadre (\[(.*?)\]), ovvero tutti i commenti sull’interazione, ad esempio “[ride]”:
# elimina il testo fra parentesi quadre
interviste <- interviste %>%
mutate(text = str_remove_all(text, "\\[(.*?)\\]"))Notare che fare questa operazione in questa fase, è molto più semplice che effettuarla su Word, o sul corpus.
- Importiamo il file con i metadati delle interviste, con il pacchetto googlesheets4 [Bryan (2023)]10: lo chiamiamo metadocs, e trasformiamo le variabili in fattore dove serve:
library(googlesheets4)
# url del foglio
ss <- "https://docs.google.com/spreadsheets/d/..."
metadocs <- read_sheet(ss, sheet = "dati") %>%
# indichiamo quali variabili trasformare in fattori
mutate_at(vars(c(1:5,7:9)), list(factor))
metadocs# A tibble: 29 x 24
ID doc_id Genere Partecipa Studia Area Comune
<chr> <chr> <fct> <fct> <fct> <fct> <fct>
1 UNASP1 UNASP1 Uomo si si Nord altro~
2 UCMSP1 UCMSP1 Uomo si si Centro metro~
...
Si noterà che:
- Ci sono più righe delle interviste trascritte, perché analizziamo le interviste man mano che procedono: usando lo script per l’analisi, possiamo ripetere tutta la procedura in pochi secondi;
- Abbiamo due variabili uguali (
doc_ideID). La prima serve ad unire i dataframe e verrà modificata in fase di segmentazione; la seconda non verrà modificata e ci permetterà quindi, volendolo, di raggruppare tutti i segmenti o tutti i token appartenenti alla stessa intervista.
- Uniamo i dataframe, per aggiungere le variabili dei documenti ai testi, con
left_join()di dplyr, che unisce le variabili del secondo dataset indicato al primo, in base ad un campo comune (in questo caso,doc_id):
- Costruiamo il corpus (§ 3.3):
library(quanteda)
quanteda_options(language_stemmer ="italian")
demo_corpus <- interviste %>%
# correzione degli apostrofi
mutate(text = str_replace_all(text, "[\'’](?!\\s)", "' ")) %>%
corpus()
demo_corpusCorpus consisting of 10 documents and 9 docvars.
"#RIC Buon pomeriggio. #INT Salve, buon pomeriggio. #RIC Buon..."
DSA0P0 :
"#RIC Buongiorno Alessia, mi sente #INT Sì, la sento, ma non ..."
...
Le variabili sono state correttamente importate e associate ai testi:
ID Genere Partecipa Studia Area Comune ...
1 DCMSP0 Donna no si Centro metropoli ...
2 DSA0P0 Donna no no Sud altro comune ...
3 DSMSP1 Donna no si Sud metropoli ...
...
3.4 Statistiche riassuntive del corpus
3.4.1 Quanteda: summary
La funzione summary() restituisce — come abbiamo visto — una veloce descrizione del corpus:
Corpus consisting of 2 documents, showing 2 documents:
Text Types Tokens Sentences ID cantante
01_Achille Lauro.txt 151 367 1 1 Achille Lauro
02_Alberto Urso.txt 95 207 1 2 Alberto Urso
Length Class Mode
01_Achille Lauro.txt 362 -none- character
02_Alberto Urso.txt 207 -none- character
Dal summary, possiamo ricavare un grafico analogo a quello in Figura 5:
summary(sanremo_21) %>%
ggplot(aes(x = Text, y = Tokens, group = 1)) +
geom_line(aes(lty = "Tokens")) +
geom_line(aes(y = Types, lty = "Types")) +
scale_x_discrete (labels = NULL) +
labs(x = "Sanremo 2021: brani", y = "", lty = NULL)Oppure una tabella (dataframe):
Text Types Tokens Sentences ID cantante
1 01_Achille Lauro.txt 151 367 1 1 Achille Lauro
2 02_Alberto Urso.txt 95 207 1 2 Alberto Urso
che potrà essere formattata, e resa adatta alla pubblicazione in un report, con la funzione kable():
# tabella di summary
summary(sanremo) %>% as.data.frame() %>%
# selezione delle colonne di interesse
select(1:4) %>%
knitr::kable()| Text | Types | Tokens | Sentences |
|---|---|---|---|
| 01_Achille Lauro.txt | 151 | 367 | 1 |
| 02_Alberto Urso.txt | 95 | 207 | 1 |
3.4.2 Quanteda: quanteda.textstats
Esistono funzioni specifiche per esplorare questi oggetti, inserite, nelle versioni più recenti, in pacchetti (moduli) separati:
La funzione textstat_summary() restituisce un dataframe con statistiche più dettagliate, per corpora e oggetti token:
document chars sents tokens types puncts numbers
1 01_Achille Lauro.txt 1697 1 367 129 5 0
2 02_Alberto Urso.txt 996 1 207 83 0 0
symbols urls tags emojis
1 0 0 0 0
2 0 0 0 0
Da questi dataframe, possiamo creare tabelle e grafici, in modo del tutto simile a quanto visto sopra.
Vediamo ad esempio il codice per un grafico a linee e punti che rappresenta l’estensione dei testi (n. token), analogo a quello in Figura 5.
# grafico a punti e linee
textstat_summary(sanremo_21) %>%
ggplot(aes(x = document, y = tokens, group = 1)) +
geom_line(col = palette()[1]) +
geom_point(col = palette()[2]) +
scale_x_discrete (labels = NULL) +
labs(x = "Sanremo 2021: brani", y = "Tokens")
Nota: Per creare un grafico con ggplot, dobbiamo usare — di base — due funzioni: ggplot() e una funzione geom_(). Nella prima, indicheremo un dataframe come origine dei dati (ggplot(data = summary(sanremo_21), ...), e con la funzione aes() le variabili da rappresentare sugli assi (ggplot(data = ..., aes(x = ..., y = ...))). Infine, si aggiunge la funzione che indica il tipo di rappresentazione desiderata (+ geom_line()).
3.5 I corpora in tm
In tm lo strumento principale, tanto per la gestione dei testi quanto per la normalizzazione, è il corpus.
I corpora sono implementati allo scopo di poter utilizzare direttamente alcune funzioni previste dal pacchetto NLP. Essi sono dunque alquanto diversi da quelli utilizzati da Quanteda.
Dal momento che non ci occuperemo specificamente delle funzioni di tm, prenderemo in considerazione solo la struttura e i metadati del corpus, per valutare le possibilità di esportazione/importazione dei dati da un formato all’altro.
3.5.1 La costruzione del corpus
La creazione del corpus richiede due funzioni. La prima (in questo caso DirSource()) serve ad indicare l’origine dei dati (una cartella, o directory; Tabella 6).
La seconda funzione (VCorpus()) costruisce il corpus e consente di specificare, attraverso l’argomento readerControl, la lingua dei testi e, eventualmente la funzione per l’importazione (Tabella 7).
DataframeSource() |
Dataframe |
DirSource() |
Cartella (percorso) |
URISource() |
uri |
VectorSource() |
Vettore di testi |
Corpus() |
readerControl = list() |
VCorpus(): database non permanente |
readerControl = list() |
PCorpus(): database permanente |
readerControl = list() |
SimpleCorpus() |
control = list() |
Per importare i due documenti contenuti nella cartella “dati/canzoni”, scriveremo dunque DirSource("dati/canzoni") e poi useremo la funzione VCorpus().
# da una directory
sanremo.tm <- DirSource("dati/canzoni") %>%
VCorpus(readerControl = list(language = "it-IT"))Per costruire un corpus da un dataframe, useremo invece DataframeSource():
# da un dataframe
sanremo21.tm <- DataframeSource(sanremo21) %>%
VCorpus(readerControl = list(language = "it-IT"))[1] "VCorpus" "Corpus"
[1] "list"
Per correggere gli apostrofi prima della costruzione del corpus, correggeremo il campo “text” del dataframe dei documenti:
sanremo21.tm <- sanremo21 %>%
mutate(text = str_replace_all(text, "[\'’](?!\\s)", "' ")) %>%
DataframeSource() %>%
VCorpus(readerControl = list(language = "it-IT"))Controlliamo i risultati, con le funzioni summary() e inspect():
Length Class Mode
Ora 2 PlainTextDocument list
Dieci 2 PlainTextDocument list
Potevi_Fare_Di_Più 2 PlainTextDocument list
E_Invece_Sì 2 PlainTextDocument list
Musica_Leggerissima 2 PlainTextDocument list
Fiamme_Negli_Occhi 2 PlainTextDocument list
Controlliamo i risultati dell’importazione dei primi 3 documenti:
<<VCorpus>>
Metadata: corpus specific: 0, document level (indexed): 2
Content: documents: 3
[[1]]
<<PlainTextDocument>>
Metadata: 7
Content: chars: 1515
[[2]]
<<PlainTextDocument>>
Metadata: 7
Content: chars: 1997
[[3]]
<<PlainTextDocument>>
Metadata: 7
Content: chars: 2017
3.5.2 I metadati
I metadati del corpus e dei documenti vengono gestiti in maniera analoga a quanto abbiamo visto per Quanteda, anche se con funzioni diverse.
Esplorando il corpus nel pannello Environment, o con il comando str(sanremo21.tm), possiamo osservare infatti che il corpus si compone di tre elementi (organizzati in una lista):
- content: il contenuto, ovvero i documenti;
- meta: i metadati del corpus;
- dmeta: i metadati dei documenti, organizzati in un dataframe.
Classes 'VCorpus', 'Corpus' hidden list of 3
$ content:List of 31
..$ :List of 2
.. ..$ content: chr "Ora ora ora ora\nMi parli come allora\nQua
ndo ancora non mi conoscevi\nPensavi le cose peggiori\nQuella
notte i"| __truncated__
.. ..$ meta :List of 7
.. .. ..$ author : chr(0)
.. .. ..$ datetimestamp: POSIXlt[1:1], format: "2021-09-19 15:23:13"
.. .. ..$ description : chr(0)
.. .. ..$ heading : chr(0)
.. .. ..$ id : chr "Ora"
.. .. ..$ language : chr "it-IT"
.. .. ..$ origin : chr(0)
.. .. ..- attr(*, "class")= chr "TextDocumentMeta"
.. ..- attr(*, "class")= chr [1:2] "PlainTextDocument" "TextDocument"
...
$ meta : list()
..- attr(*, "class")= chr "CorpusMeta"
$ dmeta : tibble [31 x 2] (S3: tbl_df/tbl/data.frame)
..$ cantante: chr [1:31] "Aiello" "Annalisa Scarrone" ...
..$ titolo : chr [1:31] "Ora" "Dieci" "Potevi Fare Di Più" ...
Il content è a sua volta una lista di 31 elementi (i documenti), ciascuno dei quali è una lista di due elementi: content (il testo delle canzoni, in questo caso) e meta (una lista di sette elementi).
Metadati del corpus
I metadati del corpus sono accessibili con la funzione meta(x, type=“corpus”).
oppure
list()
attr(,"class")
[1] "CorpusMeta"
Per modificarli o inserirne di personalizzati, usiamo l’argomento tag11.
oppure
[1] "angolotesti.it"
$fonte
[1] "angolotesti.it"
attr(,"class")
[1] "CorpusMeta"
Metadati dei documenti
Le variabili del dataframe importato, sono accessibili scrivendo sanremo21.tm$dmeta, oppure meta(sanremo21.tm, type = "indexed"), oppure, più semplicemente:
# A tibble: 6 × 2
cantante titolo
<chr> <chr>
1 Aiello Ora
2 Annalisa Scarrone Dieci
3 Arisa Potevi Fare Di Più
4 Bugo E Invece Sì
5 COLAPESCEDIMARTINO Musica Leggerissima
6 Coma Cose Fiamme Negli Occhi
Per richiamare o modificare le variabili, usiamo la notazione delle liste, in questo modo
[1] "Ora" "Dieci" "Potevi Fare Di Più"
[4] "E Invece Sì" "Musica Leggerissima" "Fiamme Negli Occhi"
o
L’identificativo dei documenti è conservato insieme al content.
Per ciascun documento, è inoltre prevista la possibilità di inserire i metadati nel formato conforme al Dublin Core Metadata Initiative.
contributor: character(0)
coverage : character(0)
creator : character(0)
date : 2021-09-17 12:19:13
description: character(0)
format : character(0)
identifier : Ora
language : it-IT
publisher : character(0)
relation : character(0)
rights : character(0)
source : character(0)
subject : character(0)
title : character(0)
type : character(0)
3.6 Convertire i formati
3.6.1 Quanteda e tm
Possiamo importare direttamente un corpus costruito con tm in Quanteda, con la funzione corpus().
Corpus consisting of 31 documents and 7 docvars.
text1 :
"Ora ora ora ora Mi parli come allora Quando ancora non mi co..."
text2 :
"Cos' è che ti ho promesso Non so Non mi ricordo adesso Me lo..."
text3 :
"Lasciarsi adesso non fa più male non è importante Cosa ci im..."
text4 :
"Le metropolitane vanno molto veloci II giornali gratis La ra..."
text5 :
"Se fosse un' orchestra a parlare per noi Sarebbe più facile ..."
text6 :
"Quando ti sto vicino sento Che a volte perdo il baricentro E..."
[ reached max_ndoc ... 25 more documents ]
Le 7 variabili sono i metadati dei documenti (compreso l’identificativo), sebbene alcuni campi siano vuoti:
author datetimestamp description heading id
1 NA 2024-03-16 18:07:55 NA NA Ora
2 NA 2024-03-16 18:07:55 NA NA Dieci
3 NA 2024-03-16 18:07:55 NA NA Potevi_Fare_Di_Più
4 NA 2024-03-16 18:07:55 NA NA E_Invece_Sì
5 NA 2024-03-16 18:07:55 NA NA Musica_Leggerissima
6 NA 2024-03-16 18:07:55 NA NA Fiamme_Negli_Occhi
language origin
1 it-IT NA
2 it-IT NA
3 it-IT NA
4 it-IT NA
5 it-IT NA
6 it-IT NA
Non è invece possibile importare in tm un corpus di Quanteda. È però possibile convertire una matrice testuale di Quanteda (dfm) in una matrice di tm (dtm); Tabella 12).
3.6.2 Tidytext: la funzione tidy()
La funzione tidy() di Tidytext consente di trasformare corpora e matrici testuali in un dataframe (una parole per riga):
# A tibble: 6 × 8
author datetimestamp description heading id language origin
<lgl> <dttm> <lgl> <lgl> <chr> <chr> <lgl>
1 NA 2024-03-16 18:07:55 NA NA Ora it-IT NA
2 NA 2024-03-16 18:07:55 NA NA Dieci it-IT NA
3 NA 2024-03-16 18:07:55 NA NA Pote… it-IT NA
4 NA 2024-03-16 18:07:55 NA NA E_In… it-IT NA
5 NA 2024-03-16 18:07:55 NA NA Musi… it-IT NA
6 NA 2024-03-16 18:07:55 NA NA Fiam… it-IT NA
# ℹ 1 more variable: text <chr>
Vengono importati però solo i metadati dei documenti, e non quelli del corpus. Come si vede, infatti, manca il tag ‘fonte’ (angolotesti.it), e l’autore e il titolo (tm.corpus$dmeta). Informazioni, che, volendo, possono essere aggiunte con bind_cols():
bind_cols(tidytext::tidy(sanremo21.tm),
sanremo21.tm$dmeta,
'fonte' = sanremo21.tm$meta$fonte) %>%
head()# A tibble: 6 × 11
author datetimestamp description heading id language origin
<lgl> <dttm> <lgl> <lgl> <chr> <chr> <lgl>
1 NA 2024-03-16 18:07:55 NA NA Ora it-IT NA
2 NA 2024-03-16 18:07:55 NA NA Dieci it-IT NA
3 NA 2024-03-16 18:07:55 NA NA Pote… it-IT NA
4 NA 2024-03-16 18:07:55 NA NA E_In… it-IT NA
5 NA 2024-03-16 18:07:55 NA NA Musi… it-IT NA
6 NA 2024-03-16 18:07:55 NA NA Fiam… it-IT NA
# ℹ 4 more variables: text <chr>, cantante <chr>, titolo <chr>,
# fonte <chr>
# A tibble: 6 × 3
text cantante titolo
<chr> <chr> <chr>
1 "Ora ora ora ora\nMi parli come allora\nQuando anco… Aiello Ora
2 "Cos' è che ti ho promesso\nNon so\nNon mi ricordo … Annalis… Dieci
3 "Lasciarsi adesso non fa più male non è importante\… Arisa Potev…
4 "Le metropolitane vanno molto veloci\nII giornali g… Bugo E Inv…
5 "Se fosse un' orchestra a parlare per noi\nSarebbe … COLAPES… Music…
6 "Quando ti sto vicino sento\nChe a volte perdo il b… Coma Co… Fiamm…
In questo caso, non sono stati importati gli identificativi dei documenti, che possono però essere recuperati con quanteda::docnames().
I metadati del corpus possono invece essere visualizzati con glance()12:
# A tibble: 1 × 1
fonte
<chr>
1 angolotesti.it
Possiamo recuperare tutte le informazioni in questo modo:
bind_cols('doc_id' = quanteda::docnames(sanremo_21),
tidytext::tidy(sanremo_21),
tidytext::glance(sanremo_21)) %>%
head()# A tibble: 6 × 5
doc_id text cantante titolo fonte
<chr> <chr> <chr> <chr> <chr>
1 Ora "Ora ora ora ora\nMi parl… Aiello Ora ango…
2 Dieci "Cos' è che ti ho promess… Annalis… Dieci ango…
3 Potevi_Fare_Di_Più "Lasciarsi adesso non fa … Arisa Potev… ango…
4 E_Invece_Sì "Le metropolitane vanno m… Bugo E Inv… ango…
5 Musica_Leggerissima "Se fosse un' orchestra a… COLAPES… Music… ango…
6 Fiamme_Negli_Occhi "Quando ti sto vicino sen… Coma Co… Fiamm… ango…
3.7 Funzioni usate
Per l’importazione e la gestione dei testi, le competenze di base necessarie riguardano principalmente la gestione dei diversi oggetti, o strutture di dati: i vettori carattere, i dataframe, e liste.
In fase di importazione e organizzazione dei dati, in particolare, può essere necessario trasformare i dataframe, le variabili — eventualmente creandone di nuove — e i valori delle variabili. In particolare, in questo capitolo abbiamo usato le seguenti funzioni:
| Funzioni | pacchetto | |
|---|---|---|
as.Date() |
trasformare un campo in formato data | base |
read_csv(),read_tsv() |
importare un file in formato csv o tab delimited | readr |
bind_cols(),bind_rows() |
unire due tabelle, per colonna o per riga | dplyr |
mutate() |
modificare i valori di una variabile | dplyr |
select(),rename() |
selezionare (e/o rinominare) le variabili di un dataset | dplyr |
str_replace(),str_replace_all() |
sostituire testo | stringr |
Le caratteristiche degli oggetti di classe Corpus utilizzati da Quanteda e tm sono state dettagliate nei paragrafi ad essi dedicati.
Per i grafici, che vedremo meglio nel capitolo 5, abbiamo usato le funzioni del pacchetto ggplot2:
| Funzioni | pacchetto | |
|---|---|---|
ggplot(), aes() |
ggplot | |
geom_line() |
grafico a linee | ggplot |
geom_point() |
grafico a punti | ggplot |
labs() |
titoli ed etichette di un grafico | ggplot |
scale_x_discrete() |
modifica l’asse delle x di un grafico | ggplot |
3.8 Codice del capitolo
## Strumenti per l'analisi testuale e il text mining con R
## Agnese Vardanega avardanega@unite.it
## Capitolo 3 - Documenti e corpora
## Pacchetti --------------------------------------------------------------
library(tidyverse)
# IMPORTAZIONE ------------------------------------------------------------
library(readtext)
# sepolcri
sepolcri <- readtext("dati/sepolcri.txt")
# articolo
articolo <- readtext('dati/ansa_2020-02-05.txt',
docvarsfrom = "filenames",
docvarnames = c("fonte",
"data"))
# modificare la data
articolo$data <- as.Date(articolo$data)
# canzoni
canzoni <- readtext('dati/canzoni/*.txt',
# impostare le variabili
docvarsfrom = "filenames",
docvarnames = c("ID",
"cantante"))
# dataset
sanremo21 <- read_tsv("dati/sanremo2021.csv")
sanremo21 <- sanremo21 %>%
# titolo come doc_id
mutate("doc_id" = str_replace_all(titolo, " ", "_")) %>%
# rinominiamo il campo "testo" e riorganizziamo le colonne
select(doc_id, "text" = testo,
cantante, titolo)
# QUANTEDA ----------------------------------------------------------------
library(quanteda)
quanteda_options(language_stemmer ="italian")
# CORPUS
corpus(canzoni)
corpus(sanremo21)
# correggere gli apostrofi
sanremo <- canzoni %>%
mutate(text = str_replace_all(text, "[\'’](?!\\s)", "' ")) %>%
corpus()
sanremo_21 <- sanremo21 %>%
mutate(text = str_replace_all(text, "[\'’](?!\\s)", "' ")) %>%
corpus()
sep.corpus <- sepolcri %>%
mutate(text = str_replace_all(text, "[\'’](?!\\s)", "' ")) %>%
corpus()
# METADATI
docnames(sanremo)
# metadati dei documenti
docvars(sanremo_21)
docvars(sanremo_21)$cantante
sanremo_21$cantante
# metadati del corpus
meta(sanremo_21, type = "all")
meta(sanremo_21, type = "system")
meta(sanremo_21, type = "object")
# modificare i metadati
meta(sanremo_21,
field = "fonte") <- "angolotesti.it"
# in fase di costruzione del corpus
sanremo <- corpus(canzoni, meta =
list("fonte" = "angolotesti.it"))
meta(sanremo)
meta(sanremo_21, type = "user")
# informazioni sul corpus
summary(sanremo)
ndoc(sanremo)
nsentence(sanremo)
# statistiche del corpus --------------------------------------------------
# Quanteda
library(quanteda.textstats)
library(quanteda.textplots)
summary(sanremo)
# tabella di summary
summary(sanremo) %>% as.data.frame()
# textstat_summary
textstat_summary(sanremo)
# grafico a punti e linee
textstat_summary(sanremo_21) %>%
ggplot(aes(x = document, y = tokens, group = 1)) +
geom_line(col = palette()[1]) +
geom_point(col = palette()[2]) +
scale_x_discrete (labels = NULL) +
labs(x = "Sanremo 2021: brani", y = "Tokens")
detach("package:quanteda")
# TM ----------------------------------------------------------------------
library(tm)
# CORPUS
# da una directory
sanremo.tm <- DirSource("dati/canzoni") %>%
VCorpus(readerControl = list(language = "it-IT"))
# da un dataframe
sanremo21.tm <- DataframeSource(sanremo21) %>%
VCorpus(readerControl = list(language = "it-IT"))
# correggere gli apostrofi prima
sanremo21.tm <- sanremo21 %>%
mutate(text = str_replace_all(text, "[\'’](?!\\s)", "' ")) %>%
DataframeSource() %>%
VCorpus(readerControl = list(language = "it-IT"))
summary(sanremo21.tm)
# inspect
inspect(sanremo21.tm)
# primi tre documenti
inspect(sanremo21.tm[1:3])
# METADATI
# visualizzare i metadati (del corpus)
meta(sanremo21.tm, type="corpus")
sanremo21.tm$meta
# modificare i metadati
meta(sanremo21.tm, type="corpus", tag = "fonte") <- "angolotesti.it"
# oppure
sanremo21.tm$meta$fonte <- "angolotesti.it"
# controllare
meta(sanremo21.tm, type="corpus")
# tag
sanremo21.tm$meta$fonte
# metadati dei documenti
meta(sanremo21.tm, type = "indexed")
# o
meta(sanremo21.tm)
# singole variabili
meta(sanremo21.tm)$titolo
meta(sanremo21.tm)['titolo']
detach("package:tm")
# Conversione dei formati -------------------------------------------------
# QUANTEDA e TM
library(quanteda)
quanteda_options(language_stemmer ="italian")
# corpus tm -> corpus quanteda
corpus(sanremo21.tm)
detach("package:quanteda")
# TIDYTEXT
# corpus tm -> tibble
tidytext::tidy(sanremo21.tm)
# aggiungere le altre informazioni
bind_cols(tidytext::tidy(sanremo21.tm),
sanremo21.tm$dmeta,
'fonte' = sanremo21.tm$meta$fonte)
# corpus Quanteda -> tibble
tidytext::tidy(sanremo_21)
# metadati del corpus
tidytext::glance(sanremo_21)
bind_cols('doc_id' = quanteda::docnames(sanremo_21),
tidytext::tidy(sanremo_21),
tidytext::glance(sanremo_21))
Si vedano gli esempi nell’help della funzione.↩︎
Vedi: https://www.agnesevardanega.eu/wiki/r/gestione_dei_dati/grep_ricerca_sostituzione_testo↩︎
Per l’uso vedi la voce Importare i dati dai fogli Google↩︎
tm usa il linguaggio del NLP, mentre Quanteda usa il linguaggio delle matrici. I database hanno campi, a differenza delle unità linguistiche e delle liste.↩︎
Una funzione che tidytext mutua dal pacchetto broom (vedi voce: broom)↩︎