【问题标题】:inner_join() with range of values for one of the keys (year)inner_join(),其中一个键的值范围(年份)
【发布时间】:2019-10-29 14:57:47
【问题描述】:

我有两个格式如下的数据集:

df1
#>           Artist          Album Year
#> 1        Beatles  Sgt. Pepper's 1967
#> 2 Rolling Stones Sticky Fingers 1971

和

df2
#>            Album Year      Producer
#> 1  Sgt. Pepper's 1966 George Martin
#> 2 Sticky Fingers 1971  Jimmy Miller

我想按专辑和年份创建inner_join,但有时“年份”字段会偏离一年:例如,Sgt. Peppers 在 df1 中被列为 1967 年,在 df2 中被列为 1966 年。

所以如果我跑:

df3 <- inner_join(df1, df2, by = c("Album", "Year"))

我明白了:

df3
#>           Artist          Album Year     Producer
#> 1 Rolling Stones Sticky Fingers 1971 Jimmy Miller

然而,我希望两张专辑都加入,只要像 (df1$Year == df2$Year + 1)|(df1$Year == df2$Year - 1) 这样的东西。

我不能只是简单地通过“专辑”连接,因为在我的真实数据集中有一些同名的“专辑”以“年份”来区分。

以下数据集的代码:

df1 <- data.frame(stringsAsFactors=FALSE,
      Artist = c("Beatles", "Rolling Stones"),
       Album = c("Sgt. Pepper's", "Sticky Fingers"),
        Year = c(1967, 1971)
)
df1

df2 <- data.frame(stringsAsFactors=FALSE,
       Album = c("Sgt. Pepper's", "Sticky Fingers"),
        Year = c(1966, 1971),
    Producer = c("George Martin", "Jimmy Miller")
)
df2

【问题讨论】:

    标签: r dplyr inner-join tidyverse fuzzyjoin


    【解决方案1】:

    为了完整起见,这也可以使用data.table的非等连接来解决:

    library(data.table)
    setDT(df1)[, c(.SD, .(ym1 = Year - 1, yp1 = Year + 1))][
      setDT(df2), on = .(Album, ym1 <= Year, yp1 >= Year), nomatch = 0L]
    
               Artist          Album Year  ym1  yp1      Producer
    1:        Beatles  Sgt. Pepper's 1967 1966 1966 George Martin
    2: Rolling Stones Sticky Fingers 1971 1971 1971  Jimmy Miller
    

    或

    setDT(df1)[, c("ym1", "yp1") := .(Year - 1, Year + 1)][setDT(df2), 
               on = .(Album, ym1 <= Year, yp1 >= Year), nomatch = 0L]
    
               Artist          Album Year  ym1  yp1      Producer
    1:        Beatles  Sgt. Pepper's 1967 1966 1966 George Martin
    2: Rolling Stones Sticky Fingers 1971 1971 1971  Jimmy Miller
    

    修改df1。


    顺便说一句:https://github.com/Rdatatable/data.table/issues/1639 有一个功能请求以允许 on 中的动态列。如果实现了,上面的表达式会变成

    setDT(df1)[setDT(df2), on = .(Album, Year - 1 <= Year, Year + 1 >= Year), nomatch = 0L]
    

    【讨论】:

      【解决方案2】:

      如果以后有人读到这个问题,上面的答案很好。另一个答案是:

      1. 加入所有匹配的相册
      2. 只过滤掉年份接近的记录:

      https://stackoverflow.com/a/55863846/8742237

      inner_join(df1, df2, by = c("Album")) %>% 
        filter(abs(Year.x - Year.y)<2)
      
      >           Artist          Album Year.x Year.y      Producer
      > 1        Beatles  Sgt. Pepper's   1967   1966 George Martin
      > 2 Rolling Stones Sticky Fingers   1971   1971  Jimmy Miller
      

      【讨论】:

        【解决方案3】:

        也许rolling join 会解决这个问题。它适用于您的数据样本,但您的实际数据中可能存在棘手的边缘情况。

        在下面的代码中,roll="nearest" 将匹配每个专辑的最接近年份值(“滚动”部分仅适用于最后一个连接列,在本例中为 Year)。

        library(data.table)
        
        setDT(df1)
        setDT(df2)
        
        setkey(df1, Album, Year)
        setkey(df2, Album, Year)
        
        joined = df1[df2, roll="nearest"]
        
        joined
        
                   Artist          Album Year      Producer
        1:        Beatles  Sgt. Pepper's 1966 George Martin
        2: Rolling Stones Sticky Fingers 1971  Jimmy Miller
        

        【讨论】:

          【解决方案4】:

          我们可以在这里尝试使用sqldf 包,因为您的要求可以使用 SQL 连接轻松表达:

          library(sqldf)
          
          sql <- "SELECT t1.Artist, t1.Album, t1.Year, t2.Album, t2.Year, t2.Producer
                  FROM df1 t1
                  INNER JOIN df2 t2
                      ON ABS(t1.Year - t2.Year) <= 1"
          df3 <- sqldf(sql)
          

          如果要从两个表中选择所有字段,请使用:

          SELECT t1.*, t2.* FROM ...
          

          但请注意,一般来说SELECT * 是不受欢迎的,最好总是列出要选择的列。

          【讨论】:

          • 如果我想SELECT t1 和 t2 中的所有字段,第一行代码是否有一个简单的通配符?谢谢。
          【解决方案5】:

          将Year + 1 添加到df2 然后加入?如果你想覆盖两个方向的范围,你也可以添加Year - 1。

          library(dplyr)
          
          inner_join(df1, df2 %>%  bind_rows(df2 %>%  mutate(Year = Year + 1)),
                          by = c("Album", "Year"))
          
          #          Artist          Album Year      Producer
          #1        Beatles  Sgt. Pepper's 1967 George Martin
          #2 Rolling Stones Sticky Fingers 1971  Jimmy Miller
          

          【讨论】:

          • 我可以检查一下我对您的解决方案的理解吗?我是否认为您使用bind_rows 基本上复制并添加了 df2 的副本,并且所有年份都增加了,所以 df2 的大小是原来的两倍,并且更有可能加入?
          • 是的,完全正确。它只是 df2 的副本,年份值增加了 1。
          • 有没有办法可以在同一代码中覆盖另一个方向?我试过inner_join(df1, df2 %&gt;% bind_rows(df2 %&gt;% mutate(Year = Year + 1) %&gt;% bind_rows(df2 %&gt;% mutate(Year = Year - 1)), by = c("Album", "Year"))),但我一定做错了什么。谢谢
          • @RANdStata 是的,做inner_join(df1, bind_rows(df2, df2 %&gt;% mutate(Year = Year + 1), df2 %&gt;% mutate(Year = Year - 1)), by = c("Album", "Year"))
          猜你喜欢
          • 1970-01-01
          • 2014-12-02
          • 2021-04-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-12-16
          • 1970-01-01
          • 2018-06-09
          相关资源
          最近更新 更多