[1] 5
Vedremo i principali costrutti della programmazione e la loro applicazione in R. Molti dei concetti presentati sono trasversali, quindi applicabili anche ad altri linguaggi di programmazione. Qui affronteremo gli aspetti più basici, per applicazioni più avanzate vi suggeriamo il libro: Advanced R
Costrutti della programmazione
Programmazione condizionale
Programmazione iterativa
Analogalmente alle funzioni matematiche, la funzione in programmazione consiste nell’ astrarre una serie di operazioni (nel nostro caso una porzione di codice) definendo una serie di operazioni che forniti degli input forniscono degli output eseguendo una serie di operazioni.
Il comando usato per creare una funzione in R è function()
seguito da una coppia di parentesi graffe { }
al cui interno deve essere specificato il corpo della funzione:
La funzione che ho creato prende in input x e y, li somma, e fornisce in output il risulato.
E’ possibile svolgere svariate operazioni dentro una sola funzione. E’ preferibile usare il comando return()
per definire esplicitamente l’ouput che desideriamo, per esempio…
Prendiamo un operazione ripetitiva che spesso si fa in analisi dati, standardizzare (trasformare in punti z) una variabile ovvero sottrarre da un vettore x di osservazioni la sua media \(\mu_x\) e poi dividere per la deviazione standard \(\sigma_x\) :
\[ x_z = \frac{x - \mu_x}{\sigma_x} \]
Per creare questa funzione dobbiamo quindi definire:
argomenti: variabili da definire (se non già definite)
corpo: le operazioni che la funzione deve eseguire usando gli argomenti
output: cosa la funzione deve restituire come risultato
Gli argomenti sono quelle parti variabili della funzione che vengono definiti e poi sono necessari ad eseguire la funzione stessa. Nel caso della nostra funzione l’unico argomento è il vettore in input. Possiamo analogalmente a mean
e sd
impostare un secondo argomento che indichi se eliminare gli NA:
Il corpo della funzione sono le operazioni da eseguire utilizzando gli argomenti in input. Nel nostro caso dobbiamo sottrarre la media da e dividere per la deviazione standard.
L’output è il risultato che la funzione ci restituisce dopo aver eseguito tutte le operazioni. Nel nostro caso vogliamo che la funzione restituisca il vettore ma trasformato in punti z. Come abbiamo visto in precedenza, possiamo utilizzare la funzione return()
che esplicitamente dice alla funzione cosa restituire:
Abbiamo quindi creato questa funzione, che diventa un oggetto nel nostro enviroment e possiamo utilizzarla
{}
possono essere omesse nel caso in cui il codice sia tutto in una stessa rigareturn()
può essere omesso se l’ultima riga rappresenta l’output desideratoif
In programmazione solitamente è necessario non solo eseguire una serie di operazioni MA eseguire delle operazioni in funzione di alcune condizioni.
if
Il concetto di se condizione allora fai operazione si traduce in programmazione tramite quelli che si chiamano if
statement
if
statementVediamo un’esempio
if
statement - STOP
Esiste una famiglia di funzioni con prefisso is.
che fornisce TRUE
quando la tipologia di oggetto corrisponde a quella richiesta e FALSE
in caso contrario.
myfun_stop = function(x){ # argomento
if (!is.numeric(x)) { # utile quando vogliamo evitare che la funzione venga eseguita
stop("il vettore deve essere numerico")
}
mean(x, na.rm = TRUE)
}
x = 1:10 # vettore num
y = letters[1:10] # vettore chr
myfun_stop(x)
[1] 5.5
Error in myfun_stop(y): il vettore deve essere numerico
if...else
Il semplice utilizzo di un singolo if
potrebbe non essere sufficiente in alcune situazioni. Sopratutto perchè possiamo vedere l’if
come una deviazione temporanea dallo script principale che viene imboccata solo se è vera una condizione, altrimenti lo script continua.
if...else
Se vogliamo una struttura più “simmetrica” possiamo eseguire delle operazioni se la condizone è vera if
e altre per tutti gli altri scenari (else
).
if...else
Vediamo un esempio
if...else
- nestedPer poter capire quale struttura condizionale utilizzare è importante capire bene il problema che dobbiamo risolvere. Facciamo un esempio, immaginiamo di avere 2 tipi di dati in R: stringhe e numeri. In questo caso è sufficiente avere un if statement
che controlla se l’elemento è una stringa/numero e fa una determinata operazione.
Scriviamo una funzione che restituisca la media quando il vettore è numerico e la tabella di frequenza.
Testiamo la funzione
Un limite nell’utilizzare gli if statement
riguarda il fatto che funzionano solo su un singolo valore (i.e. non sono vettorizzati). Quindi mentre la funzione my_summary
funziona perchè valuta l’intero vettore come numerico (is.numeric()
)…
Se volessiamo utilizzare la funzione myfun_if
…
function (x)
{
if (x > 0) {
cat("Il valore è maggiore di 0\n")
}
cat("Fine funzione")
}
questa non funzionerebbe…
La versione vettorizzata si ottiene tramite la funzione ifelse()
, i cui argomenti sono la condizione da testare, l’output in caso la condizione risulti TRUE
, nel caso sia FALSE
Quando abbiamo bisogno di testare più alternative possiamo creare degli ifelse()
nested. Immaginiamo di avere un vettore age e voler creare un altro vettore dove l’età è divisa in 3 fasce, bambino, adulto, anziano:
dplyr::case_when
Quando le condizioni da testare sono numerose (indicativamente > 3) può essere tedioso scrivere molti ifelse() multipli. Possiamo allora usare la funzione dplyr::case_when()
del pacchetto dplyr
che è una generalizzazione di ifelse():
i risultati ottenuti sono identici…
dplyr::case_when
Ricodificare i valori di una variabile come ad esempio invertire gli item di un questionario è un operazione facilmente eseguibile in con dplyr::case_when()
:
[1] 5 5 5 4 1 5 1 1 5 1 4 5 5 5 1 1 5 1 5 4
dplyr::case_when
Queste funzioni si possono applicare anche alle variabili presenti in un dataframe:
# creo un dataframe con variabili id e età
mydf = data.frame(id = factor(1:30), age = sample(14:50, 30))
# lo salvo per dopo
readr::write_csv(mydf, file = "data/mydf.csv")
# creo una terza variabile con "adolescelte","giovane", "adulto
mydf$age_cat = with(mydf,
factor(
case_when( age > 30 ~ "adulto",
age <= 30 & age >= 20 ~ "giovane",
age < 20 ~ "adolescente",
TRUE ~ "errore" # check errori di codifica
)))
str(mydf)
'data.frame': 30 obs. of 3 variables:
$ id : Factor w/ 30 levels "1","2","3","4",..: 1 2 3 4 5 6 7 8 9 10 ...
$ age : int 47 48 33 34 43 49 22 28 37 40 ...
$ age_cat: Factor w/ 3 levels "adolescente",..: 2 2 2 2 2 2 3 3 2 2 ...
Abbiamo già visto che il comando library()
carica un certo pacchetto, rendendo le funzioni contenute disponibili all’utilizzo. Senza la necessità di creare un pacchetto, possiamo comunque organizzare le nostre funzioni in modo efficace.
Abbiamo due opzioni
scrivere le funzioni nello stesso script dove esse vengono utilizzate
scrivere uno script separato e importare tutte le funzioni contenute
Anche in questo caso è una questione di stile e comodità, in generale:
se abbiamo tante funzioni, è meglio scriverle in uno o più file separati e poi importarle all’inizio dello script principale
se abbiamo poche funzioni possiamo tenerle nello script principale, magari in una sezione apposita nella parte iniziale
Nel secondo caso è sufficiente quindi scrivere la funzione e questa sarà salvata come oggetto nell’ambiente principale. Mentre per il primo scenario è possibile utilizzare la funzione source("utl/script.R")
:
Ora avrò le mie funzioni disponibili come oggetti nel mio enviroment e potrò utilizzarle:
# carico il dataframe salvato in precedenza
mydf_1 = data.frame(readr::read_csv("data/mydf.csv"))
str(mydf_1) #check
'data.frame': 30 obs. of 2 variables:
$ id : num 1 2 3 4 5 6 7 8 9 10 ...
$ age: num 47 48 33 34 43 49 22 28 37 40 ...
id age age_cat age_z
1 1 47 adulto 1.4292031
2 2 48 adulto 1.5236438
3 3 33 adulto 0.1070328
4 4 34 adulto 0.2014736
5 5 43 adulto 1.0514402
6 6 49 adulto 1.6180845
Aprite e tenete aperto questo link:
https://etherpad.wikimedia.org/p/arca-corsoR