Membersihkan data format tidak konsisten dalam R?

16

Saya sering berurusan dengan data survei yang berantakan yang membutuhkan banyak pembersihan sebelum statistik dapat dilakukan. Saya biasa melakukan ini "secara manual" di Excel, kadang-kadang menggunakan rumus Excel, dan kadang-kadang memeriksa entri satu-per-satu. Saya mulai melakukan semakin banyak tugas ini dengan menulis skrip untuk melakukannya di R, yang sangat bermanfaat (manfaatnya termasuk memiliki catatan tentang apa yang telah dilakukan, lebih sedikit kemungkinan kesalahan, dan mampu menggunakan kembali kode jika kumpulan data adalah diperbarui).

Tetapi masih ada beberapa jenis data yang saya kesulitan penanganan secara efisien. Sebagai contoh:

> 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.dayberarti jumlah rata-rata jam per hari yang dihabiskan untuk kegiatan tertentu, tetapi apa yang kita miliki adalah persis apa yang ditulis subjek. Misalkan saya membuat beberapa keputusan tentang apa yang harus dilakukan dengan tanggapan yang ambigu, dan saya ingin variabel yang dirapikan hours.per.day2sebagai berikut.

   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

Dengan asumsi bahwa jumlah kasus cukup besar (katakanlah 1000) dan mengetahui bahwa subjek bebas untuk menulis apa pun yang mereka suka, apa cara terbaik untuk mendekati ini?

mark999
sumber

Jawaban:

12

Saya akan menggunakan gsub () untuk mengidentifikasi string yang saya tahu dan kemudian mungkin melakukan sisanya dengan tangan.

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)

Untuk mendapatkan pekerjaan dengan yang perlu Anda ubah dengan tangan saya sarankan sesuatu seperti ini:

# 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])
}

Ini memberi:

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

Regex bisa sedikit rumit, setiap kali saya melakukan apa pun dengan regex saya menjalankan beberapa tes sederhana. Se? Regex untuk manual. Inilah beberapa perilaku dasar:

> # 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"
Max Gordon
sumber
Terima kasih atas jawabannya, Max. Saya tidak terbiasa dengan ekspresi reguler sehingga harus mempelajarinya. Maukah Anda memberikan deskripsi singkat tentang bagaimana Anda akan melakukan sisanya dengan tangan? Apakah ada cara yang lebih baik daripada hanya melakukan sesuatu new_var[by.hand] <- c(2, 1, ...)dengan by.handmenjadi TRUEuntuk kasus-kasus yang dilakukan dengan tangan?
mark999
@ mark999: Menambahkan beberapa contoh dan saran tentang bagaimana Anda dapat melakukannya dengan tangan.
Max Gordon
1
Ekspresi reguler sangat penting untuk segala jenis manipulasi data: membersihkan data seperti yang dimiliki OP, atau untuk mengekstraksi data dari file, HTML, dll. (Untuk HTML yang tepat, ada perpustakaan, ingin XMLmembantu Anda mengekstrak data, tetapi ini tidak berfungsi saat HTML rusak.)
Wayne
6

Saran Max adalah saran yang bagus. Tampaknya jika Anda menulis algoritme yang mengenali angka dan juga kata / singkatan yang terkait dengan waktu, Anda akan mendapatkan sebagian besar caranya. Ini bukan kode yang indah, tetapi ini akan berhasil dan Anda dapat memperbaikinya seiring waktu ketika Anda menemukan kasus-kasus yang bermasalah.

Tetapi untuk pendekatan yang lebih kuat (dan awalnya memakan waktu), cobalah Googling "mengurai string waktu bahasa alami." Beberapa temuan menarik adalah Ini open time API , modul Python yang bagus , dan salah satu dari banyak untaian seperti ini di Stack Overflow .

Pada dasarnya, penguraian bahasa alami adalah masalah umum dan Anda harus mencari solusi dalam bahasa selain R. Anda dapat membuat alat dalam bahasa lain yang dapat Anda akses menggunakan R, atau paling tidak Anda bisa mendapatkan ide bagus untuk algoritma Anda sendiri.

Abu
sumber
4

