【问题标题】:dplyr mutate - How to properly apply custom function with mutate?dplyr mutate - 如何使用 mutate 正确应用自定义函数?
【发布时间】:2018-10-17 14:00:59
【问题描述】:

我正在尝试迁移数据库并希望使用 R 来协助迁移。作为迁移过程的一部分,我需要更新“项目 ID”,因为它们已更改。我创建了一个函数来将旧 ID 映射到新 ID:

old_to_new <- function(id, df) {
  return (df[which(df$Old == id), ]$New)
}

但是,每当我尝试应用它来在我的数据框中添加一个新列(从数据库表加载)时:

library(tidyverse)
library(RODBC)

cn <- odbcDriverConnect(connection="Driver={SQL Server Native Client 11.0};server=xxx;database=xxx;uid=xxx;pwd=xxx;")
df <- sqlQuery(cn, "SELECT * FROM [MaintDB_New].[dbo].[Priority]")
ticket_df <- sqlQuery(cn, "SELECT * FROM [MaintDB_New].[dbo].[Tickets]")
ticket_details_df <- sqlQuery(cn, "SELECT * FROM [MaintDB_New].[dbo].[Ticket_Details]")
new_items <- read_csv("./ticket_itm_export_temp.csv", col_names = c("Old", "Name", "New"))

ticket_df_new <- ticket_df %>% mutate(item_id = old_to_new(itemID, new_items))

我收到以下错误:

Error in `[[<-.data.frame`(`*tmp*`, col, value = c(NA_integer_, NA_integer_,  : 
  replacement has 280 rows, data has 69430
In addition: Warning message:
In df$Old == id :
  longer object length is not a multiple of shorter object length

我做错了什么,正确的方法是什么。我在尝试使用 ddplyr 时收到了类似的错误。

我是 R 新手,所以如果这是一个明显的问题,我深表歉意。

编辑 - 添加数据结构:

    head(ticket_df)
  ticketID propertyID itemID roomNumber assignedToID isOpen openID latestID
1       11         10      1       <NA>           NA      0     22       23
2       12         17      1       <NA>           NA      0     24      289
3       13         17      1       <NA>           NA      0     25      292
4       14         17     17       <NA>           NA      0     26     4411
5       15         17     68       <NA>           NA      0     27      296
6       16         17     74       <NA>           NA      0     28      294

head(new_items)
           Old Name                    New
      <int> <chr>                 <int>
    1   257 Register Cash Drawers   425
    2   253 Alarm System            426
    3   135 CREDENZA/ ARMOIRE       427
    4    55 Back Office PC          428
    5   183 Backup All Data         429
    6   260 Base Boards             430

到 dput 输出的链接:ticket_dfnew_items

【问题讨论】:

  • 我建议只做一个left_join,类似ticket_df %&gt;% left_join(new_items, by = c("id" == "Old")) %&gt;% mutate(item_id = New)。还要确保new_items 没有重复的Old 条目,否则您最终会得到比开始时更多的行。如果这不起作用,请发布可重现的示例数据,以便我们了解发生了什么。使用 dput 为我们提供相关数据帧的前 10 行的复制/粘贴版本(看起来像 ticket_dfnew_items 是相关的)。
  • 谢谢,格雷戈尔。我不相信这对我有用。我已经为第一个数据帧添加了 head 的输出。
  • 您的数据仍然不完整:帧之间没有任何东西可以联系在一起(没有共同的“关键”)。 (请使用dput。)
  • 关于可重现问题的一件事:代表性数据需要表明您试图展示的行为。在这种情况下,ticket_df$itemIdnew_items$Old 之间没有匹配项,因此我们可能处理的任何代码都不会执行任何操作。 (我试图根据找到任何匹配的列来推断“键”。感谢您澄清底层数据结构,所以我们现在只需要一个更具代表性的数据样本......请使用dput。)
  • 我通过更改您的示例数据(在问题中)为我们提供匹配来回答。通常,许多人在问题中避开链接(例如,Dropbox)有两个原因:(1)由于病毒等风险而犹豫下载随机二进制文件; (2) 当这些链接失效时,这个问题不再是可重现的。目标是尽可能地封装在问题文本中。这建议将示例数据和代码修剪到演示问题所需的最小限度。

标签: r dplyr


【解决方案1】:

我(真的!)认为 Gregor 对left_joining 的评论很有道理。我将通过更改您的一些值来强制进行一些匹配:

new_items$Old[1:2] <- c(17L,74L)

现在加入:

library(dplyr)

ticket_df %>%
  left_join(select(new_items, Old, New), by=c("itemID" = "Old"))
#   ticketID propertyID itemID roomNumber assignedToID isOpen openID latestID New
# 1       11         10      1         NA           NA      0     22       23  NA
# 2       12         17      1         NA           NA      0     24      289  NA
# 3       13         17      1         NA           NA      0     25      292  NA
# 4       14         17     17         NA           NA      0     26     4411 425
# 5       15         17     68         NA           NA      0     27      296  NA
# 6       16         17     74         NA           NA      0     28      294 426

如果您对此感到满意,只需重新分配:

ticket_df %>%
  left_join(select(new_items, Old, New), by=c("itemID" = "Old")) %>%
  mutate(itemID = if_else(is.na(New), itemID, New)) %>%
  select(-New)
#   ticketID propertyID itemID roomNumber assignedToID isOpen openID latestID
# 1       11         10      1         NA           NA      0     22       23
# 2       12         17      1         NA           NA      0     24      289
# 3       13         17      1         NA           NA      0     25      292
# 4       14         17    425         NA           NA      0     26     4411
# 5       15         17     68         NA           NA      0     27      296
# 6       16         17    426         NA           NA      0     28      294

您也可以使用mutate(itemID = coalesce(New, itemID)),谢谢@Gregor。


但是,如果你需要使用一个函数(也许你的问题更复杂或者你需要更通用的东西),那么请注意:

  • 通常,mutate 中使用的函数需要返回长度为 1 或与给定长度相同的向量;这意味着子集(就像您对df[which(df$Old == id), ]$New 所做的那样)通常不起作用。 (如果你可以保证它总是返回长度 1,那么它就不会出错,但我猜这不安全。)。同样,summarize 要求(我相信)函数返回长度 1。

这是一个有点草率但得到相同结果的想法:

myfunc <- function(id, changes) {
  ind <- match(id, changes[["Old"]])
  indnonna <- !is.na(ind)
  id[which(indnonna)] <- changes[["New"]][ind[indnonna]]
  id
}

ticket_df %>%
  mutate(newid = myfunc(itemID, new_items))
#   ticketID propertyID itemID roomNumber assignedToID isOpen openID latestID newid
# 1       11         10      1         NA           NA      0     22       23     1
# 2       12         17      1         NA           NA      0     24      289     1
# 3       13         17      1         NA           NA      0     25      292     1
# 4       14         17     17         NA           NA      0     26     4411   425
# 5       15         17     68         NA           NA      0     27      296    68
# 6       16         17     74         NA           NA      0     28      294   426

您显然可以直接分配给itemID 而不是不同的列。我仍然不鼓励这样做,因为(1)连接效率更高; (2)我想更多地使用这个函数,也许可以找到一个更健壮的方法; (3) 它将new_items 的结构(即特定的列名)硬编码到函数中,而进行连接允许您在连接时指定发生的情况,使代码紧挨着使用结构的元素.

【讨论】:

  • 哇,非常感谢您提供的有效答案和解释。希望我能给你更多的支持(感谢 Gregor 的 left_join 推荐)。
  • 对一个好答案的小评论:dplyr 从一些 SQL 风格中移植了方便的 coalesce 函数,以获取第一个非缺失值,简化 if_else(is.na(New), itemID , 新)` 到coalesce(New, itemID)
  • 我正在处理其他一些事情并尝试了coalesce,但它没有工作......不知道为什么,它现在工作了,谢谢@Gregor。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-05
  • 2020-04-28
  • 1970-01-01
  • 2017-03-06
相关资源
最近更新 更多