Pulizia dei dati con Go Parte 1

Panoramica

Uno degli aspetti più importanti di qualsiasi applicazione è la convalida del suo input. L'approccio più semplice è fallito se l'input non soddisfa i requisiti. Tuttavia, in molti casi questo non è abbastanza. In molti sistemi la raccolta dei dati è separata dall'analisi dei dati. Potrebbe essere un sondaggio o un vecchio set di dati. 

In questi casi, è necessario esaminare l'intero set di dati prima dell'analisi, rilevare dati non validi o mancanti, correggere ciò che può essere corretto e contrassegnare o rimuovere i dati che non possono essere recuperati. È anche utile fornire statistiche sulla qualità dei dati e sui tipi di errori riscontrati. 

In questa serie in due parti imparerai come utilizzare le strutture di testo di Go, affetta e taglia i file CSV e assicurati che i tuoi dati siano perfettamente puliti. Nella prima parte, ci concentreremo sulle basi dell'elaborazione del testo in go-byte, rune e stringhe, oltre a lavorare con i file CSV.

Testo in Go

Prima di immergerci nella pulizia dei dati, iniziamo con le fondamenta del testo in Go. I blocchi predefiniti sono byte, rune e stringhe. Vediamo cosa rappresenta ciascuno e quali sono le relazioni tra di loro. 

byte

I byte sono numeri a 8 bit. Ogni byte può rappresentare uno dei possibili 256 valori (2 alla potenza di 8). Ogni carattere nel set di caratteri ASCII può essere rappresentato da un singolo byte. Ma i byte lo sono non personaggi. Il motivo è che Go come linguaggio moderno supporta Unicode, dove esistono più di 256 caratteri separati. Inserisci le rune. 

Runes

Una runa in Go è un altro nome per il tipo int32. Ciò significa che ogni runa può rappresentare più di quattro miliardi di valori separati (2 alla potenza di 32), che è abbastanza buono da coprire l'intero set di caratteri Unicode. 

Nel codice seguente puoi vedere che la runa 'Δ' (alt-J su Mac) è solo una int32. Per stampare il carattere che rappresenta sullo schermo, devo convertirlo in una stringa.

pacchetto main import ("fmt") func main () r: = 'Δ' fmt.Println (r) fmt.Println (int32 (r)) fmt.Println (string (r)) Output: 8710 8710 Δ 

Unicode è complicato. Una runa rappresenta ufficialmente un punto di codice Unicode. I caratteri Unicode sono solitamente rappresentati da un singolo punto di codice Unicode, ma a volte più di uno.

stringhe

Le stringhe sono ufficialmente solo sezioni di byte di sola lettura. Se indicizzi una stringa, ottieni un byte indietro:

func main () s: = "abc" per i: = 0; io < len(s); i++  fmt.Println(s[i])   Output: 97 98 99 

I valori letterali stringa sono una sequenza di caratteri UTF-8 racchiusi tra virgolette. Possono contenere sequenze di escape, che sono un backslash seguito da un carattere ASCII come \ n (newline) o \ t (Scheda). Hanno significati speciali. Ecco la lista completa:

\ a U + 0007 avviso o campana \ b U + 0008 backspace \ f U + 000C avanzamento modulo \ n U + 000A avanzamento riga o nuova riga \ r U + 000D ritorno carrello \ t U + 0009 scheda orizzontale \ v U + 000b verticale tab \\ U + 005c backslash \ 'U + 0027 virgoletta singola (valido solo all'interno di rune letterali) \ "U + 0022 virgoletta doppia (valido solo all'interno di stringhe letterali) 

A volte è possibile che si desideri archiviare i byte letterali direttamente in una stringa, indipendentemente dalle sequenze di escape. Potresti scappare da ogni backslash, ma è noioso. Un approccio molto migliore consiste nell'utilizzare stringhe non elaborate racchiuse tra apici inversi. 

