Analisi dei Dati ed Estrazione della Conoscenza

Il sistema R come linguaggio funzionale

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).

Funzioni definite dall'utente

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

Esercizi

Calcolo dell'entropia

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

Soluzione

Indice di curtosi

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

Soluzione

Indice di concentrazione di Gini

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

Soluzione

Funzioni come argomento ad altre funzioni

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}

Esercizio

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.

Soluzione

Funzioni che restituiscono funzioni

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).

Esercizio

Scrivere la funzione log.func che prende un numero n e restituisce la funzione di logaritmo in base n.

Esercizio

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

Soluzione

Valid XHTML 1.1 Valid CSS!