使用 merge() 和 sqldf::sqldf() 获得相同的交叉连接

Obtain the same cross join with merge() and sqldf::sqldf()

我有两个数据框:SalesClients。我想使用 sqldf::sqldf()merge() 对这些数据帧执行交叉连接,并使用这两种方法获得完全相同的结果。

到目前为止,我只能获得两个行排序不同的数据框。

这是生成 SalesClients 数据帧的代码:

set.seed(1)

Sales <- data.frame(
  Product = sample(c("Toaster", "Radio", "TV"), size = 7, replace = TRUE),
  CustomerID = c(rep("1_2019", 2), paste(2:3, "2019", sep = "_"), paste(1:3, "2020", sep = "_"))
  )

Sales$Price <- round(ifelse(Sales$Product == "TV", rnorm(1, 400, 20),
                            ifelse(Sales$Product == "Toaster", rnorm(1, 40, 2), 
                                   rnorm(1, 35, 2))))

Clients <- data.frame(
  CustomerID = c(paste(2:4, "2019", sep = "_"), paste(1:2, "2020", sep = "_")),
  State = sample(c("CA", "AZ", "IL", "MA"), size = 5, replace = TRUE)
  )

这是我得到的:


library(sqldf)

# cross join with base R
out1 <- merge(x = Sales, y = Clients, by = NULL)

# cross join with sqldf      
out2 <- sqldf("SELECT *
               FROM Sales
               CROSS JOIN Clients")

out1out2 具有不同的行顺序。如何调整 sqldf() 调用以使 out1out2 完全相同?

这是我得到的最接近的:

merge(x = Sales, y = Clients, by = NULL)  

sqldf("SELECT *
       FROM Sales
       CROSS JOIN Clients 
       ORDER BY State DESC, Clients.CustomerID")

我认为在 sqldf 中包含 ORDER BY 很重要,因为它清楚地表明在 SQL 中,除非明确指示,否则永远无法保证顺序。

如果你在两个变量上只用 "increasing" 做简单的 ORDER BY,那么在 R 中到 order 的翻译将是直接的。但是,由于一个变量在减少,一个在增加,order 本身并不能解决这个问题。然而,正如 所建议的,我们可以对 xtfrm.

做同样的事情
out1 <- merge(x = Sales, y = Clients, by = NULL)
out1 <- out1[order(-xtfrm(out1$State), out1$CustomerID.y),]

out2 <- sqldf::sqldf(
  "SELECT *
   FROM Sales
   CROSS JOIN Clients 
   ORDER BY State DESC, Clients.CustomerID")

### proof they are identical
all(unlist(Map(`==`, out1, out2)))
# [1] TRUE

这里的 xtfrm 辅助函数允许我们为了排序的目的否定列的 "values"。来自 ?xtfrm:

A generic auxiliary function that produces a numeric vector which will sort in the same order as 'x'.

如果该字段已经是数字,我们可以只做 order(-State, CustomerID.y),但它是 character 的事实需要进一步的步骤。阿尔戈 xtfrm.


Edit:在评论中,确定 OP 想要在 SQL 语句中模仿 merge 的排序顺序。不幸的是,因为这是两个帧的笛卡尔积,所以没有应用排序:merge 只是 cbind 将第一帧的所有行与第二帧的第一行进行比较,然后对每一行重复第二个。

这可以通过使用来自 merge:

的一些代码来证明
nx <- nrow(x) # Sales
ny <- nrow(y) # Clients
expand.grid(seq_len(nx), seq_len(ny))
#    Var1 Var2
# 1     1    1
# 2     2    1
# 3     3    1
# 4     4    1
# 5     5    1
# 6     1    2
# ...
# 33    3    7
# 34    4    7
# 35    5    7

其中每个数字都是来自相应帧的一行(x 表示 Var1y 表示 Var2)。如果原始数据是:

## Sales                        ## Clients        
  Product CustomerID Price        CustomerID State
1 Toaster     1_2019    37      1     2_2019    AZ
2   Radio     1_2019    33      2     3_2019    MA
3   Radio     2_2019    33      3     4_2019    AZ
4      TV     3_2019   408      4     1_2020    IL
5 Toaster     1_2020    37      5     2_2020    MA
6      TV     2_2020   408
7      TV     3_2020   408

然后这导致

out1
#    Product CustomerID.x Price CustomerID.y State
# 1  Toaster       1_2019    37       2_2019    AZ
# 2    Radio       1_2019    33       2_2019    AZ
# 3    Radio       2_2019    33       2_2019    AZ
# 4       TV       3_2019   408       2_2019    AZ
# 5  Toaster       1_2020    37       2_2019    AZ
# 6       TV       2_2020   408       2_2019    AZ
# 7       TV       3_2020   408       2_2019    AZ
# 8  Toaster       1_2019    37       3_2019    MA
# ...
# 33 Toaster       1_2020    37       2_2020    MA
# 34      TV       2_2020   408       2_2020    MA
# 35      TV       3_2020   408       2_2020    MA

这将极大地破坏 x (Sales) 中存在的任何排序,即使 y (Clients) 已预先排序(它确实如此) .

因为这个,如果你想要 R 和 SQL 交叉连接解决方​​案之间的一致性,我建议最 transparent/clear 的方法是 merge,然后以类似于 SQL 的方式应用 post-merge 排序。事实上,从教学的角度来看,问这个问题:*"What ordering makes sense to humans?" 如果你在课程计划中断言,在明确强势进入过程之前可能无法保证排序(通过 dplyr::arrangex[order(...),],或 SQL 的 ORDER BY 子句)。找到数据的直观排序,然后在 R 和 SQL.

中证明这一点

旁注:

  1. 您的 sqldf 查询会产生同名列,如果您开始使用列,这会导致一些错误 post-sqldf。这可以通过 select ... as ... 字段命名来缓解。
  2. 不幸的是,目前您的数据按字典顺序排序是违反直觉的:在客户 ID 末尾有年份 建议(是的,我在推断)时间线客户入职,但他们将首先按领先数字排序。类似于 "2020-05-04" 即使作为字符串也能正确排序,而 "05/04/2020" 不能,它可能支持更直观的排序,使最重要的部分成为 id 字符串的前导部分。或者使它们成为整数。或者 UUID(当然是 v4),这些总是很有趣。