两组列的并行 pivot_longer

17
我有以下数据框:

I have the following data frame:

library(tidyverse)
dat <- tribble(
  ~Scenario,     ~V1,    ~V2,    ~V3,    ~V4,
  1,    0.97,   0.46,   0.79,   0.25,
  1,    0.21,   0.45,   0.23,   0.63,
  1,    0.95,   0.97,   0.07,   0.61,
  1,    0.93,   0.79,   0.23,   0.86,
  2,    0.22,   0.01,   0.42,   0.47,
  2,    0.71,   0.17,   0.16,   0.88,
  3,    0.73,   0.38,   0.10,   0.77,
  3,    0.49,   0.37,   0.90,   0.52,
  3,    0.99,   0.71,   0.66,   0.05,
  3,    0.72,   0.75,   0.69,   0.01,
  3,    0.15,   0.87,   0.12,   0.02,
  4,    0.94,   0.30,   0.91,   0.99)

我正在为这个数据添加四列新的内容,其中每一列新的内容都代表了每个V1:V4列根据Scenario分组求和的结果:

dat_new <- dat %>%
  group_by(Scenario) %>%
  mutate_at(vars(-group_cols()), .funs = list(sum = sum))

现在我想将这些数据转换为长格式,其中一组是我的V1:V4列,另一组是我的V1_sum:V4_sum列。普通的 pivot_longer 不起作用,因为它只接受一个值列,然而我需要两个。

在tidyverse参考文献中(底部示例),我找到了一个可能的解决方案,但我无法定义正确的names_patternhttps://tidyr.tidyverse.org/reference/pivot_longer.html

是否有一种简单直接的方法(理想情况下是tidyverse),可以处理这两个pivot_longer集合?感觉这是重新塑造数据集时的基本任务之一,但我无法让其正常工作。

期望输出:

Scenario    set    V    sum
1           1      0.97 3.06
1           2      0.46 2.67
1           3      0.79 1.32
1           4      0.25 2.35
...
4           4      0.99 0.99

注意:“set”、“V”和“sum”列名仅为示例,如果其他列名更容易动态生成,则我也可以接受其他列名。

3个回答

20

我在“数据透视表”(vignette("pivot"))的“一行多个观测值”部分找到了这个解决方案。

你需要为每组变量设置一个并行命名结构,并添加一个分隔符。

对于寻求通用解决方案的用户,请跳转至下面的“通用解决方案”部分。

准备提问者的示例数据

在您的情况中(这不是非常通用),从mutate_at函数中出来,您会得到每个变量附加的_sum。但是,您需要在前四个变量后面加上一些内容。我使用标记_orig重新命名了原始的四个变量,并将总和变量的名称从V1_orig_sum简化为V1_sum,以避免双下划线的复杂情况。

dat <- tribble(
  ~Scenario,     ~V1_orig,    ~V2_orig,    ~V3_orig,    ~V4_orig,
  1,    0.97,   0.46,   0.79,   0.25,
  1,    0.21,   0.45,   0.23,   0.63,
  1,    0.95,   0.97,   0.07,   0.61,
  1,    0.93,   0.79,   0.23,   0.86,
  2,    0.22,   0.01,   0.42,   0.47,
  2,    0.71,   0.17,   0.16,   0.88,
  3,    0.73,   0.38,   0.10,   0.77,
  3,    0.49,   0.37,   0.90,   0.52,
  3,    0.99,   0.71,   0.66,   0.05,
  3,    0.72,   0.75,   0.69,   0.01,
  3,    0.15,   0.87,   0.12,   0.02,
  4,    0.94,   0.30,   0.91,   0.99)

dat_new <- dat %>%
  group_by(Scenario) %>%
  mutate_at(vars(-group_cols()), .funs = list(sum = sum)) %>%
  rename_with(.cols=ends_with('_sum'), .fn=str_remove, pattern='_orig')

