根据指定变量和标识符的列名收集多个变量

时间:2018-12-27 20:25:09

标签: r dplyr tidyr data-manipulation

我正在处理数据记录器的时间序列输出,这些输出具有在不同位置的数据框中共享的公共环境变量(例如光,温度,风速)。因此,每列均以要测量的环境变量(例如“ a”)命名,然后以其物理位置(例如“ 1”)命名,并以“ _”分隔。

作为一个示例,我们可以想象一个在三个不同位置同时测量环境变量“ a”,“ b”和“ c”的数据框。这样就给出了日期时间的列名以及可变位置的六个独特组合中的每一个,如下所示:

“ dt”“ a_1”“ a_2”“ a_3”“ b_1”“ b_2”“ b_3”“ c_1”“ c_2”“ c_3”

我需要将数据帧转换为长格式,以便“ dt”,“ a”,“ b”和“ c”各有一个列,并在该位置添加新列“ loc”与每个环境变量测量相关。

下面的代码创建一个模拟数据帧,然后使用一个非常麻烦的方法来创建我想要的输出。但是,此示例代码太麻烦了,无法用于大型数据帧(即数十个变量和位置)。

如何通过使用列名中的信息自动转换数据(最好是通过tidyr和dplyr使用tidyverse方法)来提高效率?

### Mock data:
start_time <- as.POSIXct("2000-10-01 10:10:10")
df <- data.frame(
    dt= seq.POSIXt(from = start_time, length.out = 100, by = 1),
    a_1=abs(rnorm(100, 1000, 500)),
    b_1=abs(rnorm(100, 35, 5)),
    c_1=abs(rnorm(100, 10, 2.5)),
    a_2=abs(rnorm(100, 1000, 500)),
    b_2=abs(rnorm(100, 35, 5)),
    c_2=abs(rnorm(100, 10, 2.5)),
    a_3=abs(rnorm(100, 1000, 500)),
    b_3=abs(rnorm(100, 35, 5)),
    c_3=abs(rnorm(100, 10, 2.5))
)

### New data frames for each location, with location identifier column:
loc1 <- df %>%
  select(dt, a_1, b_1, c_1) %>%
  rename(a = a_1) %>%
  rename(b = b_1) %>%
  rename(c = c_1) %>%
  mutate(loc = as.character("1"))

loc2 <- df %>%
  select(dt, a_2, b_2, c_2) %>%
  rename(a = a_2) %>%
  rename(b = b_2) %>%
  rename(c = c_2) %>%
  mutate(loc = as.character("2"))

loc3 <- df %>%
  select(dt, a_3, b_3, c_3) %>%
  rename(a = a_3) %>%
  rename(b = b_3) %>%
  rename(c = c_3) %>%
  mutate(loc = as.character("3"))

### Data in desired long format:
all_data_long <- rbind(loc1, loc2, loc3)

3 个答案:

答案 0 :(得分:3)

按照要求使用整洁的方法对您有用吗?

library(dplyr)
library(tidyr)
out <- df %>% 
  gather(Letter, Val, -dt) %>% 
  separate(Letter, into = c("Letter", "Loc")) %>% 
  spread(Letter, Val)

答案 1 :(得分:2)

您可以将data.table::meltpatterns一起使用。

注意:如@Istrel所述,此处的measure.vars列实际上指示给定行属于具有该模式的列的哪个出现,而不是列名的第二部分。例如,如果variable列是a_1,a_2,a_99,则与最后一列对应的a_*值仍为3,而不是99。

variable

稍作更改后,这与您的输出相同

library(data.table)
setDT(df)

all_data_long2 <- melt(df, id.vars = 'dt', 
                       measure.vars = patterns(a = 'a_*', b = 'b_*', c = 'c_*'))

#                       dt variable          a        b         c
#   1: 2000-10-01 10:10:10        1 1181.68131 30.12497  7.733530
#   2: 2000-10-01 10:10:11        1  402.04443 35.97919 11.972216
#   3: 2000-10-01 10:10:12        1 1002.14735 37.94243 10.570481
#   4: 2000-10-01 10:10:13        1  574.04331 30.69238 11.131428
#   5: 2000-10-01 10:10:14        1  221.77960 36.41496  5.349643
#  ---                                                           
# 296: 2000-10-01 10:11:45        3  900.11802 36.16800  8.150693
# 297: 2000-10-01 10:11:46        3  820.79518 34.56636 10.771145
# 298: 2000-10-01 10:11:47        3  825.68334 29.42049 14.811727
# 299: 2000-10-01 10:11:48        3   17.55973 42.44830 14.625586
# 300: 2000-10-01 10:11:49        3  971.93711 37.43062 11.339470

基准测试在速度上存在较大的相对差异,但是这两种方法都不会花费一秒钟的时间来测试数据,因此除非您的实际数据更大,这无关紧要。

setnames(all_data_long2, 'variable', 'loc')

all_data_long2$loc <- as.character(all_data_long2$loc)

all.equal(all_data_long, 
          all_data_long2[,names(all_data_long), with = F],
          check.attributes = F)
# [1] TRUE

答案 2 :(得分:1)

您可以在融化的数据帧上使用tidyr separate函数,以将列名分为参数和位置。下一步是dcast到更宽(仍然很长)的格式,每个参数都有单独的列。

library(reshape2)
library(tidyr)
library(dplyr)

df <- data.frame(
    dt= seq.POSIXt(from = start_time, length.out = 100, by = 1),
    a_1=abs(rnorm(100, 1000, 500)),
    b_1=abs(rnorm(100, 35, 5)),
    c_1=abs(rnorm(100, 10, 2.5)),
    a_2=abs(rnorm(100, 1000, 500)),
    b_2=abs(rnorm(100, 35, 5)),
    c_2=abs(rnorm(100, 10, 2.5)),
    a_3=abs(rnorm(100, 1000, 500)),
    b_3=abs(rnorm(100, 35, 5)),
    c_3=abs(rnorm(100, 10, 2.5))
)

df_long <- melt(df, "dt") %>% 
    separate(variable, c("param", "loc") ) %>% 
    dcast(dt + loc ~ param)

head(df_long)

               dt loc         a        b         c
1 2000-10-01 10:10:10   1 1131.0953 47.29221 10.195120
2 2000-10-01 10:10:10   2 1734.8935 36.09479  9.156366
3 2000-10-01 10:10:10   3 2153.6998 31.95065  8.786107
4 2000-10-01 10:10:11   1  201.1407 34.64221 13.548707
5 2000-10-01 10:10:11   2 1874.0571 40.27503  8.622356
6 2000-10-01 10:10:11   3  867.9888 38.17056 10.339052