Не могу понять следующее поведение.
>ddd <- data.frame(a=c(2,3,4), b=c(10,20,30)) ## creating a simple dataframe with 2 columns
> ddd
a b
1 2 10
2 3 20
3 4 30
применение lapply()
дает ожидаемые результаты, как показано ниже:
> lapply(ddd, function(x) x*100 )
$a
[1] 200 300 400
$b
[1] 1000 2000 3000
Однако когда is.numeric()
используется внутри FUN, он применяется только к первой строке. Почему?
> lapply(ddd, function(x) ifelse( is.numeric(x), x*100, x ) )
$a
[1] 200
$b
[1] 1000
когда is.numeric()
каким-то образом используется вместе с is.na()
, он снова работает как обычно.
> lapply(ddd, function(x) ifelse( is.numeric(x) & !is.na(x), x*100, x ) )
$a
[1] 200 300 400
$b
[1] 1000 2000 3000
Почему это происходит?
lapply(data.frame(a=c(2,3,4), b=c(10,20,30)), \(x) ifelse(x[is.numeric(x)], x * 100, x))
?
Может быть, станет яснее, когда вы это сделаете lapply(ddd, \(x) is.numeric(x))
или даже is.numeric(c(1,2,3,4))
, а затем lapply(ddd, \(x) is.numeric(x) & !is.na(x))
почему есть разница. Вектор может быть только одного типа, в то время как каждое значение может быть NA или нет.
Проблема здесь в том, что is.numeric(x)
возвращает одно значение. Причина, по которой он работает с is.na()
, заключается в том, что is.na()
возвращает объект той же длины, что и входные данные. Когда вы используете их вместе, TRUE из is.numeric
восстанавливается до нужной длины.
> is.na(ddd$a)
[1] FALSE FALSE FALSE
> is.numeric(ddd$a)
[1] TRUE
> is.numeric(ddd$a) & !is.na(ddd$a)
[1] TRUE TRUE TRUE
Как упоминает @jay.sf в комментариях, ifelse()
возвращает результат той же длины, что и тестовый параметр. Таким образом, ваш код применяется только к первому значению каждого столбца.
Один из способов обойти это — заменить ifelse()
на if ( ) { } else { }
:
lapply(ddd, function(x) if (is.numeric(x)) {x*100} else {x} )
Спасибо. Так всегда ли лучше использовать if () {} else{} из-за такого поведения ifelse()? Или есть более чистый способ добиться того, чего я хотел?
Я думаю, это вопрос личных предпочтений. Другими вариантами этой задачи могут быть dplyr::mutate(ddd, across(where(is.numeric), \(x) x * 100))
, который возвращает фрейм данных вместо списка в качестве выходных данных, или ddd[sapply(ddd, is.numeric)] <- ddd[sapply(ddd, is.numeric)] * 100
, который изменяет существующий фрейм данных. Самый быстрый из них - тот, что в моем ответе. Версия dplyr
самая медленная.
Это объяснено в
?ifelse
вкл. примеры, вы читали?