Giảm mức yếu tố trong khung dữ liệu được đặt lại


543

Tôi có một khung dữ liệu chứa a factor. Khi tôi tạo một tập hợp con của subsetkhung dữ liệu này bằng cách sử dụng hoặc một chức năng lập chỉ mục khác, một khung dữ liệu mới được tạo. Tuy nhiên, factorbiến vẫn giữ tất cả các mức ban đầu của nó, ngay cả khi / nếu chúng không tồn tại trong khung dữ liệu mới.

Điều này gây ra vấn đề khi thực hiện âm mưu khía cạnh hoặc sử dụng các chức năng dựa trên mức độ yếu tố.

Cách ngắn gọn nhất để loại bỏ các cấp độ từ một yếu tố trong khung dữ liệu mới là gì?

Đây là một ví dụ:

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"

Câu trả lời:


420

Tất cả những gì bạn cần làm là áp dụng lại yếu tố () cho biến của mình sau khi đặt lại:

> 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

BIÊN TẬP

Từ ví dụ trang yếu tố:

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

Để giảm cấp độ từ tất cả các cột yếu tố trong khung dữ liệu, bạn có thể sử dụng:

subdf <- subset(df, numbers <= 3)
subdf[] <- lapply(subdf, function(x) if(is.factor(x)) factor(x) else x)

22
Điều đó tốt cho một lần, nhưng trong một data.frame với số lượng cột lớn, bạn có thể làm điều đó trên mỗi cột là một yếu tố ... dẫn đến nhu cầu về một chức năng như drop.levels () từ gdata.
Dirk Eddelbuettel

6
Tôi thấy ... nhưng từ góc độ người dùng, thật nhanh chóng để viết một cái gì đó như subdf [] <- lapply (subdf, function (x) if (is.factor (x)) Fact (x) other x) ... Is drop.levels () hiệu quả hơn nhiều về mặt tính toán hoặc tốt hơn với các tập dữ liệu lớn? (Tôi sẽ phải viết lại dòng trên trong một vòng lặp for cho một khung dữ liệu khổng lồ, tôi cho rằng.)
hatmatrix

1
Cảm ơn Stephen & Dirk - Tôi sẽ đưa ra ý kiến ​​này cho các yếu tố của một yếu tố, nhưng hy vọng mọi người sẽ đọc những bình luận này cho các đề xuất của bạn về việc làm sạch toàn bộ khung dữ liệu của các yếu tố.
medriscoll

9
Là một tác dụng phụ, chức năng chuyển đổi khung dữ liệu thành một danh sách, vì vậy mydf <- droplevels(mydf)giải pháp được đề xuất bởi Roman Luštrik và Tommy O'Dell dưới đây là thích hợp hơn.
Johan

1
Ngoài ra: phương pháp này không bảo toàn thứ tự của biến.
webelo

492

Kể từ phiên bản R 2.12, có một droplevels()chức năng.

levels(droplevels(subdf$letters))

7
Một lợi thế của phương pháp này so với việc sử dụng factor()là không cần thiết phải sửa đổi khung dữ liệu gốc hoặc tạo một khung dữ liệu liên tục mới. Tôi có thể bao droplevelsquanh một khung dữ liệu được đặt lại và sử dụng nó làm đối số dữ liệu cho hàm mạng và các nhóm sẽ được xử lý chính xác.
Sao Hỏa

Tôi đã nhận thấy rằng nếu tôi có cấp NA trong yếu tố của mình (cấp NA chính hãng), thì nó sẽ bị giảm bởi các cấp giảm, ngay cả khi NA có mặt.
Meep

46

Nếu bạn không muốn hành vi này, đừng sử dụng các yếu tố, thay vào đó hãy sử dụng các vectơ ký tự. Tôi nghĩ rằng điều này có ý nghĩa hơn là vá mọi thứ sau đó. Hãy thử làm như sau trước khi tải dữ liệu của bạn bằng read.tablehoặc read.csv:

options(stringsAsFactors = FALSE)

Nhược điểm là bạn bị hạn chế theo thứ tự bảng chữ cái. (sắp xếp lại là bạn của bạn cho các lô)


38

Đây là một vấn đề đã biết và một biện pháp khắc phục có thể được cung cấp drop.levels()trong gói gdata nơi ví dụ của bạn trở thành

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

Ngoài ra còn có dropUnusedLevelschức năng trong gói H'misc . Tuy nhiên, nó chỉ hoạt động bằng cách thay đổi toán tử tập hợp con [và không áp dụng ở đây.

Như một hệ quả tất yếu, cách tiếp cận trực tiếp trên cơ sở mỗi cột là đơn giản 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"

5
Các reordertham số của drop.levelschức năng là đáng nhắc đến: nếu bạn có để giữ gìn trật tự ban đầu của các yếu tố của bạn, sử dụng nó với FALSEgiá trị.
daroczig

