Analisi dei Metadati dei Dataset della PA

To Do - API_Dati_Pubblici

  1. Implementa funzione in API_Dati_Pubblici che recupera in ordine di importanza le resources con un certo mymetype sulla base del machine-readble, nel seguente ordine: json, rdf, xml, csv,xls, altrimenti quello che c'è
  2. Schedula scarico mensile e ingestion, oppure automatizza tweet con un grafico

In [3]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime
import os
plt.style.use('ggplot')
get_ipython().magic('pylab inline')

import nltk
from nltk.tokenize import word_tokenize
import re
from collections import Counter
from nltk.corpus import stopwords
import string


Populating the interactive namespace from numpy and matplotlib

In [2]:
dir_in = os.path.join(os.path.abspath(''),'input')
dir_out = os.path.join(os.path.abspath(''),'output')

df_file = os.path.join(dir_in, '2017-10-19_DSMetadatiPA.csv')

In [4]:
df = pd.read_csv(df_file, delimiter=';')

1. Numero Dataset PA


In [6]:
df['ds_name'].count()


Out[6]:
18252

2. Numero Dataset PA senza Metadati


In [7]:
df[pd.isnull(df['ds_title'])]['ds_name'].count()


Out[7]:
451

In [8]:
# Check DS senza metadati
# http://www.dati.gov.it/api/3/action/package_show?id=popolazione-straniera
df_test = df[pd.isnull(df['ds_title'])][-5:]
df_test


Out[8]:
Unnamed: 0 ds_title _catalog_parent_name gruppo note ultima_modifica ds_name ds_id ds_license url mymtype
17909 17909 NaN NaN NaN NaN NaN corruzione NaN NaN NaN NaN
18034 18034 NaN NaN NaN NaN NaN anpil NaN NaN NaN NaN
18146 18146 NaN NaN NaN NaN NaN popolazione-residente NaN NaN NaN NaN
18147 18147 NaN NaN NaN NaN NaN popolazione-straniera NaN NaN NaN NaN
18200 18200 NaN NaN NaN NaN NaN inquinamento-atmosferico NaN NaN NaN NaN

In [9]:
df.head(2)


Out[9]:
Unnamed: 0 ds_title _catalog_parent_name gruppo note ultima_modifica ds_name ds_id ds_license url mymtype
0 0 NaN NaN NaN NaN NaN circoscrizioni NaN NaN NaN NaN
1 1 Elezioni Amministrative 2014 - Eletti Comune di Bari Popolazione e società <p>Gli eletti delle Amministrative e Municipi... 2014-12-17 elezioni-amministrative-2014-eletti ebad7b1c-ca8c-4dda-ac1f-a9f550e8fac6 CC0 1.0 NaN NaN

3. Group by Catalogo Padre


In [10]:
df_cat = df.groupby(['_catalog_parent_name']).count().reset_index().sort_values(by='ds_name', ascending=False).set_index('_catalog_parent_name')

In [11]:
df_cat.head(2)


Out[11]:
Unnamed: 0 ds_title gruppo note ultima_modifica ds_name ds_id ds_license url mymtype
_catalog_parent_name
Dati trentino 5998 5998 1326 5998 5998 5998 5998 5798 5940 5840
Regione Toscana 2649 2649 1346 2637 2649 2649 2649 2385 2635 2448

In [12]:
# Primi 10 elementi per numero di dataset
df_cat_10 = df_cat['ds_name'][:10]
df_cat_10.sum()


Out[12]:
15605

In [13]:
# resto degli elementi
df_cat['ds_name'][11:].count()


Out[13]:
26

In [14]:
# Numero di dataset sul resto degli elementi
df_cat['ds_name'][11:].sum()


Out[14]:
1850

In [15]:
# Lista Cataloghi -> Occhio alle label settate a mano
df_cat_10


Out[15]:
_catalog_parent_name
Dati trentino               5998
Regione Toscana             2649
Dati Lombardia              1805
INPS                        1581
Comune di Albano Laziale    1190
Regione Sardegna             707
Dati Veneto                  454
Open Data Alto Adige         438
Dati Emilia Romagna          400
Comune di Lecce              383
Name: ds_name, dtype: int64

