在 R 中,拆分数据帧,使子集数据帧包含前一个数据帧的最后一行和后续数据帧的第一行

In R, split a dataframe so subset dataframes contain last row of previous dataframe and first row of subsequent dataframe

关于如何拆分数据帧有很多答案,例如How to split a data frame?

但是,我想拆分一个数据帧,以便较小的数据帧包含前一个数据帧的最后一行和下一个数据帧的第一行。

这是一个例子

n <- 1:9
group <- rep(c("a","b","c"), each = 3)
data.frame(n = n, group)

  n  group
1 1     a
2 2     a
3 3     a
4 4     b
5 5     b
6 6     b
7 7     c
8 8     c
9 9     c

我希望输出如下所示:

 d1 <- data.frame(n = 1:4, group = c(rep("a",3),"b"))
 d2 <- data.frame(n = 3:7, group = c("a",rep("b",3),"c"))
 d3 <- data.frame(n = 6:9, group = c("b",rep("c",3)))
 d <- list(d1, d2, d3)
 d

[[1]]
  n group
1 1     a
2 2     a
3 3     a
4 4     b

[[2]]
  n group
1 3     a
2 4     b
3 5     b
4 6     b
5 7     c

[[3]]
  n group
1 6     b
2 7     c
3 8     c
4 9     c

完成此任务的有效方法是什么?

也可以用 data.frame 来完成,但是有没有理由不使用 data.table?这也有并行执行的选项。

library(data.table)
n <- 1:9
group <- rep(c("a","b","c"), each = 3)
df <- data.table(n = n, group)
df[, `:=` (group = factor(df$group))]
df[, `:=` (group_i = seq_len(.N), group_N = .N), by = "group"]

library(doParallel)
groups <- unique(df$group)
foreach(i = seq(groups)) %do% {
  df[group == groups[i] | (as.integer(group) == i + 1 & group_i == 1) | (as.integer(group) == i - 1 & group_i == group_N), c("n", "group"), with = FALSE]  
}
[[1]]
   n group
1: 1     a
2: 2     a
3: 3     a
4: 4     b
[[2]]
   n group
1: 3     a
2: 4     b
3: 5     b
4: 6     b
5: 7     c
[[3]]
   n group
1: 6     b
2: 7     c
3: 8     c
4: 9     c

我本来打算在@cdetermans 的回答下发表评论,但现在为时已晚。 您可以使用 data.table::shift(或 dyplr::lag)概括他的方法以找到组索引,然后 运行 范围内的简单 lapply,例如

library(data.table) # v1.9.6+ 
indx <- setDT(df)[, which(group != shift(group, fill = TRUE))]
lapply(Map(`:`, c(1L, indx - 1L), c(indx, nrow(df))), function(x) df[x,])
# [[1]]
#    n group
# 1: 1     a
# 2: 2     a
# 3: 3     a
# 4: 4     b
# 
# [[2]]
#    n group
# 1: 3     a
# 2: 4     b
# 3: 5     b
# 4: 6     b
# 5: 7     c
# 
# [[3]]
#    n group
# 1: 6     b
# 2: 7     c
# 3: 8     c
# 4: 9     c

假设 DF 是原始的 data.frame,即具有列 ngroup 的那个。设 nDF 中的行数。现在定义一个函数 extract,给定一系列索引 ix 将其扩大到包括第一个之前和最后一个之后的那个,然后 returns DF 的那些行。现在我们已经定义了 extract,按组拆分向量 1,...,n 并将 extract 应用于拆分的每个组件。

n <- nrow(DF)
extract <- function(ix) DF[seq(max(1, min(ix) - 1), min(n, max(ix) + 1)), ]
lapply(split(seq_len(n), DF$group), extract)

$a
  n group
1 1     a
2 2     a
3 3     a
4 4     b

$b
  n group
3 3     a
4 4     b
5 5     b
6 6     b
7 7     c

$c
  n group
6 6     b
7 7     c
8 8     c
9 9     c

或者为什么不尝试 good'ol by,它“[a]ppl[ies] 数据帧的函数按因子 [INDICES] 拆分”。

by(data = df, INDICES = df$group, function(x){
   id <- c(min(x$n) - 1, x$n, max(x$n) + 1)
   na.omit(df[id, ])
   })


# df$group: a
#   n group
# 1 1     a
# 2 2     a
# 3 3     a
# 4 4     b
# -------------------------------------------------------------------------------- 
#   df$group: b
# n group
# 3 3     a
# 4 4     b
# 5 5     b
# 6 6     b
# 7 7     c
# -------------------------------------------------------------------------------- 
#   df$group: c
#   n group
# 6 6     b
# 7 7     c
# 8 8     c
# 9 9     c

尽管 byprint 方法创建了 'fancy' 输出,但(默认)结果是 list,其元素由分组级别命名变量(只需在结果对象上尝试 strnames)。

这是另一种 dplyr 方式:

library(dplyr)

data = 
  data_frame(n = n, group) %>%
  group_by(group)

firsts = 
  data %>%
  slice(1) %>%
  ungroup %>%
  mutate(new_group = lag(group)) %>%
  slice(-1)

lasts = 
  data %>%
  slice(n()) %>%
  ungroup %>%
  mutate(new_group = lead(group)) %>%
  slice(-n())

bind_rows(firsts, data, lasts) %>%
  mutate(final_group = 
           ifelse(is.na(new_group),
                  group,
                  new_group) ) %>%
  arrange(final_group, n) %>%
  group_by(final_group)