Secara otomatis memperluas faktor R menjadi kumpulan variabel indikator 1/0 untuk setiap tingkat faktor

108

Saya memiliki bingkai data R yang berisi faktor yang ingin saya "kembangkan" sehingga untuk setiap tingkat faktor, ada kolom terkait dalam bingkai data baru, yang berisi indikator 1/0. Misal, misalkan saya memiliki:

df.original <-data.frame(eggs = c("foo", "foo", "bar", "bar"), ham = c(1,2,3,4))

Saya ingin:

df.desired  <- data.frame(foo = c(1,1,0,0), bar=c(0,0,1,1), ham=c(1,2,3,4))

Karena untuk analisis tertentu yang memerlukan kerangka data numerik lengkap (misalnya, analisis komponen utama), saya pikir fitur ini mungkin sudah ada di dalamnya. Menulis fungsi untuk melakukan ini seharusnya tidak terlalu sulit, tetapi saya dapat memperkirakan beberapa tantangan yang berkaitan dengan nama kolom dan jika sudah ada sesuatu, saya lebih suka menggunakannya.

John Horton
sumber

Jawaban:

131

Gunakan model.matrixfungsi:

model.matrix( ~ Species - 1, data=iris )
Greg Snow
sumber
1
Dapatkah saya menambahkan bahwa metode ini jauh lebih cepat daripada yang castsaya gunakan .
Matt Weller
3
@GregSnow Saya meninjau paragraf ke-2 ?formulajuga ?model.matrix, tetapi tidak jelas (mungkin saja karena kurangnya kedalaman pengetahuan saya dalam aljabar matriks dan formulasi model). Setelah menggali lebih lanjut, saya dapat menyimpulkan bahwa -1 hanya menentukan untuk tidak menyertakan kolom "intersep". Jika Anda membiarkan -1, Anda akan melihat kolom intersep 1 dalam output dengan satu kolom biner tersisa. Anda dapat melihat nilai mana dari kolom yang dihilangkan adalah 1 berdasarkan baris di mana nilai kolom lainnya adalah 0. Dokumentasinya tampak samar-adakah sumber lain yang bagus?
Ryan Chase
1
@RyanChase, ada banyak tutorial dan buku online tentang R / S (beberapa di antaranya memiliki deskripsi singkat di halaman web r-project.org). Pembelajaran S dan R saya sendiri agak eklektik (dan lama), jadi saya bukan yang terbaik untuk memberikan pendapat tentang bagaimana buku / tutorial saat ini menarik bagi pemula. Saya, bagaimanapun, adalah penggemar eksperimen. Mencoba sesuatu dalam sesi R baru bisa sangat mencerahkan dan tidak berbahaya (hal terburuk yang terjadi pada saya adalah menabrak R, dan jarang sekali, yang mengarah pada perbaikan pada R). Stackoverflow kemudian menjadi sumber yang bagus untuk memahami apa yang terjadi.
Greg Snow
7
Dan jika Anda ingin mengonversi semua kolom faktor, Anda dapat menggunakan:model.matrix(~., data=iris)[,-1]
user890739
1
@ Colin, Tidak sepenuhnya otomatis, tetapi Anda dapat menggunakan naresiduntuk mengembalikan nilai yang hilang setelah menggunakan na.exclude. Contoh cepat:tmp <- data.frame(x=factor(c('a','b','c',NA,'a'))); tmp2 <- na.exclude(tmp); tmp3 <- model.matrix( ~x-1, tmp2); tmp4 <- naresid(attr(tmp2,'na.action'), tmp3)
Greg Snow
17

Jika bingkai data Anda hanya terdiri dari faktor-faktor (atau Anda mengerjakan subset variabel yang semuanya merupakan faktor), Anda juga dapat menggunakan acm.disjonctiffungsi dari ade4paket:

R> library(ade4)
R> df <-data.frame(eggs = c("foo", "foo", "bar", "bar"), ham = c("red","blue","green","red"))
R> acm.disjonctif(df)
  eggs.bar eggs.foo ham.blue ham.green ham.red
1        0        1        0         0       1
2        0        1        1         0       0
3        1        0        0         1       0
4        1        0        0         0       1

Tidak persis seperti kasus yang Anda gambarkan, tetapi ini juga bisa berguna ...

juba
sumber
Terima kasih, ini sangat membantu saya karena menggunakan lebih sedikit memori daripada model.matrix!
Serhiy
Saya suka cara variabel dinamai; Saya tidak suka bahwa mereka dikembalikan sebagai numerik yang haus penyimpanan ketika mereka harus (IMHO) hanya menjadi logis.
dsz
9

Cara cepat menggunakan reshape2paket:

require(reshape2)

> dcast(df.original, ham ~ eggs, length)

Using ham as value column: use value_var to override.
  ham bar foo
1   1   0   1
2   2   0   1
3   3   1   0
4   4   1   0

Perhatikan bahwa ini menghasilkan nama kolom yang Anda inginkan dengan tepat.

Prasad Chalasani
sumber
Baik. Tapi jaga duplikat ham. ucapkan, d <- data.frame (telur = c ("foo", "bar", "foo"), ham = c (1,2,1)); dcast (d, ham ~ telur, panjang) membuat foo = 2.
kohske
@Kohske, benar, tapi saya berasumsi hamadalah id baris unik. Jika hambukan sebuah id unik maka seseorang harus menggunakan id unik lainnya (atau membuat dummy) dan menggunakannya sebagai pengganti ham. Mengonversi label kategorikal menjadi indikator biner hanya masuk akal untuk id unik.
Prasad Chalasani
6

