【问题标题】:Fill in values between start and end value in R填写R中开始值和结束值之间的值
【发布时间】:2021-04-19 04:34:11
【问题描述】:

我的 data.frame 中的 W(下面的蓝线)表示河流中的水位与海拔剖面相交的位置。

在我的data.frame中,对于ID中的每个组,我需要填写开始和结束值(W)之间的值

我的数据

> head(df, 23)
   ID elevation code
1   1       150 <NA>
2   1       140 <NA>
3   1       130    W
4   1       120 <NA>
5   1       110 <NA>
6   1       120 <NA>
7   1       130    W
8   1       140 <NA>
9   1       150 <NA>
10  2        90 <NA>
11  2        80 <NA>
12  2        70 <NA>
13  2        66    W
14  2        60 <NA>
15  2        50 <NA>
16  2        66    W
17  2        70 <NA>
18  2        72 <NA>
19  2        68    W
20  2        65 <NA>
21  2        60 <NA>
22  2        68    W
23  2        70 <NA>

我希望最终结果如下所示

   ID elevation code
1   1       150 <NA>
2   1       140 <NA>
3   1       130    W
4   1       120    W
5   1       110    W
6   1       120    W
7   1       130    W
8   1       140 <NA>
9   1       150 <NA>
10  2        90 <NA>
11  2        80 <NA>
12  2        70 <NA>
13  2        66    W
14  2        60    W
15  2        50    W
16  2        66    W
17  2        70 <NA>
18  2        72 <NA>
19  2        68    W
20  2        65    W
21  2        60    W
22  2        68    W
23  2        70 <NA>

我尝试了很多东西,但我的尝试都没有成功。您的帮助将不胜感激。

数据

> dput(df)
structure(list(ID = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 
2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L), elevation = c(150L, 
140L, 130L, 120L, 110L, 120L, 130L, 140L, 150L, 90L, 80L, 70L, 
66L, 60L, 50L, 66L, 70L, 72L, 68L, 65L, 60L, 68L, 70L), code = c(NA, 
NA, "W", NA, NA, NA, "W", NA, NA, NA, NA, NA, "W", NA, NA, "W", 
NA, NA, "W", NA, NA, "W", NA)), class = "data.frame", row.names = c(NA, 
-23L))

【问题讨论】:

  • 你能告诉我们你是如何决定开始和结束的吗?我的意思是在你的例子中,为什么第 7 行不能开始而第 13 行不能结束?
  • 第 3 行是 ID1 的开始,第 7 行是 ID1 的结束。对于 ID2,第 13 行是开始,第 16 行是结束。第 19 行是开始,第 22 行是结束。

标签: r


【解决方案1】:

你可以这样做:

df %>%
  group_by(ID)%>%
  mutate(code = coalesce(code, c(NA, "W")[cumsum(!is.na(code)) %% 2 + 1]))

   ID elevation code
1   1       150 <NA>
2   1       140 <NA>
3   1       130    W
4   1       120    W
5   1       110    W
6   1       120    W
7   1       130    W
8   1       140 <NA>
9   1       150 <NA>
10  2        90 <NA>
11  2        80 <NA>
12  2        70 <NA>
13  2        66    W
14  2        60    W
15  2        50    W
16  2        66    W
17  2        70 <NA>
18  2        72 <NA>
19  2        68    W
20  2        65    W
21  2        60    W
22  2        68    W
23  2        70 <NA>

