Нормализация столбцов в смешанных числовых / нечисловых DataFrame с Tidyverse (dplyr) - PullRequest
2 голосов
/ 29 октября 2019

Мне часто нужно нормализовать столбцы DataFrames, которые имеют смесь числовых и нечисловых столбцов. Иногда я знаю имена числовых столбцов, а иногда нет.

Я пробовал то, что мне кажется очень логичным и аккуратным методом eval. Большинство не работает. Я нашел только один, который работает.

Для лучшего понимания аккуратной оценки, могу ли я дать объяснение, почему следующие либо работают, либо не работают?

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

Создано в 2019-10-29 пакетом Представления (v0.3.0)

РЕДАКТИРОВАТЬ !!!

Ack! Только что заметил огромную проблему В последнем примере, который «работает», столбец 0s удаляется. Я не понимаю этого вообще. Я хочу сохранить этот столбец, только не пытайтесь его нормализовать.

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

Ответы [ 2 ]

3 голосов
/ 30 октября 2019

Первая проблема

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 возвращает значение с той же формой, что и для тестатак что в вашем случае, поскольку вы проверяете «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 затем, но ваш объект являетсяvector и apply корректны только для таких объектов, как matrix или data.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.

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})
1 голос
/ 30 октября 2019

@ Реми Куло уже дал хорошее объяснение того, почему все работает / не работает. Теперь другой способ решения этой проблемы может быть (обновлено на основе замечаний от @ 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 
...