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

How to use lag functionality on columns of a data frame

我有一个宽格式的数据框,大约有 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 循环,我可以得到我想要的东西,但这不是 "The R way" 做事。

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

您可以像这样使用基本 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

您可以使用 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

如果你想做这种整洁的方式,你应该先 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

我们可以使用 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