【问题标题】:Update/Replace Values in Dataframe with Tidyverse Join使用 Tidyverse Join 更新/替换 Dataframe 中的值
【发布时间】:2019-01-05 10:26:04
【问题描述】:

用查找表中的(正确)值更新/替换主数据集中的 NA 的最有效方法是什么?这是很常见的操作!类似的问题似乎没有整齐的解决方案。

约束: 1)请假设比给出的示例有大量缺失值和更大的查找表。所以按大小写替换操作是不切实际的(没有case_whenif_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

```

reprex package (v0.2.0) 于 2018 年 7 月 28 日创建。

【问题讨论】:

  • 您可以只创建一个完整的查找表并进行合并(左连接)吗?
  • 如果查找表多于两列,则为更新连接,在 dplyr (for now) 中不存在。不过,基本功能是在 coalesce 中定义的,如果您不想指定大量列名,可以通过编程方式应用。
  • @Ronak:这个问题被标记为重复,虽然链接没有去整洁的解决方案。
  • @alistaire:你的建议,我想? left_join(df1, lookup_df, by = "state_abbrev") %&gt;% mutate(merged.col = coalesce(df.merged$state_name.x, state_name.y) ) %&gt;% select(state_abbrev, state_name= merged.col, value)
  • @alistaire: 立即将其放入 dplyr!

标签: r dplyr


【解决方案1】:

采纳Alistaire'sNettle's 的建议并转化为可行的解决方案

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

OP 已声明更喜欢“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 的 upate 加入总是更快(注意日志时间刻度)。

update join 修改数据对象时,每次运行基准测试都会使用一个新副本。

【讨论】:

    【解决方案2】:

    这是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
    

    【讨论】:

    • 喜欢这个!值得注意的是rows_update() 不起作用(但是,我希望!)如果键是重复的(比如 state_abbrev "CO" 的多行)。这些数据没有问题,但我必须为我的问题找到另一种解决方案。
    【解决方案3】:

    虽然there has been discussion 说明了如何实现这种行为,但目前没有尝试合并多个列的一次性方法(可以通过在ifelse(is.na(value), ..., value) 中使用a lookup table approach 来完成)。现在,您可以手动构建它。如果你有很多列,你可以通过编程方式coalesce,甚至put it in a function

    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
    

    【讨论】:

    • coalesce 方法是否仅适用于 NA 而不适用于空字符串等其他类型的缺失?
    • 正确。空字符串是 R 中的数据,例如整数值 0。如果需要,请使用 na_if 将它们转换为 NA。
    【解决方案4】:

    为了保持列顺序:

    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)
    

    【讨论】:

      【解决方案5】:

      如果缩写列完整,查找表也完整,你可以直接删除 state_name 列然后加入吗?

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

      另一种选择是在使用内置状态名称和缩写列表的mutate 调用中使用matchif_else

      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       
      

      【讨论】:

        猜你喜欢
        • 2020-06-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-28
        • 1970-01-01
        • 2021-09-12
        • 1970-01-01
        相关资源
        最近更新 更多