domenica 3 aprile 2022

Regressione polinomiale di secondo grado

Supponiamo di avere, per ciascuno di una serie di valori prefissati x (riportati in ascisse), il risultato di altrettante misure y (riportate in ordinate) e riportiamo con la funzione plot() i dati sotto forma di un grafico xy.

Copiante il codice che segue nella Console di R e premete ↵ Invio:

#
x <- c(20, 40, 60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280, 300) # dati in ascisse
y <- c(0.365, 0.399, 0.423, 0.448, 0.472, 0.494, 0.512, 0.530, 0.548, 0.576, 0.588, 0.596, 0.607, 0.621, 0.625) # dati in ordinate
plot(y~x, col="black", pch=1, main="Grafico xy", xlab="Valori prefissati (x)", ylab="Risultati delle misure effettuate (y)") # traccia il grafico xy
#
 
Dalla ispezione del grafico risulta evidente una relazione non lineare.


Questo ci fa escludere la possibilità di descrivere la relazione di funzione che intercorre tra le due variabili mediante una retta. E pone il tema di quale sia la curva che potrebbe meglio descriverla.

La prima cosa da valutare è se esiste un modello deterministico di un evento fisico, chimico-fisico, biologico, econometrico o quant'altro, che si assume descriva adeguatamente la relazione di funzione che intercorre tra le due variabili: in tal caso non resta che applicare l'equazione fornita dal modello. In caso contrario è necessario ricorrere ad un modello empirico: ed è questo che consideriamo essere il caso nostro.

Qualora si debba ricorrere ad un modello empirico, la regola fondamentale è impiegare l'equazione più semplice compatibile con una adeguata descrizione dei dati. Nel nostro caso visto che è evidente dall'ispezione del grafico che l'equazione di una retta 

y = a + b· ​⁠⁠x

calcolata mediante la classica regressione lineare con il metodo dei minimi quadrati sarebbe inadeguata a descrivere la relazione di funzione che lega la y alla x, possiamo pensare di ricorrere ad una equazione appena più complessa, come quella fornita da una regressione polinomiale di secondo grado nella forma

y = a + b· x + c· x²

e calcolata anch'essa con il metodo dei minimi quadrati [1].

Il razionale di questa scelta è basato sul fatto che i nostri dati descrivono una curva con la concavità rivolta in basso e un raggio di curvatura omogeneo, senza massimi, senza minimi e senza punti di flesso - cioè senza punti di passaggio tra due curvature che vanno in senso opposto - tutti elementi compatibili con il ramo ascendente di una parabola descritta appunto da un polinomio di secondo grado. 

Per capire se questa scelta genera una curva che approssima in modo adeguato i nostri dati, predisponiamo un breve script per calcolare e rappresentare una regressione polinomiale di secondo grado. 

Copiate lo script nella Console di R e premete ↵ Invio

# REGRESSIONE POLINOMIALE di secondo grado
#
x <- c(20, 40, 60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280, 300) # dati in ascisse
y <- c(0.365, 0.399, 0.423, 0.448, 0.472, 0.494, 0.512, 0.530, 0.548, 0.576, 0.588, 0.596, 0.607, 0.621, 0.625) # dati in ordinate
#
options(scipen=999) # esprime i numeri in formato fisso
#
# calcola la regressione 
#
polisec <- lm(y~poly(x, degree=2, raw=TRUE)) # calcola la regressione
summary(polisec) # riporta le statistiche della regressione
#
# traccia il grafico
#
newx <- seq(min(x), max(x), (max(x)-min(x))/1000) # valori x in corrispondenza dei quali calcolare il valore del polinomio
newy <- predict(polisec, list(x=newx)) # calcola il valore corrispondente del polinomio
plot(y~x, col="black", pch=1, main="Regressione polinomiale di secondo grado", xlab="Valori prefissati (x)", ylab="Risultati delle misure effettuate (y)") # predispone il grafico e riporta i dati
lines(newx, newy, col="black", lty=2, lwd=1) # traccia il polinomio
#
options(scipen=0) # ripristina la notazione scientifica
#

Le prime due righe di codice servono semplicemente ad assegnare (<-) i valori inclusi nella funzione c() al vettore x e al vettore y che in questo modo conterranno i nostri dati.

Poi mediante la funzione options() e con l'argomento scipen=999 facciamo si che i risultati numerici siano espressi in formato fisso, cosa che li renderà meglio leggibili (questo almeno a mio modo di vedere: se volete lasciare i risultati nella notazione scientifica eliminate questa riga di codice o, forse meglio, anteponete a questa riga di codice il simbolo #).

