在每个分组之前插入行

4
我有以下列表,我想在每组ID之前添加一行,并保留ID并将A和B设置为1.00。
       ID      DATEE       A      B 
   102984 2016-11-23      2.0    2.0
   140349 2016-11-23      1.5    1.5
   167109 2017-04-16      2.0    2.0
   167109 2017-06-21      1.5    1.5

最终结果:
  ID      DATEE           A      B     
  102984    NA           1.0    1.0
  102984 2016-11-23      2.0    2.0       
  140349    NA           1.0    1.0      
  140349 2016-11-23      1.5    1.5
  167109    NA           1.0    1.0             
  167109 2017-04-16      2.0    2.0       
  167109 2017-06-21      1.5    1.5       

到目前为止,我使用以下代码在每个组的底部添加一行空行 do.call(rbind, by(df,df$ID,rbind,"")) 然而,当我用一个值向量替换 "" 时,我无法将特定值引入其各自的列中。


1
请参考以下链接:https://dev59.com/xIbca4cB1Zd3GeqPW4UY/,并且可以考虑eddi在这里的评论:https://dev59.com/omQn5IYBdhLWcg3wrouL#XpTjnYgBc1ULPQZF6e8v - Frank
6个回答

7

这里有一个使用 tidyverse 的选项。我们通过“ID”获取数据集的不同行,使用mutate将变量“A”、“B”设置为1,“DATEE”设置为NA,然后使用bind_rows将行与原始数据集绑定,并按“ID”进行排序。

library(tidyverse)
df1 %>%
  distinct(ID, .keep_all= TRUE) %>%
  mutate_at(vars("A", "B"), funs((1))) %>% 
  mutate(DATEE = NA) %>%
  bind_rows(., df1) %>%
  arrange(ID)
#     ID      DATEE   A   B
#1 102984       <NA> 1.0 1.0
#2 102984 2016-11-23 2.0 2.0
#3 140349       <NA> 1.0 1.0
#4 140349 2016-11-23 1.5 1.5
#5 167109       <NA> 1.0 1.0
#6 167109 2017-04-16 2.0 2.0
#7 167109 2017-06-21 1.5 1.5

(我假设日期格式已经被修复,例如使用 df1 $ DATEE = as.Date(df1 $ DATEE)。)
或者翻译成基本的R:
new1 = data.frame(ID = unique(df1$ID), DATEE = Sys.Date()[NA_integer_], A = 1, B = 1)
tabs = list(new1, df1)
res  = do.call(rbind, tabs)
res <- res[order(res$ID), ]

#       ID      DATEE   A   B
# 1 102984       <NA> 1.0 1.0
# 4 102984 2016-11-23 2.0 2.0
# 2 140349       <NA> 1.0 1.0
# 5 140349 2016-11-23 1.5 1.5
# 3 167109       <NA> 1.0 1.0
# 6 167109 2017-04-16 2.0 2.0
# 7 167109 2017-06-21 1.5 1.5

或者使用data.table:
library(data.table)
new1 = data.table(ID = unique(df1$ID), DATEE = Sys.Date()[NA_integer_], A = 1, B = 1)
tabs = list(new1, df1)
res  = rbindlist(tabs)
setorder(res)

#       ID      DATEE   A   B
#1: 102984       <NA> 1.0 1.0
#2: 102984 2016-11-23 2.0 2.0
#3: 140349       <NA> 1.0 1.0
#4: 140349 2016-11-23 1.5 1.5
#5: 167109       <NA> 1.0 1.0
#6: 167109 2017-04-16 2.0 2.0
#7: 167109 2017-06-21 1.5 1.5

还有一些其他的方式:
# or let DATEE and other cols be filled as NA
library(data.table)
new1 = data.table(ID = unique(df1$ID), A = 1, B = 1)
tabs = list(df1, new1)
res  = rbindlist(tabs, fill = TRUE, idcol = "src")
setorder(res, ID, -src)
res[, src := NULL ]

# or a more compact option (assuming df1$A has no missing values)
library(data.table)
setDT(df1)[, .SD[c(.N+1, seq_len(.N))], ID][is.na(A), c("A", "B") := 1][]

2
@Frank 谢谢你的更新。我正在通话中,所以没能查看这个。 - akrun

4

以下是两种使用基本R的解决方案

1

根据ID将数据分成子组,向每个子组的顶部添加一行,并在最后使用rbind将所有内容合并。

do.call(rbind, lapply(split(df, df$ID), function(a){
    rbind(setNames(c(a$ID[1], NA, 1, 1), names(a)), a)
}))
#             ID      DATEE   A   B
#102984.1 102984       <NA> 1.0 1.0
#102984.2 102984 2016-11-23 2.0 2.0
#140349.1 140349       <NA> 1.0 1.0
#140349.2 140349 2016-11-23 1.5 1.5
#167109.1 167109       <NA> 1.0 1.0
#167109.3 167109 2017-04-16 2.0 2.0
#167109.4 167109 2017-06-21 1.5 1.5

2

或者你可以通过识别首行(通过 ave 函数)来初始复制,然后在每列中替换相应的值。

df = df[sort(c(1:NROW(df), which(ave(df$A, df$ID, FUN = seq_along) == 1))),]
df$DATEE = replace(df$DATEE, which(ave(df$A, df$ID, FUN = seq_along) == 1), NA)
df$A = replace(df$A, which(ave(df$A, df$ID, FUN = seq_along) == 1), 1)
df$B = replace(df$B, which(ave(df$A, df$ID, FUN = seq_along) == 1), 1)
df
#        ID      DATEE   A   B
#1   102984       <NA> 1.0 1.0
#1.1 102984 2016-11-23 2.0 2.0
#2   140349       <NA> 1.0 1.0
#2.1 140349 2016-11-23 1.5 1.5
#3   167109       <NA> 1.0 1.0
#3.1 167109 2017-04-16 2.0 2.0
#4   167109 2017-06-21 1.5 1.5

