【问题标题】:R Generating values based on comparison of previous columnsR根据前列的比较生成值
【发布时间】:2018-10-20 06:53:42
【问题描述】:

我希望生成一个列 (Min) 来查找所选名称列的最小值,并将列的名称提取为其值。以下是示例数据框:

          Amy  Abe  Donna  Racheal  Mike     Min       u
          5    34    54     56       23      Amy       0
          43   11    3      33       21      Donna     1
          54   32    21     54       1       Mike      1 
          21   5     43     32       21      Abe       1
          32   21    23     5        32      Racheal   0
          43   2     2      13       45      Abe Donna 1
                            .
                            .
                            .

列 u 只是位于数据集末尾的列。数据集相当大,因此我试图找到一种有效的方法来生成列 Min。

我想到的代码:

     MinData <- Data %>% mutate(Min = 
     min(colnames(Data)[1:5]))

这仅提取列的名称。我应该添加什么来使列能够比较每一行中的值并选择具有最小值的列名?

【问题讨论】:

  • 如果出现平局会发生什么(Min 在 2 列或更多列中的值相同)?
  • @neilfws 我们能否将列名的组合提取为平局,例如“Amy Mike”作为单个值?

标签: r dplyr tidyverse data-cleaning


【解决方案1】:

您的原始数据:

df1 <- structure(list(Amy = c(5L, 43L, 54L, 21L, 32L, 43L), 
                      Abe = c(34L, 11L, 32L, 5L, 21L, 2L), 
                      Donna = c(54L, 3L, 21L, 43L, 23L, 2L), 
                      Racheal = c(56L, 33L, 54L, 32L, 5L, 13L), 
                      Mike = c(23L, 21L, 1L, 21L, 32L, 45L), 
                      u = c(0, 1, 1, 1, 0, 1)), 
                      row.names = c(NA, -6L), 
                      class = "data.frame")

我们可以使用tidyrdplyr 将宽转换为长,进行计算和聚合,最后将它们重新组合在一起。

library(dplyr)
library(tidyr)

df1 %>% 
  gather(name, value, -u) %>%                      # convert from wide to long
  group_by(name) %>% 
  mutate(idx = row_number()) %>%                   # add a grouping variable
  ungroup() %>% 
  group_by(idx) %>% 
  mutate(Min = min(value)) %>%                     # calculate min per group (= per row)
  filter(value == Min) %>%                         # keep names with value = Min
  arrange(idx) %>%                                 # order rows as original data
  select(idx, Min = name) %>% 
  summarise(Min = paste(Min, collapse = ",")) %>%  # combine names where Min tied
  ungroup() %>% 
  select(Min) %>% 
  bind_cols(df1, .)                                # combine column Min (names) with 
                                                   # original data

  Amy Abe Donna Racheal Mike u       Min
1   5  34    54      56   23 0       Amy
2  43  11     3      33   21 1     Donna
3  54  32    21      54    1 1      Mike
4  21   5    43      32   21 1       Abe
5  32  21    23       5   32 0   Racheal
6  43   2     2      13   45 1 Abe,Donna

【讨论】:

  • 与仅将函数应用于行相比,进行这种转换有什么优势吗?
  • 大多数事情都有利有弊。对于大型数据集,我猜这可能比逐行慢。另一方面,您可以证明整洁的长格式数据 - 每列一个变量(名称,值),每一行一个观察 - 在进行计算和后续操作方面具有优势。
【解决方案2】:

我会使用 apply 函数:)

设置我们的名称向量

person_names= names(df[,1:5]) #Presumably the column names are the names

1:5 只是在那里,以防您的数据集中有其他列您不想考虑进行最低检查。

现在我们可以在自定义函数上使用 apply ,该函数从每行具有最低值的列返回名称。

df$Min <- apply(df[,1:5], 1, function(x){person_names[which.min(x)]})

我们的自定义函数正如我已经描述的那样,apply 只是将函数应用于数据框或矩阵的每一列或每一行。第二个参数1 表示行,如果我们想要列,我们可以将其更改为2

which.min 只返回最小值所在位置的元素编号。 person_names 按顺序排列我们的名字,which.min 返回一个数字,表示哪个名字的值最小。

如果您想取消 person_names 变量,您可以将这一切压缩成一个单行解决方案。

df$Min <- apply(df[,1:5], 1, function(x){names(df[,1:5])[which.min(x)]})

如果您只有 5 个名称列,请删除 1:5,如果您在任何地方都有列,只需将其替换为您的列名称或编号的向量。

编辑:我看到了您对另一个答案的评论。为了适应平局,我将更改自定义函数,以便它检查具有最小值 x 的所有匹配项,然后将它们与一些自定义分隔符粘贴在一起。我还将修改您的数据,以便唐娜和雷切尔在第二行并列。

df <- read.table(text = 'Amy  Abe  Donna  Racheal  Mike     Min       u
      5    34    54     56       23      Amy       0
       43   11    3      3       21      Donna     1
       54   32    21     54       1       Mike      1 
       21   5     43     32       21      Abe       1
       32   21    23     5        32      Racheal   0', header = T)

person_names <- names(df[,1:5])

df$Min <- apply(df[,1:5], 1, function(x){paste(person_names[x == min(x)], 
collapse = ", ")})

> df
  Amy Abe Donna Racheal Mike            Min u
1   5  34    54      56   23            Amy 0
2  43  11     3       3   21 Donna, Racheal 1
3  54  32    21      54    1           Mike 1
4  21   5    43      32   21            Abe 1
5  32  21    23       5   32        Racheal 0

我已将collapse 参数设置为“,”,这是我任意选择的分隔符。您可以将其调整为空格“”、分号或任何您想要的内容。

同样,通过去掉person_names 的单独行,可以将其压缩为单行答案。

【讨论】:

  • 我们可以算平局吗?我已经为这种情况编辑了我的帖子。非常感谢您的详细解释。
  • 看到您对其他建议的答案发表评论并添加了一个“并列”版本:)
【解决方案3】:

以下是我的处理方法:

library(tidyverse) # we use dplyr and tidyr
Data <- Data %>% 
  mutate(row = 1:length(u)) 

MinData <- Data %>% 
  gather(name, score, -u, -row, -Min) %>% 
  group_by(row) %>%
  summarize(Min2 = paste(name[score == min(score)], collapse = " ")) %>% # called "Min2" to differentiate it from the "Min" column provided in the example.
  left_join(df %>% mutate(row = 1:length(u)), .)

【讨论】:

  • 我们可以算平局吗?在这种情况下,如果 Amy = Mike?,该值将是列名的组合,例如“Amy Mike”?
  • 它现在应该处理关系
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多