Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

R - How to one hot encoding a single column while keep other columns still?

I have a data frame like this:

group   student exam_passed subject 
A       01      Y           Math
A       01      N           Science
A       01      Y           Japanese
A       02      N           Math
A       02      Y           Science
B       01      Y           Japanese
C       02      N           Math

What I would like to achieve is the below result:

group   student exam_passed subject_Math  subject_Science  subject_Japanese   
A       01      Y           1             0                0
A       01      N           0             1                0
A       01      Y           0             0                1
A       02      N           1             0                0           
A       02      Y           0             1                0
B       01      Y           0             0                1
C       02      N           1             0                0

Here is the test data frame:

df <- data.frame(
group = c('A', 'A', 'A', 'A', 'A', 'B', 'C'),
student = c('01', '01', '01', '02', '02', '01', '02'),
exam_pass = c('Y', 'N', 'Y', 'N', 'Y', 'Y', 'N'),
subject = c('Math', 'Science', 'Japanese', 'Math', 'Science', 'Japanese', 'Math')
)

I have tried for loop, however, the original data is too large to deal with, and

mltools::one_hot(df, col = 'subject')

doesn't work either because of the this error:

Error in `[.data.frame`(dt, , cols, with = FALSE) :
unused argument (with = FALSE)

Could anyone help me with this? Thanks!

like image 325
J.D Avatar asked Sep 27 '18 14:09

J.D


4 Answers

require(tidyr)
require(dplyr)

df %>% mutate(value = 1)  %>% spread(subject, value,  fill = 0 ) 


group student exam_pass Japanese Math Science
1     A      01         N        0    0       1
2     A      01         Y        1    1       0
3     A      02         N        0    1       0
4     A      02         Y        0    0       1
5     B      01         Y        1    0       0
6     C      02         N        0    1       0
like image 192
Mouad_Seridi Avatar answered Oct 19 '22 01:10

Mouad_Seridi


another option

library(dplyr)
df %>% 
  mutate(subject_Math = ifelse(subject=='Math', 1, 0),
         subject_Science = ifelse(subject=='Science', 1, 0),
         subject_Japanese = ifelse(subject=='Japanese', 1, 0))
like image 34
B Williams Avatar answered Oct 19 '22 03:10

B Williams


You can do this with the arcanely-named contrasts function.

Relevant section of the docs:

if contrasts = FALSE an identity matrix is returned.

So here's a basic implementation:

encode_onehot <- function(x, colname_prefix = "", colname_suffix = "") {
  if (!is.factor(x)) {
      x <- as.factor(x)
  }

  encoding_matrix <- contrasts(x, contrasts = FALSE)
  encoded_data <- encoding_matrix[as.integer(x)]

  colnames(encoded_data) <- paste0(colname_prefix, colnames(encoded_data), colname_suffix)

  encoded_data
}


df <- cbind(df, encode_onehot(df$subject, "subject_"))

This is fairly generic, has no dependencies on other libraries, and should be reasonably fast except on very large datasets.

like image 2
shadowtalker Avatar answered Oct 19 '22 02:10

shadowtalker


Here is a more generic solution using data.table library and caret

library(caret)
library(data.table)

dt <- data.table(
  group = c('A', 'A', 'A', 'A', 'A', 'B', 'C'),
  student = c('01', '01', '01', '02', '02', '01', '02'),
  exam_pass = c('Y', 'N', 'Y', 'N', 'Y', 'Y', 'N'),
  subject = c('Math', 'Science', 'Japanese', 'Math', 'Science', 'Japanese', 'Math')
)

vars <- 'subject'
separator <- '_'


bin_vars <- predict(dummyVars( as.formula(paste0("~",paste0(vars,collapse = "+"))),
                               data = dt, na.action = na.pass), newdata = dt)

colnames(bin_vars) <- paste0(gsub(vars,paste0(vars,separator),colnames(bin_vars)))

dt[,vars:=NULL]
dt <- cbind(dt,bin_vars)
like image 2
quant Avatar answered Oct 19 '22 02:10

quant