Turunkan tingkat faktor dalam bingkai data yang disubsidi

543

Saya memiliki bingkai data yang mengandung a factor. Saat saya membuat subset dari subsetkerangka data ini menggunakan atau fungsi pengindeksan lainnya, bingkai data baru dibuat. Namun, factorvariabel mempertahankan semua level aslinya, bahkan ketika / jika mereka tidak ada dalam kerangka data baru.

Ini menyebabkan masalah ketika melakukan faceted plotting atau menggunakan fungsi yang bergantung pada tingkat faktor.

Apa cara paling ringkas untuk menghapus level dari faktor dalam kerangka data baru?

Ini sebuah contoh:

df <- data.frame(letters=letters[1:5],
                    numbers=seq(1:5))

levels(df$letters)
## [1] "a" "b" "c" "d" "e"

subdf <- subset(df, numbers <= 3)
##   letters numbers
## 1       a       1
## 2       b       2
## 3       c       3    

# all levels are still there!
levels(subdf$letters)
## [1] "a" "b" "c" "d" "e"
medriscoll
sumber

Jawaban:

420

Yang harus Anda lakukan adalah menerapkan faktor () ke variabel Anda lagi setelah berlangganan:

> subdf$letters
[1] a b c
Levels: a b c d e
subdf$letters <- factor(subdf$letters)
> subdf$letters
[1] a b c
Levels: a b c

EDIT

Dari contoh halaman faktor:

factor(ff)      # drops the levels that do not occur

Untuk menjatuhkan level dari semua kolom faktor dalam kerangka data, Anda dapat menggunakan:

subdf <- subset(df, numbers <= 3)
subdf[] <- lapply(subdf, function(x) if(is.factor(x)) factor(x) else x)
hatmatrix
sumber
22
Itu bagus untuk sekali saja, tetapi dalam data.frame dengan sejumlah besar kolom, Anda bisa melakukannya pada setiap kolom yang merupakan faktor ... yang mengarah pada kebutuhan akan fungsi seperti drop.levels () dari gdata.
Dirk Eddelbuettel
6
Saya mengerti ... tetapi dari sudut pandang pengguna lebih cepat menulis sesuatu seperti subdf [] <- lapply (subdf, function (x) if (is.factor (x)) factor (x) else x) ... Is drop.levels () jauh lebih efisien secara komputasi atau lebih baik dengan kumpulan data besar? (Satu harus menulis ulang baris di atas dalam for-loop untuk frame data yang besar, saya kira.)
hatmatrix
1
Terima kasih Stephen & Dirk - Saya memberikan ini jempol untuk caes satu faktor, tapi semoga orang-orang akan membaca komentar ini untuk saran Anda tentang membersihkan seluruh kerangka data faktor.
medriscoll
9
Sebagai efek samping, fungsi mengubah bingkai data menjadi daftar, jadi mydf <- droplevels(mydf)solusi yang disarankan oleh Roman Luštrik dan Tommy O'Dell di bawah ini lebih disukai.
Johan
1
Juga: metode ini tidak mempertahankan urutan variabel.
webelo
492

Sejak R versi 2.12, ada droplevels()fungsi.

levels(droplevels(subdf$letters))
Roman Luštrik
sumber
7
Keuntungan dari metode ini dibandingkan menggunakan factor()adalah tidak perlu memodifikasi kerangka data asli atau membuat kerangka data persisten baru. Saya bisa membungkus droplevelsdataframe yang sudah di-subsettens dan menggunakannya sebagai argumen data ke fungsi kisi, dan grup akan ditangani dengan benar.
Mars
Saya perhatikan bahwa jika saya memiliki level NA di faktor saya (level NA asli), levelnya akan turun, bahkan jika NAS ada.
Meep
46

Jika Anda tidak menginginkan perilaku ini, jangan gunakan faktor, gunakan vektor karakter. Saya pikir ini lebih masuk akal daripada memperbaiki keadaan setelahnya. Coba yang berikut ini sebelum memuat data Anda dengan read.tableatau read.csv:

options(stringsAsFactors = FALSE)

Kerugiannya adalah Anda terbatas pada pemesanan berdasarkan abjad. (menyusun ulang adalah teman Anda untuk plot)

