从宽格式转换为长格式时,保持列的顺序

6
我正在尝试在从宽格式到长格式收集列时保留其顺序。 我遇到的问题是在我进行了gathersummarize之后,顺序被打乱了。 由于列数很多,因此我不想手动输入顺序。
以下是示例:
library(tidyr)
library(dplyr)

N <- 4
df <- data.frame(sample = c(1,1,2,2),
                 y1.1 = rnorm(N), y2.1 = rnorm(N), y10.1 = rnorm(N))
> df
  sample      y1.1      y2.1      y10.1
1      1  1.040938 0.8851727 -0.3617224
2      1  1.175879 1.0009824 -1.1352406
3      2 -1.501832 0.3446469 -1.8687008
4      2 -1.326817 0.4434628 -0.8795962

我想保留列的顺序。在进行一些操作后,顺序会丢失。如下所示:

dfg <- df %>% 
  gather(key="key", value="value", -sample) %>%
  group_by(sample, key) %>%
  summarize(mean = mean(value))

> filter(dfg, sample == 1)
  sample   key       mean
   <dbl> <chr>      <dbl>
1      1  y1.1  0.2936335
2      1 y10.1  0.6170505
3      1  y2.1 -0.2250543

你可以看到它将y10.1放在了y2.1的前面,这不是我想要的。我想要保留这个顺序,就像这样:
dfg <- df %>% 
  gather(key="key", value="value", -sample)

> filter(dfg, sample == 1)
  sample   key       value
1      1  y1.1  0.60171521
2      1  y1.1 -0.01444823
3      1  y2.1  0.81566726
4      1  y2.1 -1.26577581
5      1 y10.1  0.41686388
6      1 y10.1  0.81723707

出于某种原因,group_bysummarize操作会改变顺序。我不确定原因。我尝试使用ungroup命令,但它没有起任何作用。正如我之前所说,我的实际数据框有许多列,我需要保留顺序。保留顺序的原因是为了能够以正确的顺序绘制数据。

有任何想法吗?

6个回答

4

或者您可以将关键列转换为一个因子,其级别反映了原始列名的顺序:

df %>% 
    gather(key="key", value="value", -sample) %>%
    mutate(key=factor(key, levels=names(df)[-1])) %>% # add this line to convert the key to a factor
    group_by(sample, key) %>%
    summarize(mean = mean(value)) %>%
    filter(sample == 1)

# A tibble: 3 x 3
# Groups:   sample [1]
#  sample    key       mean
#   <dbl> <fctr>      <dbl>
#1      1   y1.1  0.8310786
#2      1   y2.1 -1.2596933
#3      1  y10.1  0.8208812

我选择这个作为答案,因为它是最通用的解决方案。然而,@Moody_Mudskipper提供了一个独特的选项,它可以按数字顺序排序,这在某些情况下可能是需要的(当列不按所需顺序排列时)。 - Lloyd Christmas

3

tidyverse 包现在提供了优雅的解决方案:

    library(tidyverse)
    N <- 4
    df <- data.frame(sample = c(1,1,2,2),
                    y1.1 = rnorm(N), y2.1 = rnorm(N), y10.1 = rnorm(N))
    df %>% 
        gather("key", "value", -sample, factor_key = T) %>% 
        group_by(sample, key) %>%
        summarise(mean = mean(value))

这会导致...
    # A tibble: 6 x 3
    # Groups:   sample [2]
    sample key      mean
    <dbl> <fct>   <dbl>
    1      1 y1.1   0.0894
    2      1 y2.1   0.551 
    3      1 y10.1  0.254 
    4      2 y1.1  -0.555 
    5      2 y2.1  -1.36  
    6      2 y10.1 -0.794 

2
这是一个非常好的解决方案。gather现在已经被弃用,推荐使用pivot_longer,但是在帮助文件中初看似乎没有提到pivot_longer具有此功能。 - Lloyd Christmas
3
tidyr 的下一个版本(1.0.2 之后)将引入 pivot_longer() 函数的 names_transform 参数。该参数会对 pivot_longer() 创建的变量名应用一个函数。factor_key = TRUE 参数等同于 names_transform = list(key = forcats::fct_inorder) - Mitchell O'Hara-Wild

