【问题标题】:Update column of dataframe1 based on column of dataframe2 + create new row if column1 is not empty根据dataframe2的列更新dataframe1的列+如果column1不为空则创建新行
【发布时间】:2019-08-07 11:21:15
【问题描述】:

我有一个数据框,我想用另一个数据框(查找数据框)中的信息进行更新。

特别是,我想根据 idid2 列将 df1$value 的单元格更新为 df2$value 的单元格。

  • 如果df1$value的单元格是NA,我知道如何使用包data.table

但是

  • 如果df1$value 的单元格不为空,data.table 无论如何都会用df2$value 的单元格更新它。

我不想那样。我想要那个:

如果df1$value 的单元格不为空(在这种情况下,df1$id 所在的行是c),请不要更新该单元格,而是创建一个重复的 df1 行,其中 df1$value 的单元格从df2$value的单元格中获取值

我已经在网上寻找解决方案,但我找不到任何解决方案。有没有办法使用 tidyverse 或 data.table 或 sql-like 包轻松完成?

感谢您的帮助!

编辑:我刚刚意识到我忘了把两个数据帧中的行都是 NA 的极端情况放在其中。根据我到目前为止的回复 (07/08/19 14:42),行 e 已从最后一个数据帧中删除。但我真的需要保留它!

大纲:

> df1
  id id2 value
1 a         1   100
2 b         2   101
3 c         3    50
4 d         4    NA
5 e         5    NA

> df2
  id id2 value
1 c         3   200
2 d         4   201
3 e         5    NA

# I'd like:

> df5
  id id2 value
1 a         1   100
2 b         2   101
3 c         3    50
4 c         3   200
5 d         4   201
6 e         5    NA

这就是我设法解决我的问题的方法,但它很麻烦。

# I create the dataframes
df1 <- data.frame(id=c('a', 'b', 'c', 'd'), id2=c(1,2,3,4),value=c(100, 101, 50, NA))
df2 <- data.frame(id=c('c', 'd', 'e'),id2=c(3,4, 5), value=c(200, 201, 300))

# I first do a left_join so I'll have two value columnes: value.x and value.y
df3 <- dplyr::left_join(df1, df2, by = c("id","id2"))

# > df3
#   id id2 value.x value.y
# 1  a   1     100      NA
# 2  b   2     101      NA
# 3  c   3      50     200
# 4  d   4      NA     201

# I keep only the rows in which value.x is NA, so the 4th row
df4 <- df3 %>%
  filter(is.na(value.x)) %>% 
  dplyr::select(id, id2, value.y)

# > df4
#   id id2 value.y
# 1  d   4     201

# I rename the column "value.y" to "value". (I don't do it with dplyr because the function dplyr::replace doesn't work in my R version)
colnames(df4)[colnames(df4) == "value.y"] <- "value"

# > df4
#   id id2 value
# 1  d   4     201

# I update the df1 with the df4$value. This step is necessary to update only the rows of df1 in which df1$value is NA
setDT(df1)[setDT(df4), on = c("id","id2"), `:=`(value = i.value)]

# > df1
#    id id2 value
# 1:  a   1   100
# 2:  b   2   101
# 3:  c   3    50
# 4:  d   4   201

# I filter only the rows in which both value.x and value.y are NAs
df3 <- as_tibble(df3) %>%
  filter(!is.na(value.x), !is.na(value.y)) %>% 
  dplyr::select(id, id2, value.y)

# > df3
# # A tibble: 1 x 3
#   id      id2 value.y
#   <chr> <dbl>   <dbl>
# 1 c         3     200

# I rename column df3$value.y to value
colnames(df3)[colnames(df3) == "value.y"] <- "value"

# I bind by rows df1 and df3 and I order by the column id
df5 <- rbind(df1, df3) %>% 
  arrange(id)

# > df5
#   id id2 value
# 1  a   1   100
# 2  b   2   101
# 3  c   3    50
# 4  c   3   200
# 5  d   4   201