Untuk sesuatu seperti itu, jika itu cukup lama, saya pikir saya ingin daftar ekspresi reguler dan aturan transformasi, dan mengambil nilai-nilai baru ke kolom lain (jadi Anda selalu memiliki kesempatan untuk memeriksa ulang tanpa memuat ulang data mentah) ; RE akan diterapkan untuk data yang tidak terlalu jauh berubah sampai semua data ditransformasikan atau semua aturan habis. Mungkin sebaiknya juga menyimpan daftar nilai-nilai logis yang menunjukkan baris mana yang belum diubah.

Beberapa aturan seperti itu sudah jelas dan mungkin akan menangani 80-90% kasus, tetapi masalahnya adalah bahwa akan selalu ada beberapa yang Anda tidak tahu akan muncul (orang-orang sangat inventif).

Maka Anda memerlukan skrip yang melewati dan menyajikan sumber asli dari nilai-nilai aturan yang belum-berubah-oleh-daftar-jelas-untuk Anda satu per satu, memberi Anda kesempatan untuk membuat ekspresi reguler (katakanlah ) untuk mengidentifikasi kasus-kasus tersebut dan memberikan a transformasi baru untuk kasus-kasus yang cocok, yang ditambahkan ke daftar asli dan berlaku untuk baris-baris vektor asli yang belum diubah sebelum memeriksa apakah ada kasus yang tersisa untuk disajikan kepada Anda .

Mungkin juga masuk akal untuk memiliki opsi untuk melewati suatu kasus (sehingga Anda dapat melanjutkan ke kasus yang lebih mudah), sehingga Anda dapat mendorong kasus yang sangat sulit sampai akhir.

Kasus terburuk, Anda melakukan beberapa dengan tangan.

Anda kemudian dapat menyimpan daftar lengkap aturan yang Anda hasilkan, untuk menerapkan kembali ketika data tumbuh atau set data baru yang serupa muncul.

Saya tidak tahu apakah ini mendekati praktik terbaik (saya pikir sesuatu yang jauh lebih formal akan dibutuhkan di sana), tetapi dalam hal memproses data dalam jumlah besar dengan cepat, mungkin ada beberapa nilai.

Glen_b -Reinstate Monica
sumber
Terima kasih atas jawabannya, Glen. Kedengarannya sangat menarik. Apakah Anda melihatnya sebagai keuntungan besar memiliki nilai-nilai yang belum berubah disajikan satu per satu, sebagai lawan hanya menampilkan semuanya dan melihat output itu? Saya tidak pernah melakukan hal-hal seperti yang disajikan satu per satu.
mark999
1
@ mark999, saya akan berpikir ada kelebihan dan kekurangan dari presentasi satu per satu. Keuntungannya adalah kesederhanaan - menggunakan cat () untuk menampilkan waktu yang ambigu, dan memindai () untuk merekam interpretasi Anda tentang waktu itu mudah diimplementasikan. Kerugiannya adalah Anda mungkin kehilangan gambaran besar dari banyak entri yang bisa Anda perbaiki secara massal dengan satu baris kode regex. Anda mungkin memiliki pemikiran tentang apa yang Anda harapkan: jika Anda hanya ingin menyelesaikan masalah ini, lakukan dengan tangan. Jika Anda ingin mempelajari lebih lanjut tentang R, coba kode solusi.
Ash
Maaf atas kurangnya balasan; Saya secara luas setuju dengan komentar Ash
Glen_b -Reinstate Monica
4

R berisi beberapa standar fungsi untuk manipulasi data, yang dapat digunakan untuk membersihkan data, dalam nya dasar paket ( gsub, transform, dll), serta di berbagai paket pihak ketiga, seperti stringr , membentuk kembali , reshape2 , dan plyr . Contoh dan praktik terbaik penggunaan untuk paket ini dan fungsinya dijelaskan dalam makalah berikut: http://vita.had.co.nz/papers/tidy-data.pdf .

Selain itu, R menawarkan beberapa paket yang secara khusus berfokus pada pembersihan dan transformasi data:

Pendekatan yang komprehensif dan koheren untuk pembersihan data di R, termasuk contoh dan penggunaan editrules dan paket deducorrect , serta deskripsi alur kerja ( kerangka kerja ) pembersihan data di R, disajikan dalam makalah berikut, yang sangat saya rekomendasikan: http : //cran.r-project.org/doc/contrib/de_Jonge+van_der_Loo-Introduction_to_data_cleaning_with_R.pdf .

Aleksandr Blekh
sumber