Faktor di R: lebih dari sekadar gangguan?

95

Salah satu tipe data dasar di R adalah faktor. Dalam pengalaman saya, faktor-faktor pada dasarnya menyakitkan dan saya tidak pernah menggunakannya. Saya selalu mengonversi ke karakter. Saya merasa aneh seperti saya melewatkan sesuatu.

Adakah beberapa contoh penting dari fungsi yang menggunakan faktor sebagai variabel pengelompokan di mana tipe data faktor menjadi diperlukan? Apakah ada keadaan khusus ketika saya harus menggunakan faktor?

JD Long
sumber
7
Saya menambahkan komentar ini untuk pengguna R pemula yang cenderung menemukan pertanyaan ini. Baru-baru ini saya menulis posting blog yang mengumpulkan banyak informasi dari jawaban di bawah ini menjadi tutorial instruksional tentang kapan, bagaimana dan mengapa menggunakan faktor-faktor di R. gormanalysis.com/?p=115
Ben
Saya selalu berasumsi bahwa faktor disimpan lebih efisien daripada karakter — seolah-olah setiap entri adalah penunjuk ke level. Tetapi saat mengujinya untuk menulis ini, saya menemukan itu tidak benar!
isomorfisma
2
@isomorphismes baik, itu dulu benar, di hari-hari sebelumnya R, tapi itu telah berubah. Lihat posting blog ini: simplystatistics.org/2015/07/24/…
MichaelChirico
4
5+ tahun kemudian "stringsAsFactors: An unauthorized biography" ini ditulis: simplystatistics.org/2015/07/24/…
JD Long

Jawaban:

49

Anda harus menggunakan faktor. Ya mereka bisa menyebalkan, tetapi teori saya adalah bahwa 90% mengapa mereka menyebalkan adalah karena dalam read.tabledan read.csv, argumen stringsAsFactors = TRUEsecara default (dan sebagian besar pengguna melewatkan kehalusan ini). Saya katakan mereka berguna karena paket model fitting seperti faktor penggunaan lme4 dan faktor yang dipesan untuk menyesuaikan model secara berbeda dan menentukan jenis kontras yang akan digunakan. Dan paket grafik juga menggunakannya untuk mengelompokkan. ggplotdan sebagian besar fungsi pemasangan model memaksa vektor karakter menjadi faktor, sehingga hasilnya sama. Namun, Anda berakhir dengan peringatan di kode Anda:

lm(Petal.Length ~ -1 + Species, data=iris)

# Call:
# lm(formula = Petal.Length ~ -1 + Species, data = iris)

# Coefficients:
#     Speciessetosa  Speciesversicolor   Speciesvirginica  
#             1.462              4.260              5.552  

iris.alt <- iris
iris.alt$Species <- as.character(iris.alt$Species)
lm(Petal.Length ~ -1 + Species, data=iris.alt)

# Call:
# lm(formula = Petal.Length ~ -1 + Species, data = iris.alt)

# Coefficients:
#     Speciessetosa  Speciesversicolor   Speciesvirginica  
#             1.462              4.260              5.552  

Pesan peringatan: Dalam model.matrix.default(mt, mf, contrasts):

variabel Speciesdiubah menjadifactor

Satu hal yang rumit adalah keseluruhannya drop=TRUE. Dalam vektor, ini berfungsi dengan baik untuk menghilangkan tingkat faktor yang tidak ada dalam data. Sebagai contoh:

s <- iris$Species
s[s == 'setosa', drop=TRUE]
#  [1] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [11] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [21] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [31] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [41] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# Levels: setosa
s[s == 'setosa', drop=FALSE]
#  [1] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [11] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [21] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [31] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [41] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# Levels: setosa versicolor virginica

