Abbiamo visto la lezione scorsa come utilizzare in R le funzioni predefinite dal linguaggio. Ma R consente anche di definire nuove funzioni, in maniera analoga alle funzioni in PHP o ai metodi in Java. A differenza degli altri linguaggi, però, in R le funzioni sono degli oggetti primitivi, al pari di (vettori di) interi, booleani e stringhe. Pertanto è possibile: assegnare funzioni a variabili, passare come argomento ad una funzione un'altra funzione, restituire una funzione come risultato. Questa libertà e centralità delle funzioni nel linguaggio R si indicano dicendo che R è un linguaggio funzionale (mentre PHP e Java sono linguaggi imperativi a oggetti).
Per definire una funzione in R su usa il comando function
. La sintassi è:
function (lista parametri) espressione
in maniera simile al linguaggio PHP. Ad esempio,
function (x) x+1
è la funzione che prende un vettore x
in input e restituisce il vettore x+1
. La funzione in se non ha un nome, perché una funzione è un valore come un intero o un booleano, che ha una essenza al di fuori del nome che gli diamo. Così come 1
è il valore 1. Se vogliamo assegnare il valore 1 ad una variabile z
scriveremo z=1
. Analogamente se vogliamo assegnare una funzione ad una variabile f
, scriveremo f=function(.....)
.
> function (x) x+1 function (x) x+1 > f=function (x) x+1 > f function (x) x+1
Così si può fare con le funzioni? Fondamentalmente una cosa sola: invocare la funzione con dei parametri opportuni, causando l'esecuzione dell'espressione che la definisce. Si può invocare sia una funzione assegnata ad una variabile ma anche una funzione "anonima".
> f(c(3,4)) [1] 4 5 > (function(x) x+1)(c(3,4)) [1] 4 5
Guadiamo la derivazione corrispondente alla prima riga dell'esempio precedente:
f(c(3,4)) => {function x (x+1)}({3 4}) => {3 4} + 1 => {4 5}
Quando si crea una funzione è possibile specificare dei parametri di default, come si fa in PHP, semplicemente scrivendo parametro=espressione
nella lista dei parametri.
> inc=function (x,y=5) x+y > inc(4,4) [1] 8 > inc(4) [1] 9
Nella definizione di una funzione, posso mettere anche più espressioni. In tal caso queste espressioni vanno racchiuse tra parentesi graffe. Il valore restituito dalla funzione è il valore dell'ultima espressione. Una delle espressioni all'interno della funzione può essere anche un assegnamento ad una variabile. In tal caso si tratta di un variabile locale che ha validità solo all'interno della funzione stessa.
> inc2 = function (x,y=5) { + z = y*y + x + z + } > inc2(2) [1] 27 > z Error: object "z" not found
Scrivere la funzione entropia(data)
che calcola il valore dell'indice di entropia sulla distribuzione data
. Considerare anche il caso di valori nulli.
> entropia(c(0.5,0.3,0.2)) [1] 1.485475 > entropia(c(0,0.5,0.5)) [1] 1
Scrivere la funzione curtosi(attr)
che calcola il valore dell'indice di curtosi per l'attributo attr
. Si possono usare le funzioni mean
ed sd
che calcolano media e scarto quadratico medio di un vettore, e l'operatore ^
per l'elevamento a potenza (x^4
è x alla quarta potenza).
> curtosi(c(10,10,5,2)) [1] 0.7634423
Scrivere la funzione giniconc(attr)
che calcola il rapporto di concentrazione dell'attributo attr
. Si può usare a tale scopo la funzione cumsum(v)
che calcola le somme cumulate del vettore v. Per somma cumulata si intende che il risultato di cumsum(v) è un vettore w della stessa lunghezza di v, tale che w[i] è la somma v[1]+...+v[i].
> giniconc(c(1,2,2,20)) [1] 0.57
Una funzione in R può essere l'argomento di altre funzioni. Consideriamo ad esempio la funzione double
così definita:
double = function (f,x) f(f(x))
Scrivere double(log,3)
causa l'esecuzione della espressione f(f(x))
dove f
è la funzione logaritmo. Una cosa simile avviene se uso una qualunque altra funzioni invece di log
, anche una funzione anonima.
> double = function (f,x) f(f(x)) > double(sqrt,16) [1] 2 > double(function (x) x+1,16) [1] 18
Notare che quando scriviamo sqrt
all'interno di double(sqrt,16)
non usiamo le parentesi tonde dopo sqrt
. Le parentesi tonde si usano infatti per invocare una funzione, e noi qui non stiamo invocando un bel niente: stiamo solo passando la funzione come parametro a double
.
Questa è la derivazione corrispondente a double(sqrt,16)
:
double(sqrt,16) => {function (f,x) f(f(x))}({sqrt},{16}) => {sqrt}({sqrt}({16})) => {sqrt}({4}) => {2}
Questa è la derivazione corrispondente a double(function (x) x+1,16)
:
double(function (x) x+1,16) => {function (f,x) f(f(x))}({function (x) x+1},{16}) => => {function (x) x+1}({function (x) x+1}({16})) => {function (x) x+1}({16} + 1) => {function (x) x+1}({17}) => {17} +1 => {18}
Scrivere una funzione compose(f1,f2,x)
che prende due funzioni f1
ed f2
ed un vettore numerico x
e restituisce f1(f2(x))
. Si faccia in modo che se f2
non viene specificato, il suo valore di default sia la funzione di elevamento al quadrato.
Non solo una funzione può essere il parametro di ingresso di un'altra funzione ma, al di pari di qualunque valore, può essere anche il valore restituito da una funzione. Ad esempio, si consideri il seguente esempio:
> power.func = function (n) function (x) x^n > power.func(3) function (x) x^n <environment: 0x9e09f98> > p3 = power.func(3) > p3(2) [1] 8 > power.func(3)(2) [1] 8
La funzione power.func
prende come input un valore n
e, in corrispondenza, calcola l'espressione function (x) x^n
che è a sua volta una funzione. Quindi il valore di ritorno di power.func
è una funzione. Ma di che funzione si tratta? È la funzione che prende x
e restituisce x
alla n
-esima potenza. Ne segue che power.func(3)
è la funzione di elevamento al cubo, power.func(2)
la funzione di elevamento al quadrato e così via. La scritta <environment: 0x9e09f98>
in risposta alla richiesta di visualizzare power.func(3)
vuol dire che R non è in grado di farci vedere il codice della funzione power.func(3)
, sebbene sia in grado di eseguirla correttamente.
Consideriamo per esempio la derivazione corrispondente all'ultima espressione:
p3(2) => power.func(3)({2}) => {function (n) function (x) x^n}({3})({2}) => {function (x) x^{3}}({2}) => {2} ^ {3} => {8}
Ovviamente è possibile combinare le due cose, e scrivere delle funzioni che non solo prendono altre funzioni come parametro di ingresso, ma restituiscono altre funzioni come output. Per esempio, la funzione double2
che segue prende una funzione f
come input, e restituisce la funzione f ° f
(f composto f), ovvero la funziona che applica f
due volte all'input.
> double2 = function (f) function (x) f(f(x)) > sqrt.doubled = double2(sqrt) > sqrt.doubled(16) [1] 2 > double2(sqrt)(16) [1] 2
A differenza della funzione double
definita prima, che prendeva anche il valore x
su cui applicare due volte la funzione in input, questa non prende il parametro x
direttamente, ma restituisce una funzione che si occuperà poi di prendere x
ed applicare ad esso due volte f
. In pratica, double
e double2
sono legati dalla relazione double(f,x)
=double2(f)(x)
.
Scrivere la funzione log.func
che prende un numero n
e restituisce la funzione di logaritmo in base n
.
Scrivere la funzione compose2
che prende due funzioni f1
ed f2
e restituisce la funzione composizione di f1 ed f2.
> f = compose2(log2,sqrt) > f(1024) [1] 5