Tutti i dati sugli acquisti pubblici devono essere pubblici – Legge 190/2012
Ê Entro il 31 gennaio di ogni anno, tali informazioni, relativamente all'anno precedente, sono pubblicate in Ê tabelle riassuntive rese liberamente scaricabili Ê in un formato digitale standard aperto Ê che consenta di analizzare e rielaborare, anche a fini statistici,
i dati informatici.
Ê L’Autorità competente (AVCP – ANAC) ha stabilito il formato: Ê http://dati.avcp.it/schema/datasetAppaltiL190.xsd Ê http://dati.avcp.it/schema/datasetIndiceAppaltiL190.xsd Ê http://dati.avcp.it/schema/TypesL190.xsd
Principali dati disponibili
Ê Descrizione acquisto
Ê Data inizio e fine fornitura
Ê Importo aggiudicato e importo liquidato
Ê Tipo di procedura utilizzata
Ê Aggiudicatario
Ê Elenco dei partecipanti alla gara
Ê Ogni acquisto dal 1/12/12 indipendentemente dall’importo
Possibili utilizzi di questi dati
Ê Trasparenza
Ê Anticorruzione
ma anche
Ê Analisi di benchmark per le amministrazioni pubbliche
Ê Analisi di mercato
Ê Monitoraggio della concorrenza
Ê Individuazione di potenziali clienti (pubblici)
Dati analizzati
Ê 42 università
Ê 7 comuni
Ê 2 anni
Ê 28.634 file XML
Ê 467.527 acquisti
Ê 3.015.189.341,15 € di importo totale
Ê 68.138 aziende partecipanti
Importazione dei dati da XML
Ê Genropy Bag
Ê from gnr.core.gnrbag import Bag
Ê b = Bag(xml_source_dati)
Ê b['legge190:pubblicazione.data.#0.cig'] Ê 5835815AE0
numpy, il 75% degli acquisti valgono meno di quanti € ?
import numpy as np# query su database -> oltre 230.000 righe di risultatoimporti = self.query_limiti(…)x = np.array([float(r[0]) for r in importi])result.append(('Numero acquisti', len(x))result.append(('Totale aggiudicato’, x.sum()))result.append(('min', x.min()))result.append(('media', x.mean()))result.append(('mediana, ’np.median(x)))result.append(('deviazione standard’, x.std()))result.append(('75%: <= ', np.percentile(x, 75)))
scipy.stats, quanti acquisti ci vogliono per fare l’80% del valore speso?
totale = x.sum()x = np.flipud(x)#ordino al contrario dal più grande al più piccolodef sommeParziali(): somma = np.float(0) for r in x:
somma += r yield somma iterable = sommeParziali()somme = np.fromiter(iterable, np.float)
result.append(('',''))result.append(('Concentrazione del valore', '_titolo_'))result.append((‘80% del valore dato da % acquisti',
self.fmtNumber(st.percentileofscore(somme, totale * 0.8))))
Rendiamole multiprocesso
Ê 42 enti: analizziamoli in parallelo
pool = multiprocessing.Pool(multiprocessing.cpu_count(),
initAnalizzatore, (… parametri di init …))
analisi = pool.map(analisiUnGruppoUnAnno,
[(anno,un_ente_id) for un_ente_id in lista_enti])
Ê L’analisi dei 42 enti è indipendente l’una dall’altra
Ê I dati vengono letti dal DB parallelamente da ciascun processo
Ê I tempi di analisi vengono effettivamente divisi per il numero di processori disponibili (se il consumo di RAM è ragionevole)
Come mostriamo gli output?
Ê import xlsxwriter
Ê Perché un foglio di calcolo come output? Ê è facile fare ulteriori analisi al volo sugli output Ê si ottengono facilmente tabelle e grafici per presentazioni o testi
Ê la griglia è un modo logico per visualizzare dati di questo tipo, soprattutto se semilavorati
Ê non perdiamo tempo con la grafica, ci interessano i numeri!
I grafici facili li lasciamo fare ad excel
def writeGraficoTorta(self, f_strutt, row, col, value, i, data): chart1 = self.workbook.add_chart({'type': 'pie'})
#Configure the series. Note the use of the list syntax to define ranges
chart1.add_series({
'name': value['grafico'],
'categories': [f_strutt.name, row - value['dati'], 0, row - 1, 0], 'values': [f_strutt.name, row - value['dati'], col, row - 1, col],
'data_labels': {'percentage': True} #, 'category':True}})
chart1.set_legend({'height': 0.9})
# Add a title.
chart1.set_title({'none': True}) # Set an Excel chart style. Colors with white outline and shadow.
chart1.set_style(10)
chart1.set_size({'width': 314, 'height': 250, 'x_offset':4, 'y_offset':4})
# Insert the chart into the worksheet (with an offset).
f_strutt.insert_chart(row, col, chart1) f_strutt.set_row(row, 200)
I grafici difficili li facciamo fare ad R
jpeg(filename = "%(filename)s_DIM_NUM_plot”, width = 11, height = 7,
units = "cm", pointsize = 8, res=300, quality=50, bg = "white")
plot(x=data$numero, y=data$importo_tot, xlab="numero acquisti",
ylab="importo acquisti", col=data$cluster)
d1 <- subset(data, subset=c(data$numero > mean(data$numero) |
data$importo_tot > mean(data$importo_tot)))
textxy(d1$numero, d1$importo_tot, d1$sigla)
abline(v=mean(data$numero))
abline(h=mean(data$importo_tot))
dev.off()
Appunto R… ma da python!
import rpy2.robjects as robjectsr_code = ” … il codice R lo abbiamo visto prima … “
robjects.r(r_code)
result.append(('Plot numero / importo acquisti',{'tipo':'immagine’,
'filename':connection_params['filename']+'_DIM_NUM_plot'}))
result.append(('Numero università per cluster',str(robjects.r(’cluster$size'))))
Ê I dati in input sono presi da R direttamente dal database -‐> si evita la serializzazione dei dati pesanti
Ê I grafici in output sono salvati in path conosciuti da entrambi i linguaggi
Ê Per casi non troppo complessi il passaggio dati tra i due linguaggi è totalmente trasparente
Altre cose difficili in R: le regressioni
drv <- dbDriver("PostgreSQL")con <- dbConnect(drv, dbname="%(database)s", user="%(user)s”)
rs <- dbSendQuery(con, "%(query)s")
data <- fetch(rs,n=-1)
data$area_geografica<-factor(data$area_geografica)
data$area_geografica<-relevel(data$area_geografica, ref="N”)m1geo <- lm(numero~area_geografica, data=data)
plot(x=data$area_geografica, y=data$numero, xlab='Area geografica’,
ylab='Numero acquisti’)
R… magia nera!
rs <- dbSendQuery(con, "%(query)s")data <- fetch(rs,n=-1)data$imp_doc <- data$importo_tot / data$num_docenti
data$dim_docenti_cat_2 <- cut(data$num_docenti, breaks=c(0,500,1000,1500,4000), dig.lab=10)
paste(c('milano', 'varese', 'como'), '2014', sep=' - ')[1] "milano - 2014" "varese - 2014" "como - 2014"
Ê Questa divisione non è fra due numeri… ma fra decine (o milioni) di numeri
Ê Prendiamo il numero di docenti di ciascuna università e lo trasformiamo in un fattore a 4 livelli, in modo da ottenere quattro gruppi di università divisi per classe dimensionale
Ê Altro esempio di come R ragioni sempre magicamente per vettori
Cose ancora più difficili in R: clustering
dataClustering <- subset(dataK, select=c('i_2’,'i_40’,'i_207’,'i_inf'))rownames(dataClustering) <- dataK$codice_fiscaledataClustering[is.na(dataClustering)==T]<-0k_cluster <- kmeans(dataClustering, 3)d1 <- data.frame(cluster = k_cluster$cluster,
codice_fiscale=names(k_cluster$cluster))data <- merge(data, d1, by='codice_fiscale')data$cluster<-factor(data$cluster)plot(dataClustering, col=data$cluster)
Concludendo
Rispetto a fare l’analisi in excel, il vantaggio è che si può…
Ê … ripetere le analisi decine di volte con poco sforzo, cambiando qualche ipotesi o qualche tipo di analisi
Ê … aggiungere dati (es. un anno di analisi in più) senza nessuno sforzo
Ê … analizzare centinaia di migliaia o milioni di righe
Ê … interagire con SQL senza lavoro manuale (una elaborazione di analisi può richiedere decine di query)
Ê … cambiare quando si vuole l’ambito delle analisi (es. vediamo solo gli acquisti < 1.000 € nelle regioni del nord)