วางระดับตัวคูณในเฟรมข้อมูลที่ถูกเซตย่อย


543

factorฉันมีกรอบข้อมูลที่มี เมื่อฉันสร้างเซตย่อยของ dataframe นี้โดยใช้subsetหรือฟังก์ชั่นการทำดัชนีอื่นเฟรมข้อมูลใหม่จะถูกสร้างขึ้น อย่างไรก็ตามfactorตัวแปรยังคงรักษาระดับเดิมทั้งหมดแม้เมื่อ / หากไม่มีอยู่ใน dataframe ใหม่

สิ่งนี้ทำให้เกิดปัญหาเมื่อทำการพล็อตแบบเหลี่ยมเพชรพลอยหรือใช้ฟังก์ชั่นที่ขึ้นอยู่กับระดับปัจจัย

วิธีรวบรัดที่สุดในการลบระดับออกจากปัจจัยในดาต้าเฟรมใหม่คืออะไร

นี่คือตัวอย่าง:

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"

คำตอบ:


420

สิ่งที่คุณต้องทำคือใช้ตัวคูณ () กับตัวแปรของคุณอีกครั้งหลังจากการตั้งค่าใหม่:

> 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

แก้ไข

จากตัวอย่างหน้าปัจจัย:

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

สำหรับการปล่อยระดับจากคอลัมน์ปัจจัยทั้งหมดใน dataframe คุณสามารถใช้:

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

22
มันดีสำหรับการปิดครั้งเดียว แต่ใน data.frame ที่มีคอลัมน์จำนวนมากคุณต้องทำเช่นนั้นในทุก ๆ คอลัมน์ที่เป็นปัจจัย ... นำไปสู่ความต้องการฟังก์ชั่นเช่น drop.levels () จาก gdata
Dirk Eddelbuettel

6
ฉันเห็น ... แต่จากมุมมองของผู้ใช้มันรวดเร็วในการเขียนบางอย่างเช่น subdf [] <- lapply (subdf, function (x) if (is.factor (x)) factor (x) else x) ... คือ drop.levels () มีประสิทธิภาพมากขึ้นในการคำนวณหรือดีกว่าด้วยชุดข้อมูลขนาดใหญ่? (หนึ่งจะต้องเขียนบรรทัดข้างต้นในสำหรับห่วงสำหรับกรอบข้อมูลขนาดใหญ่ผมคิดว่า.)
hatmatrix

1
ขอบคุณ Stephen & Dirk - ฉันให้นิ้วหัวแม่มือนี้กับ caes ของปัจจัยหนึ่ง แต่หวังว่าคนจะอ่านความคิดเห็นเหล่านี้สำหรับคำแนะนำของคุณในการทำความสะอาดกรอบข้อมูลทั้งหมดของปัจจัย
medriscoll

9
ในฐานะที่เป็นผลข้างเคียงฟังก์ชันจะแปลงเฟรมข้อมูลเป็นรายการดังนั้นการmydf <- droplevels(mydf)แก้ปัญหาที่แนะนำโดย Roman Luštrikและ Tommy O'Dell ด้านล่างจึงเหมาะสมกว่า
Johan

1
นอกจากนี้: วิธีนี้จะรักษาลำดับของตัวแปร
webelo

492

ตั้งแต่รุ่น R 2.12 มีdroplevels()ฟังก์ชั่น

levels(droplevels(subdf$letters))

7
ข้อดีของวิธีนี้มากกว่าการใช้factor()คือไม่จำเป็นต้องแก้ไขดาต้าเฟรมเดิมหรือสร้างดาต้าเฟรมแบบถาวรใหม่ ฉันสามารถdroplevelsล้อมรอบ dataframe ที่ถูกเซ็ตและใช้เป็นอาร์กิวเมนต์ data ของฟังก์ชัน lattice และกลุ่มจะได้รับการจัดการอย่างถูกต้อง
ดาวอังคาร

