如何在数据框的列上使用滞后功能

时间:2017-11-12 12:34:26

标签: r dplyr tidyverse

我有一个宽格式的数据框,大约有100,000多行和700多列。我需要计算单个列作为其前一列的比率。以下模拟我的问题

df = data.frame(
        name = c("c1", "c2", "c3"), 
        m12 = c(50, 150, 200), 
        m23 = c(100, 180, 120), 
        m37 = c(150, 414, 180)
      )

所需的输出数据帧(dfo)将是

  name m12  r2 r3
1   c1  50 2.0  1.5
2   c2 150 1.2  2.3
3   c3 200 0.6  1.5

其中

dfo$r2 = df$m23/df$m12
dfo$r3 = df$m37/df$m23

我需要的是识别给定列位置获取前一列并使用它来计算比率。使用for循环,我可以得到我正在寻找的东西,但那不是" R方式"做事。

我如何获得R-ish解决方案?我的偏好是使用tidyverse或base R方式,但我也对其他方法持开放态度。它应该以一种通用的方式在具有给定结构的任意数量的行或列的数据框上工作。

4 个答案:

答案 0 :(得分:2)

您可以使用lapply计算列,然后将它们绑定到现有的data.frame。

dfo = cbind(df, lapply(3:ncol(df), function(i) df[,i]/df[,i-1]))
names(dfo)[5:6] = c("r2", "r3")
dfo
  name m12 m23 m37  r2  r3
1   c1  50 100 150 2.0 1.5
2   c2 150 180 414 1.2 2.3
3   c3 200 120 180 0.6 1.5

答案 1 :(得分:1)

您可以使用基本的R这样做:

df = data.frame(
    name = c("c1", "c2", "c3"),
    m12 = c(50, 150, 200),
    m23 = c(100, 180, 120),
    m37 = c(150, 414, 180)
)

# Get the index of all columns that start with "m"
z = which(grepl("^m",colnames(df)))

# calculate the proportion to the previous column
proportions = df[,z[-1]]/df[,z[-length(z)]]

结果:

> proportions
  m23 m37
1 2.0 1.5
2 1.2 2.3
3 0.6 1.5

计算新列后,使用colnames

相应地更改其名称
newName = paste0("r",2:length(z))
colnames(proportions) = newName

> proportions
   r2  r3
1 2.0 1.5
2 1.2 2.3
3 0.6 1.5

答案 2 :(得分:0)

如果你想以整齐的方式做这件事,你应该先将gather()列放入行中:


library(dplyr, warn.conflicts = FALSE)
library(tidyr)

df = data.frame(
  name = c("c1", "c2", "c3"), 
  m12 = c(50, 150, 200), 
  m23 = c(100, 180, 120), 
  m37 = c(150, 414, 180)
)

df_gathered <- gather(df, "key", "value", starts_with("m"))

df_gathered
#>   name key value
#> 1   c1 m12    50
#> 2   c2 m12   150
#> 3   c3 m12   200
#> 4   c1 m23   100
#> 5   c2 m23   180
#> 6   c3 m23   120
#> 7   c1 m37   150
#> 8   c2 m37   414
#> 9   c3 m37   180

然后,您可以像往常一样使用lag(),因为列现在是行。虽然我的代码在这里可能不够智能,但如果您熟悉争论行,则应该更容易使用这种形式的数据:

df_normalized <- df_gathered %>%
  group_by(name) %>%
  mutate(value_normalized =  value / lag(value),
         # treat the first item (m12) differently
         key   = if_else(is.na(value_normalized), key,   paste0("r", row_number() - 1L)),
         value = if_else(is.na(value_normalized), value, value_normalized)) %>%
  select(-value_normalized)

df_normalized
#> # A tibble: 9 x 3
#> # Groups:   name [3]
#>     name   key value
#>   <fctr> <chr> <dbl>
#> 1     c1   m12  50.0
#> 2     c2   m12 150.0
#> 3     c3   m12 200.0
#> 4     c1    r1   2.0
#> 5     c2    r1   1.2
#> 6     c3    r1   0.6
#> 7     c1    r2   1.5
#> 8     c2    r2   2.3
#> 9     c3    r2   1.5

最后,如果需要,可以将数据spread()添加到列范围内。

spread(df_normalized, key, value)
#> # A tibble: 3 x 4
#> # Groups:   name [3]
#>     name   m12    r1    r2
#> * <fctr> <dbl> <dbl> <dbl>
#> 1     c1    50   2.0   1.5
#> 2     c2   150   1.2   2.3
#> 3     c3   200   0.6   1.5

答案 3 :(得分:0)

我们可以使用dplyrpurrr中的函数。我们的想法是将数据帧转换为列表进行操作,然后将其转换回数据帧。

library(dplyr)
library(purrr)

df2 <- df %>% select(-name)

df3 <- map2_dfc(df2[-1], df2[-ncol(df2)], ~.x/.y) %>%
  setNames(paste0("r", 2:ncol(df2)))

df4 <- bind_cols(df, df3)
df4
#   name m12 m23 m37  r2  r3
# 1   c1  50 100 150 2.0 1.5
# 2   c2 150 180 414 1.2 2.3
# 3   c3 200 120 180 0.6 1.5

来自dplyrtidyr的解决方案。它使用gather将数据框从宽格式转换为长格式,使用mutatelag计算值,然后重新排列列。最后,将其转换回宽格式。 df3是最终输出。

library(dplyr)
library(tidyr)

df2 <- df %>%
  gather(M, value1, -name) %>%
  arrange(name, M) %>%
  group_by(name) %>%
  mutate(value2 = value1/lag(value1)) %>%
  mutate(R = paste0("r", 1:n()))

df3 <- bind_rows(df2 %>% select(name, column = M, value = value1),
                 df2 %>% select(name, column = R, value = value2)) %>%
  drop_na(value) %>%
  spread(column, value)
df3

# # A tibble: 3 x 6
# # Groups:   name [3]
#     name   m12   m23   m37    r2    r3
# * <fctr> <dbl> <dbl> <dbl> <dbl> <dbl>
# 1     c1    50   100   150   2.0   1.5
# 2     c2   150   180   414   1.2   2.3
# 3     c3   200   120   180   0.6   1.5