#r #dataframe #dplyr #training-data #standardized
#r #dataframe #dplyr #обучение-данные #стандартизированный
Вопрос:
Я выбираю разделение обучения / тестирования на 90/10 с некоторыми данными в R. После того, как у меня будет обучающий набор. Я хотел бы его стандартизировать. Затем я хотел бы использовать то же среднее значение и стандартное отклонение, которые использовались в обучающем наборе, и применить эту стандартизацию к тестовому набору.
Я хотел бы сделать это максимально простым способом, но с dplyr
решением тоже все в порядке. Обратите внимание, что у меня есть столбцы, которые являются factors/chr
и numeric
. Конечно, сначала мне нужно выбрать числовые.
Моя первая настройка приведена ниже с воспроизводимым примером кода. У меня есть средние значения и стандартные отклонения для соответствующих числовых столбцов, теперь, как я могу применить стандартизацию к конкретным столбцам в обучающих и тестовых данных?
library(tidyverse)
rm(list = ls())
x <- data.frame("hame" = c("Bob", "Roberta", "Brady", "Jen", "Omar", "Phillip", "Natalie", "Aaron", "Annie", "Jeff"),
"age" = c(60, 55, 25, 30, 35, 40, 47, 32, 34,67),
"income" = c(50000, 60000, 100000, 90000, 100000, 95000, 75000, 85000, 95000, 105000))
train_split_pct = 0.90
train_size <- ceiling(nrow(x)*train_split_pct) # num of rows for training set
test_size <- nrow(x) - train_size # num of rows for testing set
set.seed(123)
ix <- sample(1:nrow(x)) # shuffle
x_new = x[ix, ]
Train_set = x_new[1:train_size, ]
Test_set = x_new[(train_size 1):(train_size test_size), ]
Train_mask <- Train_set %>% select_if(is.numeric)
Train_means <- Train_mask %>% apply(2, mean)
Train_stddevs <- Train_mask %>% apply(2, sd)
Ответ №1:
Мы можем сделать это в сжатом виде. Получите mean
, sd
из набора данных ‘Train’ (‘mean_sd’). Обратите внимание, что с dplyr
версией> = 1.0 summarise
может возвращать более одной строки. Итак, используйте эту функцию для создания двухстрочного набора данных — первая строка => среднее значение, вторая строка => sd
library(dplyr) # >= 1.0.0
library(purrr)
mean_sd <- Train_set %>%
summarise(across(where(is.numeric), ~ c(mean(., na.rm = TRUE),
sd(., na.rm = TRUE))))
Затем создайте функцию (‘f1’) для выполнения стандартизации.
f1 <- function(x, y) (x -y[1])/y[2]
Цикл по list
набору данных ‘Train’, ‘Test’, используется map2
для перебора соответствующих столбцов на основе набора данных ‘mean_sd’, примените f1
и назначьте этот вывод столбцам. Затем, с list2env
помощью, мы можем обновлять одни и те же объекты в глобальной среде
list2env(map(lst(Train_set, Test_set), ~ {
.x[names(mean_sd)] <- map2(select(.x, names(mean_sd)), mean_sd, f1)
.x}), .GlobalEnv)
-вывод
Train_set
# hame age income
#3 Brady -1.3286522 0.7745967
#10 Jeff 1.6256451 1.0327956
#2 Roberta 0.7815601 -1.2909944
#8 Aaron -0.8362693 0.0000000
#6 Phillip -0.2735460 0.5163978
#9 Annie -0.6955885 0.5163978
#1 Bob 1.1332622 -1.8073922
#7 Natalie 0.2188368 -0.5163978
#5 Omar -0.6252481 0.7745967
Test_set
# hame age income
#4 Jen -0.9769502 0.2581989
Комментарии:
1. Не могли бы вы обсудить
~
,summarise
пожалуйста?2. @Coldchain9 это просто лямбда-функция, т.Е.
function(x)
Сокращение в синтаксисе tidyverse
Ответ №2:
Рассмотрите это как вариант. Вы можете использовать scale()
функцию, которая позволяет вам нормализовать ваши переменные. В конце вы можете найти код. Кроме того, вы можете использовать mutate_if()
для выбора числовых переменных и избежать создания других фреймов данных. Вот код, с помощью dplyr
которого я создал два новых фрейма данных с требуемыми значениями:
library(tidyverse)
rm(list = ls())
x <- data.frame("hame" = c("Bob", "Roberta", "Brady", "Jen", "Omar", "Phillip", "Natalie", "Aaron", "Annie", "Jeff"),
"age" = c(60, 55, 25, 30, 35, 40, 47, 32, 34,67),
"income" = c(50000, 60000, 100000, 90000, 100000, 95000, 75000, 85000, 95000, 105000))
train_split_pct = 0.90
train.size <- ceiling(nrow(x)*train_split_pct) # num of rows for training set
test.size <- nrow(x) - train.size # num of rows for testing set
set.seed(123)
ix <- sample(1:nrow(x)) # shuffle
x_new = x[ix, ]
Train.set = x_new[1:train.size, ]
Test.set = x_new[(train.size 1):(train.size test.size), ]
#Normalize
Train.set2 <- Train.set %>%
mutate_if(is.numeric, scale)
Test.set2 <- Test.set %>%
mutate_if(is.numeric, scale)
Обновление: если scale()
это не работает, вы можете попробовать изменить форму данных и объединить их с вычисленными значениями для среднего и SD:
#Define indexes for numeric vars
index.train <- which(names(Train.set)%in% names(Train_means))
#Format means and sd to merge
Train2 <- Train.set %>%
mutate(id=row_number()) %>%
pivot_longer(cols=index.train) %>%
left_join(
Train_means %>% t() %>%data.frame %>%
pivot_longer(everything()) %>%
rename(Mean=value) %>%
left_join(Train_stddevs %>% t() %>%data.frame %>%
pivot_longer(everything()) %>%
rename(SD=value))
) %>%
#Compute standard values
mutate(SValue=(value-Mean)/SD) %>%
select(-c(value,Mean,SD)) %>%
pivot_wider(names_from = name,values_from=SValue) %>% select(-id)
Вывод:
# A tibble: 9 x 3
hame age income
<fct> <dbl> <dbl>
1 Brady -1.33 0.775
2 Jeff 1.63 1.03
3 Roberta 0.782 -1.29
4 Aaron -0.836 0
5 Phillip -0.274 0.516
6 Annie -0.696 0.516
7 Bob 1.13 -1.81
8 Natalie 0.219 -0.516
9 Omar -0.625 0.775
И для тестового набора процесс аналогичен:
#Define indexes
index.test <- which(names(Test.set)%in% names(Train_means))
#Format means and sd 2
Test2 <- Test.set %>%
mutate(id=row_number()) %>%
pivot_longer(cols=index.test) %>%
left_join(
Train_means %>% t() %>%data.frame %>%
pivot_longer(everything()) %>%
rename(Mean=value) %>%
left_join(Train_stddevs %>% t() %>%data.frame %>%
pivot_longer(everything()) %>%
rename(SD=value))
) %>%
#Compute standard values
mutate(SValue=(value-Mean)/SD) %>%
select(-c(value,Mean,SD)) %>%
pivot_wider(names_from = name,values_from=SValue) %>% select(-id)
Вывод:
# A tibble: 1 x 3
hame age income
<fct> <dbl> <dbl>
1 Jen -0.977 0.258
Ключевым моментом является объединение значений после изменения формы. В качестве доказательства я покажу промежуточный шаг для окончательного набора данных. Это выглядит так:
# A tibble: 2 x 7
hame id name value Mean SD SValue
<fct> <int> <chr> <dbl> <dbl> <dbl> <dbl>
1 Jen 1 age 30 43.9 14.2 -0.977
2 Jen 1 income 90000 85000 19365. 0.258
Таким образом легко вычислить требуемые стандартные значения.
Комментарии:
1. Я пробовал этот метод ранее, но он не достиг желаемой цели. Я хочу применить ту же
mean/sd
выборочную статистику, которую я получаю при масштабированииTraining set
, и применить их кTesting set
. Например. мойmean
возраст составляет 43,89 дляTrain_set
, аsd
возраст — 14,22 дляTrain_set
. Я хочу использовать их для стандартизацииTest_set
возраста и так далее.2. @Coldchain9 Есть способ, но более длинный, позвольте мне добавить его!
3. @Coldchain9 я добавил обновление для вас. Я надеюсь, что это может быть полезным и полезным для вас!
4. Я вижу, что это работает, но это довольно сложно и не просто.
Ответ №3:
Итак, после просмотра предыдущих ответов, которые работали нормально, я обнаружил, что они немного непонятны в использовании и не интуитивно понятны. Я достиг желаемого результата с помощью цикла for. Хотя это немного элементарно, я считаю, что это более понятный подход. Учитывая вариант использования, когда у меня не так много столбцов, я не вижу серьезной проблемы в этом решении, если только не было много столбцов данных для просмотра. В этом случае мне понадобится помощь в поиске более быстрого решения.
Несмотря на это, мой метод заключается в следующем. Я собираю все имена столбцов в моем Train_mask
, которые являются только числовыми столбцами. Затем я перебираю каждое из имен и соответствующим образом обновляю значения с помощью стандартизации из их соответствующих Train_means
и Train_stddevs
.
Из-за того, как я создаю свои обучающие и тестовые наборы, не должно быть никаких проблем с порядком моих фреймов столбцов, и их можно использовать последовательно следующим образом.
library(tidyverse)
rm(list = ls())
x <- data.frame("name" = c("Bob", "Roberta", "Brady", "Jen", "Omar", "Phillip", "Natalie", "Aaron", "Annie", "Jeff"),
"age" = c(60, 55, 25, 30, 35, 40, 47, 32, 34,67),
"income" = c(50000, 60000, 100000, 90000, 100000, 95000, 75000, 85000, 95000, 105000))
train_split_pct = 0.90
train_size <- ceiling(nrow(x)*train_split_pct) # num of rows for training set
test_size <- nrow(x) - train_size # num of rows for testing set
set.seed(123)
ix <- sample(1:nrow(x)) # shuffle
x_new = x[ix, ]
Train_set = x_new[1:train_size, ]
Test_set = x_new[(train_size 1):(train_size test_size), ]
Train_mask <- Train_set %>% select_if(is.numeric)
Train_means <- data.frame(as.list(Train_mask %>% apply(2, mean)))
Train_stddevs <- data.frame(as.list(Train_mask %>% apply(2, sd)))
col_names <- names(Train_mask)
for (i in 1:ncol(Train_mask)){
Train_set[,col_names[i]] <- (Train_set[,col_names[i]] - Train_means[,col_names[i]])/Train_stddevs[,col_names[i]]
Test_set[,col_names[i]] <- (Test_set[,col_names[i]] - Train_means[,col_names[i]])/Train_stddevs[,col_names[i]]
}
Train_set
Test_set
Вывод:
> Train_set
name age income
3 Brady -3.180620 0.7745967
10 Jeff -2.972814 1.0327956
2 Roberta -3.032187 -1.2909944
8 Aaron -3.145986 0.0000000
6 Phillip -3.106404 0.5163978
9 Annie -3.136090 0.5163978
1 Bob -3.007448 -1.8073922
7 Natalie -3.071769 -0.5163978
5 Omar -3.131143 0.7745967
> Test_set
name age income
4 Jen -0.9769502 0.2581989