【问题标题】:Concatenate two columns of a dataframe in R if all values between them are NA?如果它们之间的所有值都是NA,则连接R中数据框的两列?
【发布时间】:2021-08-14 21:19:07
【问题描述】:

我有一个如下所示的数据框:

> sample
# A tibble: 6 x 10
  Level_1 Level_2 Level_3 Level_4 Level_5 Level_6 Level_7 Level_8 Level_9 Supplier 
    <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl> <lgl>   <chr>    
1       1       2       3       4       8      NA      NA      NA NA      orioles  
2       1       2       3       4       9      13      NA      NA NA      nationals
3       1       2       3       5      10      14      16      18 NA      dodgers  
4       1       2       3       5      10      14      17      19 NA      cardinals
5       1       2       3       6      11      NA      NA      NA NA      giants   
6       1       2       3       7      12      15      NA      NA NA      padres  

如果它们之间的所有值都是NA,我想做的是将供应商列与任何级别列连接起来。我想到的另一种方法是,如果 Level 列右侧的列是 NA,则将该列与供应商列连接。

我在考虑一个 for 循环,但我还没有弄清楚如何实现逻辑。我想的逻辑是这样的:

for (level in levels) {
   if is.na(level n + 1) {
     paste0(level, Supplier)
     }
   else {
    level}
   }

我也可以像这样打一堆mutate,但它似乎超级重复且不必要:

sample %>%
  mutate(
    Level_5 = ifelse(
      is.na(Level_6),
      paste0(Supplier, "<br>", Level_5),
      Level_5)
  )

这是数据的输出:

structure(list(Level_1 = c(1, 1, 1, 1, 1, 1), Level_2 = c(2, 
2, 2, 2, 2, 2), Level_3 = c(3, 3, 3, 3, 3, 3), Level_4 = c(4, 
4, 5, 5, 6, 7), Level_5 = c(8, 9, 10, 10, 11, 12), Level_6 = c(NA, 
13, 14, 14, NA, 15), Level_7 = c(NA, NA, 16, 17, NA, NA), Level_8 = c(NA, 
NA, 18, 19, NA, NA), Level_9 = c(NA, NA, NA, NA, NA, NA), Supplier = c("orioles", 
"nationals", "dodgers", "cardinals", "giants", "padres")), row.names = c(NA, 
-6L), class = c("tbl_df", "tbl", "data.frame"))

【问题讨论】:

  • 我想我明白了,但如果已经有一名员工来实施它,我将不胜感激

标签: r dataframe na


【解决方案1】:

说实话,我不能 100% 确定您想要的输出。使用dplyrtidyr

library(tidyr)
library(dplyr)

sample %>%
  pivot_longer(cols=starts_with("Level_"), names_prefix="Level_", names_to="level") %>%
  drop_na() %>%
  group_by(Supplier) %>%
  mutate(new_val=ifelse(level==max(level), paste0(Supplier, "<br>", value), value)) %>%
  select(-value) %>%
  pivot_wider(names_from=level, names_prefix="Level_", values_from=new_val)

返回

# A tibble: 6 x 9
# Groups:   Supplier [6]
  Supplier  Level_1 Level_2 Level_3 Level_4 Level_5      Level_6         Level_7 Level_8        
  <chr>     <chr>   <chr>   <chr>   <chr>   <chr>        <chr>           <chr>   <chr>          
1 orioles   1       2       3       4       orioles<br>8 NA              NA      NA             
2 nationals 1       2       3       4       9            nationals<br>13 NA      NA             
3 dodgers   1       2       3       5       10           14              16      dodgers<br>18  
4 cardinals 1       2       3       5       10           14              17      cardinals<br>19
5 giants    1       2       3       6       giants<br>11 NA              NA      NA             
6 padres    1       2       3       7       12           padres<br>15    NA      NA  

我丢失了 Level_9 列,因为它只包含 NA。您可以轻松地再次添加它。