【讨论】:

    【解决方案2】:

    我们可以试试replace + cumsum

    df %>%
      group_by(ID) %>%
      mutate(code = replace(code, cumsum(!is.na(code)) %% 2 == 1, "W")) %>%
      ungroup()
    

    给了

    # A tibble: 23 x 3
          ID elevation code
       <int>     <int> <chr>
     1     1       150 NA
     2     1       140 NA
     3     1       130 W
     4     1       120 W    
     5     1       110 W
     6     1       120 W
     7     1       130 W
     8     1       140 NA
     9     1       150 NA
    10     2        90 NA
    # ... with 13 more rows
    

    【讨论】:

      【解决方案3】:

      您可以创建一个辅助函数,在每个开始和结束之间创建一个序列并为其分配'W'

      assign_w <- function(code) {
        inds <- which(code == 'W')
        code[unlist(Map(seq, inds[c(TRUE, FALSE)], inds[c(FALSE, TRUE)]))] <- 'W'
        code
      }
      

      并为每个ID 应用它:

      library(dplyr)
      
      df %>%
        group_by(ID) %>%
        mutate(result = assign_w(code)) %>%
        ungroup
      
      #   ID elevation code result
      #1   1       150 <NA>   <NA>
      #2   1       140 <NA>   <NA>
      #3   1       130    W      W
      #4   1       120 <NA>      W
      #5   1       110 <NA>      W
      #6   1       120 <NA>      W
      #7   1       130    W      W
      #8   1       140 <NA>   <NA>
      #9   1       150 <NA>   <NA>
      #10  2        90 <NA>   <NA>
      #11  2        80 <NA>   <NA>
      #12  2        70 <NA>   <NA>
      #13  2        66    W      W
      #14  2        60 <NA>      W
      #15  2        50 <NA>      W
      #16  2        66    W      W
      #17  2        70 <NA>   <NA>
      #18  2        72 <NA>   <NA>
      #19  2        68    W      W
      #20  2        65 <NA>      W
      #21  2        60 <NA>      W
      #22  2        68    W      W
      #23  2        70 <NA>   <NA>
      

      【讨论】:

        【解决方案4】:
        library(dplyr)
        df %>%
          group_by(ID) %>%
          mutate(water_flag = (1 * !is.na(code)) * if_else(elevation < lag(elevation, default = 0), 1, -1),
                 water = if_else(cumsum(water_flag) == 1, "W", NA_character_))
        

        【讨论】:

          【解决方案5】:

          这个答案类似于@Onyambu 的:创建一个“索引”(ind),每当在“代码”列中遇到非 NA 时,该索引就会增加一。如果索引值可被 2 整除(即它是偶数),则将“NA”插入新列。如果索引不能被 2 整除,则在新列中添加“W”。然后,如果“代码”或“新”列中有“W”,请将“代码”列中的 NA 替换为 W,并从数据框中删除“新”列。

          df %>% 
            mutate(ind = ifelse(cumsum(!is.na(code)) %% 2 == 0, NA, "W")) %>% 
            mutate(code = ifelse(ind == "W" | code == "W", "W", NA)) %>% 
            select(-c(ind))
          
          #>   ID elevation code
          #>1   1       150 <NA>
          #>2   1       140 <NA>
          #>3   1       130    W
          #>4   1       120    W
          #>5   1       110    W
          #>6   1       120    W
          #>7   1       130    W
          #>8   1       140 <NA>
          #>9   1       150 <NA>
          #>10  2        90 <NA>
          #>11  2        80 <NA>
          #>12  2        70 <NA>
          #>13  2        66    W
          #>14  2        60    W
          #>15  2        50    W
          #>16  2        66    W
          #>17  2        70 <NA>
          #>18  2        72 <NA>
          #>19  2        68    W
          #>20  2        65    W
          #>21  2        60    W
          #>22  2        68    W
          #>23  2        70 <NA>
          

          【讨论】:

            【解决方案6】:

            首先我尝试使用fill,但没有成功。然后我在这里了解到 R 的回收属性Rename first and second occurence of the same specific value in a column iteratively 的好处(感谢 Ronak!)

            # prepare data with renaming `start` and `stop` sequence
            df$code[is.na(df$code)] <- "NA"
            df$code[df$code == 'W'] <- c('start', 'end')
            df$code[df$code=="NA"]<-NA
            
            # Now with different names of start and stop sequence I was able to implement `cumsum`
            library(tidyverse)
            
            df <- df %>% 
              group_by(grp = cumsum(!is.na(code))) %>% 
              dplyr::mutate(code = replace(code, first(code) == 'start', 'W'),
                            code = replace(code, code=='end', 'W')) %>% 
              ungroup() %>% 
              select(-grp) 
            

            输出:

            # A tibble: 23 x 3
                  ID elevation code 
               <int>     <int> <chr>
             1     1       150 NA   
             2     1       140 NA   
             3     1       130 W    
             4     1       120 W    
             5     1       110 W    
             6     1       120 W    
             7     1       130 W    
             8     1       140 NA   
             9     1       150 NA   
            10     2        90 NA   
            11     2        80 NA   
            12     2        70 NA   
            13     2        66 W    
            14     2        60 W    
            15     2        50 W    
            16     2        66 W    
            17     2        70 NA   
            18     2        72 NA   
            19     2        68 W    
            20     2        65 W    
            21     2        60 W    
            22     2        68 W    
            23     2        70 NA  
            

            【讨论】:

              【解决方案7】:

              虽然该问题已被标记为已解决(已接受答案)但供进一步/将来参考,但库 runner 中有一个函数 fill_run 正是这样做的。

              fill_run 替换 NA 值,如果它们被一对相同的值包围。由于我们的额外要求也是查看elevation,我们可以这样做

              df %>% group_by(ID) %>%
                mutate(code = runner::fill_run(ifelse(!is.na(code), paste(elevation,code), code), only_within = T))
              
              # A tibble: 23 x 3
              # Groups:   ID [2]
                    ID elevation code 
                 <int>     <int> <chr>
               1     1       150 NA   
               2     1       140 NA   
               3     1       130 130 W
               4     1       120 130 W
               5     1       110 130 W
               6     1       120 130 W
               7     1       130 130 W
               8     1       140 NA   
               9     1       150 NA   
              10     2        90 NA   
              # ... with 13 more rows
              

              不用说,如果需要,您可以非常轻松地再次将 mutate 非 NA 值从 code 转换为 W

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2013-01-11
                • 2022-01-03
                • 2022-12-12
                • 1970-01-01
                • 2021-11-30
                • 1970-01-01
                • 1970-01-01
                • 2016-08-12
                相关资源
                最近更新 更多