Использование тидиверсии
Недавно я построил модель машинного обучения для классификации использования зданий. В процессе переключения модели на производство формат файла данных, требующих прогнозирования, внезапно изменился. Один из параметров, который модель использует для прогнозирования, - это стоимость проекта. В новом формате данных стоимость указывается не в числовом, а в строковом виде. В некоторых случаях цены указаны в пределах диапазона, а не одного значения. Моя модель не может использовать этот формат, поэтому мне нужно очистить и преобразовать входные данные.
В следующих нескольких строках я покажу вам, как я решил эту проблему, написав функцию, которая выполняет очистку и преобразование за меня. Также я покажу вам, как отобразить разработанную функцию на определенном столбце в тибле.
Решение проблемы
Во-первых, давайте посмотрим на данную структуру данных. Мы сосредоточимся на столбце ProjectValue. Примечание. Вы можете найти полный код в моем репозитории GitHub.
# Look at the given df # A tibble: 5 x 4 ProjectID ProjectName ProjectMainCatecory ProjectValue <int> <chr> <fct> <chr> 1 1 Project A O “2.0–3.0 Mio USD” 2 2 Project B S “300.0 Mio USD” 3 3 Project C N “” 4 4 Project D C “0.1–0.4 Mio USD” 5 5 Project E J NA
Мы хотим извлечь стоимость, чтобы использовать ее как числовое значение, а не строку символов.
Мы можем определить четыре базовых случая для всех заданных строк:
Случай 1 - строка с двумя значениями
«2,0–3,0 млн долл. США»
Случай 2 - строка с одним значением
«300,0 млн долл. США»
Случай 3 - строка без значения
“”
Случай 4 - значение NA
NA
По сути, мы хотим избавиться от всей «бесполезной» информации. В данном случае это валюта и порядок величины. Вторая важная вещь - решить проблемы, в которых у нас есть два значения. Я решил взять среднюю стоимость из двух. Это казалось наиболее разумным. Кроме того, нам нужно обрабатывать пустые строки и строки NA соответственно.
Рабочий процесс
Сначала я определяю четыре базовых случая, чтобы проверить функциональность каждого отдельного шага.
# Define base cases case_1 <- “2.0–3.0 Mio USD” case_2 <- “300.0 Mio USD” case_3 <- “” case_4 <- NA
Затем я начинаю чистить шнурок. Я могу избавиться от всех пробелов и буквенных знаков. Для этого я использую следующий код и проверяю, делает ли он то, что я хочу.
# Delete all spacing del_space <- str_replace_all(case1, “[:space:]”, “”) # Output [1] "2.0-3.0MioUSD" # Delete all alphabetic components, referring to string without # spacing del_char <- str_replace_all(del_space, “[:alpha:]”, “”) # Output [1] "2.0-3.0"
Отлично.
Теперь мы лечим АН.
# Replace NA functionality of tidyr no_empties <- replace_na(case_4, “”) # Output [1] ""
Теперь давайте подумаем, как мы могли бы определить предложение if-else для обработки заданных данных. Мы могли бы использовать длину струн. Однако мы получим крайние случаи, когда длина чистой строки с одним значением равна длине самого короткого варианта для двух значений (например, «30000,0 млн долларов США» будет иметь ту же длину, что и «1,2–1,4». Млн долл. США »). Это, в свою очередь, приведет к ошибкам.
Поэтому я решил использовать знак «-». Мы можем определить простой if-else: например, если мы находим «-» в строке, мы знаем, что это должен быть случай с двумя значениями. Если мы не найдем такого символа, мы знаем, что в данной строке есть только одно значение. Пустые строки будут обработаны в конце предложения if-else. Мы заменим пустые строки значением NA.
Пришло время определить нашу функцию. Назовем это cost_transform. Входными данными этой функции является простая строка, поэтому нам не нужно беспокоиться о квазиквотации, квотах и enquos. Если вас это интересует, прочтите аккуратную виньетку здесь.
# Definiton of cost transform function cost_transform <- function(string) { # Treat NAs as empty strings ("") no_empties <- replace_na(string, "") # Insert string clean functionality del_space <- str_replace_all(no_empties, "[:space:]", "") del_char <- str_replace_all(del_space, "[:alpha:]", "") # If-else clause definition # If no "-" is found return the cost as numeric type if (!str_detect(del_char, "-")) { cost <- as.numeric(del_char) return(cost) # If "-" is found return the average of the two values } else if (str_detect(del_char, "-")) { split <- str_split(del_char, pattern = "-", simplify = TRUE) avg_cost <- (as.numeric(split[1]) + as.numeric(split[2])) / 2 return(avg_cost) # If the string is empty, return NA_real_ } else if (string == "") { return(NA_real_) } }
Давайте проверим это на всех наших случаях:
# Test case 1 test_p1 <- cost_transform(case_1) # Output [1] 2.5 # Test case 2 test_p2 <- cost_transform(case_2) # Output [1] 300 # Test case 3 test_p3 <- cost_transform(case_3) # Output [1] NA # Test case 4 test_p4 <- cost_transform(case_4) # Output [1] NA
Ясс! Оно работает.
Переходим к следующему вопросу. В конечном итоге у нас есть небольшой кусочек, но до сих пор мы тестировали нашу функцию только на отдельных входах. Но цель состоит в том, чтобы отобразить эту функцию на каждое отдельное значение в столбце произвольной длины. Мы могли бы использовать цикл for, но пакет purrr обеспечивает классную функциональность сопоставления. Об этом подробнее здесь".
В нашем случае мы используем одну специальную форму функций отображения. А именно map_dbl (). Эта опция возвращает двойное значение, которое мы и хотим использовать в нашем случае. Наконец, мы можем использовать нашу функцию в конвейере обработки данных и подготовить данные для всего, что нам нужно. В моем случае это будет кормить мою модель машинного обучения для прогнозирования использования при построении.
# See how function works within data wrangling pipeline df_pipeline <- df %>% # Select some columns select(ProjectID, ProjectMainCatecory, ProjectValue) %>% mutate( # Apply map function # (only renaming to get direct comparison) ProjectValueNew = map_dbl(ProjectValue, cost_transform) ) # Output # A tibble: 5 x 4 ProjectID ProjectMainCatecory ProjectValue ProjectValueNew <int> <fct> <chr> <dbl> 1 1 O "2.0 - 3.0 Mio USD" 2.5 2 2 S "300.0 Mio USD" 300 3 3 N "" NA 4 4 C "0.1 - 0.4 Mio USD" 0.25 5 5 J NA NA
Примечание. Новый столбец ProjectValueNew создан только для прямого сравнения.
Заключение
Проблема очистки и преобразования данных вездесуща в рабочей среде, связанной с наукой о данных. Таким образом, очень важно извлечь из этого концепцию. Написание функции - обработка отдельных случаев - и последующее сопоставление этой функции со списками или атомарными векторами произвольной длины. Это просто фантастика!