Namun , dengan data.frames, perilaku [.data.frame()berbeda: lihat email ini atau ?"[.data.frame". Penggunaan drop=TRUEpada data.frames tidak berfungsi seperti yang Anda bayangkan:

x <- subset(iris, Species == 'setosa', drop=TRUE)  # susbetting with [ behaves the same way
x$Species
#  [1] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [11] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [21] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [31] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [41] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# Levels: setosa versicolor virginica

Untungnya Anda dapat menghilangkan faktor dengan mudah dengan droplevels()menurunkan tingkat faktor yang tidak digunakan untuk satu faktor atau untuk setiap faktor dalam a data.frame(sejak R 2.12):

x <- subset(iris, Species == 'setosa')
levels(x$Species)
# [1] "setosa"     "versicolor" "virginica" 
x <- droplevels(x)
levels(x$Species)
# [1] "setosa"

Ini adalah cara menjaga level yang Anda pilih agar tidak menjadi ggplotlegenda.

Secara internal, factors adalah bilangan bulat dengan vektor karakter tingkat atribut (lihat attributes(iris$Species)dan class(attributes(iris$Species)$levels)), yang bersih. Jika Anda harus mengubah nama level (dan Anda menggunakan string karakter), ini akan menjadi operasi yang jauh kurang efisien. Dan saya banyak mengubah nama level, terutama untuk ggplotlegenda. Jika Anda memalsukan faktor dengan vektor karakter, ada risiko bahwa Anda hanya akan mengubah satu elemen, dan secara tidak sengaja membuat level baru yang terpisah.

Vince
sumber
1
stringsAsFactorsbukanlah sebuah fungsi.
IRTFM
30

faktor urutan luar biasa, jika saya kebetulan menyukai jeruk dan membenci apel tetapi tidak keberatan anggur, saya tidak perlu mengelola beberapa indeks aneh untuk mengatakannya:

d <- data.frame(x = rnorm(20), f = sample(c("apples", "oranges", "grapes"), 20, replace = TRUE, prob = c(0.5, 0.25, 0.25)))
d$f <- ordered(d$f, c("apples", "grapes", "oranges"))
d[d$f >= "grapes", ]
mdsumner
sumber
itu aplikasi yang rapi. Tidak pernah terpikir olehnya.
JD Long
Apa yang d$f <- ordered(d$f, c("apples", "grapes", "oranges"))dilakukannya? Saya akan menebak bahwa itu memesan ini dalam bingkai data, tetapi setelah saya menjalankan baris itu dan mencetak bingkai data, tidak ada yang berubah. Apakah itu hanya memberlakukan pesanan internal meskipun pesanan cetak tidak berubah?
Addem
... Ya, saya pikir apa yang saya tulis adalah sesuatu seperti kalimat yang benar. Jika saya memahami maksud Anda, Anda menunjukkan kepada kami bahwa Anda dapat menetapkan urutan faktor, yang merupakan sesuatu yang tidak dapat Anda lakukan untuk string.
Addem
4
order () membuat pengurutan arbitrer dari nilai apa pun - dalam urutan yang Anda ucapkan. Sangat disayangkan saya menggunakan nilai yang diurutkan secara leksikografis, itu kebetulan. Misalnya saya menggunakan ini untuk data di mana "Z" buruk, "3" bagus tetapi labelnya tidak numerik atau abjad - jadi saya mengurutkan (data, c ("Z", "B", "A", " 0 "," 1 "," 2 "," 3 ")) dan kemudian saya dapat melakukan data>" A "dan ini hari-hari bahagia.
mdsumner
19

A factorpaling mirip dengan tipe enumerasi dalam bahasa lain. Penggunaan yang tepat adalah untuk variabel yang hanya dapat mengambil satu dari kumpulan nilai yang ditentukan. Dalam kasus ini, tidak setiap kemungkinan nilai yang diizinkan dapat ada dalam kumpulan data tertentu dan level "kosong" secara akurat mencerminkannya.

Perhatikan beberapa contoh. Untuk beberapa data yang dikumpulkan di seluruh Amerika Serikat, negara bagian harus dicatat sebagai faktor. Dalam kasus ini, fakta bahwa tidak ada kasus yang dikumpulkan dari negara bagian tertentu adalah relevan. Mungkin ada data dari negara bagian itu, tetapi terjadi (untuk alasan apa pun, yang mungkin menjadi alasan yang menarik) tidak ada. Jika kampung halaman dikumpulkan, itu tidak akan menjadi faktor. Tidak ada kumpulan kemungkinan asal yang telah ditentukan sebelumnya. Jika data dikumpulkan dari tiga kota dan bukan secara nasional, kota tersebut akan menjadi faktor: ada tiga pilihan yang diberikan di awal dan jika tidak ada kasus / data yang relevan ditemukan di salah satu dari tiga kota tersebut, itu relevan.

Aspek lain dari factors, seperti menyediakan cara untuk memberikan urutan arbitrer ke sekumpulan string, merupakan karakteristik sekunder yang berguna dari factors, tetapi bukan alasan keberadaannya.

Brian Diggs
sumber
3
+1. Brian, saya pikir Anda tepat sasaran dengan level penangkapan yang tidak ada dalam data.
Ricardo Saporta
13

Faktor-faktor luar biasa ketika seseorang melakukan analisis statistik dan benar-benar menjelajahi data. Namun, sebelum itu ketika seseorang membaca, membersihkan, memecahkan masalah, menggabungkan dan umumnya memanipulasi data, faktor-faktornya sangat merepotkan. Baru-baru ini, seperti dalam beberapa tahun terakhir, banyak fungsi telah ditingkatkan untuk menangani faktor-faktor tersebut dengan lebih baik. Misalnya, rbind bermain bagus dengan mereka. Saya masih merasa sangat merepotkan untuk memiliki level kosong yang tersisa setelah fungsi subset.

#drop a whole bunch of unused levels from a whole bunch of columns that are factors using gdata
require(gdata)
drop.levels(dataframe)

Saya tahu bahwa sangat mudah untuk mengodekan ulang level faktor dan mengatur ulang label dan ada juga cara yang bagus untuk menyusun ulang level. Otak saya tidak dapat mengingatnya dan saya harus mempelajarinya kembali setiap kali saya menggunakannya. Pengodean ulang seharusnya jauh lebih mudah daripada sebelumnya.

Fungsi string R cukup mudah dan logis untuk digunakan. Jadi saat memanipulasi saya biasanya lebih memilih karakter daripada faktor.

Farrel
sumber
1
Apakah Anda memiliki contoh analisis statistik yang menggunakan faktor?
JD Long
3
sekarang ada fungsi basis-R droplevels(). Dan itu tidak mengatur ulang faktor secara default.
Ben Bolker
6

Judul yang sangat tajam!

Saya percaya banyak fungsi estimasi memungkinkan Anda menggunakan faktor untuk dengan mudah mendefinisikan variabel dummy ... tapi saya tidak menggunakannya untuk itu.

Saya menggunakannya ketika saya memiliki vektor karakter yang sangat besar dengan sedikit pengamatan unik. Ini dapat mengurangi konsumsi memori, terutama jika string dalam vektor karakter lebih panjang.

PS - Saya bercanda tentang judulnya. Saya melihat tweet Anda. ;-)