ฉันสังเกตเห็นว่าถ้าฉันมีระดับ NA ในปัจจัยของฉัน (ระดับ NA ของแท้) มันจะลดลงตามระดับที่ลดลงแม้ว่าจะมี NA
Meep

46

หากคุณไม่ต้องการพฤติกรรมนี้อย่าใช้ปัจจัยใช้ตัวอักษรแทน ฉันคิดว่าสิ่งนี้สมเหตุสมผลมากกว่าการปะแก้ในภายหลัง ลองสิ่งต่อไปนี้ก่อนที่จะโหลดข้อมูลของคุณด้วยread.tableหรือread.csv:

options(stringsAsFactors = FALSE)

ข้อเสียคือคุณ จำกัด การเรียงตามตัวอักษร (จัดลำดับใหม่เป็นเพื่อนของคุณสำหรับแปลง)


38

มันเป็นปัญหาที่ทราบกันดีและมีวิธีแก้ไขหนึ่งวิธีที่เป็นไปได้drop.levels()ในแพ็คเกจgdataที่ตัวอย่างของคุณกลายเป็น

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

นอกจากนี้ยังมีdropUnusedLevelsฟังก์ชั่นในแพ็คเกจHmisc อย่างไรก็ตามมันจะทำงานได้โดยการเปลี่ยนโอเปอเรเตอร์ย่อย[และไม่สามารถใช้ได้ที่นี่

ในฐานะที่เป็นข้อพิสูจน์วิธีการโดยตรงบนพื้นฐานต่อคอลัมน์เป็นเรื่องง่าย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
reorderพารามิเตอร์ของdrop.levelsฟังก์ชั่นเป็นมูลค่าการกล่าวขวัญ: ถ้าคุณมีการรักษาคำสั่งเดิมของปัจจัยของคุณที่ใช้งานได้FALSEคุ้มค่า
daroczig

การใช้ gdata เพียงแค่ปล่อยให้ระดับ "gdata: read.xls รองรับไฟล์ 'XLS' (Excel 97-2004) เปิดใช้งาน" "gdata: ไม่สามารถโหลด lib ที่จำเป็นโดย read.xls ()" "gdata: เพื่อรองรับไฟล์ 'XLSX' (Excel 2007+)" "gdata: เรียกใช้ฟังก์ชัน 'installXLSXsupport ()'" gdata: เพื่อดาวน์โหลดและติดตั้ง Perl โดยอัตโนมัติ " ใช้หยดจาก baseR ( stackoverflow.com/a/17218028/9295807 )
Vrokipal

สิ่งที่เกิดขึ้นเมื่อเวลาผ่านไป คุณกำลังแสดงความคิดเห็นต่อคำตอบที่ฉันเขียนเก้าปีที่ผ่านมา งั้นลองมาดูกันว่านี่เป็นคำใบ้ที่นิยมใช้โซลูชั่นฐาน R เนื่องจากเป็นฟังก์ชันที่ใช้ฟังก์ชันที่ยังคงมีอยู่ในอีกNปีนับจากนี้
Dirk Eddelbuettel

25

อีกวิธีในการทำเช่นเดียวกัน แต่ด้วย dplyr

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

แก้ไข:

ยังใช้งานได้! ขอบคุณagenis

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

17

เพื่อความสมบูรณ์ขณะนี้ยังมีแพ็คเกจhttp://forcats.tidyverse.org/reference/fct_drop.htmlfct_dropในforcatsแพ็คเกจด้วย

มันแตกต่างจากdroplevelsวิธีที่เกี่ยวข้องกับ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

นี่เป็นอีกวิธีหนึ่งซึ่งฉันเชื่อว่าเทียบเท่ากับfactor(..)วิธีการ:

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

ฮาหลังจากหลายปีเหล่านี้ฉันไม่ทราบว่ามี`[.factor`วิธีการที่มีdropข้อโต้แย้งและคุณได้โพสต์สิ่งนี้ในปี 2009 ...
David Arenburg

8

สิ่งนี้น่ารังเกียจ นี่คือวิธีที่ฉันมักจะทำเพื่อหลีกเลี่ยงการโหลดแพคเกจอื่น ๆ :

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

สิ่งที่ทำให้คุณได้รับ:

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

โปรดทราบว่าระดับใหม่จะแทนที่สิ่งที่มีอยู่ในดัชนีของพวกเขาในระดับเก่า (subdf $ ตัวอักษร) ดังนั้นสิ่งที่ชอบ:

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

จะไม่ทำงาน

เห็นได้ชัดว่าไม่เหมาะเมื่อคุณมีหลายระดับ แต่สำหรับบางคนมันเร็วและง่าย


8

ดูที่รหัสdroplevelsวิธีการในแหล่ง R คุณสามารถเห็นมันล้อมรอบfactorฟังก์ชั่น นั่นหมายความว่าคุณสามารถสร้างคอลัมน์ใหม่ได้ด้วยfactorฟังก์ชั่น
ด้านล่างวิธี data.table เพื่อวางระดับจากคอลัมน์ปัจจัยทั้งหมด

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
ฉันคิดว่าdata.tableวิธีจะเป็นอย่างไรfor (j in names(DT)[sapply(DT, is.factor)]) set(DT, j = j, value = factor(DT[[j]]))
David Arenburg

1
@DavidArenburg มันไม่ได้เปลี่ยนแปลงมากนักที่นี่เมื่อเราเรียก[.data.tableเพียงครั้งเดียว
jangorecki

7

นี่คือวิธีในการทำเช่นนั้น

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

2
นี่คือคำตอบของคำตอบนี้ที่โพสต์เมื่อ 5 ปีก่อน
David Arenburg

6

ฉันเขียนฟังก์ชั่นยูทิลิตี้เพื่อทำสิ่งนี้ ตอนนี้ฉันรู้เกี่ยวกับ gdata ของ drop.levels แล้วมันก็ค่อนข้างคล้ายกัน ที่นี่พวกเขา (จากที่นี่ ):

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

หัวข้อที่น่าสนใจมากฉันชอบความคิดโดยเฉพาะอย่างยิ่งเพียงแยกตัวเลือกอีกครั้ง ฉันมีปัญหาที่คล้ายกันมาก่อนและฉันเพิ่งแปลงเป็นตัวละครแล้วกลับมาเป็นปัจจัย

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

ผมหมายถึงfactor(as.chracter(...))งาน factor(...)แต่เพียงน้อยอย่างมีประสิทธิภาพและรัดกุมกว่า ดูเหมือนจะเลวร้ายยิ่งกว่าคำตอบอื่น ๆ
Gregor Thomas

1

น่าเสียดายที่ปัจจัย () ดูเหมือนว่าจะไม่ทำงานเมื่อใช้ rxDataStep ของ RevoScaleR ฉันทำได้สองขั้นตอน: 1) แปลงเป็นตัวละครและเก็บไว้ในกรอบข้อมูลภายนอกชั่วคราว (.xdf) 2) แปลงกลับเป็นปัจจัยและจัดเก็บในกรอบข้อมูลภายนอกที่ชัดเจน สิ่งนี้จะช่วยลดระดับปัจจัยที่ไม่ได้ใช้โดยไม่ต้องโหลดข้อมูลทั้งหมดลงในหน่วยความจำ

# 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

ลองใช้ตัวอย่างส่วนใหญ่ที่นี่ถ้าไม่ใช่ทั้งหมด แต่ดูเหมือนว่าจะไม่มีใครทำงานในกรณีของฉัน หลังจากดิ้นรนมาระยะหนึ่งแล้วฉันได้ลองใช้as.character ()บนคอลัมน์แฟคเตอร์เพื่อเปลี่ยนเป็นคอลัมน์ที่มีสตริงซึ่งดูเหมือนว่าจะทำงานได้ดี

ไม่แน่ใจสำหรับปัญหาด้านประสิทธิภาพ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.