Настраиваемая зависящая от данных перекодировка в логику в R

У меня есть два фрейма данных, data и meta. Некоторые, но не все, столбцы в data являются логическими значениями, но они кодируются по-разному. Строки в meta описывают столбцы в data, указывают, должны ли они интерпретироваться как логические, и если да, то какое отдельное значение кодирует ИСТИНА, а какое отдельное значение кодирует ЛОЖЬ.

Мне нужна процедура, которая заменяет все значения data в концептуально логических столбцах соответствующими логическими значениями из кодов в соответствующей строке meta. Любые значения data в концептуально логическом столбце, которые не соответствуют значению в соответствующей строке meta, должны стать NA.

Пример маленькой игрушки для meta:

name                 type     false  true
-----------------------------------------
a.char.var           char     NA     NA
a.logical.var        logical  NA     7
another.logical.var  logical  1      0
another.char.var     char     NA     NA

Пример маленькой игрушки для data:

a.char.var  a.logical.var  another.logical.var  another.char.var
----------------------------------------------------------------
aa          7              0                    ba
ab          NA             1                    bb
ac          7              NA                   bc
ad          4              3                    bd

Вывод примера маленькой игрушки:

a.char.var  a.logical.var  another.logical.var  another.char.var
----------------------------------------------------------------
aa          TRUE           TRUE                 ba
ab          FALSE          FALSE                bb
ac          TRUE           NA                   bc
ad          NA             NA                   bd

Я не могу, на всю жизнь, найти способ сделать это в идиоматическом R, который обрабатывает все угловые случаи. Наборы данных большие, поэтому идиоматическое решение было бы идеальным, если это возможно. Я унаследовал этот абсолютно безумный бардак с управлением данными и буду благодарен всем, кто поможет это исправить. Я ни в коем случае не гуру R, но это кажется обманчиво сложной проблемой.


person rg6    schedule 01.12.2016    source источник


Ответы (2)


Сначала мы устанавливаем данные

meta <- data.frame(name=c('a.char.var', 'a.logical.var', 'another.logical.var', 'another.char.var'),
                   type=c('char', 'logical', 'logical', 'char'),
                   false=c(NA, NA, 1, NA),
                   true=c(NA, 7, 0, NA), stringsAsFactors = F)

data <- data.frame(a.char.var=c('aa', 'ab', 'ac', 'ad'),
                   a.logical.var=c(7, NA, 7, 4),
                   another.logical.var=c(0,1,NA,3),
                   another.char.var=c('ba', 'bb', 'bc', 'bd'), stringsAsFactors = F)

Затем мы выделяем только логические столбцы. Мы пройдемся по ним, используя столбец name, чтобы выбрать соответствующий столбец в data, и изменим значения в data_out с инициализированного NA на T или F в соответствии с соответствующими значениями в data.

Обратите внимание, что data[,logical_meta$name[1]] эквивалентно data[,'a.logical.var'] или data$a.logical.var, если logical_meta$name является символом. Если это фактор (например, если мы не указали stringsAsFactors=F), нам нужно преобразовать его в символ, после чего мы могли бы также дать ему имя - colname ниже.

Наличие NA для борьбы со средствами, использующими which, выгодно: c(0, 1,NA,3)==0 возвращает T,F,NA,F, но which затем игнорирует NA и возвращает только позицию 1. Подмножество логическим вектором, включающим NA, дает строки или столбцы NA, использование which устраняет это.

logical_meta <- meta[meta$type=='logical',]

data_out <- data #initialize


for(i in 1:nrow(logical_meta)) {
  colname <- as.character(logical_meta$name[i]) #only need as.character if factor
  data_out[,colname] <- NA
  #false column first
  if(is.na(logical_meta$false[i])) {
    data_out[is.na(data[,colname]),colname] <- FALSE
  } else {
    data_out[which(data[,colname]==logical_meta$false[i]),
             colname] <- FALSE
  }
  #true column next
  if(is.na(logical_meta$true[i])) {
    data_out[is.na(data[,colname]),colname] <- TRUE
  } else {
    data_out[which(data[,colname]==logical_meta$true[i]),
             colname] <- TRUE
  }
}