In [16]:
#Grafico
style.use('fivethirtyeight')
colors = [['red']]
tp = df_cat_10.plot(
    kind='bar',
    legend = False,
    figsize = (10,4),
    color = colors)

for p in tp.patches:
    tp.annotate(str(int(round(p.get_height()))), (p.get_x() * 1.005, p.get_height() * 1.005), ha='center', va='center', xytext=(15, 5), textcoords='offset points',fontsize=9)
    tp.plot()

tp.tick_params(axis = 'both', which = 'major', labelsize = 10)
tp.set_xlabel('Catalogo',fontsize=8)
tp.set_ylabel('n. dataset',fontsize=8)

tp.axhline(y = 0, color = 'black', linewidth = 4, alpha = 0.7)

labels = [item.get_text() for item in tp.get_xticklabels()]
labels[0] = 'Dati \nTrentino'
labels[1] = 'Regione \nToscana'
labels[2] = 'Dati \nLombardia'
labels[3] = 'INPS'
labels[4] = 'Comune di \nAlbano Laziale'
labels[5] = 'Regione \nSardegna'
labels[6] = 'Dati \nVeneto'
labels[7] = 'Open Data \nAlto Adige'
labels[8] = 'Dati Emilia \nRomagna'
labels[9] = 'Comune di \nLecce'

tp.set_xticklabels(labels)
plt.xticks(rotation=0)

# Adding a title and a subtitle
tp.text(x = -1.0, y = 7640, s = "I dati aperti della Pubblica Amministrazione",
               fontsize = 14, weight = 'bold', alpha = .75)
tp.text(x = -1.0, y = 6700, 
               s = u"I primi 10 cataloghi sorgente producono oltre l' 80% dei dataset. Su un totale di 18.000 dataset, 15.600 sono prodotti \ndai primi 10 Enti Pubblici.",
              fontsize = 10, alpha = .85)

text = tp.text(x = -1.2, y = -1500,
    s = 'www.ildatomancante.it                                                                                                                                                        Fonte: dati.gov.it',
    fontsize = 10, color = '#f0f0f0', backgroundcolor = 'grey')
text.set_url('http://www.ildatomancante.it')

fig_posts = tp.get_figure()
df_file_cat = os.path.join(dir_out, 'Cataloghi Dataset.png')
fig_posts.savefig(df_file_cat, format='png', dpi=300,bbox_inches='tight')


4. Group by Gruppo


In [17]:
df_grp= df.groupby(['gruppo']).count().reset_index().sort_values(by='ds_name', ascending=False).set_index('gruppo')

In [18]:
df_grp.head(2)


Out[18]:
Unnamed: 0 ds_title _catalog_parent_name note ultima_modifica ds_name ds_id ds_license url mymtype
gruppo
Popolazione e società 1832 1832 1832 1825 1683 1832 1832 1807 1827 1822
Ambiente 1783 1783 1783 1773 1425 1783 1783 1052 1760 1575

In [19]:
# Primi 10 elementi per numero di dataset
df_grp_10 = df_grp['ds_name'][:10]
df_grp_10.sum()


Out[19]:
6414

In [20]:
# resto degli elementi
df_grp['ds_name'][11:].count()


Out[20]:
2

In [21]:
# Numero di dataset sul resto degli elementi
df_grp['ds_name'][11:].sum()


Out[21]:
23

In [22]:
df_grp_10


Out[22]:
gruppo
Popolazione e società                                     1832
Ambiente                                                  1783
Istruzione, cultura e sport                               1118
Governo e settore pubblico                                 836
Economia e finanze                                         500
Agricoltura, pesca, silvicoltura e prodotti alimentari     125
Trasporti                                                   90
Salute                                                      60
Giustizia, sistema giuridico e sicurezza pubblica           35
Regioni e città                                             35
Name: ds_name, dtype: int64

In [24]:
#Grafico
style.use('fivethirtyeight')
colors = [['red']]
tp = df_grp_10.plot(
    kind='bar',
    legend = False,
    figsize = (10,4),
    color = colors)

for p in tp.patches:
    tp.annotate(str(int(round(p.get_height()))), (p.get_x() * 1.005, p.get_height() * 1.005), ha='center', va='center', xytext=(15, 5), textcoords='offset points',fontsize=9)
    tp.plot()

