Обновление / замена значений в датафрейме с Tidyverse Join

Каков наиболее эффективный способ обновить / заменить NA в основном наборе данных (правильными) значениями в таблице поиска? Это такая обычная операция! Подобные вопросы, похоже, не дают аккуратных решений.

Ограничения: 1) Пожалуйста, примите большое количество пропущенных значений и таблицу поиска большего размера, чем приведенный пример. Таким образом, операции замены в каждом конкретном случае были бы непрактичными (нет case_when, if_else, так далее.)

2) Таблица поиска содержит не все значения основного информационного кадра, а только заменяющие.

Решение Tidyverse гораздо предпочтительнее. Подобные вопросы, похоже, не дают аккуратных решений.

library(tidyverse)

### Main Dataframe ###
df1 <- tibble(
  state_abbrev = state.abb[1:10],
  state_name = c(state.name[1:5], rep(NA, 3), state.name[9:10]),
  value = sample(500:1200, 10, replace=TRUE)
)


#> # A tibble: 10 x 3
#>    state_abbrev state_name value
#>    <chr>        <chr>      <int>
#>  1 AL           Alabama      551
#>  2 AK           Alaska       765
#>  3 AZ           Arizona      508
#>  4 AR           Arkansas     756
#>  5 CA           California   741
#>  6 CO           <NA>        1100
#>  7 CT           <NA>         719
#>  8 DE           <NA>         874
#>  9 FL           Florida      749
#> 10 GA           Georgia      580


### Lookup Dataframe ###
lookup_df <- tibble(
  state_abbrev = state.abb[6:8],
  state_name = state.name[6:8]
)

#> # A tibble: 3 x 2
#>   state_abbrev state_name 
#>   <chr>        <chr>      
#> 1 CO           Colorado   
#> 2 CT           Connecticut
#> 3 DE           Delaware

В идеале, у left_join должна быть опция замены для пропущенных значений. Увы...

left_join(df1, lookup_df)
#> Joining, by = c("state_abbrev", "state_name")
#> # A tibble: 10 x 3
#>    state_abbrev state_name value
#>    <chr>        <chr>      <int>
#>  1 AL           Alabama      551
#>  2 AK           Alaska       765
#>  3 AZ           Arizona      508
#>  4 AR           Arkansas     756
#>  5 CA           California   741
#>  6 CO           <NA>        1100
#>  7 CT           <NA>         719
#>  8 DE           <NA>         874
#>  9 FL           Florida      749
#> 10 GA           Georgia      580

