【问题标题】:Normalizing columns in mixed numeric/non-numeric DataFrame with tidyverse (dplyr)?使用 tidyverse (dplyr) 对混合数字/非数字 DataFrame 中的列进行规范化?
【发布时间】:2020-02-25 03:37:06
【问题描述】:

我经常需要对混合了数字列和非数字列的 DataFrame 列进行规范化。有时我知道数字列的名称,有时我不知道。

我尝试了在我看来非常合乎逻辑的整洁评估方法。大多数都不起作用。我只找到了一个。

为了更好地理解整洁的评估,我能否解释一下为什么以下工作或不工作?

library(tidyverse)

df = data.frame(
  A=runif(10, 1, 10),
  B=runif(10, 1, 10),
  C=rep(0, 10), 
  D=LETTERS[1:10]
)

df
#>           A        B C D
#> 1  2.157171 1.434351 0 A
#> 2  7.746638 6.987983 0 B
#> 3  7.861337 1.528145 0 C
#> 4  8.657990 4.101441 0 D
#> 5  8.307844 5.809815 0 E
#> 6  1.376084 9.202047 0 F
#> 7  7.197999 5.532681 0 G
#> 8  1.878676 1.012917 0 H
#> 9  2.231955 4.572273 0 I
#> 10 4.340488 2.640728 0 J

print("Does normalize columns, but can't handle col of 0s")
#> [1] "Does normalize columns, but can't handle col of 0s"
test = df %>% mutate_if(is.numeric, ~./sum(.))
test %>% select_if(is.numeric) %>% colSums()
#>   A   B   C 
#>   1   1 NaN

print("Virtually the same as above, but tries to handle col of 0s, but doesn't work")
#> [1] "Virtually the same as above, but tries to handle col of 0s, but doesn't work"
test = df %>% mutate_if(is.numeric, ~ifelse(sum(.)>0, ./sum(.), 0))
test %>%  select_if(is.numeric) %>% colSums()
#>         A         B         C 
#> 0.4167949 0.3349536 0.0000000

print("Does normalize columns, but can't handle col of 0s")
#> [1] "Does normalize columns, but can't handle col of 0s"
test = df %>% mutate_if(is.numeric, function(x) x/sum(x))
test %>% select_if(is.numeric) %>% colSums()
#>   A   B   C 
#>   1   1 NaN

print("Virtually the same as above, but tries to handle col of 0s, but doesn't work")
#> [1] "Virtually the same as above, but tries to handle col of 0s, but doesn't work"
test = df %>% mutate_if(is.numeric, function(x) ifelse(sum(x)>0, x/sum(x), 0))
test %>% select_if(is.numeric) %>% colSums()
#>         A         B         C 
#> 0.4167949 0.3349536 0.0000000

print("Strange error I don't understand")
#> [1] "Strange error I don't understand"
test = df %>% mutate_if(is.numeric, ~apply(., 2, function(x) x/sum(x)))
#> Error in apply(., 2, function(x) x/sum(x)): dim(X) must have a positive length

print("THIS DOES WORK! Why?")
#> [1] "THIS DOES WORK! Why?"
test = df %>% mutate_if(is.numeric, function(x) if(sum(x)>0) x/sum(x))
test %>% select_if(is.numeric) %>% colSums()
#> A B 
#> 1 1

reprex package (v0.3.0) 于 2019 年 10 月 29 日创建

编辑!!!

确认!刚刚发现一个大问题 在最后一个示例中,即“有效”,删除了 0 列。我完全不明白这一点。我想保留该列,只是不尝试对其进行规范化。

test = df %>% mutate_if(is.numeric, function(x) if(sum(x)>0) x/sum(x))
> test
#             A          B D
# 1  0.15571120 0.12033237 A
# 2  0.10561824 0.11198394 B
# 3  0.06041408 0.12068372 C
# 4  0.16785724 0.06241538 D
# 5  0.03112945 0.02559354 E
# 6  0.02791520 0.06363215 F
# 7  0.17132200 0.16625761 G
# 8  0.06641540 0.14038458 H
# 9  0.04015548 0.12420858 I
# 10 0.17346171 0.06450813 J

编辑 2

发现我需要包含else

test = df %>% mutate_if(is.numeric, function(x) if(sum(x)>0) {x/sum(x)}else{0})
> test
#             A          B C D
# 1  0.15571120 0.12033237 0 A
# 2  0.10561824 0.11198394 0 B
# 3  0.06041408 0.12068372 0 C
# 4  0.16785724 0.06241538 0 D
# 5  0.03112945 0.02559354 0 E
# 6  0.02791520 0.06363215 0 F
# 7  0.17132200 0.16625761 0 G
# 8  0.06641540 0.14038458 0 H
# 9  0.04015548 0.12420858 0 I
# 10 0.17346171 0.06450813 0 J

numeric_columns = 
  df %>%
  select_if(is.numeric) %>%
  colnames()

