【问题标题】:Sweep-like operations with dplyr/tidyverse使用 dplyr/tidyverse 进行类似扫描的操作
【发布时间】:2019-01-16 09:55:13
【问题描述】:

例如,我有兴趣将data.frametibble 的(几乎)所有列替换为从每行中减去行最小值的列。例如,如果X 是一个数值矩阵,那么在底数 R 中我会写:

X = sweep(X, 1, apply(X, 1, min))

我当前使用我拥有的数据执行此操作的函数-我将立即解释格式-将数字列拉出到矩阵中,进行扫描,然后cbinds 转换后的数据和非数值数据重新组合在一起。那就是:

subtractMin = function(data){
  X = data %>% 
    select(starts_with("X")) %>% 
    as.matrix()

  X = sweep(X, 1, apply(X, 1, min))

  labels = data %>% 
    select(-starts_with("X"))

  return(cbind(labels, X))
}

这让我觉得效率低下,必须有一个更聪明的方法。

我认为了解上下文并不重要,但我的数据有 77 行和 1133 列。其中四列包含标签信息,其余 1129 列包含每个观察值的数值测量值(如果你关心的话,它们是光谱)。数值变量的数量使得单个mutates 不是前进的方向。同样 - 您仍然需要知道行的最小值才能对每一行进行标准化。

我被要求添加一些数据。原始数据有1000多列,所以我会提供一个较小的数据集

> x.df
      nm X1799.38928 X1798.01526 X1796.64124 source color rep
1 s001c1   13901.944   13889.056   13883.334     01     c   1
2 s001c2   17293.586   17279.375   17291.365     01     c   2
3 s001c3    8011.764    8028.584    8033.548     01     c   3
4 s001c4    7499.272    7510.719    7517.064     01     c   4
5 s001c5   20300.408   20293.604   20297.185     01     c   5

【问题讨论】:

  • 只是一个想法,但是否有可能因为数据格式错误而导致数据操作很尴尬? data.frames 并不适合“水平”操作,尽管有一些矢量化函数(如 pmin)可以在您的情况下使用。这取决于您的上下文,但您最好将 1129 列放在一个矩阵中,其中行名作为当前 nm,旁边有一个 4 列元数据 data.frame/tibble。在矩阵上,您可以使用sweepapplymargin=1 等,因为这是矩阵的用途。

标签: r dplyr tidyverse


【解决方案1】:

我们可以使用pmin 来获取行最小值,然后使用mutate_at 来查找列与最小值之间的差异

library(tidyverse)
ins <- x.df %>%
            select(starts_with("X")) %>% 
            reduce(pmin)
x.df %>% 
      mutate_at(vars(starts_with("X")), funs(. - mins))
#  nm X1799.38928 X1798.01526 X1796.64124 source color rep
#1 s001c1      18.610       5.722       0.000      1     c   1
#2 s001c2      14.211       0.000      11.990      1     c   2
#3 s001c3       0.000      16.820      21.784      1     c   3
#4 s001c4       0.000      11.447      17.792      1     c   4
#5 s001c5       6.804       0.000       3.581      1     c   5

或将其组合成一条链

x.df %>% 
      mutate(mins = reduce(.[grepl("^X", names(.))], pmin)) %>% # get min by row
      mutate_at(vars(starts_with("X")), funs(. - mins)) %>% # take difference
      select(-mins) # remove the column mins

注意:pmin 最初发布在我们的帖子中

【讨论】:

    【解决方案2】:

    我知道你要求 tidyverse / dplyr 但如果你忽略了基础 R,这里有一个解决方案:

    ind <- !names(df) %in% c("nm","source","color","rep")
    df[ind] <- df[ind] - do.call(pmin, df[ind])
    df
    #       nm X1799.38928 X1798.01526 X1796.64124 source color rep
    # 1 s001c1      18.610       5.722       0.000      1     c   1
    # 2 s001c2      14.211       0.000      11.990      1     c   2
    # 3 s001c3       0.000      16.820      21.784      1     c   3
    # 4 s001c4       0.000      11.447      17.792      1     c   4
    # 5 s001c5       6.804       0.000       3.581      1     c   5
    

    而且我认为这可以作为tidyverse 解决方案(虽然不是很惯用):

    df %>% 
      split.default(!names(df) %in% c("nm","source","color","rep")) %>%
      map_at("TRUE", ~ .x - invoke(pmin,.x)) %>%
      bind_cols
    #       nm source color rep X1799.38928 X1798.01526 X1796.64124
    # 1 s001c1      1     c   1      18.610       5.722       0.000
    # 2 s001c2      1     c   2      14.211       0.000      11.990
    # 3 s001c3      1     c   3       0.000      16.820      21.784
    # 4 s001c4      1     c   4       0.000      11.447      17.792
    # 5 s001c5      1     c   5       6.804       0.000       3.581
    

    【讨论】:

      【解决方案3】:

      (对于它的价值,我认为这里的否决票有点苛刻和没有根据。问题陈述很清楚,并且示例数据已包含在编辑中。)

      您可以通过将数字列中的数据从宽转换为长(使用gather)、按行分组(使用group_by)、减去最小值(使用mutate)和转换从长到宽(使用spread)。

      library(tidyverse)
      df %>%
          gather(k, v, starts_with("X")) %>%
          group_by(nm) %>%
          mutate(v = v - min(v)) %>%
          spread(k, v) %>%
          select(names(df))
      ## A tibble: 5 x 7
      ## Groups:   nm [5]
      #  nm     X1799.38928 X1798.01526 X1796.64124 source color   rep
      #  <fct>        <dbl>       <dbl>       <dbl>  <int> <fct> <int>
      #1 s001c1       18.6         5.72        0.        1 c         1
      #2 s001c2       14.2         0.         12.0       1 c         2
      #3 s001c3        0.         16.8        21.8       1 c         3
      #4 s001c4        0.         11.4        17.8       1 c         4
      #5 s001c5        6.80        0.          3.58      1 c         5
      

      样本数据

      df <- read.table(text =
          "nm X1799.38928 X1798.01526 X1796.64124 source color rep
      1 s001c1   13901.944   13889.056   13883.334     01     c   1
      2 s001c2   17293.586   17279.375   17291.365     01     c   2
      3 s001c3    8011.764    8028.584    8033.548     01     c   3
      4 s001c4    7499.272    7510.719    7517.064     01     c   4
      5 s001c5   20300.408   20293.604   20297.185     01     c   5")
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-10-09
        • 1970-01-01
        • 2014-09-26
        • 2016-11-06
        • 2019-03-29
        • 2015-04-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多