tp.tick_params(axis = 'both', which = 'major', labelsize = 10)
tp.set_xlabel('Gruppo',fontsize=8)
tp.set_ylabel('n. dataset',fontsize=8)

tp.axhline(y = 0, color = 'black', linewidth = 4, alpha = 0.7)

labels = [item.get_text() for item in tp.get_xticklabels()]
labels[0] = u'Popolazione \ne società'
labels[1] = 'Ambiente'
labels[2] = 'Istruzione, \ncultura \ne sport'
labels[3] = 'Governo e \nsettore \npubblico'
labels[4] = 'Economia \ne finanze'
labels[5] = 'Agricoltura'
labels[6] = 'Trasporti'
labels[7] = 'Salute'
labels[8] = 'Giustizia'
labels[9] = u'Regioni \ne città'

tp.set_xticklabels(labels)
plt.xticks(rotation=0)

# Adding a title and a subtitle
tp.text(x = -1.0, y = 2150, s = "I dati aperti della Pubblica Amministrazione",
               fontsize = 14, weight = 'bold', alpha = .75)
tp.text(x = -1.0, y = 2000, 
               s = u"Su ca.18.000 dataset solo in 6.400 hanno il campo gruppo popolato.",
              fontsize = 10, alpha = .85)

text = tp.text(x = -1.2, y = -500,
    s = 'www.ildatomancante.it                                                                                                                                                        Fonte: dati.gov.it',
    fontsize = 10, color = '#f0f0f0', backgroundcolor = 'grey')
text.set_url('http://www.ildatomancante.it')

fig_posts = tp.get_figure()
df_file_grp = os.path.join(dir_out, 'Gruppi Dataset.png')
fig_posts.savefig(df_file_grp, format='png', dpi=300,bbox_inches='tight')


Group by Ultima Modifica -> Time series chart


In [25]:
np.isnan(df['ultima_modifica'][0])


Out[25]:
True

In [26]:
from datetime import datetime
def calcolo_anno(x):
    try:
        np.isnan(x['ultima_modifica'])
        anno = '9999'
    except:
        anno = x['ultima_modifica'][0:4]
    return anno

In [27]:
df['dt_ultima_modifica'] = df.apply(lambda x: calcolo_anno(x), axis=1)

In [28]:
df.head(2)


Out[28]:
Unnamed: 0 ds_title _catalog_parent_name gruppo note ultima_modifica ds_name ds_id ds_license url mymtype dt_ultima_modifica
0 0 NaN NaN NaN NaN NaN circoscrizioni NaN NaN NaN NaN 9999
1 1 Elezioni Amministrative 2014 - Eletti Comune di Bari Popolazione e società <p>Gli eletti delle Amministrative e Municipi... 2014-12-17 elezioni-amministrative-2014-eletti ebad7b1c-ca8c-4dda-ac1f-a9f550e8fac6 CC0 1.0 NaN NaN 2014

In [29]:
df_tms = df.groupby(['dt_ultima_modifica']).count().reset_index().sort_values(by='dt_ultima_modifica', ascending=False).set_index('dt_ultima_modifica')

In [30]:
df_tms


Out[30]:
Unnamed: 0 ds_title _catalog_parent_name gruppo note ultima_modifica ds_name ds_id ds_license url mymtype
dt_ultima_modifica
9999 2485 2034 2034 1069 2034 0 2485 2034 1910 1990 1990
2017 10441 10441 10441 4248 10390 10441 10441 10441 9114 10371 9831
2016 2067 2067 2067 561 2058 2067 2067 2067 2031 2059 2043
2015 1209 1209 1209 317 1170 1209 1209 1209 1040 1201 1197
2014 873 873 873 234 872 873 873 873 477 863 861
2013 1031 1031 1031 29 1028 1031 1031 1031 333 1030 1029
2012 143 143 143 0 143 143 143 143 143 143 143
2011 3 3 3 0 3 3 3 3 3 3 3

6. Word Cloud su Titolo


In [31]:
df.head(2)