Hadley
sumber
38

Ini adalah masalah yang diketahui, dan satu kemungkinan obat disediakan oleh drop.levels()dalam paket gdata di mana contoh Anda menjadi

> drop.levels(subdf)
  letters numbers
1       a       1
2       b       2
3       c       3
> levels(drop.levels(subdf)$letters)
[1] "a" "b" "c"

Ada juga dropUnusedLevelsfungsi dalam paket Hmisc . Namun, ini hanya berfungsi dengan mengubah operator subset [dan tidak berlaku di sini.

Sebagai akibat wajar, pendekatan langsung berdasarkan per kolom adalah sederhana as.factor(as.character(data)):

> levels(subdf$letters)
[1] "a" "b" "c" "d" "e"
> subdf$letters <- as.factor(as.character(subdf$letters))
> levels(subdf$letters)
[1] "a" "b" "c"
Dirk Eddelbuettel
sumber
5
The reorderparameter dari drop.levelsfungsi bernilai menyebutkan: jika Anda harus menjaga urutan asli dari faktor Anda, menggunakannya dengan FALSEnilai.
daroczig
Menggunakan gdata hanya untuk drop.levels menghasilkan "gdata: read.xls dukungan untuk file 'XLS' (Excel 97-2004) DIAKTIFKAN." "gdata: Tidak dapat memuat perpustakaan perl yang dibutuhkan oleh read.xls ()" "gdata: untuk mendukung file 'XLSX' (Excel 2007+)." "gdata: Jalankan fungsi 'installXLSXsupport ()'" "gdata: untuk secara otomatis mengunduh dan menginstal perl". Gunakan droplevel dari baseR ( stackoverflow.com/a/17218028/9295807 )
Vrokipal
Hal-hal terjadi seiring waktu. Anda sedang mengomentari jawaban saya menulis sembilan tahun yang lalu. Jadi mari kita ambil ini sebagai petunjuk untuk secara umum lebih memilih solusi R dasar karena itu adalah yang menggunakan fungsionalitas yang masih akan ada sekitar N tahun dari sekarang.
Dirk Eddelbuettel
25

Cara lain melakukan hal yang sama tetapi dengan dplyr

library(dplyr)
subdf <- df %>% filter(numbers <= 3) %>% droplevels()
str(subdf)

Edit:

Juga Berhasil! Berkat agenis

subdf <- df %>% filter(numbers <= 3) %>% droplevels
levels(subdf$letters)
Prradep
sumber
17

Demi kelengkapan, sekarang ada juga fct_dropdi forcatspaket http://forcats.tidyverse.org/reference/fct_drop.html .

Ini berbeda dari droplevelscara berurusan NA:

f <- factor(c("a", "b", NA), exclude = NULL)

droplevels(f)
# [1] a    b    <NA>
# Levels: a b <NA>

forcats::fct_drop(f)
# [1] a    b    <NA>
# Levels: a b
Aurèle
sumber
15

Inilah cara lain, yang saya percaya setara dengan factor(..)pendekatan:

> df <- data.frame(let=letters[1:5], num=1:5)
> subdf <- df[df$num <= 3, ]

> subdf$let <- subdf$let[ , drop=TRUE]

> levels(subdf$let)
[1] "a" "b" "c"
ars
sumber
Ha, setelah bertahun-tahun saya tidak tahu ada `[.factor`metode yang memiliki dropargumen dan Anda telah memposting ini pada tahun 2009 ...
David Arenburg
8

Ini menjengkelkan. Inilah yang biasanya saya lakukan, untuk menghindari memuat paket lain:

levels(subdf$letters)<-c("a","b","c",NA,NA)

yang membuat Anda:

> subdf$letters
[1] a b c
Levels: a b c

Perhatikan bahwa level baru akan menggantikan apa pun yang menempati indeks mereka di level lama (subdf $ letters), jadi kira-kira seperti:

levels(subdf$letters)<-c(NA,"a","c",NA,"b")

tidak akan bekerja

Ini jelas tidak ideal ketika Anda memiliki banyak level, tetapi untuk beberapa level, ini cepat dan mudah.

Matt Parker
sumber
8

Melihat kodedroplevels metode dalam sumber R Anda dapat melihatnya membungkus factorberfungsi. Itu berarti Anda pada dasarnya dapat membuat ulang kolom dengan factorfungsi.
Di bawah data.tabel cara untuk menjatuhkan level dari semua kolom faktor.

library(data.table)
dt = data.table(letters=factor(letters[1:5]), numbers=seq(1:5))
levels(dt$letters)
#[1] "a" "b" "c" "d" "e"
subdt = dt[numbers <= 3]
levels(subdt$letters)
#[1] "a" "b" "c" "d" "e"

upd.cols = sapply(subdt, is.factor)
subdt[, names(subdt)[upd.cols] := lapply(.SD, factor), .SDcols = upd.cols]
levels(subdt$letters)
#[1] "a" "b" "c"
jangorecki
sumber
1
Saya pikir data.tablejalannya akan sepertifor (j in names(DT)[sapply(DT, is.factor)]) set(DT, j = j, value = factor(DT[[j]]))
David Arenburg
1
@ Davidvidenburg tidak banyak berubah di sini karena kami [.data.tablehanya memanggil sekali
jangorecki
7

di sini adalah cara untuk melakukan itu

varFactor <- factor(letters[1:15])
varFactor <- varFactor[1:5]
varFactor <- varFactor[drop=T]
Diogo
sumber
2
Ini adalah penipuan jawaban ini yang telah diposting 5 tahun sebelumnya.
David Arenburg
6

Saya menulis fungsi utilitas untuk melakukan ini. Sekarang saya tahu tentang drop.levels gdata, itu terlihat sangat mirip. Inilah mereka (dari sini ):

present_levels <- function(x) intersect(levels(x), x)

trim_levels <- function(...) UseMethod("trim_levels")

trim_levels.factor <- function(x)  factor(x, levels=present_levels(x))

trim_levels.data.frame <- function(x) {
  for (n in names(x))
    if (is.factor(x[,n]))
      x[,n] = trim_levels(x[,n])
  x
}
Brendan OConnor
sumber
4

Utas yang sangat menarik, saya terutama menyukai ide untuk hanya faktor subseleksi lagi. Saya memiliki masalah yang sama sebelumnya dan saya hanya mengubah karakter dan kemudian kembali ke faktor.

   df <- data.frame(letters=letters[1:5],numbers=seq(1:5))
   levels(df$letters)
   ## [1] "a" "b" "c" "d" "e"
   subdf <- df[df$numbers <= 3]
   subdf$letters<-factor(as.character(subdf$letters))
DfAC
sumber
Maksud saya, factor(as.chracter(...))bekerja, tetapi kurang efisien dan ringkas daripada factor(...). Tampaknya benar-benar lebih buruk daripada jawaban yang lain.
Gregor Thomas
1

Sayangnya faktor () tampaknya tidak berfungsi ketika menggunakan rxDataStep dari RevoScaleR. Saya melakukannya dalam dua langkah: 1) Konversi ke karakter dan simpan dalam bingkai data eksternal sementara (.xdf). 2) Konversi kembali ke faktor dan simpan dalam bingkai data eksternal yang pasti. Ini menghilangkan tingkat faktor yang tidak digunakan, tanpa memuat semua data ke dalam memori.

# Step 1) Converts to character, in temporary xdf file:
rxDataStep(inData = "input.xdf", outFile = "temp.xdf", transforms = list(VAR_X = as.character(VAR_X)), overwrite = T)
# Step 2) Converts back to factor:
rxDataStep(inData = "temp.xdf", outFile = "output.xdf", transforms = list(VAR_X = as.factor(VAR_X)), overwrite = T)
Jerome Smith
sumber
1

Telah mencoba sebagian besar contoh di sini jika tidak semua tetapi tidak ada yang tampaknya berfungsi dalam kasus saya. Setelah berjuang cukup lama saya mencoba menggunakan as.character () pada kolom faktor untuk mengubahnya menjadi col dengan string yang sepertinya berfungsi dengan baik.

Tidak yakin untuk masalah kinerja.

Naga Pakalapati
sumber