R Отменить фиктивные переменные

У меня есть набор данных, в котором набор категориальных переменных был преобразован в фиктивные переменные (все используемые классы, НЕ n-1), а некоторые нет. Я пытаюсь перекодировать их в один столбец.

Например

Q1.1    Q1.2   Q1.3   Q1.NA    Q2    Q3.1   Q3.2
 1        0     0       0      3      0       1
 0        1     0       0      4      1       0
 0        0     1       0      2      0       1

Есть ли простой способ преобразовать это в:

Q1     Q2     Q3
1       3     2
2       4     1       
3       2     2

Прямо сейчас я просто использую strsplit() (поскольку все фиктивные имена переменных содержат '.') с парой циклов, но чувствую, что должен быть лучший способ. Какие-либо предложения?


person screechOwl    schedule 02.03.2015    source источник
comment
Должно ли Q3 быть 2, 1, 3   -  person akrun    schedule 02.03.2015
comment
@akrun: Не думайте - есть 3 вопроса, Q1 и Q3 фиктивны. Q1 имеет уровни 1,2,3; Q3 имеет уровни 1,2.   -  person screechOwl    schedule 02.03.2015
comment
Хорошо, я имел в виду положение 1   -  person akrun    schedule 02.03.2015


Ответы (3)


Некоторое время назад я написал функцию, которая делала подобные вещи.

MultChoiceCondense<-function(vars,indata){
  tempvar<-matrix(NaN,ncol=1,nrow=length(indata[,1]))
  dat<-indata[,vars]
  for (i in 1:length(vars)){
    for (j in 1:length(indata[,1])){
      if (dat[j,i]==1) tempvar[j]=i
    }
  }
  return(tempvar)
}

Если ваши данные называются Dat, то:

Dat$Q1<-MultChoiceCondense(c("Q1.1","Q1.2","Q1.3"),Dat)
person Andrew Taylor    schedule 02.03.2015

Вот подход, который использует melt из "reshape2" и cSplit из моего пакета "splitstackshape" вместе с некоторыми развлечениями "data.table". Я загрузил dplyr, чтобы мы могли передать все вещи.

library(splitstackshape)
library(reshape2)
library(dplyr)

mydf %>%
  as.data.table(keep.rownames = TRUE) %>%       # Convert to data.table. Keep rownames
  melt(id.vars = "rn", variable.name = "V") %>% # Melt the dataset by rownames
  .[value > 0] %>%                              # Subset for all non-zero values
  cSplit("V", ".") %>%                          # Split the "V" column (names) by "."
  .[is.na(V_2), V_2 := value] %>%               # Replace NA values with actual values
  dcast.data.table(rn ~ V_1, value.var = "V_2") # Go wide.
#    rn Q1 Q2 Q3
# 1:  1  1  3  2
# 2:  2  2  4  1
# 3:  3  3  2  2

Вот возможный базовый подход R:

## Which columns are binary?
Bins <- sapply(mydf, function(x) {
  all(x %in% c(0, 1))
})

## Two vectors -- part after the dot and before
X <- gsub(".*\\.(.*)$", "\\1", names(mydf)[Bins])
Y <- unique(gsub("(.*)\\..*$", "\\1", names(mydf)[Bins]))

## Use `apply` to subset the X value based on the 
## logical version of the binary variable
cbind(mydf[!Bins], 
      `colnames<-`(t(apply(mydf[Bins], 1, function(z) {
        X[as.logical(z)]
      })), Y))
#   Q2 Q1 Q3
# 1  3  1  2
# 2  4  2  1
# 3  2  3  2

В конце вы можете просто изменить порядок столбцов по мере необходимости. Вам также может понадобиться преобразовать их в числовые, так как в этом случае Q1 и Q3 будут факторами.

person A5C1D2H2I1M1N2O1R2T1    schedule 02.03.2015

другой базовый подход R

dat <- read.table(header = TRUE, text = "Q1.1    Q1.2   Q1.3   Q1.NA    Q2    Q3.1   Q3.2
 1        0     0       0      3      0       1
 0        1     0       0      4      1       0
 0        0     1       0      2      0       1")

## this will take all the unique questions; Q1, Q2, Q3; test if 
## they are dummies; and return the column if so or find which 
## dummy column is a 1 otherwise
res <- lapply(unique(gsub('\\..*', '', names(dat))), function(x) {
  tmp <- dat[, grep(x, names(dat)), drop = FALSE]
  if (ncol(tmp) == 1) unlist(tmp, use.names = FALSE) else max.col(tmp)
  })

# [[1]]
# [1] 1 2 3
# 
# [[2]]
# [1] 3 4 2
# 
# [[3]]
# [1] 2 1 2

do.call('cbind', res)
#      [,1] [,2] [,3]
# [1,]    1    3    2
# [2,]    2    4    1
# [3,]    3    2    2
person rawr    schedule 02.03.2015