Out[31]:
Unnamed: 0 ds_title _catalog_parent_name gruppo note ultima_modifica ds_name ds_id ds_license url mymtype dt_ultima_modifica
0 0 NaN NaN NaN NaN NaN circoscrizioni NaN NaN NaN NaN 9999
1 1 Elezioni Amministrative 2014 - Eletti Comune di Bari Popolazione e società <p>Gli eletti delle Amministrative e Municipi... 2014-12-17 elezioni-amministrative-2014-eletti ebad7b1c-ca8c-4dda-ac1f-a9f550e8fac6 CC0 1.0 NaN NaN 2014

In [32]:
emoticons_str = r"""
    (?:
        [:=;] # Eyes
        [oO\-]? # Nose (optional)
        [D\)\]\(\]/\\OpP] # Mouth
    )"""
 
regex_str = [
    emoticons_str,
    r'<[^>]+>', # HTML tags
    r'(?:@[\w_]+)', # @-mentions
    r"(?:\#+[\w_]+[\w\'_\-]*[\w_]+)", # hash-tags
    r'http[s]?://(?:[a-z]|[0-9]|[$-_@.&amp;+]|[!*\(\),]|(?:%[0-9a-f][0-9a-f]))+', # URLs
 
    r'(?:(?:\d+,?)+(?:\.?\d+)?)', # numbers
    r"(?:[a-z][a-z'\-_]+[a-z])", # words with - and '
    r'(?:[\w_]+)', # other words
    r'(?:\S)' # anything else
]

In [33]:
tokens_re = re.compile(r'('+'|'.join(regex_str)+')', re.VERBOSE | re.IGNORECASE)
emoticon_re = re.compile(r'^'+emoticons_str+'$', re.VERBOSE | re.IGNORECASE)

In [34]:
def tokenize(s):
    return tokens_re.findall(s)
 
def preprocess(s, lowercase=False):
    tokens = tokenize(s)
    if lowercase:
        tokens = [token if emoticon_re.search(token) else token.lower() for token in tokens]
    return tokens

In [35]:
count_only = Counter()
punctuation = list(string.punctuation)
# stop = stopwords.words('english') + punctuation + ['rt', 'via']
stop = punctuation
for i, row in df['ds_title'].iteritems():
    try:
        np.isnan(row)
    except:
        terms_only = [term for term in preprocess(row) 
                  if term not in stop and
                  not term.startswith(('per','\xc3','di','del','e','\xa0','della'
                                      ,'Anno','a','in','#039','al','dei',
                                      'con','nel','12','10','1','da','31'))] 
        count_only.update(terms_only)

In [36]:
word_freq = count_only.most_common(30)
words_json = [{'text': item[0], 'weight': item[1]} for item in word_freq]
words_json


Out[36]:
[{'text': 'Comune', 'weight': 4718},
 {'text': '2014', 'weight': 1472},
 {'text': '2013', 'weight': 1328},
 {'text': '2015', 'weight': 963},
 {'text': '2012', 'weight': 782},
 {'text': 'sesso', 'weight': 684},
 {'text': 'Numero', 'weight': 642},
 {'text': '2016', 'weight': 628},
 {'text': 'LIV', 'weight': 554},
 {'text': '2011', 'weight': 532},
 {'text': 'Popolazione', 'weight': 514},
 {'text': '2010', 'weight': 480},
 {'text': 'Servizi', 'weight': 458},
 {'text': 'Regione', 'weight': 379},
 {'text': 'Anni', 'weight': 371},
 {'text': '2017', 'weight': 361},
 {'text': 'Aree', 'weight': 351},
 {'text': 'Elenco', 'weight': 345},
 {'text': 'residente', 'weight': 340},
 {'text': '2007', 'weight': 332},
 {'text': 'Provincia', 'weight': 316},
 {'text': 'Firenze', 'weight': 298},
 {'text': 'AMB', 'weight': 296},
 {'text': 'SINTESI', 'weight': 294},
 {'text': '2008', 'weight': 289},
 {'text': 'Comunit', 'weight': 281},
 {'text': '2009', 'weight': 276},
 {'text': 'lavoratori', 'weight': 268},
 {'text': 'DETTAGLIO', 'weight': 260},
 {'text': 'classe', 'weight': 256}]

Word Cloud su Notes