【讨论】:

    【解决方案2】:

    另一种方法:

    library(tidyr)
    library(dplyr)
    
    df %>% mutate(across(contains('Level'), ~ as.character(.))) %>% 
       mutate(across(contains('Level'), ~ coalesce(., Supplier))) %>% select(-Supplier) %>% 
       mutate(ID = row_number()) %>% 
         pivot_longer(cols = -ID) %>% group_by(ID) %>% 
           mutate(value = case_when(duplicated(value) ~ NA_character_, TRUE ~ value)) %>% pivot_wider(names_from = name, values_from = value) %>% 
       ungroup() %>% select(-ID)
    # A tibble: 6 x 9
      Level_1 Level_2 Level_3 Level_4 Level_5 Level_6 Level_7   Level_8 Level_9  
      <chr>   <chr>   <chr>   <chr>   <chr>   <chr>   <chr>     <chr>   <chr>    
    1 1       2       3       4       8       orioles NA        NA      NA       
    2 1       2       3       4       9       13      nationals NA      NA       
    3 1       2       3       5       10      14      16        18      dodgers  
    4 1       2       3       5       10      14      17        19      cardinals
    5 1       2       3       6       11      giants  NA        NA      NA       
    6 1       2       3       7       12      15      padres    NA      NA       
    

    【讨论】:

    • 很有趣,但这会将其替换为供应商,而不是将其与供应商连接。
    【解决方案3】:

    非常有趣的问题。这是我没有pivot_longer的方法:

    library(dplyr)
    # check is na
    ind <- !is.na(df1)
    
    # save vector who fullfill assumption value before first NA
    values <- as.vector(tapply(df1[ind], row(df1)[ind], tail, 1))
    
    # bind to dataframe
    df2 <- cbind(df, values)
    
    # accomplish the task
    df2 %>% 
      mutate(across(Level_1:Level_9, ~ case_when(. == values ~ str_c(Supplier ,.),
                                                 . != values ~ as.character(.)))) %>% 
      select(-values)
    

    输出:

      Level_1 Level_2 Level_3 Level_4  Level_5     Level_6 Level_7     Level_8 Level_9  Supplier
    1       1       2       3       4 orioles8        <NA>    <NA>        <NA>    <NA>   orioles
    2       1       2       3       4        9 nationals13    <NA>        <NA>    <NA> nationals
    3       1       2       3       5       10          14      16   dodgers18    <NA>   dodgers
    4       1       2       3       5       10          14      17 cardinals19    <NA> cardinals
    5       1       2       3       6 giants11        <NA>    <NA>        <NA>    <NA>    giants
    6       1       2       3       7       12    padres15    <NA>        <NA>    <NA>    padres
    

    【讨论】:

      【解决方案4】:

      最终更新 我意识到我试图在每一行中找到最大值并将其替换为所需的连接字符串时的错误。所以我想出了另一个解决方案,它只替换最后一个非NA 值(它也可以不是该行的最大值),因为所有值都不是数字。所以这是我的最终解决方案:

      library(dplyr)
      library(stringr)
      library(purrr)
      
      df %>%
        pmap_dfr(., ~ {x <- c(...)[-10][!is.na(c(...)[-10])];
        ind <- which(c(...) == x[length(x)]);
        replace(c(...), ind[length(ind)], str_c(..10, x[length(x)], sep = "_"))}
        )
      
      # A tibble: 6 x 10
        Level_1 Level_2 Level_3 Level_4 Level_5   Level_6      Level_7 Level_8      Level_9 Supplier 
        <chr>   <chr>   <chr>   <chr>   <chr>     <chr>        <chr>   <chr>        <chr>   <chr>    
      1 1       2       3       4       orioles_8 NA           NA      NA           NA      orioles  
      2 1       2       3       4       9         nationals_13 NA      NA           NA      nationals
      3 1       2       3       5       10        14           16      dodgers_18   NA      dodgers  
      4 1       2       3       5       10        14           17      cardinals_19 NA      cardinals
      5 1       2       3       6       giants_11 NA           NA      NA           NA      giants   
      6 1       2       3       7       12        padres_15    NA      NA           NA      padres 
      

      【讨论】:

      • 所以我本可以在示例数据中指定这一点做得更好,但 level_9 列并不总是 NA,输入是字符而不是数字。
      • @BenG 我明白你的意思并更新了我的解决方案。我想这就是你一直在寻找的东西。再次抱歉,我一开始没有明白你的意思。
      【解决方案5】:

      结合c_acrossacross的方法

      library(tidyverse)
      df %>% rowwise() %>%
        mutate(dummy = max(which(!is.na(c_across(starts_with('Level')))))) %>% ungroup() %>%
        mutate(across(starts_with('Level_'), 
                      ~ifelse(as.numeric(str_remove(cur_column(), 'Level_')) == dummy, paste(Supplier, ., sep = '_'), .)))
      
      # A tibble: 6 x 11
        Level_1 Level_2 Level_3 Level_4 Level_5   Level_6      Level_7 Level_8      Level_9 Supplier  dummy
          <dbl>   <dbl>   <dbl>   <dbl> <chr>     <chr>          <dbl> <chr>        <lgl>   <chr>     <int>
      1       1       2       3       4 orioles_8 NA                NA NA           NA      orioles       5
      2       1       2       3       4 9         nationals_13      NA NA           NA      nationals     6
      3       1       2       3       5 10        14                16 dodgers_18   NA      dodgers       8
      4       1       2       3       5 10        14                17 cardinals_19 NA      cardinals     8
      5       1       2       3       6 giants_11 NA                NA NA           NA      giants        5
      6       1       2       3       7 12        padres_15         NA NA           NA      padres        6
      

      结合上面我朋友Anoushiravan的answer使用的which策略可以简化为:

      • purrr::pmap_dfr 里面做这些
        • 收集/存储列数到临时变量n
        • 收集/存储所需的索引到临时变量i
        • 创建一个长度为ntemp 变量,并在iF 其他地方有一个T
        • 使用replaceith 变量(使用temp)替换为所需值
      df %>%
        pmap_dfr(., ~ {n <- ncol(df); i <- max(which(!is.na(c(...)[-n])));
        tmp <- rep(F, n); tmp[i] <- T;
        replace(c(...), tmp, paste(c(...)[n], c(...)[i], sep = '_'))})
      
      # A tibble: 6 x 10
        Level_1 Level_2 Level_3 Level_4 Level_5   Level_6      Level_7 Level_8      Level_9 Supplier 
        <chr>   <chr>   <chr>   <chr>   <chr>     <chr>        <chr>   <chr>        <chr>   <chr>    
      1 1       2       3       4       orioles_8 NA           NA      NA           NA      orioles  
      2 1       2       3       4       9         nationals_13 NA      NA           NA      nationals
      3 1       2       3       5       10        14           16      dodgers_18   NA      dodgers  
      4 1       2       3       5       10        14           17      cardinals_19 NA      cardinals
      5 1       2       3       6       giants_11 NA           NA      NA           NA      giants   
      6 1       2       3       7       12        padres_15    NA      NA           NA      padres  
      

      【讨论】:

      • 超级有趣,几个没见过的函数
      • 非常好!在纳秒的范围内,这比我的快 10 倍!哈哈
      • @AnoushiravanR,您能否包括所有答案的基准?
      • Anil 检查我修改后的解决方案。解决方案可以更简化吗?!哈哈。我现在将发布基准测试。
      • 很有趣,但是您修改后的解决方案不起作用,但第一个解决方案起作用了。 rowise 的应用解决了很多我认为不会包含在示例数据集中的边缘情况,例如供应商与不同行上的不同级别相结合。
      猜你喜欢
      • 2022-01-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-16
      • 1970-01-01
      • 2019-07-15
      • 2021-02-21
      • 1970-01-01
      相关资源
      最近更新 更多