【问题讨论】:

    标签: r dataframe dplyr data.table


    【解决方案1】:

    与 data.table 的左连接:

    library(data.table)
    setDT(df1); setDT(df2)
    
    df2[df1, on=.(id, id2), .(value = 
      if (.N == 0) i.value 
      else na.omit(c(i.value, x.value))
    ), by=.EACHI]
    
       id id2 value
    1:  a   1   100
    2:  b   2   101
    3:  c   3    50
    4:  c   3   200
    5:  d   4   201
    

    它是如何工作的:语法是x[i, on=, j, by=.EACHI]:对于i = df1的每一行都做j

    在这种情况下,j = .(value = expr) .()list() 的快捷方式,因为通常j 应该返回一个列列表。

    关于表达式,.N 是为i = df1 的每一行找到的x = df2 的行数,因此如果没有找到匹配项,我们将保留来自i 的值;否则我们保留两个表中的值,删除缺失值。


    一种dplyr方式:

    bind_rows(df1, semi_join(df2, df1, by=c("id", "id2"))) %>% 
      group_by(id, id2) %>% 
      do(if (nrow(.) == 1) . else na.omit(.))
    
    # A tibble: 5 x 3
    # Groups:   id, id2 [4]
      id      id2 value
      <chr> <dbl> <dbl>
    1 a         1   100
    2 b         2   101
    3 c         3    50
    4 c         3   200
    5 d         4   201
    

    评论。 dplyr 方式有点尴尬,因为需要do() 才能获得动态确定的行数,但通常不鼓励使用do(),并且不支持n() 和其他辅助函数。 data.table 方式有点尴尬,因为没有简单的半连接功能。


    数据

    df1 <- data.frame(id=c('a', 'b', 'c', 'd'), id2=c(1,2,3,4),value=c(100, 101, 50, NA))
    df2 <- data.frame(id=c('c', 'd', 'e'),id2=c(3,4, 5), value=c(200, 201, 300))
    
    > df1
      id id2 value
    1  a   1   100
    2  b   2   101
    3  c   3    50
    4  d   4    NA
    > df2
      id id2 value
    1  c   3   200
    2  d   4   201
    3  e   5   300
    

    【讨论】:

      【解决方案2】:

      通过base R的另一个想法是从df2中删除与df1不匹配的行,逐行绑定两个数据帧(rbind)并省略NA,即

      na.omit(rbind(df1, df2[do.call(paste, df2[1:2]) %in% do.call(paste, df1[1:2]),]))
      
      #  id id2 value
      #1  a   1   100
      #2  b   2   101
      #3  c   3    50
      #5  c   3   200
      #6  d   4   201
      

      为了满足您的新要求,我们可以保留相同的rbind 方法并根据您的条件进行过滤,即

      dd <- rbind(df1, df2[do.call(paste, df2[1:2]) %in% do.call(paste, df1[1:2]),])
      dd[!!with(dd, ave(value, id, id2, FUN = function(i)(all(is.na(i)) & !duplicated(i)) | !is.na(i))),]
      
      #  id id2 value
      #1  a   1   100
      #2  b   2   101
      #3  c   3    50
      #5  e   5    NA
      #6  c   3   200
      #7  d   4   201
      

      【讨论】:

      • 亲爱的 Sotos,感谢您的回答。我刚刚意识到我忘记在我的代码中放置一个角落案例,现在我又被卡住了。您能帮我看看我编辑过的问题吗? :)
      • 根据您的新情况编辑
      【解决方案3】:

      使用更新连接然后完全外部合并的 data.table 的一种可能方法:

      merge(df1[is.na(value), value := df2[.SD, on=.(id, id2), x.value]], df2, all=TRUE)
      

      输出:

         id id2 value
      1:  a   1   100
      2:  b   2   101
      3:  c   3    50
      4:  c   3   200
      5:  d   4   201
      6:  e   5    NA
      

      数据:

      library(data.table)
      df1 <- data.table(id=c('a', 'b', 'c', 'd', 'e'), id2=c(1,2,3,4,5),value=c(100, 101, 50, NA, NA))
      df2 <- data.table(id=c('c', 'd', 'e'), id2=c(3,4, 5), value=c(200, 201, NA))
      

      【讨论】:

        【解决方案4】:

        这是使用full_joingather 的一种方法

        library(dplyr)
        
        left_join(df1, df2, by = c("id","id2")) %>%
           tidyr::gather(key, value, starts_with("value"), na.rm = TRUE) %>%
           select(-key)
        
        #   id id2 value
        #1   a   1   100
        #2   b   2   101
        #3   c   3    50
        #7   c   3   200
        #8   d   4   201
        

        对于更新的案例,我们可以做

        left_join(df1, df2, by = c("id","id2")) %>%
           tidyr::gather(key, value, starts_with("value")) %>%
           group_by(id, id2) %>%
           filter((all(is.na(value)) & !duplicated(value)) | !is.na(value)) %>%
           select(-key)
        
        #  id      id2 value
        #  <chr> <int> <int>
        #1 a         1   100
        #2 b         2   101
        #3 c         3    50
        #4 e         5    NA
        #5 c         3   200
        #6 d         4   201
        

        【讨论】:

        • 谢谢,我没想过要那样做!但我必须指出,我不希望 df2 的行与 df1 不匹配。所以,在这种情况下,我需要使用 left_join 而不是使用 full_join :)
        • 亲爱的罗纳克。我刚刚意识到我忘记在我的代码中放置一个角落案例,现在我又被卡住了。您能帮我看看我编辑的问题吗?
        猜你喜欢
        • 1970-01-01
        • 2021-12-03
        • 2012-10-29
        • 2019-12-29
        • 1970-01-01
        • 1970-01-01
        • 2021-09-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多