1
我通过使用查找表找到了可行的解决方案。对我来说,它似乎可行,因为我可以提取列名并分配一个有序数字给列名,然后与我的data.frame进行配对。
以下是解决方案:
lookup <- tibble(key = c("y1.1", "y2.1", "y10.1"),
                 index = c(1,2,3))

> left_join(dfg, lookup, by="key")
# A tibble: 6 x 4
  sample   key       mean index
   <dbl> <chr>      <dbl> <dbl>
1      1  y1.1  0.2936335     1
2      1 y10.1  0.6170505     3
3      1  y2.1 -0.2250543     2
4      2  y1.1  1.3652070     1
5      2 y10.1  0.9889233     3
6      2  y2.1  0.5216553     2

1
如果你的列是按照它们所包含的数字排序的,那么这应该可以工作:
library(readr)

df %>% 
  gather(key="key", value="value", -sample) %>%
  group_by(sample, key)         %>%
  summarize(mean = mean(value)) %>%
  arrange(parse_number(key))    %>%  # <- sorting by number contained in key
  filter(sample == 1)

# # A tibble: 3 x 3
# # Groups:   sample [1]
#     sample   key       mean
# <dbl> <chr>      <dbl>
#   1      1  y1.1 -0.9236688
#   2      1  y2.1 -0.2168337
#   3      1 y10.1  0.5041981

0
另一种方法是使用自定义版本的关键列来对数据框进行排序:
arrange
library(dplyr)
library(tidyr)

df %>% 
  gather(key="key", value="value", -sample) %>%
  group_by(sample, key) %>%
  summarize(mean = mean(value)) %>%
  arrange(as.numeric(stringr::str_replace(key, "y", "")), .by_group = TRUE)

#> # A tibble: 6 x 3
#> # Groups:   sample [2]
#>   sample   key        mean
#>    <dbl> <chr>       <dbl>
#> 1      1  y1.1  0.07001689
#> 2      1  y2.1  1.15349430
#> 3      1 y10.1  1.18266024
#> 4      2  y1.1  0.42616604
#> 5      2  y2.1  1.05891682
#> 6      2 y10.1 -0.12561209

这是否基本上是将键转换为数字,然后根据该数字进行排序? - Lloyd Christmas
嗨@LloydChristmas,是的,你说得对。它与Moody_Mudskipper更近期的答案执行相同的操作,使用更易读的parse_number函数。但是,如果您希望最终结果按分组变量首先排序,然后按键排序,则需要将.by_group = TRUE添加到arrange中。 - markdly

0
如果我们结合之前建议答案的想法,并且使用 pivot_longer(),因为它没有被弃用,我们可以添加一步来设置键 as_factor() 而不是字符类。如果我们将其保留为字符,则会按字母数字顺序重新排序。
library(tidyverse)

N <- 4
df <- data.frame(sample = c(1,1,2,2),
                 y1.1 = rnorm(N), y2.1 = rnorm(N), y10.1 = rnorm(N))


dfg <- df |> 
  pivot_longer(2:4, names_to = "key", values_to = "value") |> 
  mutate(key = as_factor(key)) |> 
  group_by(sample, key) |> 
  summarize(mean = mean(value)) |> 
  ungroup()

dfg

# A tibble: 6 × 3
  sample key     mean
   <dbl> <fct>  <dbl>
1      1 y1.1  -0.789
2      1 y2.1   1.16 
3      1 y10.1 -0.187
4      2 y1.1   0.962
5      2 y2.1   0.673
6      2 y10.1  0.502

|> 语法和 %>% 有什么区别? - Lloyd Christmas
@LloydChristmas |> 是 R 语言的原生管道符。 %>% 是 magrittr 管道符。 https://r4ds.hadley.nz/workflow-pipes.html 今后您将看到更多原生 R 管道符的使用。我喜欢使用带有连字号的字体,如 Fira Code 或 Cascadia Code,这样它就会显示为一个整洁的三角形。 - gradcylinder

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