【问题标题】:R: Last non-NA value among column setsR:列集中的最后一个非 NA 值
【发布时间】:2017-01-15 12:20:55
【问题描述】:

我正在寻找管道支持的以下问题的解决方案。

我的数据如下所示:

tibble(
  column_set_1_1 = c(1, 2, 3), column_set_1_2 = c(2, 3, NA), column_set_1_3 = c(3, NA, NA),
  column_set_2_1 = c(1, 2, 3), column_set_2_2 = c(4, 5, 6), column_set_2_3 = c(7, 8, 9), 
  column_set_2_4 = c(10, 11, NA), column_set_2_5 = c(13, NA, NA), column_set_2_6 = c(NA, NA, NA)
)

# A tibble: 3 × 9
  column_set_1_1 column_set_1_2 column_set_1_3 column_set_2_1 column_set_2_2 column_set_2_3 column_set_2_4 column_set_2_5 column_set_2_6
           <dbl>          <dbl>          <dbl>          <dbl>          <dbl>          <dbl>          <dbl>          <dbl>          <lgl>
1              1              2              3              1              4              7             10             13             NA
2              2              3             NA              2              5              8             11             NA             NA
3              3             NA             NA              3              6              9             NA             NA             NA

我基本上希望按列集获取最后一个非 NA 值。所以,预期的输出是:

tibble(
  column_set_1 = c(3, 3, 3), 
  column_set_2 = c(13, 11, 9)
)

# A tibble: 3 × 2
  column_set_1 column_set_2
         <dbl>        <dbl>
1            3           13
2            3           11
3            3            9

【问题讨论】:

    标签: r


    【解决方案1】:

    这是一种tidyverse 方法,无需重塑原始数据框,而是按列名模式将其分组,并使用coalesce 函数获取每个子数据框中的最后一个非NA值:

    library(tidyverse)
    df_foo %>% 
          mutate_all(as.numeric) %>% 
          split.default(f = sub("_\\d+$", "", names(.))) %>% 
          map_df(~do.call(coalesce, setNames(rev(.), NULL)))
    
    # A tibble: 3 × 2
    #  column_set_1 column_set_2
    #         <dbl>        <dbl>
    #1            3           13
    #2            3           11
    #3            3            9
    

    【讨论】:

    • 超过9个column_sets的情况下,列排序不正确的问题仍然存在。重新排序应该通过向管道添加一个命令来完成:select(colnames(.) %&gt;% str_extract("[0-9]+") %&gt;% as.integer() %&gt;% order())
    • 顺序将遵循每个子组中的原始列顺序,如果 OP 需要重新排序列以防万一,类似但str_extract("[0-9]+$") 可能是合适的。
    • df_foo 的末尾添加名称为c(NA, NA, NA) 的列column_set_10_1 后,您的解决方案的结果按column_set_1column_set_10column_set_2 的顺序显示列。可能与可能不同的语言环境有关吗?
    • 我不这么认为,这只是字符的比较方式。 "column_set_10" &lt; "column_set_2".
    【解决方案2】:

    这是我使用tidyverse 工具的解决方案:

    library(dplyr)
    library(tidyr)
    library(stringr)
    library(tibble)
    
    get_last_nonNA <- function(vec) {
      return(last(vec[!is.na(vec)]))
    }
    
    convert_table_last_nonNA <- . %>%
      rownames_to_column() %>%
      gather(key=column_type, value=value, -rowname) %>%
      mutate(column_set=str_extract(string=column_type,
                                    pattern="[0-9]+")) %>%
      group_by(column_set, rowname) %>%
      summarise(last_nonNA_value=get_last_nonNA(value)) %>%
      spread(key=column_set, value=last_nonNA_value) %>%
      select(-rowname) %>%
      select(colnames(.) %>% as.integer() %>% order()) %>%
      "colnames<-"(paste0("column_set_", colnames(.)))
    # Usage
    data_tbl <- tibble(
      column_set_1_1 = c(1, 2, 3), column_set_1_2 = c(2, 3, NA),
      column_set_1_3 = c(3, NA, NA), column_set_2_1 = c(1, 2, 3),
      column_set_2_2 = c(4, 5, 6), column_set_2_3 = c(7, 8, 9), 
      column_set_2_4 = c(10, 11, NA), column_set_2_5 = c(13, NA, NA),
      column_set_2_6 = c(NA, NA, NA)
    )
    
    convert_table_last_nonNA(data_tbl)
    
    # # A tibble: 3 × 2
    #   column_set_1 column_set_2
    # *        <dbl>        <dbl>
    # 1            3           13
    # 2            3           11
    # 3            3            9
    

    它的作用,一步一步:

    1. 使用convert_table_last_nonNA &lt;- . %&gt;% 创建可重复使用的管道;
    2. 使用rownames_to_column() 将行名称添加到单独的列中,以便获取用于提取每行最后一个非 NA 数据的信息;
    3. 使用gather(key=column_type, value=value, -rowname) 将输入表转换为长格式:行现在表示键列(rownamecolumn_type)和值(value)的组合;
    4. 通过正则表达式魔术计算列的集合编号(从column_type 字符串中提取第一个数字)并将其存储在单独的列column_set 中。这是通过mutate(column_set=str_extract(string=column_type, pattern="[0-9]+")) 完成的;
    5. group_by(column_set, rowname) %&gt;% summarise(last_nonNA_value=get_last_nonNA(value)) 以需要的方式汇总数据。那就是“对于column_setrowname 的每个组合,给出value 的最后一个非NA 值(通过get_last_nonNA 调用)并将其存储在last_nonNA_value 列中”。 注意:如果column_setrowname 的某种组合只有NA,则结果将为NA;
    6. spread(key=column_set, value=last_nonNA_value) 转换宽格式表格。现在column_set中的每一项都有一列,它们的值为last_nonNA_values;
    7. 删除列rowname,因为不再需要它;
    8. 按照 column_set number 的递增顺序对列重新排序。它是必需的,因为如果您的原始数据中有超过 9 个列集,那么排序列会有些混乱(即列 column_set_10 将直接放在 column_set_1 之后)。这是通过select(colnames(.) %&gt;% as.integer() %&gt;% order()) 完成的;
    9. 为具有"colnames&lt;-"(paste0("column_set_", colnames(.))) 的列名添加前缀column_set_

    【讨论】:

      【解决方案3】:

      这是我想出的一个适用于管道的解决方案:

      df_foo %>% 
        gather(key = Key, value = Value, -ID) %>% 
        mutate(set = str_extract(Key, "column_set_[0-9]")) %>% 
        mutate(number = str_extract(Key, "(?<=column_set_[0-9]_)[0-9]+")) %>% 
        group_by(ID, set) %>% 
        dplyr::filter(!is.na(Value)) %>%
        arrange(number) %>% 
        slice(n()) %>% 
        select(-number, -Key) %>% 
        spread(key = set, value = Value)
      

      我不喜欢我必须先arrange 然后slice 出最后一行——这对我来说似乎不雅。欢迎任何改进。

      【讨论】:

        猜你喜欢
        • 2021-08-03
        • 2022-01-03
        • 2015-01-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多