обзор
Это вторая часть из двух в серии, посвященной очистке данных с помощью Go. В первой части мы рассмотрели основные текстовые возможности Go и работу с CSV-файлами. В этом уроке мы углубимся в фактическую очистку данных.
Мы начнем с понимания проблемы запутанных данных и разработки стратегии, а затем рассмотрим проверку отдельных полей, исправим данные, где это возможно, и решим, что делать с пропущенными значениями.
Стратегия очистки данных
Стратегия очистки данных должна определять, что делать при обнаружении неверных, грязных, частичных или отсутствующих данных. Следует также определить, какой уровень отчетности необходим для процесса очистки.
Данные, на которых мы фокусируемся, являются табличными данными, где каждая строка независима. Не существует вложенных иерархий или связей между различными строками данных. Многие реальные наборы данных обладают этим хорошим свойством.
удалять
Самый простой подход для работы с недействительными данными — удалить их. Если какое-либо поле отсутствует или содержит недопустимые данные, просто избавьтесь от всей строки. Это очень легко, а иногда это правильно. Если проблемное поле является критическим, и у вас нет возможности восстановить его, все, что вы можете сделать, это удалить всю запись.
Fix
Лучшее решение — исправить плохое поле. В некоторых случаях легко обнаружить проблему и устранить ее. В наборе данных наблюдений НЛО поле штата может быть одним из 52 штатов США.
Если значение должно быть только в верхнем регистре, а некоторые строки содержат строчные буквы, вы можете просто сделать их заглавными.
отчет
Отчеты о недействительных строках, удаленных или исправленных, очень важны. Организация может решить позволить людям попытаться исправить пропущенные данные. Может потребоваться запустить фиксированные данные с помощью QA, чтобы автоматические исправления не вводили недействительные данные.
Статистика
Сбор статистики по процессу очистки необходим для оценки качества исходных данных, а иногда и для определения того, стоит ли обрабатывать очищенные данные. Статистика может включать количество пропущенных и фиксированных строк, а также количество плохих и пропущенных полей для каждого столбца.
Живая Очистка
До сих пор я описал подход предварительной обработки для очистки данных. Тем не менее, можно выполнить очистку во время обработки. Каждая строка проверяется непосредственно перед обработкой. Это иногда полезно, если нет смысла в предварительной обработке, поскольку никто не может заранее исправить неверные данные для последующего анализа или если обработка зависит от времени.
В этом сценарии основная цель очистки состоит в том, чтобы убедиться, что неверные строки данных не нарушают весь конвейер обработки и могут быть пропущены или исправлены при необходимости.
Проверочные поля
Как вы проводите проверку полей? Вы должны точно знать, какой тип данных должен быть там, а иногда и какие значения. Вот несколько примеров.
Проверка числовых полей
Числовые поля очень распространены в наборах данных. Помимо типа числа (целое, вещественное, сложное), некоторые поля являются более специализированными. Например, поле цены может потребовать ровно две десятичные точки и быть положительным. Вот функция, которая проверяет, представляет ли строка цену:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
func validate_price(s string) bool {
parts := strings.Split(s,».»)
if len(parts) != 2 {
return false
}
dollars, err := strconv.Atoi(parts[0])
if err != nil {
return false
}
if dollars < 0 {
return false
}
cents, err := strconv.Atoi(parts[1])
if err != nil {
return false
}
if cents < 0 ||
return false
}
return true
}
|
Проверка полей URL
Иногда вам нужно идти выше и выше. Если вам нужно убедиться, что URL-адрес действителен, есть два подхода:
- Разобрать URL.
- Попробуйте и получите его (или, по крайней мере, получите заголовки).
Если вам важно, чтобы URL был правильно сформирован, тогда первый подход работает. Но если вы хотите убедиться, что URL действительно указывает на реальный пункт назначения, вам нужно использовать второй подход. Поскольку второй подход является надмножеством первого подхода, давайте просто воспользуемся им:
1
2
3
4
|
func validate_url(url string) bool {
_, err := http.Head(url)
return err == nil
}
|
Проверка пользовательских полей формата
Если значения должны соответствовать пользовательскому формату, вы можете либо сопоставить его, используя простые строковые функции, такие как Split()
либо в более сложных случаях использовать регулярные выражения. Например, если ваш набор данных содержит номера социального страхования (я надеюсь, что нет) в формате XXX-XX-XXXX
то вы можете разделить на «-» и убедиться, что есть три токена, в которых первый состоит из трех цифр, а второй — из двух цифр. долго, а третье четыре цифры в длину. Но более кратко использовать регулярные выражения, такие как ^\d{3}-?\d{2}-?\d{4}$
.
Исправление неверных значений
Исправление неверных значений не является тривиальной вещью. Если ваш метод исправления неверен, вы можете получить поврежденные данные. Вы должны внимательно рассмотреть важность поля, диапазон возможных допустимых значений и насколько вы уверены, что действительно можете исправить любое недопустимое значение автоматически.
Фиксирующий чехол
Это довольно безопасное решение. Если текстовое поле должно быть написано заглавными буквами, вы можете исправить это, не рискуя сильно, потому что символы, которые изначально были строчными, не являются важной частью информации. Нет необходимости писать специальный код, так как пакет strings имеет функцию ToUpper()
. Есть также функции ToLower()
и даже ToTitle()
и ToTitleSpecific()
для правильного использования текста.
Удаление нежелательных персонажей
Другое распространенное простое решение — удаление начальных и конечных пробелов. Вы будете удивлены, сколько людей добавляют пробелы или новые строки при вводе данных. Пакет strings имеет набор функций TrimXXX()
которые могут позаботиться о большинстве ситуаций:
- Отделка()
- TrimFunc ()
- TrimLeft ()
- TrimLeftFunc ()
- TrimPrefix ()
- TrimRight ()
- TrimRightFunc ()
- TrimSpace ()
- TrimSuffix ()
Удаление неверных персонажей
В некоторых случаях можно удалить недопустимые символы. Я рекомендую делать это только для некритических и необязательных полей. Например, у вас может быть поле описания или примечаний, содержащее свободный текст, и вы хотите убедиться, что оно не содержит определенных символов, таких как кавычки или двойные кавычки. Вот как это сделать:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
func remove_quotes(s string) string {
var b bytes.Buffer
for _, r := range (s) {
if r != ‘»‘ && r != ‘\» {
b.WriteRune(r)
}
}
return b.String()
}
func main() {
original := `’quotes’ and «double quotes».`
clean := remove_quotes(original)
fmt.Println(original)
fmt.Println(clean)
}
Output:
‘quotes’ and «double quotes».
quotes and double quotes.
|
Исправление числовых значений
Числовые значения часто легко исправить. Если вам требуется точность в два десятичных знака, вы можете усечь или округлить дополнительные цифры. Таким же образом легко конвертировать целые числа в числа с плавающей точкой.
Иногда существует диапазон допустимых значений, и вы можете привести слишком большие или слишком маленькие числа, чтобы соответствовать диапазону. Следующая функция принимает строку и диапазон целых чисел и возвращает строку, представляющую целое число в диапазоне. Слишком большие значения становятся максимальными, а слишком маленькие — минимальными.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
func fit_into_range(s string, min int, max int) string {
n, _ := strconv.Atoi(s)
if 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
|
Фиксирование значений URL
URL-адреса часто можно безопасно исправить, используя различные схемы («http» или «https») или добавляя или удаляя «www» субдомены. Объединение вариантов с попыткой выбрать кандидатов может дать вам уверенность в том, что исправление было правильным.
Работа с отсутствующими ценностями
Пропущенные значения очень распространены при приеме реальных данных. Если отсутствует пропущенное значение, существует два основных способа его обработки (без полного отклонения строки) — использовать значения по умолчанию или восстановить значение из альтернативного источника.
Применение значений по умолчанию
Значения по умолчанию полезны, потому что код обработки не должен проверять, присутствует ли значение или нет. Код очистки данных гарантирует, что всегда есть значение на месте. Во многих случаях значения по умолчанию настолько распространены, что они также являются помощником для ввода данных, когда вам не нужно вводить одно и то же значение по умолчанию снова и снова.
Использование альтернативных данных
Этот подход немного сложнее. Идея состоит в том, чтобы обратиться к другому источнику данных, который имеет запрошенную информацию. Например, если у вас есть электронная почта пользователя, но отсутствуют имя и фамилия, вы можете обратиться к базе данных пользователей и извлечь имя пользователя. Это спасает код обработки от доступа к БД или даже от знания этой зависимости.
Собираем все вместе
Давайте очистим небольшой набор данных продуктов. Поля:
Имя столбца | Описание столбца |
---|---|
Я бы | PRD-XXXX-XXXX (где X — это цифра) |
имя | до 40 символов |
Цена | числовое поле с фиксированной точностью (две десятичные точки) |
Описание | длиной до 500 символов (необязательно) |
Вот набор данных в читаемой форме (пробелы будут урезаны во время очистки):
1
2
3
4
5
6
7
8
|
const data = `
Id, Name, Price, Description
PRD-1234-0000, Airzooka, 9.99, Shoots air at people
PRD-1234-0017, Pink Onesie, 34.55,
PRD-1234-666, Oh oh, 18.18, Invalid product id
PRD-1234-7777, Oh oh 2, , Missing price
prd-1234-8888, PostIt!, 13.13, Fixable: lowercase id
`
|
Первые два продукта действительны. Третий продукт, «PRD-1234-666», в его идентификаторе отсутствует цифра. Следующий продукт, «PRD-1234-7777», не имеет цены. Последний продукт, «prd-1234-8888», имеет недопустимый идентификатор продукта, но его можно безопасно исправить (сделать его заглавным).
Следующий код очистит данные, исправит то, что может быть исправлено, удалит строки, которые не могут быть исправлены, и создаст чистый набор данных и отчет, который можно использовать для ручной корректировки неверных данных.
Чтобы проверить идентификатор продукта и цену, я буду использовать регулярные выражения. Вот две вспомогательные функции:
1
2
3
4
5
6
7
8
9
|
func verifyProductId(s string) bool {
matched, _ := regexp.MatchString(`^PRD-\d{4}-\d{4}$`, s)
return matched
}
func verifyProductPrice(s string) bool {
matched, _ := regexp.MatchString(`^\d+\.\d\d$`, s)
return matched
}
|
После очистки данных и удаления всех недопустимых строк данных следующая функция запишет чистые данные в новый CSV-файл с именем «clean.csv» и распечатает их на экране.
01
02
03
04
05
06
07
08
09
10
11
|
func writeCleanData(cleanData []string) {
f, _ := os.Create(«clean.csv»)
w := bufio.NewWriter(f)
fmt.Println(«Clean data:»)
defer w.Flush()
for _, line := range cleanData {
fmt.Println(line)
w.WriteString(line)
w.WriteString(«\n»)
}
}
|
Функция main()
выполняет большую часть работы. Он перебирает исходный набор данных, устраняет лишние пробелы, исправляет все, что может, отслеживает пропущенные строки данных, записывает чистые данные в файл и, наконец, сообщает об удаленных строках.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
func main() {
cleanData := []string{«Id,Name,Price,Description»}
dropped := []string{}
// Clean up data
all_lines := strings.Split(data, «\n»)
for _, line := range all_lines {
fields := strings.Split(line, «,»)
if len(fields) != 4 {
continue
}
// Strip all leading and trailing spaces from each field
for i, f := range fields {
fields[i] = strings.TrimSpace(f)
}
// Automatic fix (no need to check)
id := strings.ToUpper(fields[0])
if !verifyProductId(id) {
dropped = append(dropped, line)
continue
}
name := fields[1]
// Product names can’t be empty
if name == «» {
dropped = append(dropped, line)
continue
}
// Truncate name at 40 characters (runes)
if len([]rune(name)) > 40 {
name = string([]rune(name)[:40])
}
price := fields[2]
if !verifyProductPrice(price) {
dropped = append(dropped, line)
continue
}
description := fields[3]
// Truncate description at 500 characters (runes)
if len([]rune(name)) > 500 {
name = string([]rune(name)[:500])
}
cleanLine := strings.Join([]string{id,
name,
price,
description}, «,»)
cleanData = append(cleanData, cleanLine)
}
writeCleanData(cleanData)
// Report
fmt.Println(«Dropped lines:»)
for _, s := range dropped {
fmt.Println(s)
}
}
|
Вывод
Go имеет хорошо продуманные пакеты для обработки текста. В отличие от большинства языков, строковый объект на самом деле представляет собой просто кусочек байтов. Вся логика обработки строк находится в отдельных пакетах, таких как «strings» и «strconv».
Во второй части руководства мы использовали множество концепций для решения общей реальной задачи очистки набора данных в формате CSV перед анализом.