Прогнозирование с помощью step_naomit и сохранение идентификатора с помощью tidymodels

Я пытаюсь сохранить идентификатор в строке при прогнозировании с использованием модели случайного леса для обратного слияния с исходным фреймом данных. Я использую step_naomit в рецепте, который удаляет строки с недостающими данными, когда я запекаю обучающие данные, но также удаляет записи с недостающими данными из тестовых данных. К сожалению, у меня нет идентификатора, чтобы легко узнать, какие записи были удалены, поэтому я могу точно объединить прогнозы.

Я попытался добавить столбец идентификатора к исходным данным, но запекание удалит все переменные, не включенные в формулу (и я не хочу включать идентификатор в формулу). Я также подумал, что, возможно, смогу сохранить row.names из исходной таблицы для слияния, но похоже, что row.name также сбрасывается при выпечке.

Я понимаю, что могу удалить значения NA до рецепта, чтобы решить эту проблему, но в чем тогда смысл step_naomit в рецепте? Я также пробовал skip=TRUE в step_naomit, но затем при подборе модели получаю ошибку из-за отсутствия данных (только для случайного леса). Я чувствую, что в tidymodels мне не хватает чего-то, что позволило бы мне сохранить все строки перед выпечкой?

См. Пример:


## R 3.6.1 ON WINDOWS 10 MACHINE

require(tidyverse)
require(tidymodels)
require(ranger)

set.seed(123)

temp <- iris %>%
    dplyr::mutate(Petal.Width = case_when(
        round(Sepal.Width) %% 2 == 0 ~ NA_real_, ## INTRODUCE NA VALUES
        TRUE ~ Petal.Width))

mySplit <- rsample::initial_split(temp, prop = 0.8)

myRecipe <- function(dataFrame) {
    recipes::recipe(Petal.Width ~ ., data = dataFrame) %>%
        step_naomit(all_numeric()) %>%
        prep(data = dataFrame)
}

myPred <- function(mySplit,myRecipe) {

    train_set <- training(mySplit)
    test_set <- testing(mySplit)

    train_prep <- myRecipe(train_set)

    analysis_processed <- bake(train_prep, new_data = train_set)

    model <- rand_forest(
            mode = "regression",
            mtry = 3,
            trees = 50) %>%
        set_engine("ranger", importance = 'impurity') %>%
        fit(Sepal.Width ~ ., data=analysis_processed)

    test_processed <- bake(train_prep, new_data = test_set)

    test_processed %>%
        bind_cols(myPrediction = unlist(predict(model,new_data=test_processed))) 

}

getPredictions <- myPred(mySplit,myRecipe)

nrow(getPredictions)

##  21 ROWS

max(as.numeric(row.names(getPredictions)))

##  21

nrow(testing(mySplit))

##  29 ROWS

max(as.numeric(row.names(testing(mySplit))))

##  150

2 ответа

Решение

Чтобы иметь возможность отслеживать, какие наблюдения были удалены, нам нужно дать исходному набору данных id переменная.

temp <- iris %>%
    dplyr::mutate(Petal.Width = case_when(
        round(Sepal.Width) %% 2 == 0 ~ NA_real_, ## INTRODUCE NA VALUES
        TRUE ~ Petal.Width),
        id = row_number()) #<<<<

Затем мы используем update_role()чтобы сначала обозначить его как "переменную id", а затем удалить его как предиктор, чтобы он не стал частью процесса моделирования. Вот и все. Все остальное должно работать как раньше. Ниже приведен полностью обновленный код с #<<<< для обозначения моих изменений.

