sapply + if - 保留列名

sapply + if - retain column names

尽管它与 sapply - retain column 个名字有关,但我在那里找不到答案...

我有一个简单的函数可以在 0 和 1 之间缩放数据,并保留列名:

scale <-  function(x){apply(x, 2, function(y) ((y)-min(y, na.rm=TRUE))/(max(y, na.rm=TRUE)-min(y, na.rm=TRUE)))}

现在我需要为 max(y) = min(y) 的情况添加一个 if 子句并像这样更改函数:

scale <- function(x){apply(x, 2, function(y) if(min(y, na.rm=TRUE)==max(y, na.rm=TRUE)) {0.5} else {((y)-min(y, na.rm=TRUE))/(max(y, na.rm=TRUE)-min(y, na.rm=TRUE))})}

像这样在输入数据帧上使用这些函数...

as.data.frame(scale(input[sapply(input,is.numeric)]))

生成不同的列名,其中原始函数保留名称,新函数以括号或连字符替换为点的方式修改它们:

示例列名称 w/o IF: INL_Avg(S-B0-ETC-CDS-06C~PM_CD1_D_B0_SI_P0V_B.NM)

带有 IF 的示例列名称: INL_Avg.S.B0.ETC.CDS.06C.PM_CD1_D_B0_SI_P0V_B.NM.

虽然我确实意识到这些列名称并不理想,但它是我需要使用的名称,我希望得到有关如何避免这种特殊字符替换的提示(将 USE.NAMES=TRUE 添加到 sapply won帮忙...)。

谢谢,马克

找到解决方案

as.data.frame(scale(input[sapply(input,is.numeric)]),check.names = FALSE)

问题的根源在于您在数据框上使用 applyapply 是为处理矩阵而构建的,所以它做的第一件事就是将你的数据框转换为矩阵,这是不必要的,然后当你转换回来时默认的数据框方法“修复”列名你不喜欢的方式。您可以通过将 check.names = FALSE 添加到 as.data.frame() 调用来解决此问题,但更好的方法是在数据框上使用 lapply,在矩阵上使用 apply,甚至如果我们给它一个向量输入,它就可以工作了。

我还强烈建议不要 用类似但不同的函数覆盖内置的scale 函数。这很容易导致错误。我重写了你的函数,调用它 scale01() 以明确区分。

我也修改了,如果输入是常量向量,有缺失值,只用0.5填充非缺失值,这样看起来更安全

我使用 S3 分派基于输入 class 适当地工作,该方法建立在适用于数字向量的 default 方法之上。在这里,在向量 data.frame 和矩阵输入上进行了演示:

## defining the functions
scale01 = function(x, ...) {
  UseMethod("scale01")
}

scale01.numeric = function(x, ...) {
  minx = min(x, na.rm = TRUE)
  maxx = max(x, na.rm = TRUE)
  if(minx == maxx) {
    x[!is.na(x)] = 0.5
    return(x)
  }
  (x - minx) / (maxx - minx)
}

scale01.data.frame = function(x, ...) {
  x[] = lapply(x, scale01)
  x
}

scale01.matrix = function(x, ...) {
  apply(x, MARGIN = 2, FUN = scale01)
}
## demonstrating usage

scale01(rnorm(5))
# [1] 0.0000000 1.0000000 0.4198958 0.6104154 0.2108150

scale01(mtcars[1:5, ])
#                 mpg cyl      disp        hp       drat        wt      qsec vs am gear      carb
# Mazda RX4         0.5609756 0.5 0.2063492 0.2073171 1.00000000 0.2678571 0.0000000  0  1    1 1.0000000
# Mazda RX4 Wag     0.5609756 0.5 0.2063492 0.2073171 1.00000000 0.4955357 0.1879195  0  1    1 1.0000000
# Datsun 710        1.0000000 0.0 0.0000000 0.0000000 0.93902439 0.0000000 0.7214765  1  1    1 0.0000000
# Hornet 4 Drive    0.6585366 0.5 0.5952381 0.2073171 0.00000000 0.7991071 1.0000000  1  0    0 0.0000000
# Hornet Sportabout 0.0000000 1.0 1.0000000 1.0000000 0.08536585 1.0000000 0.1879195  0  0    0 0.3333333

scale01(as.matrix(mtcars[1:5, ]))
#                         mpg cyl      disp        hp       drat        wt      qsec vs am gear      carb
# Mazda RX4         0.5609756 0.5 0.2063492 0.2073171 1.00000000 0.2678571 0.0000000  0  1    1 1.0000000
# Mazda RX4 Wag     0.5609756 0.5 0.2063492 0.2073171 1.00000000 0.4955357 0.1879195  0  1    1 1.0000000
# Datsun 710        1.0000000 0.0 0.0000000 0.0000000 0.93902439 0.0000000 0.7214765  1  1    1 0.0000000
# Hornet 4 Drive    0.6585366 0.5 0.5952381 0.2073171 0.00000000 0.7991071 1.0000000  1  0    0 0.0000000
# Hornet Sportabout 0.0000000 1.0 1.0000000 1.0000000 0.08536585 1.0000000 0.1879195  0  0    0 0.3333333

weird_name_df = data.frame(`weird column` = rnorm(5), `INL_Avg(S-B0-ETC-CDS-06C~PM_CD1_D_B0_SI_P0V_B.NM)` = rnorm(5), check.names = FALSE)
scale01(weird_name_df)
#   weird column INL_Avg(S-B0-ETC-CDS-06C~PM_CD1_D_B0_SI_P0V_B.NM)
# 1    0.6135744                                         0.2237905
# 2    0.0000000                                         0.4086837
# 3    1.0000000                                         1.0000000
# 4    0.7061441                                         0.2803262
# 5    0.7693184                                         0.0000000

如果要转换数据框的所有数字列,我建议:

## base version
numeric_cols = sapply(your_data, is.numeric)
your_data[numeric_cols] = scale01(your_data[numeric_cols])

## dplyr version
library(dplyr)
your_data %>%
  mutate(across(where(is.numeric), scale01))