Un caso concreto di utilizzo di python, con varie librerie, per l’analisi di dati in formato aperto in ambito economico.
Tutti gli enti pubblici italiani per legge devono pubblicare un file xml liberamente accessibile con tutti i dettagli sulla spesa per acquisti di lavori, beni e servizi.
Questa mole di dati può dare informazioni molto interessanti per il controllo della spesa pubblica: benchmark per gli operatori del settore, controllo diffuso sulla spesa pubblica da parte della cittadinanza, prevenzione della corruzione, ma anche analisi di mercato e confronto con i concorrenti per aziende fornitori della pubblica amministrazione.
Per analizzare questa mole di dati, pubblica ormai da un anno, ma ancora poco utilizzata, ho utilizzato python per scaricare e interpretare i file xml, organizzare i dati raccolti in un database, analizzarli con indici, grafici, confronti di settore, variazione nel tempo, analisi di regressione.
scipy e rpy per l'analisi degli acquisti della pubblica amministrazione
1. scipy
e
rpy
per
l'analisi
degli
acquisti
della
pubblica
amministrazione
Francesco
Cavazzana
2. Input
-‐>
Output
28.000
file
XML
à
2.000
righe
per
50
colonne
di
analisi
+
4.000
grafici
3. 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
4. I
dati
sono
disponibili
sui
siti
web
di
ciascun
ente
pubblico
5. 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
6. 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)
7. 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
8. 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
9. numpy,
il
75%
degli
acquisti
valgono
meno
di
quanti
€
?
import numpy as np
# query su database -> oltre 230.000 righe di
risultato
importi = 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)))
10. 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ù piccolo
def 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))))
11. 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)
12. 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!
13. 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)
14. 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()
15. Appunto
R…
ma
da
python!
import rpy2.robjects as robjects
r_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
16. 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’)
17. 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
18. Cose
ancora
più
difficili
in
R:
clustering
dataClustering <- subset(dataK, select=c('i_2’,'i_40’,'i_207’,'i_inf'))
rownames(dataClustering) <- dataK$codice_fiscale
dataClustering[is.na(dataClustering)==T]<-0
k_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)
19. 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)