mungkin variabel dummy mirip dengan yang Anda inginkan. Kemudian, model.matrix berguna:

> with(df.original, data.frame(model.matrix(~eggs+0), ham))
  eggsbar eggsfoo ham
1       0       1   1
2       0       1   2
3       1       0   3
4       1       0   4
kohske
sumber
6

Entri terlambat class.inddari nnetpaket

library(nnet)
 with(df.original, data.frame(class.ind(eggs), ham))
  bar foo ham
1   0   1   1
2   0   1   2
3   1   0   3
4   1   0   4
mnel
sumber
4

Baru saja menemukan utas lama ini dan berpikir saya akan menambahkan fungsi yang menggunakan ade4 untuk mengambil kerangka data yang terdiri dari faktor dan / atau data numerik dan mengembalikan kerangka data dengan faktor sebagai kode dummy.

dummy <- function(df) {  

    NUM <- function(dataframe)dataframe[,sapply(dataframe,is.numeric)]
    FAC <- function(dataframe)dataframe[,sapply(dataframe,is.factor)]

    require(ade4)
    if (is.null(ncol(NUM(df)))) {
        DF <- data.frame(NUM(df), acm.disjonctif(FAC(df)))
        names(DF)[1] <- colnames(df)[which(sapply(df, is.numeric))]
    } else {
        DF <- data.frame(NUM(df), acm.disjonctif(FAC(df)))
    }
    return(DF)
} 

Ayo coba.

df <-data.frame(eggs = c("foo", "foo", "bar", "bar"), 
            ham = c("red","blue","green","red"), x=rnorm(4))     
dummy(df)

df2 <-data.frame(eggs = c("foo", "foo", "bar", "bar"), 
            ham = c("red","blue","green","red"))  
dummy(df2)
Tyler Rinker
sumber
3

Berikut cara yang lebih jelas untuk melakukannya. Saya menggunakan model.matrix untuk membuat variabel boolean dummy dan kemudian menggabungkannya kembali ke dataframe asli.

df.original <-data.frame(eggs = c("foo", "foo", "bar", "bar"), ham = c(1,2,3,4))
df.original
#   eggs ham
# 1  foo   1
# 2  foo   2
# 3  bar   3
# 4  bar   4

# Create the dummy boolean variables using the model.matrix() function.
> mm <- model.matrix(~eggs-1, df.original)
> mm
#   eggsbar eggsfoo
# 1       0       1
# 2       0       1
# 3       1       0
# 4       1       0
# attr(,"assign")
# [1] 1 1
# attr(,"contrasts")
# attr(,"contrasts")$eggs
# [1] "contr.treatment"

# Remove the "eggs" prefix from the column names as the OP desired.
colnames(mm) <- gsub("eggs","",colnames(mm))
mm
#   bar foo
# 1   0   1
# 2   0   1
# 3   1   0
# 4   1   0
# attr(,"assign")
# [1] 1 1
# attr(,"contrasts")
# attr(,"contrasts")$eggs
# [1] "contr.treatment"

# Combine the matrix back with the original dataframe.
result <- cbind(df.original, mm)
result
#   eggs ham bar foo
# 1  foo   1   0   1
# 2  foo   2   0   1
# 3  bar   3   1   0
# 4  bar   4   1   0

# At this point, you can select out the columns that you want.
stackoverflowuser2010
sumber
0

Saya membutuhkan fungsi untuk 'meledakkan' faktor-faktor yang sedikit lebih fleksibel, dan membuatnya berdasarkan fungsi acm.disjonctif dari paket ade4. Ini memungkinkan Anda untuk memilih nilai yang meledak, yaitu 0 dan 1 dalam acm.disjonctif. Itu hanya meledakkan faktor-faktor yang memiliki level 'sedikit'. Kolom numerik dipertahankan.

# Function to explode factors that are considered to be categorical,
# i.e., they do not have too many levels.
# - data: The data.frame in which categorical variables will be exploded.
# - values: The exploded values for the value being unequal and equal to a level.
# - max_factor_level_fraction: Maximum number of levels as a fraction of column length. Set to 1 to explode all factors.
# Inspired by the acm.disjonctif function in the ade4 package.
explode_factors <- function(data, values = c(-0.8, 0.8), max_factor_level_fraction = 0.2) {
  exploders <- colnames(data)[sapply(data, function(col){
      is.factor(col) && nlevels(col) <= max_factor_level_fraction * length(col)
    })]
  if (length(exploders) > 0) {
    exploded <- lapply(exploders, function(exp){
        col <- data[, exp]
        n <- length(col)
        dummies <- matrix(values[1], n, length(levels(col)))
        dummies[(1:n) + n * (unclass(col) - 1)] <- values[2]
        colnames(dummies) <- paste(exp, levels(col), sep = '_')
        dummies
      })
    # Only keep numeric data.
    data <- data[sapply(data, is.numeric)]
    # Add exploded values.
    data <- cbind(data, exploded)
  }
  return(data)
}
rakensi
sumber