4

使用 purrr 的另一个想法。首先,我们通过 ID 进行数据拆分,然后使用 imap(索引映射)和 dfr(通过行绑定创建数据框)循环处理每个组,并使用指定的值添加行。

library(tidyverse)

df %>%
  split(.$ID) %>%
  # We don't have to specify "DATEE", absent variables get missing values
  imap_dfr(~ add_row(.x, ID = .y, A = 1, B = 1, .before = 1))

这将会得到:

#      ID      DATEE   A   B
#1 102984       <NA> 1.0 1.0
#2 102984 2016-11-23 2.0 2.0
#3 140349       <NA> 1.0 1.0
#4 140349 2016-11-23 1.5 1.5
#5 167109       <NA> 1.0 1.0
#6 167109 2017-04-16 2.0 2.0
#7 167109 2017-06-21 1.5 1.5

来自文档:

imap_xxx(x, ...),一种索引映射,是一种简写的方式,如果x有名称,则为map2(x, names(x),...),否则为map2(x, seq_along(x),...)。如果您需要对元素的值和位置进行计算,则这很有用。


3

找到非重复项的索引u,然后重复这些行以得到DF2。然后在DF2中找到非重复项uu,并在这些行中插入NA、1、1,除了第一列之外不要更改任何内容。不使用任何包。

u <- !duplicated(DF$ID)
DF2 <- DF[rep(1:nrow(DF), 1 + u), ]
uu <- !duplicated(DF2$ID)
DF2[uu, -1] <- list(NA, 1, 1)

提供:

> DF2
        ID      DATEE   A   B
1   102984       <NA> 1.0 1.0
1.1 102984 2016-11-23 2.0 2.0
2   140349       <NA> 1.0 1.0
2.1 140349 2016-11-23 1.5 1.5
3   167109       <NA> 1.0 1.0
3.1 167109 2017-04-16 2.0 2.0
4   167109 2017-06-21 1.5 1.5

注意:可重复的输入如下:

Lines <- "
     ID      DATEE       A      B 
   102984 2016-11-23      2.0    2.0
   140349 2016-11-23      1.5    1.5
   167109 2017-04-16      2.0    2.0
   167109 2017-06-21      1.5    1.5"
DF <- read.table(text = Lines, header = TRUE)

更新:已更正输出(代码正确但输出不对应),并简化了代码。


2
加入这个派对,这里有另一个基于R的解决方案。我们复制行名以扩展数据框,然后简单地替换值。
d1 <- df[rep(rownames(df), (!duplicated(df$ID)) + 1),]
d1$DATEE <- replace(d1$DATEE, !duplicated(d1$ID), NA)
d1[-c(1:2)] <- lapply(d1[-c(1:2)], function(i) replace(i, is.na(d1$DATEE), 1))

这意味着,

       ID      DATEE   A   B
1   102984       <NA> 1.0 1.0
1.1 102984 2016-11-23 2.0 2.0
2   140349       <NA> 1.0 1.0
2.1 140349 2016-11-23 1.5 1.5
3   167109       <NA> 1.0 1.0
3.1 167109 2017-04-16 2.0 2.0
4   167109 2017-06-21 1.5 1.5

最后一行取决于原始表中是否存在合法的NA值? - Frank
@Frank 不是原始的那个。是我更新了 DATEE 的那个。 - Sotos
不确定我是否理解。我的意思是,通过df = data.frame(ID = 1, DATEE = Sys.Date()[NA_integer_], A = 2, B = 3),您将覆盖A和B的值,因为原始日期为NA...? - Frank
1
@Frank 哦,好的,我明白了。我不确定它会有什么行为。我会更新带有“duplicated”条件的内容,以确保。 - Sotos

2

我们也可以使用您想要使用的by函数,甚至是基本R中的tapply函数。对于tapply,请确保将INDICES放在一个列表中,因为这是一个数据帧。对于by,没有必要放在列表中。因此,在下面的代码中,我们可以用tapply(A,list(A$ID)...替换by(A,A$ID...,两者都会给出相同的结果。

`rownames<-`(do.call(rbind,by(A,A$ID,
                  function(i) rbind(data.frame(ID=i$ID[1],DATEE=NA,A=1,B=1),i))),NULL)
      ID      DATEE   A   B
1 102984       <NA> 1.0 1.0
2 102984 2016-11-23 2.0 2.0
3 140349       <NA> 1.0 1.0
4 140349 2016-11-23 1.5 1.5
5 167109       <NA> 1.0 1.0
6 167109 2017-04-16 2.0 2.0
7 167109 2017-06-21 1.5 1.5

不需要进行排序,因为这可能会扭曲数据之前的顺序。

rownames<- 对于一些人来说可能有些难以理解。也许值得展示使用 res <- do.call...; rownames(res) <- NULL 的冗长 / 两行方式。 - Frank
1
你是对的。虽然在R中学习新技巧也很好。而且为了能够同时查看结果,应该使用(rownames(res) <- NULL) - Onyambu
我认为你可以使用rbind的参数make.row.names = FALSE... 所以也许是 do.call(rbind, c(by(...), make.row.names = FALSE) 但我不确定。 你可以检查一下。 - Sotos

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