Joshua Ulrich
sumber
1
Jadi Anda benar-benar hanya menggunakannya untuk menghemat ruang penyimpanan. Itu masuk akal.
JD Long
13
Yah setidaknya dulu ;-). Tetapi beberapa versi R yang lalu penyimpanan karakter telah ditulis ulang untuk di-hash secara internal sehingga bagian dari argumen bersejarah ini sekarang tidak berlaku. Faktor still sangat berguna untuk pengelompokan dan pemodelan.
Dirk Eddelbuettel
1
Menurut ?factoritu R-2.6.0 dan dikatakan, "Nilai integer disimpan dalam 4 byte sedangkan setiap referensi ke string karakter membutuhkan penunjuk sebesar 4 atau 8 byte." Apakah Anda akan menghemat ruang untuk mengonversi faktor jika string karakter membutuhkan 8 byte?
Joshua Ulrich
2
N <- 1000; a <- sample (c ("a", "b", "c"), N, replace = TRUE); cetak (object.size (a), units = "Kb"); cetak (object.size (faktor (a)), units = "Kb"); 8 Kb 4,5 Kb jadi sepertinya masih menghemat tempat.
Eduardo Leoni
2
@Eduardo Saya mendapat 4K vs 4.2b. Untuk N=100000saya mendapat 391,5 Kb vs 391,8 Kb. Jadi faktor membutuhkan lebih banyak memori.
Marek
1

Faktor-faktornya adalah mesin badging "casing unik" yang luar biasa. Saya telah membuat ulang ini berkali-kali, dan meskipun kadang-kadang ada beberapa kerutan, mereka sangat kuat.

library(dplyr)
d <- tibble(x = sample(letters[1:10], 20, replace = TRUE))

## normalize this table into an indexed value across two tables
id <- tibble(x_u = sort(unique(d$x))) %>% mutate(x_i = row_number())
di <- tibble(x_i = as.integer(factor(d$x)))


## reconstruct d$x when needed
d2 <- inner_join(di, id) %>% transmute(x = x_u)
identical(d, d2)
## [1] TRUE

Jika ada cara yang lebih baik untuk melakukan tugas ini, saya ingin melihatnya, saya tidak melihat kemampuan factordibahas ini.

mdsumner
sumber
-2

tapply (dan agregat ) bergantung pada faktor-faktor. Rasio informasi-ke-usaha dari fungsi-fungsi ini sangat tinggi.

Misalnya, dalam satu baris kode (panggilan untuk mengetuk di bawah) Anda bisa mendapatkan harga rata-rata berlian dengan Potong dan Warna:

> data(diamonds, package="ggplot2")

> head(dm)

   Carat     Cut    Clarity Price Color
1  0.23     Ideal     SI2   326     E
2  0.21   Premium     SI1   326     E
3  0.23      Good     VS1   327     E


> tx = with(diamonds, tapply(X=Price, INDEX=list(Cut=Cut, Color=Color), FUN=mean))

> a = sort(1:diamonds(tx)[2], decreasing=T)  # reverse columns for readability

> tx[,a]

         Color
Cut         J    I    H    G    F    E    D
Fair      4976 4685 5136 4239 3827 3682 4291
Good      4574 5079 4276 4123 3496 3424 3405
Very Good 5104 5256 4535 3873 3779 3215 3470
Premium   6295 5946 5217 4501 4325 3539 3631
Ideal     4918 4452 3889 3721 3375 2598 2629
doug
sumber
7
Ini bukan contoh yang baik, karena semua contoh tersebut akan bekerja dengan string juga.
hadley