【问题标题】:Obtain the same cross join with merge() and sqldf::sqldf()使用 merge() 和 sqldf::sqldf() 获得相同的交叉连接
【发布时间】:2020-08-15 12:03:04
【问题描述】:

我有两个数据框: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")

【问题讨论】:

  • 使用out1 &lt;- merge(...)out2 &lt;- sqldf(...),然后dplyr::arrange(out1, desc(State), CustomerID.y) 使两者相同(列名除外)。这实际上只是将您的 sql order by 转换为 dplyr。我没有尝试过基本的 R 等价物,因为你想减少一个而不是另一个......
  • @r2evans 感谢您的评论。实际上我不想要任何特定变量排序的结果,我只想获得两个相等的数据帧。 sqldf() 中的 order by 子句和 select 子句之后的变量只是我试图复制使用 merge() 获得的默认排序,但到目前为止我还没有成功。
  • @r2evans 我编辑了问题以包含更多信息,以便更容易理解我想要完成的工作。
  • 处理 SQL 数据库时的一件事(您可能已经知道这一点):您不能相信数据在 中返回的顺序,除非您使用“order by”明确设置它 ”。它可能会也可能不会尊重进入其中的数据的顺序。现在这是一般的 SQL,也许 SQLite 尊重自然(导入)顺序,我不知道。但听起来你试图让sqldf 模仿merge 输出的顺序,对吗?嗯。这听起来像是一种学术追求,到底是什么原因导致了这个兔子洞?
  • @r2evans 是的,这就是我想要做的。我有一些使用merge 连接所有表格的讲座幻灯片,我试图将这些连接中的每一个与sqldf 中的等效连接并行。我课程的重点是 R,而不是 SQL,但我决定包含一些 SQL 示例(使用sqldf),这样我的学生在工作时就不会看到他们的第一个 SQL 代码(就像几年前发生在我身上一样) )。我过去使用过 SQL,但主要是通过 R 和(在我以前的工作中)SAS,但自从我学习了 dplyr,我就停止使用 sqldf

标签: r sqldf


【解决方案1】:

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

如果你在做简单的ORDER BY,只是在两个变量上“增加”,那么在 R 中转换为order 将是直接的。然而,由于一个变量在减少,一个变量在增加,order 本身并不能解决这个问题。然而,正如https://stackoverflow.com/a/3316719 所建议的,我们可以对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 辅助函数允许我们为排序目的否定列的“值”。来自?xtfrm

一个通用的辅助函数,它产生一个数字向量,该向量将按与“x”相同的顺序排序。

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


编辑:在 cmets 中,确定 OP 想要在 SQL 语句中模仿 merge 的排序顺序。不幸的是,因为这是两帧的笛卡尔积,所以没有应用排序:merge 只是cbinds 第一帧的所有行与第二帧的第一行相对,然后对第二帧的每一行重复。

这可以通过使用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 交叉连接解决方​​案之间保持一致,我建议最透明/清晰的方法是在 R 中 merge 然后应用 post-@987654350 @ 以类似于 SQL 的方式排序。事实上,从教学的角度来看,问一个问题:*“什么排序对人类有意义?”如果您在课程计划中断言,除非明确地强行进入流程(通过dplyr::arrangex[order(...),] 或 SQL 的ORDER BY 子句),否则可能无法保证排序。找出数据的直观顺序,然后在 R 和 SQL 中进行演示。

旁注:

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

【讨论】:

  • 我明白了。因为您正在对它们进行笛卡尔积,所以顺序由输入本身控制,而不是由值本身的任何属性控制。如果您查看merge 的源代码,您会发现它是用expand.grid 简单地完成的,其中第一个参数首先被迭代。我不知道您是否可以在 SQL 中轻松模仿expand.grid 中发生的(缺少)排序。
  • 在内部,如果nx &lt;- nrow(x)ny &lt;- nrow(y),则输出的行由expand.grid(seq_len(nx), seq_len(ny)) 控制(其中Var1 现在表示来自帧xVar2 的行来自框架y)。
  • 有鉴于此,在给定数据上下文的情况下应用排序后merge 可能是有益的,然后在 SQL 中复制它。 (如果客户 ID 在字符串中没有年份 last 可能会很有用......这似乎违背了我按客户 ID 排序的假设:-)。
  • 再次感谢您的宝贵见解!考虑到数据的上下文,您建议应用 post-merge 排序可能是处理现实生活数据的最佳和最安全的方法。我知道交叉连接客户和销售表可能永远不会有意义,但我想使用相同的数据来演示所有合并。由于这是一个学术虚构的例子,我想我会采纳你的建议并使用数字CustomerID,以及由CustomerID 预先排序的Clients 表。然后使用ORDER BY Clients.CustomerID
  • 无论如何,我一直希望Clients 表按CustomerID 排序,但是当我创建示例时,我忽略了字符串的排序方式。在CustomerID 中包含最后一年,结果证明这个字符串是个坏主意。
猜你喜欢
  • 1970-01-01
  • 2021-07-19
  • 2018-07-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-01
  • 2021-07-17
  • 2012-02-10
相关资源
最近更新 更多