<jupyter_start><jupyter_text>Formati dati 2 - CSV [Scarica zip esercizi](../_static/generated/formats.zip)[Naviga file online](https://github.com/DavidLeoni/softpython-it/tree/master/formats) Ci possono essere vari formati per i file tabulari, tra cui sicuramente conoscerai gli Excel (`.xls` o `.xslx`). Peccato che se vuoi processare dati programmaticamente, faresti meglio ad evitarli e preferire se possibile i file CSV, letteralmente 'Comma separated Value'. Per capire il perchè, quando hai tempo potresti guardare [questo tutorial](https://docs.google.com/presentation/d/1RPiRFm0g9QtSiC3aO32aFldSYThl11CB9HI_HHG6hvY/) in cui si spiega a produttori di dati (in questo caso, dipendenti pubblici) come trasformare Excel in CSV, evidenziando i vari grattacapi che un Excel può presentare a chi poi riusa i dati. Oggi proveremo ad aprire qualche CSV, prendendo in considerazione i possibili problemi che possono insorgere. I CSV non sono la panacea per tutti i mali, ma offrono maggiore controllo sulla lettura e tipicamente se saltano fuori errori di conversione è perchè siamo stati noi a sbagliare, e non perchè la libreria di lettura ha magari deciso da sola di  scambiare nelle date i giorni con i mesi. Perchè scorrere un CSV ?Per caricare e processare CSV esistono già librerie molto potenti ed intuitive come Pandas in Python o i dataframe di R, che forse avrai già visto. Oggi invece caricheremo i CSV usando il mezzo più semplice possibile, che è la lettura riga per riga, più o meno come fatto nella prima parte del tutorial. Non bisogna pensare che questo metodo sia primitivo o stupido, a seconda della situazione può salvare la giornata. Come mai? Dato che alcuni file potenzialmente potrebbero occupare terabyte, e nei moderni laptop di solito abbiamo mediamente solo 4 Gigabyte di RAM, che è la memoria dove Python mette le variabili, le funzioni di base di Python per leggere file evitano di caricare tutto in RAM. Tipicamente invece un file viene scorso un po' alla volta, mettendo in RAM solo una riga alla volta.  **DOMANDA 2.1**: se vogliamo sapere se un certo file da 100 terabyte contiene almeno 3 milioni di righe in cui è presente la parola 'ciao', dobbiamo mettere in RAM contemporaneamente tutte le righe? **RISPOSTA**: no, basta mettere una riga alla volta, e tenere una variabile per il conteggio  **DOMANDA 2.2**: E se volessimo da un file da 100 terabyte crearne un'altro con gli stessi contenuti del primo file a cui a tutte le righe è aggiunta la parola 'ciao' alla fine, dovremmo mettere in RAM contemporaneamente tutte le righe del primo file? E quelle del secondo ? **RISPOSTA**:  No, basta tenere in RAM una riga alla volta, che viene prima letta dal primo file e subito scritta nel secondo  CSV di esempio Cominciamo con dei CSV artificiali di esempio, sul modello di esercizi già fatti [nella introduzione](https://it.softpython.org/intro/intro-sol.html)Per iniziare, vedremo il CSV [esempio-1.csv](esempio-1.csv) che trovi nella stessa cartella di questo foglio Jupyter. Riportiamo qui il contenuto del file:```animale, annicane, 12gatto, 14pellicano, 30scoiattolo, 6aquila, 25```Notiamo subito che il CSV è più strutturato dei file visti nella sezione precedente* la prima linea sono i nomi delle colonne, separati da virgole (_comma_ in inglese): `animale, anni`* I campi nelle righe successive sono pure separati da virgole `,` : `cane, 12`Proveremo ora ad importare questo file in Python:<jupyter_code>import csv
with open('esempio-1.csv', encoding='utf-8', newline='') as f:
    
    # creiamo un oggetto 'lettore' che pescherà righe dal file
    lettore = csv.reader(f, delimiter=',') 
    
    # 'lettore' è un oggetto cosiddetto 'iterabile', cioè se usato in un for produce una 
    #  sequenza di righe dal csv 
    #  NOTA: qui ogni riga del file viene convertita in una una lista di stringhe Python!
    for riga in lettore:
        print('Abbiamo appena letto una riga!')        
        print(riga)  # stamperà la variabile 'riga', che è una lista di stringhe
        print('')    # stampa una stringa vuota, per separare in verticale<jupyter_output>Abbiamo appena letto una riga!
['animale', 'anni']

Abbiamo appena letto una riga!
['cane', '12']

Abbiamo appena letto una riga!
['gatto', '14']

Abbiamo appena letto una riga!
['pellicano', '30']

Abbiamo appena letto una riga!
['scoiattolo', '6']

Abbiamo appena letto una riga!
['aquila', '25']<jupyter_text>Notiamo subito dall'output della print che viene stampato il file di esempio, ma ci sono delle parentesi quadre ( '[]' ). Cosa significano? Quelle che abbiamo stampato sono una _liste_ di _stringhe_.Analizziamo meglio quanto fatto:```pythonimport csv```Python è fornito nativamente di un modulo per il trattamento dei csv, col nome intuitivo `csv`. Con questa istruzione, abbiamo appena caricato questo modulo.Cosa succede dopo ? Come già fatto per i file a linee in precedenza, apriamo il file in un blocco `with`: ```pythonwith open('esempio-1.csv', encoding='utf-8', newline='') as f:    lettore = csv.reader(f, delimiter=',')    for riga in lettore:        print(riga)```Per adesso ignora il `newline=''` e nota che come prima abbiamo specificato l'encodingUna volta aperto il file, nella riga ```python    lettore = csv.reader(f, delimiter=',')```chiediamo al modulo `csv` di crearci un oggetto lettore chiamato `lettore` per il nostro file, dicendo a Python che il delimitatore per i campi sono le virgole.**NOTA:** `lettore` è il nome di una variabile che stiamo creando, potremmo dare un nome qualunque.Questo oggetto lettore può essere sfruttato come una specie di generatore di righe usando un ciclo `for`.```    for riga in lettore:        print(riga)```Nel ciclo `for` sfruttiamo l'oggetto `lettore` per iterare nella lettura del file, producendo ad ogni iterazione una riga che chiamiamo `riga` (ma potrebbe essere un qualunque nome a nostro piacimento). Ad ogni iterazione, la variabile `riga` viene stampata. Se guardi bene le stampe delle prime liste, vedrai che ogni volta a ogni riga riga viene assegnata una sola lista Python. La lista contiene tanti elementi quanti campi ci sono nel CSV.  **✪ ESERCIZIO 2.3**: Riscrivi nella cella qua sotto le istruzioni per leggere e stampare il csv, facendo come sempre attenzione all'indentazione:<jupyter_code># scrivi qui

import csv
with open('esempio-1.csv', encoding='utf-8', newline='') as f:
    
    # creiamo un oggetto 'lettore' che pescherà righe dal file
    lettore = csv.reader(f, delimiter=',') 
    
    # 'lettore' è un oggetto cosiddetto 'iterabile', cioè se usato in un for produce una 
    #  sequenza di righe dal csv 
    #  NOTA: qui ogni riga del file viene convertita in una una lista di stringhe Python!
    for riga in lettore:
        print('Abbiamo appena letto una riga!')        
        print(riga)  # stamperà la variabile 'riga', che è una lista di stringhe
        print('')    # stampa una stringa vuota, per separare in verticale<jupyter_output>Abbiamo appena letto una riga!
['animale', 'anni']

Abbiamo appena letto una riga!
['cane', '12']

Abbiamo appena letto una riga!
['gatto', '14']

Abbiamo appena letto una riga!
['pellicano', '30']

Abbiamo appena letto una riga!
['scoiattolo', '6']

Abbiamo appena letto una riga!
['aquila', '25']<jupyter_text>**✪✪ ESERCIZIO 2.4**: prova a mettere in una variabile `listona` una lista contenente tutte le righe estratte dal file, che quindi sarà una lista di liste che dovrebbe apparire così.```[['animale', ' anni'], ['cane', '12'], ['gatto', '14'], ['pellicano', '30'], ['scoiattolo', '6'], ['aquila', '25']]```**SUGGERIMENTO**: Comincia creando una lista vuota e poi aggiungendo elementi con il metodo `.append`<jupyter_code># scrivi qui 

import csv
with open('esempio-1.csv', encoding='utf-8', newline='') as f:
    
    # creiamo un oggetto 'lettore' che pescherà righe dal file
    lettore = csv.reader(f, delimiter=',') 
    
    # 'lettore' è un oggetto cosiddetto 'iterabile', cioè se usato in un for produce una 
    #  sequenza di righe dal csv 
    #  NOTA: qui ogni riga del file viene convertita in una una lista di stringhe Python!
    listona = []
    for riga in lettore:
        listona.append(riga)
    print(listona)<jupyter_output>[['animale', 'anni'], ['cane', '12'], ['gatto', '14'], ['pellicano', '30'], ['scoiattolo', '6'], ['aquila', '25']]<jupyter_text>**✪✪ ESERCIZIO 2.5**: Forse avrai notato che i numeri nella liste sono rappresentati come stringhe tipo `'12'` (nota gli apici), invece che come numeri interi Python (rappresentati senza apici), `12`:```Abbiamo appena letto una riga!['cane', '12']```Quindi, leggendo il file e usando dei normali cicli `for`, prova a creare nua variabile `listona` formata come questa, che- ha solo i dati, la riga con le intestazioni non è presente- i numeri sono rappresentati propriamente come interi```[['cane', 12],  ['gatto', 14],  ['pellicano', 30],  ['scoiattolo', 6],  ['aquila', 25]]```**SUGGERIMENTO 1**: per saltare una riga puoi usare l'istruzione `next(lettore)`**SUGGERIMENTO 2**: per convertire una stringa in un intero, si può usare per es. `int('25')`<jupyter_code># scrivi qui

import csv
with open('esempio-1.csv', encoding='utf-8', newline='') as f:    
    lettore = csv.reader(f, delimiter=',')     
    listona = []
    next(lettore)
    for riga in lettore:
        listona.append([riga[0], int(riga[1])])
    print(listona)<jupyter_output>[['cane', 12], ['gatto', 14], ['pellicano', 30], ['scoiattolo', 6], ['aquila', 25]]<jupyter_text>Cos'è esattamente `lettore` ?Abbiamo detto che `lettore` genera una sequenza di righe, ed è _iterabile_. Nel ciclo `for`, ad ogni ciclo gli chiediamo di leggere una nuova riga, che viene messa nella variabile `riga`. Quindi possiamo chiederci, cosa succede se stampiamo direttamente `lettore`, senza usare nessun `for` ? Vedremo una bella lista o qualcos'altro?  Proviamo:<jupyter_code>import csv
with open('esempio-1.csv', encoding='utf-8', newline='') as f:    
    lettore = csv.reader(f, delimiter=',')     
    print(lettore)<jupyter_output><_csv.reader object at 0x7f98ae7b39d0><jupyter_text>Quello che vediamo è piuttosto deludente.**✪ ESERCIZIO 2.6**: Se ti ricordi, nell'introduzione a un certo punto ci siamo trovati nella stessa situazione, quando [abbiamo provato a stampare una map](https://it.softpython.org/intro/intro-sol.htmlTrasformazioni-con-le-map). Che potremmo fare per risolvere la situazione?<jupyter_code># scrivi qui

import csv
with open('esempio-1.csv', encoding='utf-8', newline='') as f:    
    lettore = csv.reader(f, delimiter=',')     
    print(list(lettore))<jupyter_output>[['animale', 'anni'], ['cane', '12'], ['gatto', '14'], ['pellicano', '30'], ['scoiattolo', '6'], ['aquila', '25']]<jupyter_text>Consumare un fileNon tutte le sequenze sono uguali. Da quello che hai visto finora, in Python scorrere un file assomiglia molto a scorrere una lista. Che è molto comodo, ma bisogna stare attenti ad alcune cose. Dato che i file potenzialmente potrebbero occupare terabyte, le funzioni di base di Python per leggere file evitano di caricarli tutti in memoria e tipicamente il file viene scorso un po' alla volta. Ma se il file non viene caricato tutto nell'ambiente di Python in un colpo solo, cosa succede se proviamo a scorrerlo due volte all'interno della stessa `with`? E se proviamo ad usarlo fuori dal `with`, che succede? Guarda i prossimi esercizi per scoprirlo. **✪ ESERCIZIO 2.7**: Prendendo la soluzione all'esercizio di prima, prova a chiamare `print(list(lettore))` due volte, in sequenza. Ottieni la stessa stampa entrambe le volte?<jupyter_code>#jupman-purge-output
# scrivi qui il codice

import csv
with open('esempio-1.csv', encoding='utf-8', newline='') as f:    
    lettore = csv.reader(f, delimiter=',')     
    print(list(lettore))
    print(list(lettore))<jupyter_output>[['animale', 'anni'], ['cane', '12'], ['gatto', '14'], ['pellicano', '30'], ['scoiattolo', '6'], ['aquila', '25']]
[]<jupyter_text>**✪ ESERCIZIO 2.8**: Prendendo la soluzione all'esercizio di prima (usando una sola print), prova qua sotto a spostare la print tutta a sinistra (eliminando gli spazi). Funziona ancora?<jupyter_code># scrivi qui 

import csv
with open('esempio-1.csv', encoding='utf-8', newline='') as f:    
    lettore = csv.reader(f, delimiter=',')         
#print(list(lettore))    # COMMENTATA, PERCHE' LANCEREBBE ERRORE DI FILE CHIUSO
                         # Non possiamo usare comandi che leggono il file fuori dal with !<jupyter_output><empty_output><jupyter_text>**✪✪✪ ESERCIZIO 2.9**: Adesso che abbiamo capito un po' che tipo di bestia è `lettore`,  proviamo adesso a produrre questo risultato come già fatto in precedenza, ma usando una _list comprehension_ invece del del ciclo `for`:```[['cane', 12],  ['gatto', 14],  ['pellicano', 30],  ['scoiattolo', 6],  ['aquila', 25]]```* Se riesci, prova anche a scrivere tutta la trasformazione per creare `listona` in una sola riga, usando la funzione [itertools.islice](https://docs.python.org/3/library/itertools.htmlitertools.islice) per saltare l'intestazione (per es. `itertools.islice(['A', 'B', 'C', 'D', 'E'], 2, None)` salta i primi due elementi e produce la sequenza C D E F G  - nel nostro caso gli elementi prodotti da `lettore` sarebbero righe)<jupyter_code>import csv
import itertools
with open('esempio-1.csv', encoding='utf-8', newline='') as f:    
    lettore = csv.reader(f, delimiter=',')     
    # scrivi qui
    listona = [[riga[0], int(riga[1])] for riga in itertools.islice(lettore, 1, None)]
    print(listona)<jupyter_output>[['cane', 12], ['gatto', 14], ['pellicano', 30], ['scoiattolo', 6], ['aquila', 25]]<jupyter_text>**✪ ESERCIZIO 2.10**: Crea un file `mio-esempio.csv` nella stessa cartella dove c'è questo foglio Jupyter, copiandoci dentro il contenuto del file `esempio-1.csv`. Poi aggiungici una colonna `descrizione`, ricordandoti di separare il nome di colonna dalla precedente con una virgola. Come valori della colonna, metti nelle righe successive stringhe tipo `i cani camminano`, `i pellicani volano` a seconda dell'animale, etc ricordandoti di separarle dagli anni usando una virgola, così:  `cane,12,i cani camminano`Dopo di che, copia e incolla qua sotto il codice Python per caricare il file, mettendo il nome di file `mio-esempio.csv`, e prova a caricare il tutto, tanto per vedere se funziona:<jupyter_code># scrivi qui<jupyter_output><empty_output><jupyter_text>**RISPOSTA**:```animale,anni,descrizionecane,12,i cani camminanogatto,14,i gatti camminanopellicano,30,i pellicani volanoscoiattolo,6,gli scoiattoli volanoaquila,25,le aquile volano``` **✪ ESERCIZIO 2.11**: Non tutti i CSV sono strutturati in modo uguale, e a volte quando scriviamo i csv o li importiamo alcuni accorgimenti sono necessari. Cominciamo a vedere che problemi potrebbero sorgere:- Nel file, prova a mettere uno o due spazi prima dei numeri, per es scrivi come sotto e guarda che succede:```cane, 12,i cani volano```**DOMANDA 2.11.1**: Lo spazio viene importato o no? **RISPOSTA**: sì **DOMANDA 2.11.2**: se convertiamo ad intero, lo spazio è un problema?  **RISPOSTA**: no **DOMANDA 2.11.3** Modifica solo la descrizione dei cani da `i cani camminano` a `i cani camminano, ma non volano` e prova a rieseguire la cella che legge il file. Che succede? **RISPOSTA**: Python legge un elemento in più nella lista **DOMANDA 2.11.4**: Per ovviare al problema precedente, una soluzione che si può adottare nei CSV è circondare stringhe contenti virgole da doppi apici, così: `"i cani camminano, ma non volano"`. Funziona ?  **RISPOSTA**: sì  Leggere come dizionariInvece di leggere un CSV come se fosse una sequenza di liste, potrebbe essere più conveniente dire a Python di interpretare le linee come se fossero dizionari.Tramite l'oggetto [csv.DictReader](https://docs.python.org/3/library/csv.htmlcsv.DictReader) sarai in grado di recuperare dizionari,  in cui le chiavi saranno i nomi dei campi presi dall'intestazione. **NOTA**: diverse versioni di Python producono diversi dizionari:* $<$ 3.6: `dict`* 3.6, 3.7: `OrderedDict`* $\geq$ 3.8: `dict`La 3.8 è ritornata al vecchio `dict` perchè nella sua nuova implementazione dei dizionari l'ordine delle chiavi è garantito, quindi sarà consistente con quello degli header del CSV.<jupyter_code>import csv
with open('esempio-1.csv', encoding='utf-8', newline='') as f:    
    lettore = csv.DictReader(f, delimiter=',')   # Notice we now used DictReader
    for diz in lettore:
        print(diz)<jupyter_output>OrderedDict([('animale', 'cane'), ('anni', '12')])
OrderedDict([('animale', 'gatto'), ('anni', '14')])
OrderedDict([('animale', 'pellicano'), ('anni', '30')])
OrderedDict([('animale', 'scoiattolo'), ('anni', '6')])
OrderedDict([('animale', 'aquila'), ('anni', '25')])<jupyter_text>Scrivere un CSV Puoi facilmente creare un CSV instanziando un oggetto `writer`:**ATTENZIONE: ASSICURATI DI SCRIVERE NEL FILE GIUSTO!**Se non stai più che attento ai nomi dei file, **rischi di cancellare dati** !!!<jupyter_code>import csv

# Per scrivere, RICORDATI di specificare l'opzione 'w'
# ATTENZIONE: 'w' rimpiazza *completamente* eventuali file  esistenti!
with open('file-scritto.csv', 'w', newline='') as csv_da_scrivere: 
    
    scrittore = csv.writer(csv_da_scrivere, delimiter=',')    
    
    scrittore.writerow(['This', 'is', 'a header'])
    scrittore.writerow(['some', 'example', 'data'])
    scrittore.writerow(['some', 'other', 'example data'])<jupyter_output><empty_output><jupyter_text>Leggere e scrivere un CSV Per scrivere un nuovo CSV prendendo dati da un CSV esistente, potresti annidare un `with` per la lettura dentro uno per la scrittura:**ATTENZIONE A SCAMBIARE I NOMI DEI FILE!**Quando leggiamo e scriviamo è facile commettere un errore e sovrascrivere accidentalmente i nostri preziosi dati. **Per evitare problemi:*** usa nomi espliciti sia per i file di output (es: `esempio-1-arricchito.csv')` che per gli handle (es: `csv_da_scrivere`)* fai una copia di backup dei dati da leggere* controlla sempre prima di eseguire il codice !<jupyter_code>import csv
    
# Per scrivere, RICORDATI di specificare l'opzione 'w'
# ATTENZIONE: 'w' rimpiazza *completamente* eventuali file  esistenti!
# ATTENZIONE: l'handle *esterno* l'abbiamo chiamato  csv_da_scrivere
with open('esempio-1-arricchito.csv', 'w', encoding='utf-8', newline='') as csv_da_scrivere: 
    scrittore = csv.writer(csv_da_scrivere, delimiter=',')

    # Nota come questo 'with' sia dentro quello esterno    
    # ATTENZIONE: l'handle *interno* l'abbiamo chiamato csv_da_leggere
    with open('esempio-1.csv', encoding='utf-8', newline='') as csv_da_leggere:    
        lettore = csv.reader(csv_da_leggere, delimiter=',')      
        
        for riga in lettore:
            riga.append("qualcos'altro")
            scrittore.writerow(riga)
            scrittore.writerow(riga)
            scrittore.writerow(riga)<jupyter_output><empty_output><jupyter_text>Vediamo se il file è stato effettivamente scritto provando a leggerlo:<jupyter_code>with open('esempio-1-arricchito.csv', encoding='utf-8', newline='') as csv_da_leggere:    
    lettore = csv.reader(csv_da_leggere, delimiter=',')      

    for riga in lettore:
        print(riga)<jupyter_output>['animale', 'anni', "qualcos'altro"]
['animale', 'anni', "qualcos'altro"]
['animale', 'anni', "qualcos'altro"]
['cane', '12', "qualcos'altro"]
['cane', '12', "qualcos'altro"]
['cane', '12', "qualcos'altro"]
['gatto', '14', "qualcos'altro"]
['gatto', '14', "qualcos'altro"]
['gatto', '14', "qualcos'altro"]
['pellicano', '30', "qualcos'altro"]
['pellicano', '30', "qualcos'altro"]
['pellicano', '30', "qualcos'altro"]
['scoiattolo', '6', "qualcos'altro"]
['scoiattolo', '6', "qualcos'altro"]
['scoiattolo', '6', "qualcos'altro"]
['aquila', '25', "qualcos'altro"]
['aquila', '25', "qualcos'altro"]
['aquila', '25', "qualcos'altro"]<jupyter_text>CSV Impianti funiviari Di solito sui cataloghi open data come il popolare CKAN (es [dati.trentino.it](http://dati.trentino.it/),  [data.gov.uk](https://data.gov.uk/), [European data portal](https://www.europeandataportal.eu/)) i file sono organizzati in _dataset_, che sono collezioni di _risorse_: ogni risorsa contiene direttamente un file dentro al catalogo (tipicamente CSV, JSON o XML) oppure un link al file vero e proprio su un server di proprietà dell'organizzazione che ha creato i dati.Il primo dataset che guarderemo sarà 'Impianti funiviari in esercizio pubblico':http://dati.trentino.it/dataset/impianti-funiviari-in-esercizio-pubblicoQua troverai alcune informazioni generiche sul dataset, di importante nota la licenza che è [Creative Commons Zero v1.0](http://creativecommons.org/publicdomain/zero/1.0/deed.it), praticamente è la licenza più permissiva che si possa trovare, e garantisce la certezza di poter riusare i dati senza alcun vincolo.All'interno della pagina del dataset, è presente una  risorsa chiamata "Elenco degli impianti bifuni con movimento a va e vieni":http://dati.trentino.it/dataset/impianti-funiviari-in-esercizio-pubblico/resource/ceb488a9-696c-4b9d-b8c8-6ca727863931Alla pagina della risorsa, troviamo un link ad un file CSV (ci si arriva anche cliccando sul bottone blu 'Vai alla risorsa'): http://www.sif.provincia.tn.it/binary/pat_sif/opendata/Elenco_degli_impianti_bifuni_con_movimento_a_va_e_vieni_aggiornato_a_marzo_2015.1435662007.csvSe apro il CSV in una tab di Firefox, sul mio computer che ha sistema operativo Linux, vedo una cosa del genere:```Denominazione impianto,Codice S.I.F.,Comune,Esercente,Data Primo collaudo,Data Ultimo Collaudo,Lunghezza Inclinata (m),Dislivello (m),Stazione di Valle (m.s.l.m.),CapacitÃ  Veicolo (pers.),Portata Max (Pers/h),Opere Paravalanghe ,ServizioTrento - Sardagna,B007e,Trento,Soc. Trentino Trasporti Esercizio,08-apr-64,18-mar-03,1157,400,200,13,270,,annualeMezzocorona - Monte,B008e,Mezzocorona,Soc.Fun. Monte Mezzocorona,22-dic-04,22-dic-04,933,623,267,7,130,si (frane),annualeAlba - Ciampac,B014m,Canazei,Soc.Fun. Ciampac e Contrin,20-mag-75,27-nov-14,1902,658,1496,75,840,,bistagionalePecol - <NAME>,B017m,Canazei,S.I.T.C.,09-feb-79,05-dic-97,1313,450,1932,76,1100,si,bistagionaleP. S.Pellegrino - <NAME>,B022m,Moena,Soc.Fun. Col Margherita,11-mar-82,14-dic-01,1395,639,1874,97,1330,,bistagionaleVigo di Fassa - Ciampedie,B026m,Vigo di Fassa,Soc.Catinaccio Impianti a Fune,24-lug-85,23-giu-05,1523,570,1431,101,1300,,bistagionaleCampitello - Col Rodella ,B027m,Canazei,S.I.T.C.,06-dic-86,07-lug-05,2472,984,1411,126,1160,si,bistagionalePasso Pordoi - Sass Pordoi,B028m,Canazei,S.I.T.C.,23-mag-95,23-mag-95,1487,708,2240,65,820,,bistagionaleCol Verde - Rosetta,B029b,Tonadico,Imprese e Territorio Soc. Cons. s.r.l.,21-lug-04,21-lug-04,1336,674,1935,41,420,si + M.G.,bistagionaleTarlenta - Rifugio Mantova,B030g,Peio,Soc.Fun. Peio,30-dic-10,30-dic-10,2856,992,2001,101,860,si + frane,bistagionale```Come atteso, vediamo dei campi separati da virgole.  Problema: caratteri sbagliati ??Si vede subito un problema nella prima riga delle intestazioni, alla colonna `CapacitÃ  Veicolo (pers.)`. Pare che il file abbia la 'à' accentata sbagliata. Ma è davvero un problema del file? Vi dico subito di no. Probabilmente, è il server che non sta dicendo a Firefox quale è il giusto encoding per il file. Firefox non ha capacità di divinazione, e fa solo il suo meglio per mostrare il CSV basandosi sulle informazioni che ha, che possono essere limitate e/o addirittura incorrette. Il mondo non è mai come lo vorremmo... **✪ 2.12 ESERCIZIO**: scarica il CSV, e prova ad aprirlo in Excel, e / o in LibreOffice Calc. Vedi la à accentata corretta? Se no, prova ad impostare l'encoding (per es. in Calc si chiama 'Character set')  a 'Unicode (UTF-8)'            **ATTENZIONE SE USI Excel!**     Facendo direttamente `File->Apri in Excel`, probabilmente Excel cercherà di immaginarsi da solo come intabellare il CSV, e sbaglierà metterà tutto le righe in una colonna. Per ovviare al problema, dobbiamo dire ad Excel di mostrare un pannello per chiederci come vogliamo aprire il CSV, facendo così: * In Excel vecchi, cerca `File-> Importa`* In Excel recenti, clicca la scheda `Dati` e poi seleziona `Da testo`. Per ulteriori riferimenti su Excel, [vedere guida di Salvatore Aranzulla](https://www.aranzulla.it/come-aprire-file-csv-672903.html)- **NOTA**: Casomai il file non fosse disponibile, nella cartella dove c'è questo notebook Jupyter troverai anche lo stesso file rinominato in `impianti-bifuni.csv` ![Esempio di import in LibreOffice Calc-432](import-impianti-csv.png) Poi dovrebbe risultare più o meno una tabella come questa: ![Tabella impianti risalita-42393](impianti-csv.png)  Importare in PythonAdesso che abbiamo capito due cose sull'encoding, proviamo ad importare il file in Python (ho rinominato il file originale nel più corto `impianti-bifuni.csv`):<jupyter_code>import csv
with open('impianti-bifuni.csv',  encoding='utf-8', newline='') as f:
    
    # creiamo un oggetto 'lettore' che pescherà righe dal file
    lettore = csv.reader(f, delimiter=',') 
    
    # 'lettore' è un oggetto cosiddetto 'iterabile', cioè se usato in un for produce una 
    #  sequenza di righe dal csv 
    #  NOTA: qui ogni riga del file viene convertita in una una lista di stringhe Python!
    for riga in lettore:
        print('Abbiamo appena letto una riga!')        
        print(riga)  # stamperà la variabile 'riga', che è una lista di stringhe
        print('')    # stampa una stringa vuota, per separare in verticale<jupyter_output>Abbiamo appena letto una riga!
['Denominazione impianto', 'Codice S.I.F.', 'Comune', 'Esercente', 'Data Primo collaudo', 'Data Ultimo Collaudo', 'Lunghezza Inclinata (m)', 'Dislivello (m)', 'Stazione di Valle (m.s.l.m.)', 'Capacità Veicolo (pers.)', 'Portata Max (Pers/h)', 'Opere Paravalanghe ', 'Servizio']

Abbiamo appena letto una riga!
['Trento - Sardagna', 'B007e', 'Trento', 'Soc. Trentino Trasporti Esercizio', '08-apr-64', '18-mar-03', '1157', '400', '200', '13', '270', '#', 'annuale']

Abbiamo appena letto una riga!
['Mezzocorona - Monte', 'B008e', 'Mezzocorona', 'Soc.Fun. Monte Mezzocorona', '22-dic-04', '22-dic-04', '933', '623', '267', '7', '130', 'si (frane)', 'annuale']

Abbiamo appena letto una riga!
['Alba - Ciampac', 'B014m', 'Canazei', 'Soc.Fun. Ciampac e Contrin', '20-mag-75', '27-nov-14', '1902', '658', '1496', '75', '840', '#', 'bistagionale']

Abbiamo appena letto una riga!
['Pecol - <NAME>', 'B017m', 'Canazei', 'S.I.T.C.', '09-feb-79', '05-dic-97', '1313', '4[...]<jupyter_text>**✪✪ ESERCIZIO 2.13** Vediamo che nel dataset alla terza colonna ci sono i comuni dove ci sono gli impianti. Prova a estrarli in una variabile `comuni`. - Attenzione alla prima riga, non vogliamo che la scritta `Comune` appaia nel risultato- Se li metti in una lista, avrai un problema, perchè ci sono comuni che appaiono più volte. Ti conviene quindi creare un insieme vuoto con `set([])` e aggiungere elementi con il metodo `add`  (gli insiemi non hanno metodo `append`). Stampando `comuni` dovrebbe apparire qualcosa del genere: `{'Vigo di Fassa', 'Trento', 'Mezzocorona', 'Moena', 'Peio', 'Canazei', 'Tonadico'}`**NOTA**: gli insiemi non hanno ordine, potresti vedere un insieme ordinato diversamente !<jupyter_code>import csv
with open('impianti-bifuni.csv',  encoding='utf-8', newline='') as f:
    
    lettore = csv.reader(f, delimiter=',') 
    
    # scrivi qui 
    
    next(lettore)
    comuni = set([])
    for riga in lettore:
        comuni.add(riga[2])
    print(comuni)<jupyter_output>{'Mezzocorona', 'Tonadico', 'Peio', 'Canazei', '<NAME>', 'Trento', 'Moena'}<jupyter_text>**✪✪ ESERCIZIO 2.14**: prova a conteggiare in un dizionario quanti tipi di servizio vengono trovati. Dovresti ottenere un risultato così:```python{ 'bistagionale': 8, 'annuale': 2}```**SUGGERIMENTO**:  `'Servizio'` è all'ultima posizione, per ottenerla dalla riga puoi usare l'indice `-1`<jupyter_code>import csv
with open('impianti-bifuni.csv',  encoding='utf-8', newline='') as f:
    
    lettore = csv.reader(f, delimiter=',') 
    
    # scrivi qui 
    
    next(lettore)
    servizi = {}
    for riga in lettore:        
        if riga[-1] in servizi:
            servizi[riga[-1]] += 1
        else:
            servizi[riga[-1]] = 1 
    print(servizi)<jupyter_output>{'annuale': 2, 'bistagionale': 8}<jupyter_text>Il mistero delle opere paravalanghe Se guardiamo nella colonna 'Opere paravalanghe', notiamo che i valori possibili sono:* ``* `si`* `si (frane)`* `si + frane`* `si + M.G`Sono sempre valori testuali, ma pare esserci una qualche regolarità. Visto che il nostro scopo è convertire in oggetti Python i file in ingresso, forse si potrebbero compiere alcune operazioni per semplificare e adattare al meglio questi dati?  **✪✪ 2.15 ESERCIZIO**: `si + frane` è la stessa cosa di `si (frane)` ? Assumendo cha sia vero, prova a stampare righe modificate in cui per uniformare gli elementi uguali a `si (frane)` settandoli  a `si + frane`. Sia che conosci o non conosci Python, avendo guardato la soluzione della domanda precedente dovresti essere in grado di farlo (ricorda che per modificare un elemento di una lista all'indice `N` si scrive `lista[N] = NUOVO_VALORE` ) Scrivi qua sotto la soluzione<jupyter_code># scrivi qui 

import csv 
    
with open('impianti-bifuni.csv', encoding='utf-8', newline='') as f:
    lettore = csv.reader(f, delimiter=',')
    next(lettore, None) # saltiamo la riga con l'intestazione
    for row in lettore:
        if 'si (frane)' in riga[11]: # ricordati che l'indice delle colonne inizia da 0 
            riga[11] = 'si + frane'
        print(riga)<jupyter_output>['<NAME>', 'B030g', 'Peio', 'Soc.Fun. Peio', '30-dic-10', '30-dic-10', '2856', '992', '2001', '101', '860', 'si + frane', 'bistagionale']
['Tarlenta - <NAME>', 'B030g', 'Peio', 'Soc.Fun. Peio', '30-dic-10', '30-dic-10', '2856', '992', '2001', '101', '860', 'si + frane', 'bistagionale']
['Tarlenta - <NAME>', 'B030g', 'Peio', 'Soc.Fun. Peio', '30-dic-10', '30-dic-10', '2856', '992', '2001', '101', '860', 'si + frane', 'bistagionale']
['Tarlenta - <NAME>', 'B030g', 'Peio', 'Soc.Fun. Peio', '30-dic-10', '30-dic-10', '2856', '992', '2001', '101', '860', 'si + frane', 'bistagionale']
['Tarlenta - <NAME>', 'B030g', 'Peio', 'Soc.Fun. Peio', '30-dic-10', '30-dic-10', '2856', '992', '2001', '101', '860', 'si + frane', 'bistagionale']
['Tarlenta - <NAME>', 'B030g', 'Peio', 'Soc.Fun. Peio', '30-dic-10', '30-dic-10', '2856', '992', '2001', '101', '860', 'si + frane', 'bistagionale']
['Tarlenta - <NAME>', 'B030g', 'Peio', 'Soc[...]<jupyter_text>**✪ 2.16 DOMANDA**:  Quel `'M.G'` non chiarissimo, come neanche se ci sia qualche differenza tra `frane` e `valanghe`. Capire il significato di cosa manipoliamo è importante, forse potresti scoprire il significato nella [pagina del dataset](http://dati.trentino.it/dataset/impianti-funiviari-in-esercizio-pubblico) ?  **RISPOSTA**: Nella sezione _Descrizione Campi_ nella [pagina del dataset](http://dati.trentino.it/dataset/impianti-funiviari-in-esercizio-pubblico) vediamo scritto:```Opere paravalanghe = Presenza di opere paravalanghe o misure gestionali    Mis. Ges. / M.G. = Misure gestionali (piano P.I.S.T.E. o piano P.I.D.A.V.)    CLV = Commissione locale valanghe    Si = Presenza di opere paravalanghe```Scopriamo quindi che M.G. vuol dire 'Misure Gestionali', purtroppo non ci dicono nulla sulle frane. Questo non è un caso eccezionale, spesso accade che le descrizioni dei dati siano incomplete a si è costretti ad operare in regime di incertezza. **✪ 2.17 ESERCIZIO**: Cosa significa quel cancelletto `` ? Conosci un oggetto di Python che potrebbe rappresentarlo ? Riesci a sostituire tutti i cancelletti con quell'oggetto prima di stampare le liste?<jupyter_code># scrivi qui

import csv

with open('impianti-bifuni.csv', encoding='utf-8', newline='') as f:
    lettore = csv.reader(f, delimiter=',')
    next(lettore, None) # saltiamo la riga con l'intestazione
    for riga in lettore:
        if '#' in riga[11]: # ricordati che l'indice delle colonne inizia da 0 
            riga[11] = None
        print(riga)<jupyter_output>['<NAME>', 'B007e', 'Trento', 'Soc. Trentino Trasporti Esercizio', '08-apr-64', '18-mar-03', '1157', '400', '200', '13', '270', None, 'annuale']
['Mezzocorona - Monte', 'B008e', 'Mezzocorona', 'Soc.Fun. Monte Mezzocorona', '22-dic-04', '22-dic-04', '933', '623', '267', '7', '130', 'si (frane)', 'annuale']
['Alba - Ciampac', 'B014m', 'Canazei', 'Soc.Fun. Ciampac e Contrin', '20-mag-75', '27-nov-14', '1902', '658', '1496', '75', '840', None, 'bistagionale']
['Pecol - <NAME>', 'B017m', 'Canazei', 'S.I.T.C.', '09-feb-79', '05-dic-97', '1313', '450', '1932', '76', '1100', 'si', 'bistagionale']
['<NAME> - <NAME>', 'B022m', 'Moena', 'Soc.Fun. Col Margherita', '11-mar-82', '14-dic-01', '1395', '639', '1874', '97', '1330', None, 'bistagionale']
['<NAME> - Ciampedie', 'B026m', 'Vigo di Fassa', 'Soc.Catinaccio Impianti a Fune', '24-lug-85', '23-giu-05', '1523', '570', '1431', '101', '1300', None, 'bistagionale']
['Campitello - <NAME> ', 'B027m', 'Can[...]<jupyter_text>✪✪ **2.18 ESERCIZIO**: prova a salvare i dati sistemati (quindi con entrambe le traasformazioni `si (frane) -> si + frane` e ` -> None` nel NUOVO file `impianti-bifuni-sistemato.csv`- se nella cartella trovi già un file chiamato `impianti-bifuni-sistemato.csv`, ricordati di cancellarlo manualmente prima di iniziare l'esercizio- il valore `None` come verrà scritto ? Controlla nel file generato dal tuo codice.<jupyter_code># scrivi qui

import csv

# Per scrivere, RICORDATI di specificare l'opzione 'w'
# ATTENZIONE: 'w' rimpiazza *completamente* eventuali file  esistenti!
# ATTENZIONE: l'handle *esterno* l'abbiamo chiamato  csv_da_scrivere
with open('impianti-bifuni-sistemato.csv', 'w', encoding='utf-8', newline='') as csv_da_scrivere:
    
    scrittore = csv.writer(csv_da_scrivere, delimiter=',')
    
    # Nota come questo 'with' sia dentro quello esterno    
    # ATTENZIONE: l'handle *interno* l'abbiamo chiamato csv_da_leggere        
    with open('impianti-bifuni.csv', encoding='utf-8', newline='') as csv_da_leggere:
        lettore = csv.reader(csv_da_leggere, delimiter=',')
        next(lettore, None) # saltiamo la riga con l'intestazione
        for riga in lettore:
            if 'si (frane)' in row[11]: # ricordati che l'indice delle colonne inizia da 0 
                row[11] = 'si + frane'
            elif '#' in riga[11]: # ricordati che l'indice delle colonne inizia da 0 
                riga[11] = None            
            scrittore.writerow(riga)            
print()            
print('Completata scrittura di:  impianti-bifuni-sistemato.csv')<jupyter_output>Completata scrittura di:  impianti-bifuni-sistemato.csv
{'fraction_non_alphanumeric': 0.1265841013824885, 'fraction_numerical': 0.031451612903225803, 'mean_word_length': 4.728592641478304, 'pattern_counts': {'":': 1, '<': 84, '<?xml version=': 0, '>': 87, 'https://': 9, 'lorem ipsum': 0, 'www.': 3, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '20', 'dir': 'jupyter-structured-clean-dedup', 'id': '367823', 'license': 'CC-BY-4.0', 'n_tokens_mistral': 13671, 'n_tokens_neox': 12914, 'n_words': 4075, 'path': 'formats/formats2-csv-sol.ipynb', 'repo_name': 'DavidLeoni/softpython-', 'size': '34870'}