Ecco un esempio di una stringa con a \ t (tab) sequenza di escape, che viene rappresentata una volta così com'è, quindi con l'escape backslash e quindi come una stringa raw:

func main () s1: = "1 \ t2" s2: = "1 \\ t2" s3: = '1 \ t2' fmt.Println (s1) fmt.Println (s2) fmt.Println (s3) Uscita : 1 2 1 \ t2 1 \ t2 

Mentre le stringhe sono fette di byte, quando si scorre su una stringa con un'istruzione for-range, si ottiene una runa in ogni iterazione. Ciò significa che puoi ottenere uno o più byte. Questo è facile da vedere con l'indice for-range. Ecco un esempio pazzo. La parola ebraica "שלום" significa "Ciao" (e pace). L'ebraico è anche scritto da destra a sinistra. Costruirò una stringa che mescola la parola ebraica con la sua traduzione inglese. 

Quindi, lo stamperò rune by rune, incluso l'indice di byte di ogni runa all'interno della stringa. Come vedrai, ogni runa ebraica prende due byte, mentre i caratteri inglesi prendono un byte, quindi la lunghezza totale di questa stringa è di 16 byte, anche se ha quattro caratteri ebraici, tre simboli e cinque caratteri inglesi (12 caratteri ). Inoltre, i caratteri ebraici verranno visualizzati da destra a sinistra:

func main () hello: = "שלום = ciao" fmt.Println ("length:", len (ciao)) per i, r: = range (ciao) fmt.Println (i, string (r))  Output: lunghezza: 16 0 ש 2 ל 4 ו 6 ם 8 9 = 10 11 h 12 e 13 l 14 l 15 o 

Tutte queste sfumature possono essere estremamente importanti quando si dispone di un set di dati per pulire con citazioni strane e un mix di caratteri e simboli Unicode.

Quando si stampano stringhe e porzioni di byte, ci sono molti specificatori di formato che funzionano allo stesso modo su entrambi. Il %S il formato stampa i byte così come sono, %X stampa due caratteri esadecimali minuscoli per byte, %X stampa due caratteri esadecimali maiuscoli per byte, e q% stampa una stringa doppia citata sfuggita con la sintassi go. 

Per evitare il segno% all'interno di uno specificatore di stringhe di formato, basta raddoppiarlo. Per separare i byte durante l'utilizzo %X o %X, puoi aggiungere uno spazio, come in "% x" e "% X". Ecco la demo:

func main () s: = "שלום" fmt.Printf ("%% formato s:% s \ n", s) fmt.Printf ("%% x formato:% x \ n", s) fmt.Printf ("%% formato X:% X \ n", s) fmt.Printf ("%% x formato:% x \ n", s) fmt.Printf ("%% formato X:% X \ n", s ) fmt.Printf ("%% q formato:% q \ n", s) Output: formato% s: שלום% x formato: d7a9d79cd795d79d% formato X: D7A9D79CD795D79D formato% x: d7 a9 d7 9c d7 95 d7 9d% Formato X: D7 A9 D7 9C D7 95 D7 9D Formato% q: "שלום"

Lettura e scrittura di file CSV

I dati possono arrivare in molti modi e formati. Uno dei formati più comuni è CSV (valori separati da virgola). I dati CSV sono molto efficienti. I file hanno in genere una riga di intestazione con il nome dei campi o colonne e righe di dati in cui ogni riga contiene un valore per campo, separati da virgole. 

Ecco un piccolo frammento di un dataset di avvistamenti UFO (davvero). La prima riga (intestazione) contiene i nomi delle colonne e le altre righe contengono i dati. Puoi vedere che spesso la colonna "Colori riportati" è vuota:

Città, Colori segnalati, Forma segnalata, Stato, Tempo Itaca, TRIANGOLO, NY, 6/1/1930 22:00 Willingboro ,, OTHER, NJ, 6/30/1930 20:00 Holyoke ,, OVAL, CO, 2 / 15/1931 14:00 Abilene ,, DISK, KS, 6/1/1931 13:00 Fiera dei mondi di New York ,, LIGHT, NY, 4/18/1933 19:00 Valley City ,, DISCO, ND, 9/15 / 1934 15:30 Crater Lake ,, CIRCLE, CA, 6/15/1935 0:00 Alma ,, DISK, MI, 7/15/1936 0:00 Eklutna ,, SIGAR, AK, 10/15/1936 17: 00 Hubbard ,, CILINDRO, O, 6/15/1937 0:00 Fontana ,, LIGHT, CA, 8/15/1937 21:00 Waterloo ,, FIREBALL, AL, 6/1/1939 20:00 Belton, ROSSO, SFERA, SC, 6/30/1939 20:00 

La scrittura di questo frammento di dati CSV in un file implica alcune operazioni con le stringhe e il lavoro con i file. Prima di immergerci nella logica principale, ecco le parti obbligatorie: la definizione del pacchetto, le importazioni e la stringa di dati (notare l'uso di const).

pacchetto principale import ("os" "stringhe" "bufio") dati: = 'Città, Colori segnalati, Forma segnalata, Stato, Tempo Itaca, TRIANGOLO, NY, 6/1/1930 22:00 Willingboro ,, OTHER, NJ , 6/30/1930 20:00 Holyoke ,, OVAL, CO, 2/15/1931 14:00 Abilene ,, DISCO, KS, 6/1/1931 13:00 Fiera dei mondi di New York ,, LIGHT, NY, 4 / 18/1933 19:00 Valley City ,, DISCO, ND, 9/15/1934 15:30 Lago Crater, CIRCLE, CA, 6/15/1935 0:00 Alma ,, DISCO, MI, 7/15 / 1936 0:00 Eklutna ,, SIGARO, AK, 10/15/1936 17:00 Hubbard ,, CILINDRO, O, 6/15/1937 0:00 Fontana ,, LUCE, CA, 8/15/1937 21:00 Waterloo ,, FIREBALL, AL, 6/1/1939 20:00 Belton, RED, SFERA, SC, 6/30/1939 20:00 ' 

Il principale() funzione crea un file chiamato "ufo-sightings.csv", verifica che non ci siano errori e quindi crea uno scrittore bufferizzato w. Il differire chiama nella riga successiva, che scarica il contenuto del buffer nel file, viene eseguito alla fine della funzione. Questo è il significato di differimento. Quindi, usa il Diviso() funzione del pacchetto di stringhe per rompere le stringhe di dati in singole righe. 

Quindi, all'interno del ciclo for, gli spazi bianchi iniziali e finali vengono ritagliati da ogni riga. Le righe vuote vengono saltate e le righe non vuote vengono scritte nel buffer, seguito da un carattere di nuova riga. Questo è tutto. Il buffer verrà scaricato nel file alla fine.

func main () f, err: = os.Create ("ufo-sightings.csv") se err! = nil panico (e) w: = bufio.NewWriter (f) rimanda linee w.Flush (): = strings.Split (data, "\ n") per _, line: = range lines line: = strings.Trim (line, "") if line == "" continue w.WriteString (line) w. WriteString ("\ n")

Leggere dal file è piuttosto semplice:

pacchetto main import ("fmt" "io / ioutil") func main () data, err: = ioutil.ReadFile ("ufo-sightings.csv") se err! = nil panic (err) fmt.Println ( string (dati))

Conclusione

Go ha solide strutture per gestire il testo di tutte le forme e codifiche. In questa parte della serie, abbiamo esaminato le basi della rappresentazione del testo in Go, l'elaborazione del testo utilizzando il pacchetto stringhe e la gestione dei file CSV. 

Nella seconda parte, metteremo in pratica ciò che abbiamo imparato per ripulire i dati disordinati in preparazione all'analisi.