【问题标题】:How to know customers who placed next order before delivery/receiving of earlier order? In R如何在交付/接收较早的订单之前知道下一个订单的客户?在 R 中
【发布时间】:2021-01-06 12:46:23
【问题描述】:

我有一个有两个日期的大型数据库。例如。获取超市数据 (http://www.tableau.com/sites/default/files/training/global_superstore.zip) 'Orders' Sheet。

一个日期是订单日期,另一个是发货/交货日期(假设它是交货日期)。我想知道那些下一个订单的客户的所有订单的详细信息,而无需等待他们之前的任何一个订单的发货/交付。

例如ID 为“ZC-21910”的客户于 2014 年 6 月 12 日下订单 ID 为 CA-2014-133928,该订单于 2014 年 6 月 18 日发货。然而,同一客户于 6 月 13 日下订单 ID 为“IT-2014-3511710” 2014 年,即 2014 年 6 月 18 日之前(早期订单之一的发货日期)。

最好在单独的数据框/表中过滤掉所有此类订单(订单 ID)。

如何在 R 中做到这一点?

示例数据集

> dput(df)
structure(list(customer_id = c("A", "A", "A", "B", "B", "C", 
"C"), order_id = structure(1:7, .Label = c("1", "2", "3", "4", 
"5", "6", "7"), class = "factor"), order_date = structure(c(17897, 
17901, 17912, 17901, 17902, 17903, 17905), class = "Date"), ship_date = structure(c(17926, 
17906, 17914, 17904, 17904, 17904, 17906), class = "Date")), row.names = c(NA, 
-7L), class = c("tbl_df", "tbl", "data.frame"))

【问题讨论】:

    标签: r dataframe dplyr iteration rolling-computation


    【解决方案1】:

    这是我在 R 中构建此工作流的方式,注意:在 Tableau 中复制功能将非常困难。

    # Install pacakges if they are not already installed: necessary_packages => vector
    necessary_packages <- c("readxl")
    
    # Create a vector containing the names of any packages needing installation:
    # new_pacakges => vector
    new_packages <- necessary_packages[!(necessary_packages %in%
                                           installed.packages()[, "Package"])]
    
    # If the vector has more than 0 values, install the new pacakges
    # (and their) associated dependencies:
    if(length(new_packages) > 0){install.packages(new_packages, dependencies = TRUE)}
    
    # Initialise the packages in the session:
    lapply(necessary_packages, require, character.only = TRUE)
    
    # Store a scalar of the link to the data: durl => character scalar
    durl <- "http://www.tableau.com/sites/default/files/training/global_superstore.zip"
    
    # Store the path to the temporary directory: tmpdir_path => character scalar
    tmpdir_path <- tempdir()
    
    # Store a character scalar denoting the link to the zipped directory
    # that is to be created: zip_path => character scalar
    zip_path <- paste0(tmpdir_path, "/tableau.zip")
    
    # Store a character scalar denoting the link to the unzipped directory
    # that is to be created: unzip_path => character scalar
    unzip_path <- paste0(tmpdir_path, "/global_superstore")
    
    # Download the zip file: global_superstore.zip => stdout (zip_path)
    download.file(durl, zip_path)
    
    # Unzip the file into the unzip directory: tableau.zip => stdout (global_superstore)
    unzip(zipfile = zip_path, exdir = unzip_path)
    
    # Read in the excel file: df => data.frame
    df <- read_xls(normalizePath(list.files(unzip_path, full.names = TRUE)))
    
    # Regex the vector names to fit with R convention: names(df) => character vector 
    names(df) <- gsub("\\W+", "_", tolower(trimws(names(df), "both")))
    
    # Allocate some memory by creating an empty list the same size as the number of 
    # customers: df_list => list
    df_list <- vector("list", length(unique(df$customer_id)))
    
    # Split the data.frame into the list by the customer_id: df_list => lis
    df_list <- with(df, split(df, customer_id))      
    
    # Sort the data (by date) and test whether or not each customer waited for their 
    # order before ordering again: orders_prior_to_delivery => data.frame
    orders_prior_to_delivery <- data.frame(do.call("rbind", Map(function(x){
      # Order the data.frame: y => data.frame
      y <- x[order(x$order_date),]
      # Return only the observations where the customer didn't wait: 
      # data.frame => GlobalEnv()
      with(y, y[c(FALSE, 
        apply(data.frame(sapply(order_date[-1], `<`, ship_date[-nrow(y)])), 2, any)),])
    }, 
    df_list)), row.names = NULL, stringsAsFactors = FALSE)
    
    # Unique customers and orders that were ordered prior to shipping the 
    # previous order: cust_orders_prior_to_delivery => data.frame
    cust_orders_prior_to_delivery <- 
      unique(orders_prior_to_delivery[,c("order_id", "customer_id")])
    

    【讨论】:

    • 当我运行这个时,我得到 9952 行。当我从你的评论运行你的代码到我的答案时,我得到 19819 行。当我从我的答案中运行代码时,我得到 1625 行。很明显,这些脚本是不等价的。老实说,我不明白with(y, y[c(FALSE, (diff.Date(y$order_date) &lt; diff.Date(y$ship_date))),]) 做了什么。似乎您正在将每一行与前一行进行比较,但与前一个发货日期冲突的订单日期不一定与紧接其上方的行冲突。
    • 请看我上面修改后的解决方案。
    • 我要感谢@hello_friend。您的 cmets 促使我在原始解决方案中找到错误。 (我已经在上面更新了)。
    • @MichaelDewar 你也一样,你的 cmets 对我也一样。 with() 允许进行非标准评估,因此我可以使用不带引号和不带前缀(带有 data.frame 名称和“$”)的变量名称。 sapply() 函数获取每个订单日期(第一个除外),并再次对 &lt; 使用非标准评估,以检查它是否小于每个 ship_date(不包括最后一个 - 这使得每个向量为了比较,大小相同。
    • @MichaelDewar 然后我们可以将其转换为 data.frame 并使用按列的apply(df, margin = 2, any) 函数来检查是否有任何ship_date 小于订单日期。因为我们已经删除了第一条记录,但需要一个布尔向量来子集y,其长度与y 相同,我们知道每个客户的第一条记录不会在 ship_date 之前(不可能,因为它是第一条记录)我们将 FALSE 标量值与布尔测试向量 c() 结合起来。如果您从我的解决方案中学到了什么/学到了什么,请随时给它一个赞成票。
    【解决方案2】:

    编辑:我之前的回答没有正确处理订单日期 == 发货日期的情况。

    我假设您已经将数据加载到名为df 的对象中。您可以使用@hello_friend 代码的第一部分来获取它。

    library(tidyverse)
    df %>% 
      distinct(`Customer ID`, `Order ID`, `Order Date`, `Ship Date`) %>% 
      arrange(`Customer ID`, `Order Date`, `Ship Date`) %>% 
      mutate(sort_key = row_number()) %>% 
      pivot_longer(c(`Order Date`, `Ship Date`), names_to = "Activity", names_pattern = "(.*) Date", values_to = "Date") %>% 
      mutate(Activity = factor(Activity, ordered = TRUE, levels = c("Order", "Ship")), 
             Open = if_else(Activity == "Order", 1, -1)) %>% 
      group_by(`Customer ID`) %>% 
      arrange(Date, sort_key, Activity, .by_group = TRUE) %>% 
      mutate(Open = cumsum(Open)) %>% 
      ungroup %>% 
      filter(Open > 1, Activity == "Order") %>% 
      select(`Customer ID`, `Order ID`)
    

    首先,只取不同的订单和客户 ID,否则同一订单中的多个商品会混淆事物并导致错误的结果。然后,旋转数据,使每个订单变成两行,每行代表一个不同的活动:订购或运输。我们创建未结订单数量的运行总数。您正在寻找何时变成两个或更多。

    我对 Activity 使用有序因子来确保我总是在关闭订单之前打开订单。当订单日期和发货日期相同时,这很重要。

    我使用特殊的 sort_key 列来确保在打开新订单之前关闭旧订单,以防客户在同一天订购其他商品。您可能需要相反的逻辑。

    所有这些都假设给定的客户 ID 和订单 ID 在数据中只出现一次,而在您的数据集中实际上并非如此,如您所见:

    df %>% group_by(`Customer ID`, `Order ID`) %>% filter(n_distinct(`Ship Date`)> 1) %>% select(1:9)
    

    【讨论】:

    • 嗨迈克尔很好的解决方案(+1),怎么样:res &lt;- df %&gt;% rename_all(~str_replace(str_trim(str_to_lower(names(df))), "\\W+", "_")) %&gt;% group_by(customer_id) %&gt;% arrange(order_date) %&gt;% filter(cumsum(c(0, diff.Date(order_date))) &lt; cumsum(c(0, diff.Date(ship_date)))) %&gt;% ungroup()
    • 谢谢它在最后一行我只过滤了 Orderdates,我得到了我想要的。
    • 抱歉,我忘记过滤订单了
    猜你喜欢
    • 1970-01-01
    • 2018-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-28
    • 2021-10-21
    相关资源
    最近更新 更多