require(tidyverse)
#> Loading required package: tidyverse
require(tidymodels)
#> Loading required package: tidymodels
#> Registered S3 method overwritten by 'xts':
#>   method     from
#>   as.zoo.xts zoo
#> ── Attaching packages ───────────────────── tidymodels 0.0.3 ──
#> ✔ broom     0.5.2     ✔ recipes   0.1.7
#> ✔ dials     0.0.3     ✔ rsample   0.0.5
#> ✔ infer     0.5.0     ✔ yardstick 0.0.4
#> ✔ parsnip   0.0.4
#> ── Conflicts ──────────────────────── tidymodels_conflicts() ──
#> ✖ scales::discard() masks purrr::discard()
#> ✖ dplyr::filter()   masks stats::filter()
#> ✖ recipes::fixed()  masks stringr::fixed()
#> ✖ dplyr::lag()      masks stats::lag()
#> ✖ dials::margin()   masks ggplot2::margin()
#> ✖ dials::offset()   masks stats::offset()
#> ✖ yardstick::spec() masks readr::spec()
#> ✖ recipes::step()   masks stats::step()
require(ranger)
#> Loading required package: ranger

set.seed(1234)

temp <- iris %>%
    dplyr::mutate(Petal.Width = case_when(
        round(Sepal.Width) %% 2 == 0 ~ NA_real_, ## INTRODUCE NA VALUES
        TRUE ~ Petal.Width),
        id = row_number()) #<<<<

mySplit <- rsample::initial_split(temp, prop = 0.8)

myRecipe <- function(dataFrame) {
    recipes::recipe(Petal.Width ~ ., data = dataFrame) %>%
        update_role(id, new_role = "id variable") %>%  #<<<<
        update_role(-id, new_role = 'predictor') %>%   #<<<<
        step_naomit(all_numeric()) %>%
        prep(data = dataFrame)
}

myPred <- function(mySplit,myRecipe) {

    train_set <- training(mySplit)
    test_set <- testing(mySplit)

    train_prep <- myRecipe(train_set)

    analysis_processed <- bake(train_prep, new_data = train_set)

    model <- rand_forest(
            mode = "regression",
            mtry = 3,
            trees = 50) %>%
        set_engine("ranger", importance = 'impurity') %>%
        fit(Sepal.Width ~ ., data=analysis_processed)

    test_processed <- bake(train_prep, new_data = test_set)

    test_processed %>%
        bind_cols(myPrediction = unlist(predict(model,new_data=test_processed))) 

}

getPredictions <- myPred(mySplit, myRecipe)

getPredictions
#> # A tibble: 23 x 7
#>    Sepal.Length Sepal.Width Petal.Length Petal.Width Species     id myPrediction
#>           <dbl>       <dbl>        <dbl>       <dbl> <fct>    <int>        <dbl>
#>  1          4.6         3.1          1.5         0.2 setosa       4         3.24
#>  2          4.3         3            1.1         0.1 setosa      14         3.04
#>  3          5.1         3.4          1.5         0.2 setosa      40         3.22
#>  4          5.9         3            4.2         1.5 versico…    62         2.98
#>  5          6.7         3.1          4.4         1.4 versico…    66         2.92
#>  6          6           2.9          4.5         1.5 versico…    79         3.03
#>  7          5.7         2.6          3.5         1   versico…    80         2.79
#>  8          6           2.7          5.1         1.6 versico…    84         3.12
#>  9          5.8         2.6          4           1.2 versico…    93         2.79
#> 10          6.2         2.9          4.3         1.3 versico…    98         2.88
#> # … with 13 more rows

# removed ids
setdiff(testing(mySplit)$id, getPredictions$id)
#> [1]   5  28  47  70  90 132

Создано 26.11.2019 пакетом REPEX (v0.3.0)

С помощью skip = TRUE в step_naomit() спецификацию рецепта, а затем включение рецепта в workflowможет быть правильным решением. Например,

myRecipe <- recipe(Petal.Width ~ ., data = dataFrame) %>%
        step_naomit(all_numeric(), step = FALSE)`
# don't include the prep()

wflow <- workflow() %>% 
  add_model(model) %>% 
  add_recipe(myRecipe)

wflow_fit <- wflow %>% 
  fit(train_set)

preds <- predict(wflow_fit, new_data = (test_set))
Другие вопросы по тегам