Как изменить атрибуты переменной внутри фрейма данных внутри функции R

base::levels Файл справки https://stat.ethz.ch/R-manual/R-devel/library/base/html/levels.html содержит следующий пример изменения уровней переменной:

z <- gl(3, 2, 12, labels = c("apple", "salad", "orange"))
z
levels(z) <- c("fruit", "veg", "fruit")
z

Предположим, что этот материал находится внутри фрейма данных:

mydata <- data.frame(z=gl(3, 2, 12, labels = c("apple", "salad", "orange")), n=1:12)

Я хочу написать функцию, которая выполняет преобразование уровней, которое принимает фрейм данных и имя переменной в качестве входных данных:

modify_levels <- function(df,varname,from,to) {
  ### MAGIC HAPPENS
}

чтобы modify_levels(mydata,z,from=c("apple","orange"),to="fruit") делает часть преобразования (и modify_levels(mydata,z,from=c("salad","broccoli"),to="veg") делает вторую часть, хотя уровень broccoli может не существовать в моем наборе данных).

С некоторой нестандартной оценкой вуду я могу уменьшить то, что мне нужно изменить:

where_are_levels <- function(df,varname,from,to,verbose=FALSE) {
  # input checks
  if ( !is.data.frame(df) ) {
    stop("df is not a data frame")
  }
  if ( !is.factor(eval(substitute(varname),df)) ) {
    stop("df$varname is not a factor")
  }
  if (verbose==TRUE) {
    cat("df$varname is",
      paste0(substitute(df),"$",substitute(varname)))
    cat(" which evaluates to:\n")
    print( eval(substitute(varname),df) )
  }
  if (length(to)!=1) {
    stop("Substitution is ambiguous")
  }
  # figure out what the cases are with the supplied source values
  for (val in from) {
    r <- (eval(substitute(varname),df) == val)
    if (verbose==TRUE) {
      print(r)
      cat( paste0(substitute(df),"$",substitute(varname)),"==",val)
      cat(": ",sum(r), "case(s)\n")
    }
  }
}

Пока все хорошо (to опция ничего не делает, хотя):

> where_are_levels(mydata,z,from=c("apple","orange"),to="",verbose=TRUE)

## df$varname is mydata$z which evaluates to:
## [1] apple  apple  salad  salad  orange orange apple  apple  salad  salad  orange orange
## Levels: apple salad orange
## [1]  TRUE  TRUE FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE
## mydata$z == apple:  4 case(s)
## [1] FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE  TRUE  TRUE
## mydata$z == orange:  4 case(s)

Теперь, для следующего шага, я думаю, мне нужно добавить уровни целевой переменной с дополнительным уровнем и изменить значения этой переменной. В интерактивной работе я бы

# to <- "fruit" # passed as a function argument
l1 <- levels(mydata$z)
levels(mydata$z) <- union(l1,to)
mydata[r,"z"] <- to

из которых я могу получить только первую строку программно в пределах val цикл:

l1 <- levels(eval(substitute(varname),df))

что случилось бы внутри val цикл.

Обратите внимание, что я хочу сохранить существующие уровни яблок и апельсинов, а не просто изменить все вокруг (как это было сделано в примере с капитальным ремонтом в файле справки).

Если решение легче достичь с помощью dplyr программирование с нуля, это нормально для меня (хотя я понимаю, что NSE с ним еще более жесток в dplyr чем в базе R).

3 ответа

Нет необходимости во всех заменах, достаточно. Я сохраню все твои сообщения

where_are_levels <- function(df,varname,from,to,verbose=FALSE) {
  # input checks
  varname <- substitute(varname)

  if (!is.data.frame(df)) {
    stop("df is not a data frame")
  }
  if (!is.factor(df[[varname]])) {
    stop("df$varname is not a factor")
  }
  if (verbose) {
    cat("df$varname is", paste0(substitute(df),"$",varname))
    cat(" which evaluates to:\n")
    print(df[[varname]])
  }
  if (length(to) != 1) {
    stop("Substitution is ambiguous")
  }
  # figure out what the cases are with the supplied source values
  r <- df[[varname]] %in% from
  new_levels <- union(levels(df[[varname]]), to)
  df[[varname]] <- factor(df[[varname]], new_levels)
  df[[varname]] <- replace(df[[varname]], r, to)
  if (verbose) {
    print(r)
    cat( paste0(df[[varname]]),"==",from)
    cat(": ",sum(r), "case(s)\n")
  }
  return(df)
}
where_are_levels(mydata,z,from=c("apple","orange"),to="fruit")
       z  n
1  fruit  1
2  fruit  2
3  salad  3
4  salad  4
5  fruit  5
6  fruit  6
7  fruit  7
8  fruit  8
9  salad  9
10 salad 10
11 fruit 11
12 fruit 12

Я не вижу необходимости ни в нестандартной оценке, ни в какой-то магии приливов. Просто используйте обычные "[[" и levels<-

modify_levels <- function(dfrm, cname, from=NA,to=NA) { 
                          pos <- which( from %in% levels(dfrm[[cname]]) )
                         levels(dfrm[[cname]])[pos] <- to
                          dfrm[[cname]]}  # be sure to assign the result back

Использование:

> modify_levels(mydata,'z',from=c("salad","broccoli"),to="veg")
 [1] fruit fruit veg   veg   fruit fruit fruit fruit veg   veg   fruit fruit
Levels: fruit veg

Но нужно назначить результат:

> mydata$z <- modify_levels(mydata,'z',from=c("salad","broccoli"),to="veg")
> mydata
       z  n
1  fruit  1
2  fruit  2
3    veg  3
4    veg  4
5  fruit  5
6  fruit  6
7  fruit  7
8  fruit  8
9    veg  9
10   veg 10
11 fruit 11
12 fruit 12

Вы можете изменить свою функцию на это:

where_are_levels<-function(mydata,varname,from, to, additional){
  mydata[[varname]]<-plyr::mapvalues(mydata[[varname]], from = from, to = to)
  mydata[[varname]]<-factor(mydata[[varname]],levels=c(levels(mydata[[varname]]),additional))
  return(mydata)
}

пример:

varname="z"
from = c("apple", "salad","orange")
to = c("fruit", "veg", "fruit")
additional="Milk"    
a<-where_are_levels(mydata,varname,from, to, additional)
Другие вопросы по тегам