In [46]:
count_only = Counter()
punctuation = list(string.punctuation)
# stop = stopwords.words('english') + punctuation + ['rt', 'via']
stop = punctuation
for i, row in df['note'].iteritems():
    try:
        np.isnan(row)
    except:
        terms_only = [term for term in preprocess(row) 
                  if term not in stop and
                  not term.startswith(('per','\xc3','di','del','e','\xa0','della'
                                      ,'Anno','a','in','#039','al','dei',
                                      'con','nel','12','10','1','da','31',
                                      '<p>','</p>','<br />', 'i', 'sul',
                                      '</strong>','<strong>', 'la', 'le','\x80',
                                      'xe2', 'Tutti', 'il', 'sono', '\xa8', '\x99',
                                      'che', 'una', 'che', 'gli','Il','n','o',
                                      '\xe2','si','un','\xc2','su','questo','come',
                                      'stesso','I','Tutte','\x93','Tipo','La','\xb9',
                                      'Per','degli','formati','pi'))] 
        count_only.update(terms_only)

In [51]:
word_freq_note = count_only.most_common(30)
wordsnote__json = [{'text': item[0], 'weight': item[1]} for item in word_freq_note]
wordsnote__json


Out[51]:
[{'text': 'Comune', 'weight': 7226},
 {'text': 'pubblicati', 'weight': 4559},
 {'text': '2013', 'weight': 2064},
 {'text': '2014', 'weight': 1596},
 {'text': 'Numero', 'weight': 1534},
 {'text': 'Settore', 'weight': 1307},
 {'text': 'Firenze', 'weight': 1211},
 {'text': 'formato', 'weight': 1208},
 {'text': 'Algoritmo', 'weight': 1162},
 {'text': '2015', 'weight': 1158},
 {'text': 'territorio', 'weight': 1149},
 {'text': '2012', 'weight': 1048},
 {'text': 'Fonte', 'weight': 994},
 {'text': 'visualizzazione', 'weight': 981},
 {'text': 'relativi', 'weight': 944},
 {'text': 'presenti', 'weight': 907},
 {'text': 'Rapporto', 'weight': 890},
 {'text': 'Regione', 'weight': 868},
 {'text': 'sesso', 'weight': 845},
 {'text': '2011', 'weight': 822},
 {'text': 'Livello', 'weight': 808},
 {'text': 'Minimo', 'weight': 803},
 {'text': 'Geografico', 'weight': 799},
 {'text': 'Albano', 'weight': 776},
 {'text': 'classe', 'weight': 762},
 {'text': 'servizi', 'weight': 762},
 {'text': 'Dati', 'weight': 719},
 {'text': 'pagina', 'weight': 704},
 {'text': 'Provincia', 'weight': 697},
 {'text': '2017', 'weight': 695}]

In [48]:



Out[48]:
[{'text': 'Comune', 'weight': 7226},
 {'text': 'pubblicati', 'weight': 4559},
 {'text': '2013', 'weight': 2064},
 {'text': '2014', 'weight': 1596},
 {'text': 'Numero', 'weight': 1534},
 {'text': 'Settore', 'weight': 1307},
 {'text': 'Firenze', 'weight': 1211},
 {'text': 'formato', 'weight': 1208},
 {'text': 'Algoritmo', 'weight': 1162},
 {'text': '2015', 'weight': 1158},
 {'text': 'territorio', 'weight': 1149},
 {'text': '2012', 'weight': 1048},
 {'text': 'Fonte', 'weight': 994},
 {'text': 'visualizzazione', 'weight': 981},
 {'text': 'relativi', 'weight': 944},
 {'text': 'presenti', 'weight': 907},
 {'text': 'Rapporto', 'weight': 890},
 {'text': 'Regione', 'weight': 868},
 {'text': 'sesso', 'weight': 845},
 {'text': '2011', 'weight': 822},
 {'text': 'Livello', 'weight': 808},
 {'text': 'Minimo', 'weight': 803},
 {'text': 'Geografico', 'weight': 799},
 {'text': 'Albano', 'weight': 776},
 {'text': 'classe', 'weight': 762},
 {'text': 'servizi', 'weight': 762},
 {'text': 'Dati', 'weight': 719},
 {'text': 'pagina', 'weight': 704},
 {'text': 'Provincia', 'weight': 697},
 {'text': '2017', 'weight': 695}]