`` `

Создано в 2018-07-28 пакетом представлением (v0.2.0).

5 ответов

Решение

Принятие предложений Алистера и Неттла и превращение в рабочее решение

df1 %>% 
  left_join(lookup_df, by = "state_abbrev") %>% 
  mutate(state_name = coalesce(state_name.x, state_name.y)) %>% 
  select(-state_name.x, -state_name.y)
# A tibble: 10 x 3
   state_abbrev value state_name 
   <chr>        <int> <chr>      
 1 AL             671 Alabama    
 2 AK             501 Alaska     
 3 AZ            1030 Arizona    
 4 AR             694 Arkansas   
 5 CA             881 California 
 6 CO             821 Colorado   
 7 CT             742 Connecticut
 8 DE             665 Delaware   
 9 FL             948 Florida    
10 GA             790 Georgia

ФП заявил, что предпочитает решение "Tidyverse". Тем не менее, объединения обновлений уже доступны с data.table пакет:

library(data.table)
setDT(df1)[setDT(lookup_df), on = "state_abbrev", state_name := i.state_name]
df1
    state_abbrev  state_name value
 1:           AL     Alabama  1103
 2:           AK      Alaska  1036
 3:           AZ     Arizona   811
 4:           AR    Arkansas   604
 5:           CA  California   868
 6:           CO    Colorado  1129
 7:           CT Connecticut   819
 8:           DE    Delaware  1194
 9:           FL     Florida   888
10:           GA     Georgia   501

эталонный тест

library(bench)
bm <- press(
  na_share = c(0.1, 0.5, 0.9),
  n_row = length(state.abb) * 2 * c(1, 100, 10000),
  {
    n_na <- na_share * length(state.abb)
    set.seed(1)
    na_idx <- sample(length(state.abb), n_na)
    tmp <- data.table(state_abbrev = state.abb, state_name = state.name)
    lookup_df <-tmp[na_idx] 
    tmp[na_idx, state_name := NA]
    df0 <- as_tibble(tmp[sample(length(state.abb), n_row, TRUE)])
    mark(
      dplyr = {
        df1 <- copy(df0)
        df1 <- df1 %>% 
          left_join(lookup_df, by = "state_abbrev") %>% 
          mutate(state_name = coalesce(state_name.x, state_name.y)) %>% 
          select(-state_name.x, -state_name.y)
        df1
      },
      upd_join = {
        df1 <- copy(df0)
        setDT(df1)[setDT(lookup_df), on = "state_abbrev", state_name := i.state_name]
        df1
      }
    )
  }

)
ggplot2::autoplot(bm)

введите описание изображения здесь

data.table's upate join всегда быстрее (обратите внимание на временную шкалу журнала).

Поскольку объединение обновлений изменяет объект данных, для каждого запуска теста используется свежая копия.

Вот однострочное решение с rows_update():

      df1 %>% 
  rows_update(lookup_df, by = "state_abbrev")

Демо:

      library(dplyr)

### Main Dataframe ###
df1 <- tibble(
  state_abbrev = state.abb[1:10],
  state_name = c(state.name[1:5], rep(NA, 3), state.name[9:10]),
  value = sample(500:1200, 10, replace=TRUE)
)

### Lookup Dataframe ###
lookup_df <- tibble(
  state_abbrev = state.abb[6:8],
  state_name = state.name[6:8]
)

df1 %>% 
  rows_update(lookup_df, by = "state_abbrev")
#> # A tibble: 10 x 3
#>    state_abbrev state_name  value
#>    <chr>        <chr>       <int>
#>  1 AL           Alabama       532
#>  2 AK           Alaska        640
#>  3 AZ           Arizona       521
#>  4 AR           Arkansas      523
#>  5 CA           California    970
#>  6 CO           Colorado      695
#>  7 CT           Connecticut   504
#>  8 DE           Delaware     1088
#>  9 FL           Florida       979
#> 10 GA           Georgia      1059

В настоящее время не существует единого способа попытаться объединить более одного столбца (что можно сделать, используя подход таблицы поиска в ifelse(is.na(value), ..., value)), хотя было обсуждение того, как такое поведение может быть реализовано. На данный момент вы можете построить его вручную. Если у вас много столбцов, вы можете coalesce программно, или даже поместить его в функцию.

library(tidyverse)

df1 <- tibble(
    state_abbrev = state.abb[1:10],
    state_name = c(state.name[1:5], rep(NA, 3), state.name[9:10]),
    value = sample(500:1200, 10, replace=TRUE)
)

lookup_df <- tibble(
    state_abbrev = state.abb[6:8],
    state_name = state.name[6:8]
)

df1 %>% 
    full_join(lookup_df, by = 'state_abbrev') %>% 
    bind_cols(map_dfc(grep('.x', names(.), value = TRUE), function(x){
        set_names(
            list(coalesce(.[[x]], .[[gsub('.x', '.y', x)]])), 
            gsub('.x', '', x)
        )
    })) %>% 
    select(union(names(df1), names(lookup_df)))
#> # A tibble: 10 x 3
#>    state_abbrev state_name  value
#>    <chr>        <chr>       <int>
#>  1 AL           Alabama       877
#>  2 AK           Alaska       1048
#>  3 AZ           Arizona       973
#>  4 AR           Arkansas      860
#>  5 CA           California    938
#>  6 CO           Colorado      639
#>  7 CT           Connecticut   547
#>  8 DE           Delaware      672
#>  9 FL           Florida       667
#> 10 GA           Georgia      1142

Чтобы сохранить порядок столбцов:

df1 %>% 
  left_join(lookup_df, by = "state_abbrev") %>% 
  mutate(state_name.x = coalesce(state_name.x, state_name.y)) %>% 
  rename(state_name = state_name.x) %>%
  select(-state_name.y)

Если столбец аббревиатур заполнен и таблица поиска заполнена, не могли бы вы просто отбросить столбец state_name и затем присоединиться?

left_join(df1 %>% select(-state_name), lookup_df, by = 'state_abbrev') %>% 
  select(state_abbrev, state_name, value)

Другим вариантом может быть использование match а также if_else в mutate вызов с использованием встроенных списков имен и сокращений состояний:

df1 %>% 
  mutate(state_name = if_else(is.na(state_name), state.name[match(state_abbrev,state.abb)], state_name))

Оба дают одинаковый вывод:

# A tibble: 10 x 3
   state_abbrev state_name  value
   <chr>        <chr>       <int>
 1 AL           Alabama       525
 2 AK           Alaska        719
 3 AZ           Arizona      1186
 4 AR           Arkansas     1051
 5 CA           California    888
 6 CO           Colorado      615
 7 CT           Connecticut   578
 8 DE           Delaware      894
 9 FL           Florida       536
10 GA           Georgia       599       
Другие вопросы по тегам