Очистка данных несовместимого формата в R?

16

Я часто имею дело с грязными данными опросов, которые требуют большой очистки, прежде чем можно будет сделать какую-либо статистику. Я делал это вручную в Excel, иногда используя формулы Excel, а иногда проверял записи по одному. Я начал выполнять все больше и больше этих задач, написав сценарии для их выполнения на языке R, что было очень полезно (преимущества включают в себя запись того, что было сделано, меньшую вероятность ошибок и возможность повторного использования кода, если набор данных обновлено).

Но есть все еще некоторые типы данных, с которыми у меня возникают проблемы при эффективной обработке. Например:

> d <- data.frame(subject = c(1,2,3,4,5,6,7,8,9,10,11),
+   hours.per.day = c("1", "2 hours", "2 hr", "2hr", "3 hrs", "1-2", "15 min", "30 mins", "a few hours", "1 hr 30 min", "1 hr/week"))
> d
   subject hours.per.day
1        1             1
2        2       2 hours
3        3          2 hr
4        4           2hr
5        5         3 hrs
6        6           1-2
7        7        15 min
8        8       30 mins
9        9   a few hours
10      10   1 hr 30 min
11      11     1 hr/week

hours.per.dayподразумевается среднее количество часов в день, потраченных на определенную деятельность, но мы имеем именно то, что написал субъект. Предположим, я принимаю некоторые решения о том, что делать с неоднозначными ответами, и я хочу, чтобы приведенная переменная была hours.per.day2следующей.

   subject hours.per.day hours.per.day2
1        1             1      1.0000000
2        2       2 hours      2.0000000
3        3          2 hr      2.0000000
4        4           2hr      2.0000000
5        5         3 hrs      3.0000000
6        6           1-2      1.5000000
7        7        15 min      0.2500000
8        8       30 mins      0.5000000
9        9   a few hours      3.0000000
10      10   1 hr 30 min      1.5000000
11      11     1 hr/week      0.1428571

Если предположить, что количество дел достаточно велико (скажем, 1000), и зная, что испытуемые могут свободно писать все, что им нравится, как лучше всего подойти к этому?

mark999
источник

Ответы:

13

Я бы использовал gsub () для идентификации строк, которые я знаю, а затем, возможно, сделал бы все остальное вручную.

test <- c("15min", "15 min", "Maybe a few hours", 
          "4hr", "4hour", "3.5hr", "3-10", "3-10")
new_var <- rep(NA, length(test))

my_sub <- function(regex, new_var, test){
    t2 <- gsub(regex, "\\1", test)
    identified_vars <- which(test != t2)
    new_var[identified_vars] <- as.double(t2[identified_vars])
    return(new_var)    
}

new_var <- my_sub("([0-9]+)[ ]*min", new_var, test)
new_var <- my_sub("([0-9]+)[ ]*(hour|hr)[s]{0,1}", new_var, test)

Чтобы получить работу с теми, что вам нужно изменить вручную, я предлагаю что-то вроде этого:

# Which have we not found
by.hand <- which(is.na(new_var))

# View the unique ones not found
unique(test[by.hand])
# Create a list with the ones
my_interpretation <- list("3-10"= 5, "Maybe a few hours"=3)
for(key_string in names(my_interpretation)){
    new_var[test == key_string] <- unlist(my_interpretation[key_string])
}

Это дает:

> new_var
[1] 15.0 15.0  3.0  4.0  4.0  3.5  5.0  5.0

Regex может быть немного сложнее, каждый раз, когда я что-то делаю с regex, я запускаю несколько простых тестов. Se? Regex для руководства. Вот несколько основных правил поведения:

> # Test some regex
> grep("[0-9]", "12")
[1] 1
> grep("[0-9]", "12a")
[1] 1
> grep("[0-9]$", "12a")
integer(0)
> grep("^[0-9]$", "12a")
integer(0)
> grep("^[0-9][0-9]", "12a")
[1] 1
> grep("^[0-9]{1,2}", "12a")
[1] 1
> grep("^[0-9]*", "a")
[1] 1
> grep("^[0-9]+", "a")
integer(0)
> grep("^[0-9]+", "12222a")
[1] 1
> grep("^(yes|no)$", "yes")
[1] 1
> grep("^(yes|no)$", "no")
[1] 1
> grep("^(yes|no)$", "(yes|no)")
integer(0)
> # Test some gsub, the \\1 matches default or the found text within the ()
> gsub("^(yes|maybe) and no$", "\\1", "yes and no")
[1] "yes"
Макс Гордон
источник
Спасибо за ответ Макс. Я не знаком с регулярными выражениями, поэтому должен будет узнать о них. Не могли бы вы дать краткое описание того, как вы будете делать остальное вручную? Есть ли лучший способ , чем просто делать что - то подобное new_var[by.hand] <- c(2, 1, ...)с by.handтого , TRUEза исключением случаев , которые сделаны вручную?
mark999
@ mark999: Добавлено несколько примеров и предложение, как вы можете сделать их вручную.
Макс Гордон
1
Регулярные выражения очень важны для любого вида манипулирования данными: очистки данных, как у OP, или для извлечения данных из файлов, HTML и т. Д. (Для правильного HTML есть библиотеки, например, XMLкоторые помогут вам извлечь данные, но это не работает, когда HTML искажен.)
Уэйн
6

