使用dplyr标准化变量 [r]

8

我想在R中规范化变量。我知道有多种方法可以做到这一点,但我真的很喜欢使用下面的方法:

library(tidyverse)

df <- mtcars

df %>% 
  gather() %>% 
  group_by(key) %>% 
  mutate(value = value - mean(value)) %>% 
  ungroup() %>% 
  pivot_wider(names_from = key, values_from = value)

由于某些原因,这种方法不起作用,因为我无法将数据返回到原始格式。因此,我想请求建议。


1
为什么不直接使用 as.data.frame(lapply(mtcars, function(x) x - mean(x))) 呢? - jay.sf
4个回答

8
根据当前文档,您应该使用基于across的语法来对所需子集的列执行操作。您可以使用everything来选择所有列,或者使用任何其他可用限定词。如果您想对分组执行操作,请仅使用group_by动词。选择变量不是使用group_by的正确选择。
mtcars %>%
    as_tibble() %>%
    mutate(across(where(is.numeric), ~ . - mean(.)))

关于将某些列应用实际标准化或其他操作,您可以使用:

.fns 用于对所选列中的每一列应用的函数。可能的值为:

  • NULL,返回未转换的列。
  • 函数,例如 mean
  • Purrr 样式匿名函数,例如 ~ mean(.x, na.rm = TRUE)
  • 函数/匿名函数列表,例如 list(mean = mean, n_miss = ~ sum(is.na(.x))

因此,对于 scale,您可以执行以下操作:

mtcars %>%
    as_tibble() %>%
    mutate(across(where(is.numeric), scale))

或者使用额外参数。
mtcars %>%
    as_tibble() %>%
    mutate(across(where(is.numeric), scale, center = FALSE))

备注

?scale文档中可以看到,该函数返回矩阵。对于上面的示例,如果您只想得到一列矩阵,可以进行以下操作:

mtcars %>%
    as_tibble() %>%
    mutate(across(where(is.numeric),  ~ scale(.)[,1]))

对比

>> mtcars %>%
...     as_tibble() %>%
...     mutate(across(where(is.numeric),  ~ scale(.)[,1])) %>% 
...     glimpse()
Rows: 32
Columns: 11
$ mpg  <dbl> 0.15088482, 0.15088482, 0.44954345, 0.21725341, -0.23073453, -0.33028740, -0.96078…
$ cyl  <dbl> -0.1049878, -0.1049878, -1.2248578, -0.1049878, 1.0148821, -0.1049878, 1.0148821, …
$ disp <dbl> -0.57061982, -0.57061982, -0.99018209, 0.22009369, 1.04308123, -0.04616698, 1.0430…
$ hp   <dbl> -0.53509284, -0.53509284, -0.78304046, -0.53509284, 0.41294217, 
...
>> 
>> 
>> mtcars %>%
...     as_tibble() %>%
...     mutate(across(where(is.numeric), scale)) %>% 
...     glimpse()
Rows: 32
Columns: 11
$ mpg  <dbl[,1]> <matrix[32 x 1]>
$ cyl  <dbl[,1]> <matrix[32 x 1]>
$ disp <dbl[,1]> <matrix[32 x 1]>
$ hp   <dbl[,1]> <matrix[32 x 1]>
...

1

不清楚为什么要先将数据转换成长格式,然后再返回宽格式,也不知道为什么不选择计算速度更快的scale(df)

无论如何,如果确实想使用类似喜欢的代码,需要进一步执行unnest操作以将数据返回到原始格式。

df %>% 
  gather() %>% 
  group_by(key) %>% 
  mutate(value = value - mean(value)) %>% 
  ungroup() %>% 
  pivot_wider(names_from = key, values_from = value) %>% 
  unnest(everything())

# A tibble: 32 x 11
#       mpg    cyl    disp    hp    drat       wt   qsec     vs     am   gear   carb
#     <dbl>  <dbl>   <dbl> <dbl>   <dbl>    <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>
#  1  0.909 -0.188  -70.7  -36.7  0.303  -0.597   -1.39  -0.438  0.594  0.312  1.19 
#  2  0.909 -0.188  -70.7  -36.7  0.303  -0.342   -0.829 -0.438  0.594  0.312  1.19 
#  3  2.71  -2.19  -123.   -53.7  0.253  -0.897    0.761  0.562  0.594  0.312 -1.81 
#  4  1.31  -0.188   27.3  -36.7 -0.517  -0.00225  1.59   0.562 -0.406 -0.688 -1.81 
#  5 -1.39   1.81   129.    28.3 -0.447   0.223   -0.829 -0.438 -0.406 -0.688 -0.812
#  6 -1.99  -0.188   -5.72 -41.7 -0.837   0.243    2.37   0.562 -0.406 -0.688 -1.81 
#  7 -5.79   1.81   129.    98.3 -0.387   0.353   -2.01  -0.438 -0.406 -0.688  1.19 
#  8  4.31  -2.19   -84.0  -84.7  0.0934 -0.0272   2.15   0.562 -0.406  0.312 -0.812
#  9  2.71  -2.19   -89.9  -51.7  0.323  -0.0673   5.05   0.562 -0.406  0.312 -0.812
# 10 -0.891 -0.188  -63.1  -23.7  0.323   0.223    0.451  0.562 -0.406  0.312  1.19 
# ... with 22 more rows

编辑

为了使用最新的tidyr函数,您应该考虑用pivot_longer替换现已停用的gather,如下面的代码所示。得到的结果是相同的。

df %>% 
  pivot_longer(everything()) %>% 
  group_by(name) %>% 
  mutate(value = value - mean(value)) %>% 
  ungroup() %>% 
  pivot_wider(names_from = name, values_from = value) %>% 
  unnest(everything())

这是一个对于微不足道的问题而言,复杂且计算成本高昂的解决方案。 - Konrad
2
我知道这是计算密集型的,事实上我写了“为什么不选择计算速度更快的scale(df)”。我遵循OP的指示,因为他/她说:“我知道有多种方法可以完成这个任务。然而,我真的很喜欢使用下面的方法。” - Ric S
就像我在原始问题中所写的那样 - 我知道有许多更复杂的方法来做到这一点。这种方法的主要思想是每个人,例如学生,都可以“看到”我们减去平均值并除以标准差的公式。 - Petr
@Petr,我的方法是否符合您的期望?它解决了您的问题吗? - Ric S
scale(df) 是错误的。scale() 函数期望一个矩阵作为输入。如果你的 df 恰好只包含数值数据,它可能会有所作用,但这种情况很少见,并且返回的对象不再是数据框。一个微不足道的反例是:scale(iris)。 - nth

0
df %>% 
  gather() %>% 
  group_by(key) %>% 
  mutate(row = row_number(), value = value - mean(value)) %>% 
  pivot_wider(names_from = key, values_from = value) %>%
  select(-row)

2
为什么这可能比得到最高票数的答案更受欢迎?你能给出一个解释吗? - Jeremy Caney

0

警告信息非常有用:

Values are not uniquely identified; output will contain list-cols.

您需要一个可以唯一标识每行的列:
df %>% 
  gather() %>% 
  group_by(key) %>% 
  mutate(row = row_number(), value = value - mean(value)) %>% 
  pivot_wider(names_from = key, values_from = value) %>%
  select(-row)

你可能想把行名添加回结果中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接