Sử dụng gdata cho chỉ drop.levels mang lại "gdata: read.xls hỗ trợ cho các tệp 'XLS' (Excel 97-2004) ENABLED." "gdata: Không thể tải perl libaries cần thiết bởi read.xls ()" "gdata: để hỗ trợ các tệp 'XLSX' (Excel 2007+)." "gdata: Chạy chức năng 'installXLSXsupport ()'" "gdata: để tự động tải xuống và cài đặt perl". Sử dụng droplevels từ baseR ( stackoverflow.com/a/17218028/9295807 )
Vrokipal

Chuyện xảy ra theo thời gian. Bạn đang bình luận về một câu trả lời tôi đã viết chín năm trước. Vì vậy, hãy coi đây là một gợi ý để thường thích các giải pháp cơ sở R vì đó là những giải pháp sử dụng chức năng vẫn còn tồn tại khoảng N năm nữa.
Dirk Eddelbuettel

25

Một cách khác để làm tương tự nhưng với dplyr

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

Biên tập:

Cũng hoạt động! Nhờ agenis

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

17

Để hoàn thiện, giờ đây cũng có fct_droptrong forcatsgói http://forcats.tidyverse.org/reference/fct_drop.html .

Nó khác với droplevelscách nó đối phó với 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

15

Đây là một cách khác, mà tôi tin là tương đương với factor(..)cách tiếp cận:

> 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"

Ha, sau ngần ấy năm tôi không biết có một `[.factor`phương pháp có droptranh luận và bạn đã đăng nó vào năm 2009 ...
David Arenburg

8

Điều này thật đáng ghét. Đây là cách tôi thường làm, để tránh tải các gói khác:

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

mà có được bạn:

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

Lưu ý rằng các cấp độ mới sẽ thay thế bất cứ thứ gì chiếm chỉ số của chúng trong các cấp độ cũ (subdf $ chữ cái), vì vậy đại loại như:

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

sẽ không làm việc

Điều này rõ ràng không lý tưởng khi bạn có nhiều cấp độ, nhưng đối với một số ít, nó nhanh chóng và dễ dàng.


8

Nhìn vào droplevels phương thức trong nguồn R, bạn có thể thấy nó kết thúc factorhoạt động. Điều đó có nghĩa là về cơ bản bạn có thể tạo lại cột với factorchức năng.
Bên dưới cách data.table để giảm cấp độ từ tất cả các cột yếu tố.

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"

1
Tôi nghĩ rằng data.tablecách này sẽ là một cái gì đó giống nhưfor (j in names(DT)[sapply(DT, is.factor)]) set(DT, j = j, value = factor(DT[[j]]))
David Arenburg

1
@DavidArenburg nó không thay đổi nhiều ở đây vì chúng tôi [.data.tablechỉ gọi một lần
jangorecki

7

đây là một cách để làm điều đó

varFactor <- factor(letters[1:15])
varFactor <- varFactor[1:5]
varFactor <- varFactor[drop=T]

2
Đây là một bản sao của câu trả lời này đã được đăng 5 năm trước.
David Arenburg

6

Tôi đã viết các chức năng tiện ích để làm điều này. Bây giờ tôi đã biết về drop.levels của gdata, nó trông khá giống nhau. Họ đây (từ đây ):

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
}

4

Chủ đề rất thú vị, tôi đặc biệt thích ý tưởng chỉ là yếu tố phụ. Tôi đã có vấn đề tương tự trước đây và tôi chỉ chuyển đổi thành nhân vật và sau đó trở lại yếu tố.

   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))

Ý tôi là, factor(as.chracter(...))hoạt động, nhưng chỉ kém hiệu quả và cô đọng hơn factor(...). Có vẻ nghiêm trọng hơn các câu trả lời khác.
Gregor Thomas

1

Thật không may, yếu tố () dường như không hoạt động khi sử dụng rxDataStep của RevoScaleR. Tôi thực hiện theo hai bước: 1) Chuyển đổi thành ký tự và lưu trữ trong khung dữ liệu ngoài tạm thời (.xdf). 2) Chuyển đổi trở lại thành yếu tố và lưu trữ trong khung dữ liệu ngoài dứt khoát. Điều này giúp loại bỏ bất kỳ mức yếu tố không sử dụng, mà không tải tất cả dữ liệu vào bộ nhớ.

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

1

Đã thử hầu hết các ví dụ ở đây nếu không phải tất cả nhưng dường như không có gì hoạt động trong trường hợp của tôi. Sau khi vật lộn một thời gian, tôi đã thử sử dụng as.character () trên cột yếu tố để thay đổi nó thành một col với các chuỗi có vẻ hoạt động tốt.

Không chắc chắn cho các vấn đề hiệu suất.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.