test = df %>% mutate_at(numeric_columns, function(x) if (sum(x) > 0) x/sum(x))
> test
#             A          B C D
# 1  0.15571120 0.12033237 0 A
# 2  0.10561824 0.11198394 0 B
# 3  0.06041408 0.12068372 0 C
# 4  0.16785724 0.06241538 0 D
# 5  0.03112945 0.02559354 0 E
# 6  0.02791520 0.06363215 0 F
# 7  0.17132200 0.16625761 0 G
# 8  0.06641540 0.14038458 0 H
# 9  0.04015548 0.12420858 0 I
# 10 0.17346171 0.06450813 0 J

【问题讨论】:

    标签: r dplyr tidyverse tidyeval


    【解决方案1】:

    @Rémi Coulaud 已经很好地解释了为什么工作/不工作。现在,处理这个问题的另一种方法可能是(根据@42-的评论更新):

    df %>% 
     mutate_if(~ is.numeric(.) && sum(.) != 0, ~ ./sum(.))
    
                A          B C D
    1  0.15735803 0.12131787 0 A
    2  0.08098114 0.10229536 0 B
    3  0.06108911 0.09802935 0 C
    4  0.13152492 0.15719599 0 D
    5  0.10684839 0.10477812 0 E
    6  0.14204157 0.10385447 0 F
    7  0.09731823 0.11015997 0 G
    8  0.15532621 0.10458007 0 H
    9  0.02579446 0.05748756 0 I
    10 0.04171793 0.04030124 0 J
    

    然后:

    df %>% 
     mutate_if(~ is.numeric(.) && sum(.) != 0, ~ ./sum(.)) %>%
     select_if(is.numeric) %>%
     colSums()
    
    A B C 
    1 1 0 
    

    【讨论】:

    • 这很优雅!
    • 不隐藏 a 向量的情况,例如:c(0,0,0,0,-1,1)。第二个测试应该匹配问题的根源,即应该是sum(.) != 0。甚至可能是使用!all.equal(0, sum(.))
    • @42- 这绝对是一个好点,但是,我不确定 OP 是指 sum != 0 的向量还是所有元素都不为 0 的向量。
    • 显然他需要测试总和,因为他是除以那个数字。
    • @42- 他将一个只有零的向量除以它的总和,因此得到 NaN,这不是主要的吗?我的意思是,如果不是全为零,那么基本上所有的可能性都会起作用,不是吗?
    【解决方案2】:

    第一个问题

    test = df %>% mutate_if(is.numeric, ~./sum(.))
    test %>% select_if(is.numeric) %>% colSums( ,na.rm = T)
    
    test = df %>% mutate_if(is.numeric, function(x) x/sum(x))
    test %>% select_if(is.numeric) %>% colSums()
    

    您可以指定na.rm = T 来处理您的问题,这样您就不会保留NA。 它们发生是因为你除以 0。 第二种语法也是一样的。 mutate_if 为每个数字列应用所需的操作,因此对于第三个它返回 Nan 因为 0。

    第二个问题

    test = df %>% mutate_if(is.numeric, function(x){ifelse(x > 0, x/sum(x), rep(0, length(x)))})
    test %>%  select_if(is.numeric) %>% colSums()
    
    test = df %>% mutate_if(is.numeric, function(x) ifelse(sum(x)>0, x/sum(x), 0))
    test %>% select_if(is.numeric) %>% colSums()
    

    ifelse 返回一个与 test 形状相同的值,因此在您的情况下,因为您检查 'sum(x) > 0' 您只返回第一个值。见:

    https://www.rdocumentation.org/packages/base/versions/3.6.1/topics/ifelse

    第三个问题

    test = df %>% mutate_if(is.numeric, ~apply(., 2, function(x) x/sum(x)))
    

    在这里,它很棘手,mutate_if 通过向量应用并且您想使用 apply next 但是您的对象是向量并且 apply 仅适用于具有至少两列的 matrixdata.frame 之类的对象。

    一个很好的答案

    test = df %>% mutate_if(is.numeric, function(x) if(sum(x)>0) x/sum(x))
    test %>% select_if(is.numeric) %>% colSums()
    

    确实这是一个正确的语法,因为if 不需要返回特定大小的对象。

    但是,您也可以使用ifelse,但在向量条件下,如果至少有一个元素不同于 0,则正值的总和确实不是 nul。

    test = df %>% mutate_if(is.numeric, function(x){ifelse(x > 0, x/sum(x), rep(0, length(x)))})
    test %>%  select_if(is.numeric) %>% colSums()
    

    我希望它可以帮助您了解出现错误时发生的情况。解决方案不是唯一的。

    编辑1:

    原因是:只有当你的总和严格大于 0 时,你才会返回一些东西。如果不是,你必须指定要做什么。比如这样:

    test = df %>% mutate_if(is.numeric, function(x) if(sum(x)>0){x/sum(x)}else{0})
    

    【讨论】:

    • @Remi--谢谢!我还没有遵循所有这些,但我会阅读文档并进行一些实验。但是,即使使用“工作”解决方案,我也意识到了一个大问题。你能看看我的帖子的编辑吗?
    • 是的!谢谢! [填充字符]
    • 非常高兴。
    猜你喜欢
    • 2013-06-30
    • 1970-01-01
    • 2018-12-12
    • 2013-03-08
    • 1970-01-01
    • 2016-11-12
    • 1970-01-01
    • 2016-07-23
    • 2015-09-20
    相关资源
    最近更新 更多