【问题标题】:How to merge two tables based on rows and columns in R?如何根据R中的行和列合并两个表?
【发布时间】:2018-12-07 01:40:16
【问题描述】:
df1 <- data.frame(MLID=c('992','992','BJR'),
              Position=c('N0','N1','N1'),
              Weight=c(0.125,0.58,0.69))


df2 <- data.frame(MLID=c('992','992','992','992',
                     'BJR','BJR','BJR','BJR'),
              Weight=c(0,0.251,0.501,1.001,
                       0,0.251,0.501,1.001),
              N0=c(2.80,4.05,4.05,4.05,
                   4.05,4.05,4.05,4.05),
              N1=c(3.47,4.73,4.95,5.15,
                   4.73,7.73,4.95,5.15) )

我想要的是合并这两个表遵循规则:

  1. MLID
  2. 查看位置(N0 或 N1)
  3. 找到重量所在的范围(如 excel 中的近似 vlookup)(2.8 表示 992 N0 的重量收费 (0,0.250),重量收费 4.05 (0.251,0.500) 992 N0,3.47 重量( 0,0.250) 992 N1 等。

所以最终的输出应该是:

MILD  Position  Weight  Charge
992      N0     0.125    2.8
992      N1     0.580    4.95
BJR      N1     0.690    4.95

可以在 R 中实现吗?特别是在 dplyr 包中?

【问题讨论】:

  • 试试library(data.table);setnames(melt(setDT(df2), measure = c("NO", "N1"), variable.name = "Position", value.name = "Charge"), "Weight", "wt")[df1, on = .(MLID, Position, wt &lt;= Weight), mult = "last"]

标签: r dataframe dplyr data.table tidyverse


【解决方案1】:

我们可以对data.table 使用非等连接。使用melt 将第二个数据集重塑为“长”格式,并与“MLID”、“位置”的第一个数据和“重量”列上的非等比较连接,并分配“电荷”的last 值在“df1”中创建列

library(data.table)
setDT(df1)[setnames(melt(setDT(df2), measure = c("NO", "N1"), 
       variable.name = "Position", value.name = "Charge"), "Weight", "wt"), 
      Charge := Charge, on = .(MLID, Position, Weight > wt), mult = "last"] 

df1
#   MLID Position Weight Charge
#1:  992       NO  0.125   2.80
#2:  992       N1  0.580   4.95
#3:  BJR       N1  0.690   4.95

【讨论】:

  • 我无法使用melt() 重现此示例的结果,但我能够使用gather(Position, Charge, -MLID, -Weight) 转换为长格式,然后代码工作:df2 &lt;- df2 %&gt;% gather(Position, Charge, -MLID, -Weight) setDT(df1)[setnames(df2,"Weight", "wt"), Charge := Charge, on = .(MLID, Position, Weight &gt; wt), mult = "last"] df1。只是无法理解为什么使用“wt”。
  • @TheSciGuy 这是2018年发布的。可能版本有一些变化
【解决方案2】:

可以实现使用data.tablerolling连接的选项。首先,df2 需要使用melt 转换为long-format,然后加入df1df2

library(data.table)

setDT(df1, key = c("MLID", "Position","Weight") )

df2 <- melt(df2, id.vars = c("MLID","Weight"), variable.name = "Position", 
                                                      value.name = "Charge")

setDT(df2, key = c("MLID", "Position","Weight"))

df2[df1, roll = "nearest"]
#    MLID Weight Position Charge
# 1:  992  0.580       N1   4.95
# 2:  992  0.125       NO   2.80
# 3:  BJR  0.690       N1   4.95

选项#2:基于tidyverse 的方法可以是:

library(tidyverse)
df2 %>% gather(Position, Charge, -MLID, -Weight) %>%
  right_join(df1, by=c("MLID", "Position")) %>%
  filter(Weight.x <= Weight.y) %>%
  group_by(MLID, Position) %>%
  arrange(Weight.y-Weight.x) %>% 
  slice(1) %>%
  select(MLID, Weight = Weight.y, Position, Charge)

# # A tibble: 3 x 4
# # Groups: MLID, Position [3]
#   MLID  Weight Position Charge
#   <chr>  <dbl> <chr>     <dbl>
# 1 992    0.580 N1         4.95
# 2 992    0.125 NO         2.80
# 3 BJR    0.690 N1         4.95

数据:

OP's 数据稍作修改,以在 data.frame 中包含 stringsAsFactors = FALSE 参数以避免不必要的警告。

df1 <- data.frame(MLID=c('992','992','BJR'),
                  Position=c('NO','N1','N1'),
                  Weight=c(0.125,0.58,0.69), stringsAsFactors = FALSE)


df2 <- data.frame(MLID=c('992','992','992','992',
                         'BJR','BJR','BJR','BJR'),
                  Weight=c(0,0.251,0.501,1.001,
                           0,0.251,0.501,1.001),
                  NO=c(2.80,4.05,4.05,4.05,
                       4.05,4.05,4.05,4.05),
                  N1=c(3.47,4.73,4.95,5.15,
                       4.73,7.73,4.95,5.15), stringsAsFactors = FALSE )

【讨论】:

  • 感谢您的回答。我以前从未接触过 data.table 。这里的rolling join 似乎只选择最接近的值。让我说清楚。与重量在同一行的费用,如N1 BJR重量0.501为4.95,这意味着N1 BJR上(0.501,1.001)范围内的重量为4.95。根据您的回答,如果df1 中最后输入的权重从 0.69 变为 0.88,您的输出将进入下一个充电级别,而不是停留在充电 (0.501,1.001)
  • @DingruiZhang roll 有很多选择。我使用了“最近的”,但您可以尝试使用 roll = TRUE' which is last value carried forward. Or -Inf` 作为下一个向后携带的值。
【解决方案3】:

这是一个基本的 R 版本:

outdf <- merge(df1, df2, by = "MLID")
outdf$dist <- abs(outdf$Weight.x - outdf$Weight.y)
ting <- aggregate(dist ~ MLID + Position, FUN = function(x) min(x), data = outdf)
outdf2 <- merge(outdf, ting, by.x = c("MLID", "Position", "dist"))
outdf2$charge <- ifelse(outdf2$Position == "N1", outdf2$N1, outdf2$NO)
outdf2 <- outdf2[,c("MLID", "Position", "Weight.x", "charge")]
outdf2
# MLID Position Weight.x charge
# 1  992       N1    0.580   4.95
# 2  992       NO    0.125   2.80
# 3  BJR       N1    0.690   4.95

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-07-11
    • 2013-06-07
    • 1970-01-01
    • 2020-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多