如何在使用tidyr的spread函数后控制新变量的名称?

20

我有一个面板结构的数据框:每个单位有两年的两次观测:

library(tidyr)
mydf <- data.frame(
    id = rep(1:3, rep(2,3)), 
    year = rep(c(2012, 2013), 3), 
    value = runif(6)
)
mydf
#  id year      value
#1  1 2012 0.09668064
#2  1 2013 0.62739399
#3  2 2012 0.45618433
#4  2 2013 0.60347152
#5  3 2012 0.84537624
#6  3 2013 0.33466030

我希望将这些数据转换为宽格式,可以使用tidyr::spread轻松完成。但由于year变量的值是数字,我的新变量名称也会变成数字,这使得它们更难以进一步使用。

spread(mydf, year, value)
#  id       2012      2013
#1  1 0.09668064 0.6273940
#2  2 0.45618433 0.6034715
#3  3 0.84537624 0.3346603

我知道我可以轻松地重命名列。然而,如果我想在其他操作中与之链式重塑,那就变得不方便了。例如,下面的代码显然是没有意义的。

library(dplyr)
mydf %>% spread(year, value) %>% filter(2012 > 0.5)

以下方法可行,但不是那么简洁:

tmp <- spread(mydf, year, value)
names(tmp) <- c("id", "y2012", "y2013")
filter(tmp, y2012 > 0.5)

你有什么想法可以在 spread 中更改新变量名吗?

5个回答

19

我知道这个问题最初被提出已经过去了一些年头,但为了后世纪录,我也想强调 spreadsep 参数。当不为 NULL 时,它将用作键名和值之间的分隔符:

mydf %>% 
 spread(key = year, value = value, sep = "")
#  id   year2012  year2013
#1  1 0.15608322 0.6886531
#2  2 0.04598124 0.0792947
#3  3 0.16835445 0.1744542

这不完全符合问题要求,但对我的目的足够了。请参见?spread

使用tidyr 1.0.0更新:tidyr 1.0.0 现在已经引入了pivot_wider(和pivot_longer),允许通过参数names_sepnames_prefix更好地控制数据格式。所以现在的调用将是:

mydf %>% 
  pivot_wider(names_from = year, values_from = value,
              names_prefix = "year")
# # A tibble: 3 x 3
#        id year2012 year2013
#     <int>    <dbl>    <dbl>
#   1     1    0.347    0.388
#   2     2    0.565    0.924
#   3     3    0.406    0.296

要得到原本想要的内容(仅在前缀加上“y”),当然可以直接设置names_prefix = "y"来实现。

如果您需要从多列中收集数据,可以使用names_sep,如下所示,在数据中添加了季度:

# Add quarters to data
mydf2 <- data.frame(
  id = rep(1:3, each = 8), 
  year = rep(rep(c(2012, 2013), each = 4), 3), 
  quarter  = rep(c("Q1","Q2","Q3","Q4"), 3),
  value = runif(24)
)
head(mydf2)
# id year quarter     value
# 1  1 2012      Q1 0.8651470
# 2  1 2012      Q2 0.3944423
# 3  1 2012      Q3 0.4580580
# 4  1 2012      Q4 0.2902604
# 5  1 2013      Q1 0.4751588
# 6  1 2013      Q2 0.6851755

mydf2 %>% 
  pivot_wider(names_from = c(year, quarter), values_from = value,
              names_sep = "_", names_prefix = "y")
# # A tibble: 3 x 9
#      id  y2012_Q1  y2012_Q2  y2012_Q3  y2012_Q4  y2013_Q1  y2013_Q2  y2013_Q3  y2013_Q4 
#   <int>     <dbl>     <dbl>     <dbl>     <dbl>     <dbl>     <dbl>     <dbl>     <dbl>
# 1     1     0.865     0.394     0.458    0.290      0.475     0.685     0.213     0.920
# 2     2     0.566     0.614     0.509    0.0515     0.974     0.916     0.681     0.509
# 3     3     0.968     0.615     0.670    0.748      0.723     0.996     0.247     0.449

spread 函数中更改新变量的名称。 - janosdivenyi

14

你可以使用 反引号 来表示以数字开头的列名,filter 应该按预期工作

  mydf %>%
      spread(year, value) %>%
      filter(`2012` > 0.5)
  #  id      2012      2013
  #1  3 0.8453762 0.3346603

另一个选项是在创建第二列“year1”并使用字符串“y”后,使用unite将两列合并为单列。

  mydf %>%
     mutate(year1='y') %>%
     unite(yearN, year1, year) %>%
     spread(yearN, value) %>%
     filter(y_2012 > 0.5)
 #   id    y_2012    y_2013
 #1  3 0.8453762 0.3346603

通过使用paste,我们甚至可以在mutate中更改“year”列

 mydf %>%
     mutate(year=paste('y', year, sep="_")) %>%
     spread(year, value) %>%
     filter(y_2012 > 0.5)

5
另一种选择是在管道中使用setNames()函数作为下一步操作:
mydf %>%
    spread(mydf, year, value) %>%
    setNames( c("id", "y2012", "y2013") ) %>%
    filter(y2012 > 0.5)

使用setNames的唯一问题是,在spread()它们时,您必须确切地知道您的列会是什么。大多数情况下,这不是问题,特别是如果您正在半交互式地工作。
但是,如果您在原始数据中缺少关键/值对,则有可能它不会显示为列,而您甚至不知道会错误地命名您的列。尽管setNames()会在名称数量与列数不匹配时引发错误,因此您已经具备了一定的错误检查功能。
尽管如此,对我来说,使用setNames()的便利性往往超过了风险。

2
使用spread()的替代函数pivot_wider(),我们可以给新创建的列加上前缀:
library(tidyr)
set.seed(1)
mydf <- data.frame(
  id = rep(1:3, rep(2,3)), 
  year = rep(c(2012, 2013), 3), 
  value = runif(6)
)

pivot_wider(mydf, names_from = "year", values_from = "value", names_prefix = "y")
#> # A tibble: 3 x 3
#>      id y2012 y2013
#>   <int> <dbl> <dbl>
#> 1     1 0.266 0.372
#> 2     2 0.573 0.908
#> 3     3 0.202 0.898

这段内容是由reprex包(版本0.3.0)于2019年9月14日创建的。


0

dplyr 中的 rename() 应该能解决问题

library(tidyr); library(dplyr)
mydf %>%
  spread(year,value)%>%
  rename(y2012 = '2012',y2013 = '2013')%>%
  filter(y2012>0.5)

1
从概念上讲,它与@crazybilly的答案相同。 - janosdivenyi

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