经过这些操作,您的数据格式适合回答如何使用pivot_longer()转换两组列(如下一部分所示)。

通用解决方案

我们从一个包含一个ID变量 (Scenario) 和两组每组四列的数据框 dat_new 开始:

> head(dat_new)
# A tibble: 6 x 9
# Groups:   Scenario [2]
  Scenario V1_orig V2_orig V3_orig V4_orig V1_sum V2_sum V3_sum V4_sum
     <dbl>   <dbl>   <dbl>   <dbl>   <dbl>  <dbl>  <dbl>  <dbl>  <dbl>
1        1    0.97    0.46    0.79    0.25   3.06   2.67   1.32   2.35
2        1    0.21    0.45    0.23    0.63   3.06   2.67   1.32   2.35
3        1    0.95    0.97    0.07    0.61   3.06   2.67   1.32   2.35
4        1    0.93    0.79    0.23    0.86   3.06   2.67   1.32   2.35
5        2    0.22    0.01    0.42    0.47   0.93   0.18   0.58   1.35
6        2    0.71    0.17    0.16    0.88   0.93   0.18   0.58   1.35

列名的格式为[row]_[column],其中[row][column]描述了我们希望组件在最终数据框中显示的位置。
使用pivot_longer函数,并在 names_to参数中给出两个列名:一个用于描述这些对象(在此示例中为Var),另一个是".value",表示在_分隔符之后有多少个唯一标签就会创建多少个新列。
dat_new %>% 
  pivot_longer(-Scenario, 
               names_to = c("Var", ".value"), 
               names_sep="_" )
# A tibble: 48 x 4
# Groups:   Scenario [4]
   Scenario Var    orig   sum
      <dbl> <chr> <dbl> <dbl>
 1        1 V1     0.97  3.06
 2        1 V2     0.46  2.67
 3        1 V3     0.79  1.32
 4        1 V4     0.25  2.35
 5        1 V1     0.21  3.06
 6        1 V2     0.45  2.67
 7        1 V3     0.23  1.32
 8        1 V4     0.63  2.35
 9        1 V1     0.95  3.06
10        1 V2     0.97  2.67
# ... with 38 more rows