Предложение @ Макса хорошее. Кажется, что если вы напишите алгоритм, который распознает числа, а также общие слова / сокращения, связанные со временем, вы получите большую часть пути. Это не будет красивый код, но он будет работать, и вы сможете со временем его улучшить, если столкнетесь с проблемами.

Но для более надежного (и изначально трудоемкого) подхода попробуйте поискать в Google «разбор строки времени на естественном языке». Вот некоторые интересные результаты: это API открытого времени , хороший модуль Python и один из многих немецких потоков, подобных этому, в Stack Overflow .

По сути, разбор естественного языка является распространенной проблемой, и вы должны искать решения на языках, отличных от R. Вы можете создавать инструменты на другом языке, к которому у вас есть доступ, используя R, или, по крайней мере, вы можете получить хорошие идеи для своего собственного алгоритма.

ясень
источник
4

Для чего-то подобного, если бы оно было достаточно длинным, я бы хотел получить список регулярных выражений и правил преобразования и перенести новые значения в другой столбец (чтобы у вас всегда была возможность дважды проверить, не перезагружая необработанные данные). ; RE будут применяться для не слишком далеко преобразованных данных, пока все данные не будут преобразованы или все правила не будут исчерпаны. Вероятно, лучше также сохранить список логических значений, которые указывают, какие строки еще не были преобразованы.

Несколько таких правил, конечно, очевидны и, вероятно, будут обрабатывать 80-90% случаев, но проблема в том, что всегда найдутся те, о которых вы не знаете (люди очень изобретательны).

Затем вам нужен сценарий, который просматривает и представляет вам оригиналы значений, которые еще не были преобразованы списком очевидных правил, по одному, давая вам возможность сделать регулярное выражение (скажем, ) идентифицировать эти случаи и дать новое преобразование для подходящих ему случаев, которые оно добавляет к исходному списку и применяет к еще не преобразованным строкам исходного вектора, прежде чем проверять, есть ли еще случаи, чтобы представить их вам ,

Также было бы разумно иметь возможность пропустить дело (чтобы вы могли перейти к более легким), чтобы вы могли перенести очень сложные дела до конца.

В худшем случае, вы делаете несколько вручную.

Затем вы можете сохранить полный список сгенерированных вами правил, чтобы их можно было применять снова, когда объем данных увеличивается или появляется новый аналогичный набор данных.

Я не знаю, приближается ли это к наилучшей практике (я думаю, что там понадобится что-то гораздо более формальное), но с точки зрения быстрой обработки больших объемов таких данных это может иметь определенную ценность.

Glen_b - Восстановить Монику
источник
Спасибо за ответ, Глен. Это звучит очень привлекательно. Считаете ли вы большим преимуществом представлять по-прежнему не преобразованные значения по одному, а не просто отображать их все и смотреть на этот результат? Я никогда не делал ничего подобного, чтобы вещи были представлены по одному.
mark999
1
@ mark999, я бы подумал, что есть и преимущества, и недостатки поочередной презентации. Преимущество заключается в простоте: использование cat () для отображения неоднозначного времени и scan () для записи вашей интерпретации этого времени легко реализовать. Недостатком является то, что вы можете пропустить общую картину множества записей, которые вы можете исправить в целом с помощью одной строки кода регулярного выражения. Вы можете подумать о том, что вы надеетесь получить: если вы просто хотите решить эту проблему, сделайте это вручную. Если вы хотите узнать больше о R, попробуйте написать решение.
Эш
Извините за отсутствие ответа; Я полностью согласен с комментарием Эша
Glen_b
4

R содержит некоторые стандартные функции для манипулирования данными, которые могут быть использованы для очистки данных, в базовом пакете ( gsub, transformи т.д.), а также в различные пакеты сторонних разработчиков , такие как stringr , перекроить , reshape2 и plyr . Примеры и лучшие практики использования этих пакетов и их функций описаны в следующей статье: http://vita.had.co.nz/papers/tidy-data.pdf .

Кроме того, R предлагает некоторые пакеты специально сосредоточены на очистке и преобразования данных:

Комплексный и согласованный подход к очистке данных в R, включая примеры и использование правил редактирования и дедуктивных пакетов, а также описание рабочего процесса ( каркаса ) очистки данных в R, представлен в следующей статье, которую я настоятельно рекомендую: http : //cran.r-project.org/doc/contrib/de_Jonge+van_der_Loo-Introduction_to_data_cleaning_with_R.pdf .

Александр Блех
источник