La quarta riga di codice assegna (<-) all'oggetto polisec mediante la funzione lm() i risultati del calcolo della regressione polinomiale y~poly(x, ...) di secondo grado (degree=2). L'argomento raw=TRUE è impiegato perché non ci interessano i polinomi ortogonali (l'opzione di default è raw=FALSE).

Se siete interessati ad approfondimenti, ricordo che potete visualizzare ad esempio la struttura dell'oggetto polisec digitando str(polisec) e potete avere informazioni sulle funzioni impiegate nello script digitando help(nomedellafunzione).

Alla quinta riga di codice la funzione summary(polisec) consente di visualizzare le statistiche della regressione polinomiale:

Call:
lm(formula = y ~ poly(x, degree = 2, raw = TRUE))
Residuals:
       Min         1Q     Median         3Q        Max 
-0.0043421 -0.0023941 -0.0000973  0.0013490  0.0074455 
Coefficients:
                                      Estimate    Std. Error t value             Pr(>|t|)    
(Intercept)                       0.3371692308  0.0031879149  105.77 < 0.0000000000000002 ***
poly(x, degree = 2, raw = TRUE)1  0.0015339463  0.0000458427   33.46    0.000000000000322 ***
poly(x, degree = 2, raw = TRUE)2 -0.0000018851  0.0000001393  -13.53    0.000000012542671 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 0.003579 on 12 degrees of freedom
Multiple R-squared:  0.9985,    Adjusted R-squared:  0.9982 
F-statistic:  3879 on 2 and 12 DF,  p-value: < 0.00000000000000022

La voce Residuals riporta le statistiche delle differenze che, dopo l'approssimazione dei punti con il polinomio di secondo grado, residuano tra i punti stessi e la curva calcolata: differenze piccole come quelle qui osservate indicano un'ottima approssimazione della curva ai dati.

La voce Coefficients riporta dall'alto verso il basso il valore stimato (Estimate) dell'intercetta, del coefficiente di primo grado (x) e del coefficiente di secondo  grado () e ci consente di scrivere l'equazione del polinomio che sarà quindi 

y = 0.3371692308 + 0.0015339463· x - 0.0000018851· x²

Per ciascun coefficiente viene calcolato il valore del test t di Student (t value) come rapporto tra il valore stimato (Estimate) e il suo errore standard (Std. Error) e viene infine riportato il valore p di probabilità di osservare per caso il valore di t riportato: se tale probabilità è sufficientemente bassa ci assumiamo il rischio di considerare significativo il coefficiente in questione. Nel nostro caso essendo i valori di p sempre molto piccoli (di gran lunga inferiori al valore p = 0.05 generalmente considerato come valore soglia per la significatività) possiamo affermare che tutti e tre i coefficienti del polinomio di secondo grado sono significativi e lo rendono un'equazione in grado di approssimare adeguatamente i dati forniti. 

Il valore del coefficiente di correlazione multipla R² molto elevato (Multiple R-squared: 0.9985) da una misura della bontà dell'approssimazione, confermata dalla sua significatività con il test F (p-value: < 0.00000000000000022).

Infine in quattro passaggi viene tracciato il grafico che riporta i punti e traccia il polinomio che li approssima:
→ l'intervallo compreso tra il valore minimo min(x) della x e il valore massimo max(x) della x viene suddiviso per ottenere i valori x (salvati nell'oggetto newxin corrispondenza dei quali calcolare il valore del polinomio;
→ i valori y del polinomio corrispondenti alle x contenute in newx sono calcolati e riportati nell'oggetto newy;
→ la funzione plot() viene impiegata per riportare sul grafico di dati contenuti in x e in y;
→ la funzione lines() viene impiegata per sovrapporre ai dati il tratto di polinomio calcolato e salvato in newx e in newy.

Potete riportare i dati modificando il simbolo impiegato (pch=...) e il suo colore (col="..."), come pure tracciare il polinomio modificando l'aspetto o stile della linea tracciata (lty=...), il suo colore (col="...") e il suo spessore (lwd=...) [2].

Il grafico - complemento ai test statistici da cui non è possibile prescindere - conferma che la regressione polinomiale di secondo grado fornisce una eccellente approssimazione ai dati.


Da notare che il grafico è stato tracciato esclusivamente all'interno dei dati, quindi in un'ottica di interpolazione: e questo ci ricorda anche che, trattandosi di un modello empirico, è da escludere l'impiego del polinomio di secondo grado per la estrapolazione, cioè per trarre conclusioni al di fuori dei dati impiegati per calcolarlo. 

A chiudere lo script è la funzione options() che con l'argomento scipen=0 ripristina l'espressione dei numeri nella notazione scientifica [3].

Lo script può essere riutilizzato semplicemente riportando manualmente in x e in y i nuovi dati, o meglio ancora importandoli da un file [4].

Se siete interessati al tema trovate in un altro post come - in assenza di un'equazione basata su un modello teorico - situazioni più complesse, ma che si possono riscontrare nella pratica, possono essere affrontate impiegando per approssimare i punti la regressione polinomiale di terzo grado.


----------

[1] Il metodo dei minimi quadrati prevede - ed è quello che abbiamo assunto essere il nostro caso - che la variabile in ascisse (x) abbia valori prefissati, che la variabile in ordinate (y) sia rappresentata da misure (affette da un errore casuale distribuito normalmente) effettuate corrispondenza delle x e minimizza la somma dei quadrati delle differenze y - y' ovvero delle differenze tra ciascun valore misurato y e il valore y' = f(x) corrispondente calcolato mediante la regressione (lineare o polinomiale). Esempi possono essere la risposta y di un sensore misurata in corrispondenza di una serie di concentrazioni date x di una sostanza chimica, la misurazione di un evento y in corrispondenza di una serie data di tempi x, e così via.

[2] Trovate le relative indicazioni:

[3] La notazione scientifica è una notazione esponenziale, cioè una notazione che consente di scrivere un numero N assegnato come prodotto di un opportuno numero a per la potenza di un altro numero bk essendo b la base della notazione esponenziale. In particolare la notazione scientifica è una notazione esponenziale in base 10 (b = 10). Tuttavia "notazione esponenziale" non è sinonimo di "notazione scientificain quanto esistono notazioni esponenziali che impiegano una base diversa da quella impiegata dalla notazione scientifica: ne sono un esempio la notazione esponenziale in base 2 (b = 2impiegata in informatica, e la notazione esponenziale in base e (b = e) - essendo il numero di Nepero e 2.718 281 828 459... - impiegata in campo matematico e in campo scientifico.
[Precisazione aggiuntiva: in R il separatore delle cifre decimali è il punto (.) e come già riportato altrove questa convenzione per ragioni di omogeneità viene adottata non solo negli script ma anche nei file di dati e in tutto il testo di questo sito, quindi anche nell'espressione del numero e di Nepero. Per le norme che regolano la punteggiatura all'interno dei numeri rimando al post Come separare i decimali e come raggruppare le cifre].

[4]  Alla pagina Indice nel capitolo R - gestione dei dati trovate i collegamenti ai post che contengono gli script per importare i dati da file esterni.

mercoledì 2 febbraio 2022

Come separare i decimali e come raggruppare le cifre

Nel mondo anglosassone 3.142 è un numero con tre cifre decimali, mentre 302,073 sta per trecentoduemilasettantatre.

In Italia e in genere in Europa il primo, il valore (arrotondato) di pi greco (π), si scrive 3,142 mentre il secondo, la superficie dell'Italia espressa in km2, si trova sovente riportato, anche in fonti ufficiali, come 302.073 [1].

Assumendo che il lettore, europeo o anglofono, non sappia di avere a che fare con pi-greco e con la superficie dell'Italia, quindi che interpreti il valore numerico alla luce della sola informazione derivante dal proprio contesto linguistico, il significato attribuito a questi numeri è il seguente:



Significato in relazione al contesto linguistico 
anglosassone
europeo
3,142Tremila ...Tre virgola ...
3.142Tre virgola...Tremila...
302,073Trecentoduemila ...
Trecentodue virgola ...
302.073
Trecentodue virgola ...
Trecentoduemila ...

Il significato del punto (.) e della virgola (,) dipende dal contesto linguistico a causa del fatto che sono entrambi usati, a livello internazionale, sia come separatori decimali sia come simboli per il raggruppamento delle cifre. Ma si tratta di una prassi errata.

Perché? 

Perché l'Italia ha aderito alla Convenzione del Metro il 20 maggio 1875 adottando il sistema metrico decimale – che successivamente si è evoluto nei sistemi di unità di misura denominati prima MKS (nel 1913) e poi MKSA (nel 1954) – e ha nel 1982 adottato per legge [2] il loro moderno erede, il Sistema Internazionale di Unità (SI) così battezzato ufficialmente nel 1960 [3, 4]. 

E perché nella XXII Conferenza Generale dei Pesi e delle Misure del 2003, nella risoluzione 10, la CGPM – in sostanza il Parlamento dei Paesi che come l'Italia aderiscono alla Convenzione del Metro [5] – ha stabilito che:
→ "il simbolo per il marcatore decimale deve essere il punto sulla linea [di base] o la virgola sulla linea [di base]", ammettendo quindi, per ragioni pratiche/linguistiche, la coesistenza di questi due simboli;
→ "i numeri possono essere suddivisi in gruppi di tre per facilitare la lettura; [ma] né punti né virgole devono essere inseriti negli spazi tra i gruppi, come stabilito nella risoluzione 7 della IX CGPM, 1948".

Le regole dettate dalla CGPM, concordate addirittura nel lontano 1948, sono quindi molto semplici:
→ per separare i decimali devono essere impiegati o il punto (.) o la virgola (,) liberamente, in modo consono all'uso che si è andato consolidando nel tempo e al contesto linguistico e culturale;
raggruppare le cifre è facoltativo ma, nel caso in cui lo si faccia per rendere più agevole la lettura, può essere impiegato esclusivamente lo spazio, visto che per definizione "... né punti né virgole devono essere inseriti negli spazi tra i gruppi...".

Questo cambia tutto, perché se fossero applicate queste semplici regole allora il punto (.) e la virgola (,) sarebbero sempre correttamente intesi per quello che sono, cioè entrambi, per definizione, come separatori dei decimali e mai come simboli per il raggruppamento delle cifre, e la situazione diventerebbe questa:



Significato in relazione al contesto linguistico 
anglosassone
europeo
3,142 o 3.142Tre virgola ...Tre virgola ...
302 073Trecentoduemila...Trecentoduemila...
302,073Trecentoduemila ...Trecentodue virgola ...
302.073Trecentodue virgola ...Trecentoduemila ...

Alla luce di queste regole un anglosassone che leggesse un 3,142 lo interpreterebbe per definizione alla stessa stregua di un 3.142 e un europeo che leggesse un 3.142 lo interpreterebbe alla stessa stregua di un 3,142. A fronte di una diversa scelta nell'impiego del separatore dei decimali (ma “de gustibus non disputandum est) l'interpretazione sarebbe univoca. E ovviamente nessun dubbio potrebbe mai sorgere nella lettura del 302 073 come trecentoduemilasettantatre.

Qualcuno potrebbe far notare che volendo impiegare lo spazio per raggruppare le cifre rimangono comunque da risolvere due problemi legati al comportamento dei programmi di videoscrittura, che:
→ nel caso in cui si impiega la giustificazione del testo, ampliano e riducono automaticamente gli spazi vuoti ai fini della miglior composizione della riga;
→ potrebbero andare a capo, passando alla riga successiva, proprio a livello dello spazio che separa un gruppo di cifre da quello che lo segue.

Ma questi due problemi sono già stati risolti. Oggi qualsiasi programma di videoscrittura consente di impiegare, oltre allo spazio normale, che si inserisce in un testo con la barra spaziatrice, anche il carattere spazio unificatore: questo spazio non cambia ampiezza e non consente di andare a capo, lasciando quindi il testo che lo include sempre correttamente impaginato.

Lo spazio unificatore, che nella codifica Unicode è denominato NO-BREAK SPACE (NBSP) e ha il codice U+00A0 [6]:
su Windows può essere inserito tenendo premuti contemporaneamente i tasti <ctrl> e <shift> e premendo la barra spaziatrice, oppure tenendo premuto il tasto <alt> e digitando 255 sul tastierino numerico;
su Mac può essere inserito tenendo premuto il tasto <alt> e premendo la barra spaziatrice;
su Linux può essere inserito tenendo premuti contemporaneamente i tasti <ctrl> e <shift> e premendo la barra spaziatrice o tenendo premuto il tasto <shift> e premendo la barra spaziatrice o ricorrendo a una apposita configurazione.

Per separare i decimali R impiega di default e al proprio interno esclusivamente il punto (.) ma permette di importare e di esportare i dati impiegando come separatore dei decimali sia il punto (.) sia la virgola (,) [7].

Se dovete preparare un documento nel quale i numeri (e.g.: 16 miliardi di bit) sono riportati utilizzando la notazione scientifica di default in (e.g.: 16e+09 bit), o utilizzando i multipli e sottomultipli del SI (e.g.: 16 gigabit) [8], il problema dello spazio unificatore non si pone. Ma se volete esprimere i numeri in formato fisso (e.g.: 16 000 000 000 bit) [9] ricordate che:

  per facilitarne la lettura i numeri superiori a 999 possono [facoltativamente] essere suddivisi in gruppi di tre cifre [ma esclusivamente] impiegando lo spazio [unificatore].

E c'è pure un altro problema: attualmente in Italia i fogli elettronici, in barba alla razionalità e all'ufficialità dell'approccio definito dalla CGPM, impiegano di default il punto per raggruppare le cifre. Gli utilizzatori si adeguano (difficile che qualcuno si prenda la briga di andare a riconfigurare le opzioni di formattazione dei numeri) e questo induce a perpetuare una errata modalità di rappresentazione.

Conclusione: temo che sarà necessario attendere ancora prima di vedere universalmente e senza eccezione alcuna adottata la prassi corretta che prevede di:
  separare i decimali impiegando indifferentemente il punto o la virgola (la scelta dipenderà dal contesto linguistico/culturale) con la certezza che i due si equivalgono e che all'interno dei numeri non sono impiegati in altro modo;
  raggruppare le cifre (al bisogno) impiegando esclusivamente lo spazio unificatore.

Ma sarebbe il classico uovo di Colombo, in quanto queste regole consentono di semplificare, eliminare tutti i potenziali errori di interpretazione e garantire una univoca e universale comprensione, che è poi il razionale alla base del SI.


----------

[1] Istat. Principali dimensioni geostatistiche e grado di urbanizzazione del Paese. Superficie territoriale per zona altimetrica.
https://www.istat.it/non-categorizzato/principali-dimensioni-geostatistiche-e-grado-di-urbanizzazione-del-paese/

[2] DECRETO DEL PRESIDENTE DELLA REPUBBLICA 12 agosto 1982, n. 802 Attuazione della direttiva (CEE) n. 80/181 relativa alle unita' di misura. GU Serie Generale n.302 del 03-11-1982 - Suppl. Ordinario.
https://www.gazzettaufficiale.it/eli/id/1982/11/03/082U0802/sg

[3] Per una introduzione al SI, che riporta anche le indicazioni delle modifiche apportate al sistema dopo l'adesione ufficiale dell'Italia avvenuta nel 1982, vedere: Grandezze e unità di misura. Breve storia dall'antichità al Sistema Internazionale di Unità (SI).

[4] Sul sito del Bureau International des Poids et Mesures - in una delle due sole lingue adottate, inglese e francese - si trova il documento ufficiale "SI Brochure: The International System of Units (SI)" ovvero "Brochure sur le SI: Le Système international d'unités".
https://www.bipm.org/en/publications/si-brochure

[5] La CGPM (Conférence Générale des Poids et Mesures) viene convocata periodicamente, ogni quattro o sei anni, e discute e approva unità, terminologia e raccomandazioni del Sistema internazionale di unità (SI)

[6] Unicode Explorer, U+00A0 NO-BREAK SPACE
https://unicode-explorer.com/c/00A0



lunedì 10 gennaio 2022

Gestione delle date del calendario

Se avete la necessità di gestire le date del calendario, dovete partire dal fatto che R assume essere date del calendario quelle contenute in oggetti della classe "Date" ed espresse in uno specifico formato.

Vediamo di capire questa cosa con lo script che segue, copiatelo e incollatelo nella Console di R e premete ↵ Invio:

# GESTIONE DELLE DATE DEL CALENDARIO CON R - parte prima
#
# (1/5)
lemiedate <- c("13/05/1998", "16/12/1945", "23/09/2012") # i dati inseriti
lemiedate # il contenuto dell'oggetto 
class(lemiedate) # la classe dell'oggetto
str(lemiedate) # la struttura dell'oggetto
#
# (2/5)
con_asDate <- as.Date(lemiedate, format="%d/%m/%Y") # i dati  trasformati
con_asDate # il contenuto dell'oggetto
class(con_asDate) # la classe dell'oggetto
str(con_asDate) # la struttura dell'oggetto
#
# (3/5)
con_format <- format(con_asDate, "%d/%m/%Y") # i dati trasformati
con_format # il contenuto dell'oggetto
class(con_format) # la classe dell'oggetto
str(con_format) # la struttura dell'oggetto
#
# (4/5)
as.Date("23/09/2012", format="%d/%m/%Y") # dati in formato gg/mm/aaaa
#
as.Date("09/23/2012", format="%m/%d/%Y") # dati in formato mm/gg/aaaa
#
as.Date("2012/09/23", format="%Y/%m/%d") # dati in formato aaaa/mm/gg
#
as.Date("23 settembre 2012", format="%d %B %Y") # dati in formato gg mese aaaa con spazio come separatore
#
as.Date("09-3-2012", format="%m-%d-%Y") # dati in formato mm-gg-aaaa con - come separatore
#
# (5/5)
library(lubridate)
dmy(c("13/05/1998", "16/12/1945", "23/09/2012"))
#

Questo è il risultato della prima parte dello script:

> # (1/5)
> lemiedate <- c("13/05/1998", "16/12/1945", "23/09/2012") # i dati inseriti
> lemiedate # il contenuto dell'oggetto 
[1] "13/05/1998" "16/12/1945" "23/09/2012"
> class(lemiedate) # la classe dell'oggetto
[1] "character"
> str(lemiedate) # la struttura dell'oggetto
 chr [1:3] "13/05/1998" "16/12/1945" "23/09/2012"
> #

In pratica siamo partiti dal presupposto che
→ 13/05/1998
→ 16/12/1945
→ 23/09/2012
sono tre date del calendario che vogliamo gestire con R. Le abbiamo inserite nell'oggetto lemiedate con la funzione c()

> lemiedate <- c("13/05/1998", "16/12/1945", "23/09/2012") # i dati inseriti

le abbiamo ritrovate digitando il nome dell'oggetto

> lemiedate # il contenuto dell'oggetto 
[1] "13/05/1998" "16/12/1945" "23/09/2012"

con la funzione class() che mostra la classe di un oggetto vediamo che R ha identificato le tre date come "character"

> class(lemiedate) # la classe dell'oggetto
[1] "character"

e con la funzione str() che mostra la struttura interna di un oggetto ne abbiamo riportato un ulteriore riepilogo sintetico 

> str(lemiedate) # la struttura dell'oggetto
 chr [1:3] "13/05/1998" "16/12/1945" "23/09/2012"

nel quale "chr" sta di nuovo per "character", la classe dell'oggetto. 

Quindi inserendo i dati con la funzione c() quelle che per noi erano tre date sono state identificate in R come "character", cioè come semplici stringhe o in altre parole come tre sequenze di caratteri alfanumerici: in pratica invece delle date avremmo potuto inserire "ciao Giovanni", "domani farà bel tempo", "viva la pappa col pomodoro”" o quant'altro.

Vediamo ora cosa accade con il successivo e secondo blocco di codice:

> # (2/5)
> con_asDate <- as.Date(lemiedate, format="%d/%m/%Y") # i dati  trasformati
> con_asDate # il contenuto dell'oggetto
[1] "1998-05-13" "1945-12-16" "2012-09-23"
> class(con_asDate) # la classe dell'oggetto
[1] "Date"
> str(con_asDate) # la struttura dell'oggetto
 Date[1:3], format: "1998-05-13" "1945-12-16" "2012-09-23"
> #

Questa volta impieghiamo la funzione as.Date() per inserire lemiedate (l'oggetto della classe "character" contenente tre stringhe alfanumeriche) in un nuovo oggetto denominato con_asDate (giusto per ricordarci come lo abbiamo generato) 

> con_asDate <- as.Date(lemiedate, format="%d/%m/%Y") # i dati  trasformati

Dopo avere ritrovato quello che abbiamo inserito digitando il nome dell'oggetto

> con_asDate # il contenuto dell'oggetto
[1] "1998-05-13" "1945-12-16" "2012-09-23"

con la funzione class() 

> class(con_asDate) # la classe dell'oggetto
[1] "Date"

e con la funzione str() 

> str(con_asDate) # la struttura dell'oggetto
 Date[1:3], format: "1998-05-13" "1945-12-16" "2012-09-23"
> #

vediamo che con_asDate è un oggetto della classe "Date" nel quale in R per definizione sono contenute date del calendario

L'argomento format="...." impiegato nella funzione as.Date() specifica sia l'ordine sia il formato dei dati in ingresso con i seguenti codici:

Codice    Valore
%d           Giorno del mese (numero decimale)
%m          Mese (numero decimale)
%b           Mese (abbreviato)
%B           Mese (nome esteso)
%y           Anno (2 cifre)
%Y           Anno (4 cifre) 

Se ora nella Console di R digitate help(as.Date) si apre una pagina web con la definizione della funzione in questione, che è definita come "la funzione che converte le rappresentazioni [di date] sotto forma di caratteri [stringhe di testo] in oggetti della classe "Date" che rappresentano date di calendario".

Come certamente avrete notato una volta importate in R con la funzione asDate() le date sono state convertite nel formato aaaa-mm-gg. A questo punto la domanda è d'obbligo: e se volessi tornare a convertirle nel formato gg/mm/aaaa? La cosa è possibile, e vediamo come con la terza parte di codice:

> # (3/5)
> con_format <- format(con_asDate, "%d/%m/%Y") # i dati trasformati
> con_format # il contenuto dell'oggetto
[1] "13/05/1998" "16/12/1945" "23/09/2012"
> class(con_format) # la classe dell'oggetto
[1] "character"
> str(con_format) # la struttura dell'oggetto
 chr [1:3] "13/05/1998" "16/12/1945" "23/09/2012"
> #

Con la funzione format() - da non confondere con l'argomento format= della funzione asDate() - possiamo convertire le date dal formato aaaa-mm-gg nel quale erano nell'oggetto con_asDate al formato gg/mm/aaaa specificato con l'argomento "%d/%m/%Y"

> con_format <- format(con_asDate, "%d/%m/%Y") # i dati trasformati

Questo può essere utile ad esempio se vogliamo visualizzare o stampare le date in questo formato. Ma in questo modo le date per R non sono più tali. Se esaminiamo il nuovo oggetto con_format 

> con_format # il contenuto dell'oggetto
[1] "13/05/1998" "16/12/1945" "23/09/2012"

con la funzione class() 

> class(con_format) # la classe dell'oggetto
[1] "character"

e con la funzione str() 

> str(con_format) # la struttura dell'oggetto
 chr [1:3] "13/05/1998" "16/12/1945" "23/09/2012"
> #

vediamo che si tratta di un oggetto di classe "character": imponendo il formato gg/mm/aaaa le date tornano ad essere nuovamente delle stringhe ovvero generiche sequenze di caratteri alfanumerici esattamente come quelle da cui eravamo partiti.

Quindi concludendo: le date del calendario in R sono per definizione quelle contenute in un oggetto della classe “Date” ed espresse nel formato aaaa-mm-gg.

Vediamo ora alcuni altri esempi di date importate in R mediante la funzione as.Date():

> # (4/5)
> as.Date("23/09/2012", format="%d/%m/%Y") # dati in formato gg/mm/aaaa
[1] "2012-09-23"
> #
> as.Date("09/23/2012", format="%m/%d/%Y") # dati in formato mm/gg/aaaa
[1] "2012-09-23"
> #
> as.Date("2012/09/23", format="%Y/%m/%d") # dati in formato aaaa/mm/gg
[1] "2012-09-23"
> #
> as.Date("23 settembre 2012", format="%d %B %Y") # dati in formato gg mese aaaa con spazio come separatore
[1] "2012-09-23"
> #
> as.Date("09-3-2012", format="%m-%d-%Y") # dati in formato mm-gg-aaaa con - come separatore
[1] "2012-09-03"
> #

I risultati confermano che se specificate opportunamente l'argomento format= dei dati in ingresso potete rappresentare sotto forma di stringhe alfanumeriche le vostre date predisposte:
→ nel formato gg/mm/aaaa
→ nel formato mm/gg/aaaa
→ nel formato aaaa/gg/mm
→ nel formato gg mese aaaa impiegando lo spazio come separatore
→ nel formato mm-gg-aaaa impiegando come separatore il tratto a metà altezza - 
ovvero in altri modi purché sia correttamente e completamente specificato il formato dei dati in ingresso.

Se installate il pacchetto lubridate potete infine fruire di una sintassi più concisa come nell'esempio che segue, che porta ovviamente allo stesso risultato

> # (5/5)
> library(lubridate)
> dmy(c("13/05/1998", "16/12/1945", "23/09/2012"))
[1] "1998-05-13" "1945-12-16" "2012-09-23"

Vediamo ora cosa accade nel caso di date importate da un file in formato Excel (.xls o .xlsx) o, meglio ancora, in formato .csv (il formato dei dati da importare consigliato da R).

Andate alla pagina Dati nella quale trovate diverse opzioni per scaricare i file di dati, quindi scaricate e copiate nella cartella C:\Rdati\ il file PA.xls e il file PA.csv 

Nei file sono riportati i valori della pressione arteriosa sistolica e diastolica e della frequenza cardiaca registrati alle date del calendario riportate nella variabile "Data" (prima colonna).


Se ancora non l'avete fatto, scaricate dal CRAN e installate il pacchetto xlsx, quindi copiate e incollate nella Console di R questo script e premete ↵ Invio:

# GESTIONE DELLE DATE DEL CALENDARIO CON R - parte seconda
#
# importare le date da un file .xls 
#
require(xlsx) # carica il pacchetto xlsx
PA_xls <- read.xlsx("C:/Rdati/PA.xls", sheetName="M") # importa i dati del file e del foglio specificati
PA_xls # mostra la tabella che contiene i dati
str(PA_xls) # la variabile Data viene riconosciuta come una data del calendario (classe "Date")
#
# importare le date da un file .csv 
#
PA_csv <- read.table("C:/Rdati/PA.csv", header=TRUE, sep=",") # carica i dati del file specificato
PA_csv # mostra la tabella che contiene i dati
str(PA_csv) # la variabile Data viene riconosciuta come una semplice stringa di testo (classe "character")
PA_csv$Data <- as.Date(PA_csv$Data, format="%d/%m/%Y") # la stringa Data viene convertita con as.Date
str(PA_csv) # la variabile Data viene ora riconosciuta come una data del calendario
#
# elaborare le date (esempi) 
#
Sys.Date()
PA_xls$Data[13] - PA_xls$Data[1]
PA_xls$Data[13] + 32
weekdays(PA_csv$Data[1])
#

Dopo avere caricato il pacchetto xlsx

> require(xlsx) # carica il pacchetto xlsx
Caricamento del pacchetto richiesto: xlsx

i dati sono importati dal foglio M del file PA.xls 

> PA_xls <- read.xlsx("C:/Rdati/PA.xls", sheetName="M") # importa i dati del file e del foglio specificati

e richiamando il nome dell'oggetto (PA_xls) nel quale sono stati importati vediamo che i dati sono stati importati correttamente

> PA_xls # mostra la tabella che contiene i dati
         Data Sistolica Diastolica Frequenza
1  2021-05-07       112         64        50
2  2021-05-08       118         69        49
3  2021-05-08       101         57        62
4  2021-05-09       111         65        56
5  2021-05-09       117         67        60
6  2021-05-10       110         69        59
7  2021-05-10       109         63        53
8  2021-05-16       115         66        49
9  2021-09-17       121         65        48
10 2021-09-17        92         58        63
11 2021-09-17       131         79        68
12 2021-09-17       116         68        57
13 2021-09-29       114         69        56

La funzione str() ci conferma che anche la variabile Data è stata correttamente identificata come “Date” quindi il pacchetto xlsx ha trasferito i dati della prima colonna in modo tale che R li ha riconosciuti e li riporta come date del calendario.

> str(PA_xls) # la variabile Data viene riconosciuta come una data del calendario (classe "Date")
'data.frame':   13 obs. of  4 variables:
 $ Data      : Date, format: "2021-05-07" "2021-05-08" ...
 $ Sistolica : num  112 118 101 111 117 110 109 115 121 92 ...
 $ Diastolica: num  64 69 57 65 67 69 63 66 65 58 ...
 $ Frequenza : num  50 49 62 56 60 59 53 49 48 63 ...
> #

Vediamo ora cosa accade quando si importano i dati da un file .csv

Dopo avere importato i dati nell'oggetto PA_csv 

> # importare le date da un file .csv 
> #
> PA_csv <- read.table("C:/Rdati/PA.csv", header=TRUE, sep=",") # carica i dati del file specificato

e averli visualizzati richiamando il nome dell'oggetto nel quale sono stati importati   

> PA_csv # mostra la tabella che contiene i dati
         Data Sistolica Diastolica Frequenza
1  07/05/2021       112         64        50
2  08/05/2021       118         69        49
3  08/05/2021       101         57        62
4  09/05/2021       111         65        56
5  09/05/2021       117         67        60
6  10/05/2021       110         69        59
7  10/05/2021       109         63        53
8  16/05/2021       115         66        49
9  17/09/2021       121         65        48
10 17/09/2021        92         58        63
11 17/09/2021       131         79        68
12 17/09/2021       116         68        57
13 29/09/2021       114         69        56

con la funzione str() vediamo che questa volta la variabile Data è stata identificata come una stringa / generica sequenza di caratteri alfanumerici (chr)

> str(PA_csv) # la variabile Data viene riconosciuta come una semplice stringa di testo (classe "character")
'data.frame':   13 obs. of  4 variables:
 $ Data      : chr  "07/05/2021" "08/05/2021" "08/05/2021" "09/05/2021" ...
 $ Sistolica : int  112 118 101 111 117 110 109 115 121 92 ...
 $ Diastolica: int  64 69 57 65 67 69 63 66 65 58 ...
 $ Frequenza : int  50 49 62 56 60 59 53 49 48 63 ...
> # 

Questo accade perché nel file PA.csv che aperto con un editor di testo così ci appare

Data,Sistolica,Diastolica,Frequenza
07/05/2021,112,64,50
08/05/2021,118,69,49
08/05/2021,101,57,62
09/05/2021,111,65,56
09/05/2021,117,67,60
10/05/2021,110,69,59
10/05/2021,109,63,53
16/05/2021,115,66,49
17/09/2021,121,65,48
17/09/2021,92,58,63
17/09/2021,131,79,68
17/09/2021,116,68,57
29/09/2021,114,69,56

non è riportata alcuna informazione che consenta ad R di identificare la variabile Data come una data del calendario (nel file .xls questa informazione c'è, anche se non potete visualizzarla trattandosi di un file binario).

Nel caso di dati importati da un file .csv - che di fatto è un semplice file di testo - è pertanto necessario impiegare la funzione as.Date() per convertire le date/stringhe in ingresso in date del calendario:

> PA_csv$Data <- as.Date(PA_csv$Data, format="%d/%m/%Y") # la stringa Data viene convertita con as.Date
> str(PA_csv) # la variabile Data viene ora riconosciuta come una data del calendario
'data.frame':   13 obs. of  4 variables:
 $ Data      : Date, format: "2021-05-07" "2021-05-08" ...
 $ Sistolica : int  112 118 101 111 117 110 109 115 121 92 ...
 $ Diastolica: int  64 69 57 65 67 69 63 66 65 58 ...
 $ Frequenza : int  50 49 62 56 60 59 53 49 48 63 ...
> #

Questa conversione è necessaria se vogliamo che R riconosca le operazioni che è possibile eseguire su questi oggetti, che saranno, appunto, solamente le operazioni applicabili alle date del calendario.

A questo punto la domanda: ma perché fare tanta fatica per arrivare fin qui? La risposta la trovate a conclusione dello script, laddove sono riportate alcune delle possibilità offerte da R nella gestione delle date che prima o poi possono tornare utili, come ad esempio:

→ la data attuale presente sul sistema

> Sys.Date()
[1] "2021-11-29"

→ la differenza in giorni tra due date

> PA_xls$Data[13] - PA_xls$Data[1]
Time difference of 145 days

→ la data corrispondente a una specifica differenza in giorni rispetto a una data di riferimento

> PA_xls$Data[13] + 32
[1] "2021-10-31"

→ il giorno della settimana corrispondente ad una specifica data 

> weekdays(PA_csv$Data[1])
[1] "venerdì"
> #

In un prossimo post vedremo come gestire contemporaneamente data e ora.


----------