R:如何在给定不同数据帧(训练集)的值的情况下对一个数据帧(测试集)进行归一化

R: How to normalize one dataframe (test set) given the values of a different dataframe (training set)

我有一个代表测试集 T 的数据框,另一个代表训练集 D 的数据框。这两个数据集中的列与从同一数据框中提取的列完全相同。

我用下面的代码对训练集D进行归一化

MaxMinNormalize <- function(num) {
  if (is.factor(num)) num
  else ((num - min(num)) / (max(num) - min(num)))
}

D_n <- as.data.frame(lapply(D, MaxMinNormalize))

数据中的某些列是因子,其他列是数字,这就是规范化函数的原因。

我想在测试集 T 上应用这个归一化步骤,minmax 值取自训练集中的各个列,而不是测试集。我应该怎么做?

谢谢指点!


Edits:按照@coffeinjunky 的指示,尝试了以下代码来测试使用混合类型列(数字和因子)的能力:

df <- mtcars[,c("mpg", "cyl", "am", "gear")]

df$am <- as.factor(df$am)

df$gear <- as.factor(df$gear)

df1 <- df[1:16,]
df2 <- df[17:32,]

summary(df1)
summary(df2)

new_df <- data.frame(sapply(names(df1), function(col) {
  ifelse(is.factor(df2[[col]]), 
         df2[[col]],
         (df2[[col]]-min(df1[[col]]))/(max(df1[[col]])-min(df1[[col]]))) 

}))

head(new_df)
summary(new_df)

但结果很奇怪:函数也以某种方式存储在数据框中,并且列名丢失了。

> head(new_df)
     sapply.names.df1...function.col...
mpg                           0.3071429
cyl                           1.0000000
am                            1.0000000
gear                          1.0000000
> summary(new_df)
 sapply.names.df1...function.col...
 Min.   :0.3071                    
 1st Qu.:0.8268                    
 Median :1.0000                    
 Mean   :0.8268                    
 3rd Qu.:1.0000                    
 Max.   :1.0000    

我怀疑 ifelse 处理因子列破坏了数据的结构。

可能最简单的方法是使用预先存在的功能,因为它最方便。例如,在这里,我们可以使用 caret 包中提供的函数。

为了说明,让我们获取一些玩具数据:

# get some test data:
df <- mtcars[,c("mpg", "cyl")]
df1 <- df[1:16,]  # training data
df2 <- df[17:32,] # test data to be scaled

让我们看看我们期待什么。

summary(df1) # some output ommitted
      mpg            cyl     
 Min.   :10.4   Min.   :4.0  
 Max.   :24.4   Max.   :8.0  

summary(df2)
      mpg             cyl       
 Min.   :13.30   Min.   :4.000  
 Max.   :33.90   Max.   :8.000  

我们看到 df1 中的范围 (max - min) 对于 mpg 是 14,对于 cyl 是 4。如果我们查看最大值对于 df2,对于 mpg 是 33.9。从 df1 中减去最小值,即 10.4,然后除以 14,应该得到 23.5/14=1.6785。类似的数学运算适用于其他列和值。

现在,让我们使用 caret::preProcess 看看我们是否得到相同的值。

library(caret)
train_stats <- preProcess(df1, method = "range")
new_df1 <- predict(train_stats, df1)
new_df2 <- predict(train_stats, df2)

让我们首先检查一下 new_df1 是否按比例缩放到 0-1 范围,因为它应该是。

summary(new_df1)
# some output omitted:
      mpg              cyl       
 Min.   :0.0000   Min.   :0.000  
 Max.   :1.0000   Max.   :1.000  

现在让我们看看我们是否在测试集上得到了预期值:

summary(new_df2)
# some output omitted:
      mpg              cyl        
 Min.   :0.2071   Min.   :0.0000  
 Max.   :1.6786   Max.   :1.0000  

是的,看起来这很有效。

现在,只是为了展示如何实现这个 by hand,考虑到我们需要遍历每一列,进行操作,然后 return 新列。这通常可以使用 apply 系列的函数来实现。由于两个不同的数据框涉及相同的列名,因此迭代列名似乎是一个想法。例如,

sapply(names(df1), function(x) (...) )

将应用 function 并将 df1 中的每个列名作为参数。让我们按以下方式使用它:

df2[] <- sapply(names(df1), function(col) {
    if(is.factor(df2[[col]])) df2[[col]] else (df2[[col]]-min(df1[[col]]))/(max(df1[[col]])-min(df1[[col]]))})

让我们看看这是否给出了预期的结果:

summary(df2)
      mpg              cyl        
 Min.   :0.2071   Min.   :0.0000  
 Max.   :1.6786   Max.   :1.0000  

确实如此。