Как изменить атрибуты переменной внутри фрейма данных внутри функции 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)