data_out
person ds440    schedule 01.12.2016
comment
Спасибо. Это довольно элегантно и легко для понимания. Итерация строки заставляет меня чувствовать себя немного грязным (вероятно, у меня не хватает опыта, когда нужно искать векторизацию в R). Возможно ли для этого осмысленно векторизованное решение? Есть ли причина, по которой data_out[is.na(data[,colname]),logical_meta$name[i]] <- TRUE не является data_out[is.na(data[,colname]),colname] <- TRUE, как для FALSE? Почему это необходимо в подмножестве? Я подожду еще немного, а затем соглашусь, если это останется лучшим. - person rg6; 01.12.2016
comment
Хороший улов, я менял logical_meta$name[i] на colname и пропустил это. - person ds440; 01.12.2016
comment
Если у вас нет тонн столбцов, это должно быть довольно быстро. Назначения векторизованы, по три на столбец (1. для NA, 2. установка всех «ложных» на F, 3. установка всех «истинных» на T) независимо от количества строк. - person ds440; 01.12.2016
comment
Только что видел. Кроме того, я не могу поверить, что не подумал сделать временную копию данных для этого, как это сделали вы. Это подвешивало меня со многими моими попытками. Отлично. Хорошее шоу! - person rg6; 01.12.2016

Я написал функцию, которая принимает индекс столбца data и пытается выполнить описанную вами операцию.

Функция сначала выбирает x в качестве интересующего нас столбца. Затем мы сопоставляем имя столбца в data с записями в первом столбце meta, это дает нашу интересующую строку.

Затем мы проверяем, является ли тип столбца logical, если это не так, мы просто возвращаем x, ничего менять не нужно. Если тип столбца logical, мы затем проверяем, соответствуют ли его значения столбцам true или false в meta.

convert_data <- function(colindex, dat, meta = meta){
    x <- dat[,colindex] #select our data vector

    #match the column name to the first column in meta
    find_in_meta <- match(names(dat)[colindex],
                          meta[,1])

    #what type of column is it
    type_col <- meta[find_in_meta,2]

    if(type_col != 'logical'){
        return(x)
    }else{
        #fix if logical is NA
        true_val <- ifelse(is.na(meta[find_in_meta,4]),'NA_val',
                           meta[find_in_meta,4])

        #fix if logical is NA
        false_val <- ifelse(is.na(meta[find_in_meta,3]), 'NA_val',
                            meta[find_in_meta, 3])

        #fix if logical is NA
        x <- ifelse(is.na(x), 'NA_val', x)
        x <- ifelse(x == true_val, TRUE,
               ifelse(x == false_val, FALSE, NA))
        return(x)
    }
}

Затем мы можем использовать lapply и немного поработать с данными, чтобы привести его в приемлемую форму:

res <- lapply(1:ncol(df1), function(ind) 
                      convert_data(colindex = ind, dat = df1, meta = meta))

setNames(do.call('cbind.data.frame', res), names(df1))

  a.char.var a.logical.var another.logical.var another.char.var
1         aa          TRUE                TRUE               ba
2         ab         FALSE               FALSE               bb
3         ac          TRUE                  NA               bc
4         ad            NA                  NA               bd

данные

meta <- structure(list(name = c("a.char.var", "a.logical.var", "another.logical.var", 
"another.char.var"), type = c("char", "logical", "logical", "char"
), false = c(NA, NA, 1L, NA), true = c(NA, 7L, 0L, NA)), .Names = c("name", 
"type", "false", "true"), class = "data.frame", row.names = c(NA, 
-4L))

df1 <- structure(list(a.char.var = c("aa", "ab", "ac", "ad"), a.logical.var = c(7L, 
NA, 7L, 4L), another.logical.var = c(0L, 1L, NA, 3L), another.char.var = c("ba", 
"bb", "bc", "bd")), .Names = c("a.char.var", "a.logical.var", 
"another.logical.var", "another.char.var"), class = "data.frame", row.names = c(NA, 
-4L))
person bouncyball    schedule 01.12.2016
comment
Возможно, не самое простое решение, но оно работает - я не вижу причин для отрицательного голоса... - person ds440; 01.12.2016
comment
Спасибо за ответ! На данный момент я предпочитаю ответ @ds440 за краткость и элегантность. - person rg6; 01.12.2016