注意,如果您的变量命名系统是反向的(即orig_V1sum_V1等),可以在命名参数中反转顺序:names_to=c(".value", "Var)。此解决方案不要求值为相同类型(即原始的和总和在这里都是数字,但一个可能是数字,另一个可能是字符)。

谢谢!是的,使用更有结构的列名,您可以利用.value和names_sep。然而,在我的实际用例中,我转向创建一个pivot_longer_spec数据框架(https://tidyr.tidyverse.org/reference/pivot_longer_spec.html),并将其馈送到pivot_longer函数中。这比尝试创建可能使用上述方法分隔的列名要容易得多且更具普适性。当时我不知道这个选项,但它真的很有帮助。 - deschen

4
也许在计算总和之前,您应该尝试进行“旋转”:
dat %>% pivot_longer(., -Scenario, names_to = "V", values_to = "Value") %>% 
  group_by(Scenario, V) %>%
  mutate(Sum = sum(Value))


# A tibble: 48 x 4
# Groups:   Scenario, V [16]
   Scenario V     Value   Sum
      <dbl> <chr> <dbl> <dbl>
 1        1 V1     0.97  3.06
 2        1 V2     0.46  2.67
 3        1 V3     0.79  1.32
 4        1 V4     0.25  2.35
 5        1 V1     0.21  3.06
 6        1 V2     0.45  2.67
 7        1 V3     0.23  1.32
 8        1 V4     0.63  2.35
 9        1 V1     0.95  3.06
10        1 V2     0.97  2.67
# … with 38 more rows

这是否是您正在寻找的内容?


1
哦,是的,这很有道理,也行得通。但是出于好奇,如果我没有原始数据,只有预先计算好的四个V列和四个总和列,无法在原始数据上应用pivot_longer呢? - deschen
1
实际上,这是一个非常好的问题... 到目前为止,我还没有答案。 - dc37
好的,所以我不是一个人。查看tidyverse的参考资料后,我唯一能够实现的就是将我的V1:V4列重命名为x1:x4,将所有汇总列重命名为y1:y4,并精确地应用参考资料中最后一个示例中的代码(在我的帖子中链接)。但这并不是很优雅。我想这归结于找到正确的命名模式,但我失败了。 - deschen

1

这个问题非常棘手。我认为最简单的方法可能是将数据转换为最长格式,然后在此之后重新构造为2组。以下是使用R中给出的安斯康姆四重奏的示例:

> anscombe
   x1 x2 x3 x4   y1  y2   y3   y4
1  10 10 10  8  8.0 9.1  7.5  6.6
2   8  8  8  8  7.0 8.1  6.8  5.8
3  13 13 13  8  7.6 8.7 12.7  7.7
4   9  9  9  8  8.8 8.8  7.1  8.8
5  11 11 11  8  8.3 9.3  7.8  8.5
6  14 14 14  8 10.0 8.1  8.8  7.0
7   6  6  6  8  7.2 6.1  6.1  5.2
8   4  4  4 19  4.3 3.1  5.4 12.5
9  12 12 12  8 10.8 9.1  8.2  5.6
10  7  7  7  8  4.8 7.3  6.4  7.9
11  5  5  5  8  5.7 4.7  5.7  6.9

我们可以看到有两组变量,每组有4个。我们需要一个仅包含3个变量的数据集:x、y和series,后者只是指示值属于哪一组4个中的哪一组。这是我做的方法:

> anscombe %>% 
+   mutate(row = 1:n()) %>% 
+   pivot_longer(cols = -row) %>% 
+   separate(col = name, into = c("var", "series"), sep = 1) %>% 
+   pivot_wider(id_cols = c(row, series), names_from = "var", values_from = "value") %>% print(n=Inf)
# A tibble: 44 × 4
     row series     x     y
   <int> <chr>  <dbl> <dbl>
 1     1 1         10  8.04
 2     1 2         10  9.14
 3     1 3         10  7.46
 4     1 4          8  6.58
 5     2 1          8  6.95
 6     2 2          8  8.14
 7     2 3          8  6.77
 8     2 4          8  5.76
 9     3 1         13  7.58
10     3 2         13  8.74
11     3 3         13 12.7 
12     3 4          8  7.71
13     4 1          9  8.81
14     4 2          9  8.77
15     4 3          9  7.11
16     4 4          8  8.84
17     5 1         11  8.33
18     5 2         11  9.26
19     5 3         11  7.81
20     5 4          8  8.47
21     6 1         14  9.96
22     6 2         14  8.1 
23     6 3         14  8.84
24     6 4          8  7.04
25     7 1          6  7.24
26     7 2          6  6.13
27     7 3          6  6.08
28     7 4          8  5.25
29     8 1          4  4.26
30     8 2          4  3.1 
31     8 3          4  5.39
32     8 4         19 12.5 
33     9 1         12 10.8 
34     9 2         12  9.13
35     9 3         12  8.15
36     9 4          8  5.56
37    10 1          7  4.82
38    10 2          7  7.26
39    10 3          7  6.42
40    10 4          8  7.91
41    11 1          5  5.68
42    11 2          5  4.74
43    11 3          5  5.73
44    11 4          8  6.89

步骤如下:

  1. 添加一个id列,其值为行号。
  2. 将除id列外的所有内容转换为长格式。
  3. 将x1...x4和y1...y4分别拆分成两列。
  4. 恢复为宽格式,其中x和y各有2列。

我们可以绘制数据以确保我们做得正确:

#data from above)
ggplot(aes(x, y)) +
geom_point() +
geom_smooth(method = "lm", se = F) +
facet_wrap("series") +
ggtitle("Anscombe's quartlet: Same correlations but different data")

enter image description here


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