Questa è la seconda parte di due di una serie sulla pulizia dei dati usando Go. Nella prima parte, abbiamo coperto le funzionalità di base del testo di Go e lavorando con i file CSV. In questo tutorial, ci immergeremo nella pulizia dei dati effettiva.
Inizieremo a comprendere il problema dei dati disordinati e a elaborare una strategia, quindi esamineremo i singoli campi, correggeremo i dati laddove possibile e decidiamo cosa fare in merito ai valori mancanti.
Una strategia per la pulizia dei dati dovrebbe dettare cosa fare quando si incontrano dati non validi, disordinati, parziali o mancanti. Dovrebbe anche determinare il livello di segnalazione necessario per il processo di pulizia.
I dati su cui ci stiamo concentrando qui sono dati tabulari, in cui ogni riga è indipendente. Non ci sono gerarchie nidificate o connessioni tra diverse righe di dati. Un sacco di set di dati del mondo reale hanno questa bella proprietà.
L'approccio più semplice per gestire i dati non validi è rimuoverlo. Se manca qualche campo o contiene dati non validi, basta eliminare l'intera riga. Questo è molto facile, ea volte è la cosa giusta da fare. Se il campo problematico è critico e non hai modo di recuperarlo, tutto ciò che puoi fare è eliminare l'intero record.
La soluzione migliore è la correzione del campo non valido. In alcuni casi, è facile rilevare il problema e risolverlo. Nel dataset degli avvistamenti UFO, il campo dello stato può essere uno dei 52 stati degli Stati Uniti.
Se il valore deve essere tutto in maiuscolo e alcune righe contengono lettere minuscole, puoi semplicemente renderle maiuscole.
La segnalazione su righe non valide, sia eliminate che fisse, è importante. L'organizzazione può decidere di consentire alle persone di provare a correggere i dati abbandonati. Potrebbe essere necessario eseguire dati fissi dal QA per garantire che le correzioni automatiche non abbiano introdotto dati non validi.
La raccolta di statistiche sul processo di pulizia è necessaria per valutare la qualità dei dati di origine e, talvolta, per determinare se vale la pena elaborare i dati ripuliti. Le statistiche possono includere il numero di righe eliminate e fisse e il numero di campi danneggiati e mancanti per ogni colonna.
Finora ho descritto un approccio di pre-elaborazione per la pulizia dei dati. Tuttavia, è possibile eseguire la pulizia durante l'elaborazione. Ogni riga viene controllata appena prima dell'elaborazione. Questo è a volte utile, se non vi è alcun punto nella pre-elaborazione perché nessuno può correggere i dati cattivi prima del tempo per analisi successive o se l'elaborazione è sensibile al fattore tempo.
In questo scenario, lo scopo principale della pulizia è assicurarsi che le righe di dati non valide non interrompano l'intera pipeline di elaborazione e possono essere saltate o riparate in base alle necessità.
Come si fa a verificare i campi? Hai bisogno di sapere esattamente quale tipo di dati dovrebbe essere lì e, a volte, quali valori. Ecco alcuni esempi.
I campi numerici sono molto comuni nei set di dati. Oltre il tipo di numero (intero, reale, complesso), alcuni campi sono più specializzati. Ad esempio, un campo di prezzo può richiedere esattamente due punti decimali ed essere positivo. Ecco una funzione che controlla se una stringa rappresenta un prezzo:
func validate_price (s string) bool parts: = strings.Split (s, ".") if len (parts)! = 2 return false dollari, err: = strconv.Atoi (parti [0]) se err! = nil return false se dollari < 0 return false cents, err := strconv.Atoi(parts[1]) if err != nil return false if cents < 0 || cents > 99 return false return true
A volte devi andare oltre. Se è necessario verificare che un URL sia valido, ci sono due approcci:
Se ti interessa solo se l'URL è ben formato, allora il primo approccio funziona. Ma se vuoi assicurarti che l'URL punti effettivamente verso una destinazione reale, devi utilizzare il secondo approccio. Dal momento che il secondo approccio è un superset del primo approccio, usiamolo semplicemente:
func validate_url (url string) bool _, err: = http.Head (url) return err == nil
Se i valori devono rispettare un formato personalizzato, di solito è possibile abbinarli utilizzando semplici funzioni di stringhe come Diviso()
o in casi più complessi usare espressioni regolari. Ad esempio, se il set di dati contiene numeri di previdenza sociale (spero di no) nel formato XXX-XX-XXXX
quindi puoi dividere per "-" e verificare che ci siano tre token in cui il primo è composto da tre cifre, il secondo da due cifre e il terzo da quattro cifre. Ma è più conciso usare una regex come ^ \ D 3 -? \ D 2 -? \ D 4 $
.
La correzione di valori non validi non è una cosa banale. Se il tuo metodo di fissaggio non è corretto, puoi finire con dati corrotti. Dovresti considerare attentamente l'importanza del campo, la gamma di possibili valori validi e quanto sei sicuro di poter realmente correggere automaticamente qualsiasi valore non valido.
Questa è una soluzione abbastanza sicura. Se si suppone che un campo di testo sia tutto maiuscolo, puoi correggerlo senza rischiare molto, perché i caratteri che erano in origine in minuscolo non sono un'informazione importante. Non è necessario scrivere codice speciale come il pacchetto di stringhe ha a ToUpper ()
funzione. Ci sono anche Ridurre()
e persino ToTitle ()
e ToTitleSpecific ()
funzioni per inserire correttamente il testo in maiuscolo.
Un'altra soluzione facile comune è la rimozione degli spazi bianchi iniziali e finali. Rimarrai sorpreso dal numero di persone che aggiungono spazi o nuove righe durante l'immissione dei dati. Il pacchetto di archi ha una selezione di TrimXXX ()
funzioni che possono occuparsi della maggior parte delle situazioni:
In alcuni casi, è OK per eliminare caratteri non validi. Raccomando di farlo solo per campi non critici e opzionali. Ad esempio, potresti avere una descrizione o un campo di note che contiene testo libero e vuoi assicurarti che non contenga determinati simboli come virgolette o virgolette. Ecco come farlo:
func remove_quotes (s string) string var b bytes.Buffer per _, r: = range (s) if r! = '"' && r! = '\" b.WriteRune (r) return b. String () func main () original: = "quotes" e "double quotes". " clean: = remove_quotes (original) fmt.Println (original) fmt.Println (clean) Output: "quotes" e "double quotes". virgolette e virgolette doppie.
I valori numerici sono spesso facili da risolvere. Se si richiede una precisione di due cifre decimali, è possibile troncare o arrotondare cifre aggiuntive. Allo stesso modo, è facile convertire gli interi in numeri in virgola mobile.
A volte, esiste un intervallo di valori validi e puoi inserire numeri troppo grandi o troppo piccoli per adattarli all'intervallo. La seguente funzione accetta una stringa e un intervallo di numeri interi e restituisce una stringa che rappresenta un intero all'interno dell'intervallo. I valori troppo grandi diventano il valore massimo e troppo piccoli diventano il valore minimo.
func fit_into_range (s stringa, min int, max int) stringa n, _: = strconv.Atoi (s) se n < min n = min else if n > max n = max else return s return strconv.Itoa (n) func main () fmt.Println (fit_into_range ("15", 10, 20)) fmt.Println (fit_into_range ("- 15", 10, 20)) fmt.Println (fit_into_range ("55", 10, 20)) Output: 15 10 20
Gli URL possono spesso essere corretti in modo sicuro provando diversi schemi ("http" o "https") o aggiungendo o rilasciando sottodomini "www". Combinare le opzioni con il tentativo di recuperare i candidati può darti la certezza che la correzione fosse corretta.
I valori mancanti sono molto comuni quando si acquisiscono dati reali. Se è richiesto il valore mancante, esistono due modi principali per gestirlo (senza rifiutare del tutto la riga): utilizzare i valori predefiniti o ripristinare il valore da una fonte alternativa.
I valori predefiniti sono utili perché il codice di elaborazione non deve controllare se un valore è presente o meno. Il codice di pulizia dei dati garantisce che sia sempre presente un valore. In molti casi, l'impostazione predefinita è così comune che è anche un aiuto per l'immissione dei dati in cui non è necessario immettere lo stesso valore predefinito ancora e ancora.
Questo approccio è un po 'più coinvolto. L'idea è di consultare un'altra fonte di dati che ha le informazioni richieste. Ad esempio, se si dispone di un messaggio di posta elettronica dell'utente, ma manca il nome e il cognome, è possibile consultare il proprio database utente ed estrarre il nome dell'utente. Ciò consente di salvare il codice di elaborazione dall'accesso al DB o anche di essere a conoscenza di questa dipendenza.
Puliamo un piccolo set di dati di prodotti. I campi sono:
Nome colonna | Descrizione della colonna |
---|---|
Id | PRD-XXXX-XXXX (dove X è una cifra) |
Nome | fino a 40 caratteri |
Prezzo | campo numerico preciso di precisione (due punti decimali) |
Descrizione | fino a 500 caratteri (opzionale) |
Ecco il set di dati in una forma leggibile (lo spazio verrà ritagliato durante la pulizia):
const data = 'Id, Nome, Prezzo, Descrizione PRD-1234-0000, Airzooka, 9.99, Spara aria alle persone PRD-1234-0017, Pink Onesie, 34.55, PRD-1234-666, Oh oh, 18.18, ID prodotto non valido PRD-1234-7777, Oh oh 2, prezzo mancante prd-1234-8888, PostIt !, 13.13, Fixable: ID minuscolo '
I primi due prodotti sono validi. Il terzo prodotto, "PRD-1234-666", manca una cifra nel suo id. Il prossimo prodotto, "PRD-1234-7777", manca un prezzo. L'ultimo prodotto, "prd-1234-8888", ha un ID prodotto non valido, ma può essere risolto in modo sicuro (renderlo maiuscolo).
Il seguente codice ripulirà i dati, correggerà ciò che può essere corretto, eliminerà le righe che non possono essere corrette e produrrà un set di dati pulito e un report che può essere utilizzato per correggere manualmente i dati non validi.
Per verificare l'ID del prodotto e il prezzo, userò le espressioni regolari. Ecco le due funzioni di supporto:
func verifyProductId (s string) bool matched, _: = regexp.MatchString ('^ PRD- \ d 4 - \ d 4 $', s) restituito corrisponde func verifyProductPrice (s stringa) bool abbinato, _: = regexp.MatchString ('^ \ d + \. \ d \ d $', s) restituito corrisponde
Una volta eliminati i dati e tutte le righe di dati non valide sono state eliminate, la funzione seguente scriverà i dati puliti in un nuovo file CSV chiamato "clean.csv" e li stamperà sullo schermo.
func writeCleanData (cleanData [] string) f, _: = os.Create ("clean.csv") w: = bufio.NewWriter (f) fmt.Println ("Pulisci dati:") rimanda w.Flush () per _, riga: = range cleanData fmt.Println (riga) w.WriteString (riga) w.WriteString ("\ n")
Il principale()
la funzione fa la maggior parte del lavoro. Iterizza il set di dati originale, elimina gli spazi bianchi ridondanti, corregge il possibile, tiene traccia delle righe di dati rilasciati, scrive i dati puliti in un file e infine segnala le linee disconnesse.
func main () cleanData: = [] string "Id, Name, Price, Descrizione" dropped: = [] stringa // ripulisci i dati all_lines: = strings.Split (data, "\ n") per _, line: = range all_lines fields: = strings.Split (line, ",") if len (fields)! = 4 continue // Toglie tutti gli spazi iniziali e finali da ogni campo per i, f: = range fields fields [i] = strings.TrimSpace (f) // Correzione automatica (non è necessario controllare) id: = strings.ToUpper (campi [0]) if! verifyProductId (id) dropped = append (dropped, line ) continue name: = fields [1] // I nomi dei prodotti non possono essere vuoti se name == "" dropped = append (dropped, line) continue // Tronca il nome a 40 caratteri (runes) se len ([ ] rune (nome))> 40 name = string ([] rune (name) [: 40]) price: = fields [2] if! verifyProductPrice (price) dropped = append (dropped, line) continua description : = campi [3] // Tronca la descrizione a 500 caratteri (runes) se len ([] rune (nome))> 500 name = string ([] rune (name) [: 500]) cleanLine: = stringhe. Iscriviti ([] string id, nome, prezzo, description, ",") cleanData = append (cleanData, cleanLine) writeCleanData (cleanData) // Segnala fmt.Println ("Dropped lines:") per _, s: = range dropped fmt.Println (s)
Go ha pacchetti ben progettati per l'elaborazione del testo. A differenza della maggior parte delle lingue, l'oggetto stringa è in realtà solo una porzione di byte. Tutta la logica di gestione delle stringhe è in pacchetti separati come "stringhe" e "strconv".
Nella seconda parte del tutorial, abbiamo utilizzato molti concetti per realizzare un compito comune nel mondo reale di pulire un set di dati formattato CSV prima dell'analisi.