【问题标题】:Widen data by group with multiple conditions使用多个条件按组扩展数据
【发布时间】:2018-08-21 16:30:34
【问题描述】:

我有 Jenkins Job Pipeline Executions 的数据,我正在尝试根据数据中的开始和结束时间确定从开发到生产所需的平均持续时间。数据有点像事务数据库,其中 Dev 管道的执行是唯一记录,然后将同一管道执行到生产是另一个唯一记录(仅共享一个分组变量,即运行该作业的团队)。

这是我开始使用的数据示例:

  job_id   startTime            endTime               env_type  Team_ID
1  100      8/4/2017 17:14:00   8/4/2017 17:16:00      DEV       A
2  101      8/4/2017 17:20:00   8/4/2017 17:21:00      DEV       A
3  102      8/4/2017 17:24:00   8/4/2017 17:27:00      DEV       B
4  103      8/4/2017 17:38:00   8/4/2017 17:40:00      DEV       B
5  104      8/4/2017 17:40:00   8/4/2017 17:42:00      DEV       C
6  105      8/4/2017 17:51:00   8/4/2017 17:54:00      DEV       C

在我第一次尝试开始扩展数据时,我使用 mutate 创建新列并根据 env_type 复制开始和结束时间:

df %>%
    mutate(prod_job_id = ifelse(env_type == "PROD", job_id, ""), 
           prod_start_time = ifelse(env_type == "PROD", startTime, ""), 
           prod_end_time = ifelse(env_type == "PROD", endTime, ""),  
           dev_job_id = ifelse(env_type == "DEV", job_id, ""), 
           dev_start_time = ifelse(env_type == "DEV", startTime, ""), 
           dev_end_time = ifelse(env_type == "DEV", endTime, ""))

这让我得到了这样的结果(也使用 as.POSIXct 转换时间):

Team_ID env_type      dev_start_time        dev_end_time     prod_start_time       prod_end_time
1        A      DEV 2018-08-01 12:00:00 2018-08-01 13:00:00                <NA>                <NA>
2        A      DEV 2018-08-02 12:00:00 2018-08-02 13:00:00                <NA>                <NA>
3        A     PROD                <NA>                <NA> 2018-08-02 14:00:00 2018-08-02 15:00:00
4        A     PROD                <NA>                <NA> 2018-08-02 16:00:00 2018-08-02 17:00:00
5        B      DEV 2018-08-01 12:00:00 2018-08-01 13:00:00                <NA>                <NA>
6        B      DEV 2018-08-02 12:00:00 2018-08-02 13:00:00                <NA>                <NA>
7        B     PROD                <NA>                <NA> 2018-08-02 16:00:00 2018-08-02 17:00:00
8        C      DEV 2018-08-05 12:00:00 2018-08-05 13:00:00                <NA>                <NA>
9        C      DEV 2018-08-06 12:00:00 2018-08-06 13:00:00                <NA>                <NA>
10       C     TEST 2018-08-06 14:00:00 2018-08-06 15:00:00                <NA>                <NA>

这是输出:

structure(list(Team_ID = structure(c(1L, 1L, 1L, 1L, 2L, 2L, 
2L, 3L, 3L, 3L, 4L, 4L, 4L, 4L), .Label = c("A", "B", "C", "D"
), class = "factor"), pipeline_id = c(1000L, 1000L, 1000L, 1000L, 
2000L, 2000L, 2000L, 3000L, 3000L, 3000L, 4000L, 4000L, 5000L, 
5000L), env_type = structure(c(1L, 1L, 2L, 2L, 1L, 1L, 2L, 1L, 
1L, 3L, 1L, 1L, 2L, 2L), .Label = c("DEV", "PROD", "TEST"), class = "factor"), 
    dev_start_time = structure(c(1533142800, 1533229200, NA, 
    NA, 1533142800, 1533229200, NA, 1533488400, 1533574800, 1533582000, 
    1533142800, 1533229200, NA, NA), class = c("POSIXct", "POSIXt"
    ), tzone = ""), dev_end_time = structure(c(1533146400, 1533232800, 
    NA, NA, 1533146400, 1533232800, NA, 1533492000, 1533578400, 
    1533585600, 1533146400, 1533232800, NA, NA), class = c("POSIXct", 
    "POSIXt"), tzone = ""), prod_start_time = structure(c(NA, 
    NA, 1533236400, 1533243600, NA, NA, 1533243600, NA, NA, NA, 
    NA, NA, 1533236400, 1533243600), class = c("POSIXct", "POSIXt"
    ), tzone = ""), prod_end_time = structure(c(NA, NA, 1533240000, 
    1533247200, NA, NA, 1533247200, NA, NA, NA, NA, NA, 1533240000, 
    1533247200), class = c("POSIXct", "POSIXt"), tzone = "")), class = "data.frame", row.names = c(NA, 
-14L))

棘手的部分是,管道可能会在进入 prod 之前多次进入 dev ,甚至可能在之后再次进入 prod 而无需返回 dev ,正如您在上面的数据框中看到的那样。

我试图弄清楚如何创建一个循环(或一系列 dplyr/purrr 命令或一些 *ply 函数)来对齐数据,以便我可以使用 diffTime 来获取部署持续时间。最终目标是获取从 dev 到 prod 的所有管道的 diffTimes,然后平均这个数字。 为了实现我的目标,我通过尝试将数据变成这样的方式来解决这个问题(在操作之后,env_type 将不再有效 - 但没关系,因为我只对 diffTime 感兴趣):

Team_ID env_type      dev_start_time        dev_end_time     prod_start_time       prod_end_time diffTime
1       A     PROD 2018-08-01 12:00:00 2018-08-01 13:00:00 2018-08-02 14:00:00 2018-08-02 15:00:00  2678400
2       B     PROD 2018-08-02 12:00:00 2018-08-02 13:00:00 2018-08-02 16:00:00 2018-08-02 17:00:00    18000

用英文,我想我需要的是这样的:

对于 env_type == "PROD" 的每一行,找到最接近 Dev 的时间戳,并用该值覆盖 Dev 列 - 类似于 max(dev_end_time,其中 dev_end_time 不大于 prod_start_time AND dev_end_time 大于prod_end_time 的先前值)。我知道数据需要按 Team_ID 分组并按顺序排列。我也知道我必须从查看 prod 管道开始,然后向后工作。

我是从这个开始的:

df %>% 
    group_by(Team_ID) %>% 
    arrange(Team_ID, startTime) 

使数据按时间顺序分组和排列。但是我应该从这里去哪里?我首先认为 mutate 可能有效: mutate(dev_start_time = ifelse((dev_end_time &lt; prod_start_time) &amp; (dev_end_time &gt; prod_start_time -1)), dev_start_time, "") 但我不知道如何让 R 查看正确的行(prod_start_time -1 应该是 prod 的前一行,而不是时间 -1)。

我知道必须有某种方法可以做到这一点,但我只是不熟悉完成它的功能。

编辑:

对于@LetEpsilonBeLessThanZero 我试图通过 pipeline_id 了解该分组的要点,然后过滤具有至少 1 个 dev 和 1 个 prod 行的数据将删除有价值的数据。为了证明这一点,让我们看看下面的数据:

Team_ID pipeline_id env_type      dev_start_time        dev_end_time     prod_start_time       prod_end_time
1        A        1000      DEV 2018-08-01 12:00:00 2018-08-01 13:00:00                <NA>                <NA>
2        A        1000      DEV 2018-08-02 12:00:00 2018-08-02 13:00:00                <NA>                <NA>
3        A        1000     PROD                <NA>                <NA> 2018-08-02 14:00:00 2018-08-02 15:00:00
4        A        1000     PROD                <NA>                <NA> 2018-08-02 16:00:00 2018-08-02 17:00:00
5        B        2000      DEV 2018-08-01 12:00:00 2018-08-01 13:00:00                <NA>                <NA>
6        B        2000      DEV 2018-08-02 12:00:00 2018-08-02 13:00:00                <NA>                <NA>
7        B        2000     PROD                <NA>                <NA> 2018-08-02 16:00:00 2018-08-02 17:00:00
8        C        3000      DEV 2018-08-05 12:00:00 2018-08-05 13:00:00                <NA>                <NA>
9        C        3000      DEV 2018-08-06 12:00:00 2018-08-06 13:00:00                <NA>                <NA>
10       C        3000     TEST 2018-08-06 14:00:00 2018-08-06 15:00:00                <NA>                <NA>
11       D        4000      DEV 2018-08-01 12:00:00 2018-08-01 13:00:00                <NA>                <NA>
12       D        4000      DEV 2018-08-02 12:00:00 2018-08-02 13:00:00                <NA>                <NA>
13       D        5000     PROD                <NA>                <NA> 2018-08-02 14:00:00 2018-08-02 15:00:00
14       D        5000     PROD                <NA>                <NA> 2018-08-02 16:00:00 2018-08-02 17:00:00

请注意团队 D 如何创建独特的 Dev 管道和独特的 Prod 管道。我仍然需要一种方法来链接它们并测量时间差,因为我知道部署用于同一个应用程序,但它不能按照你建议的方式通过在 pipeline_id 上分组来完成。

另一方面,我知道我们需要一种新的方式将这些团队组合在一起,以便更轻松地关联这些工作,并且现在有计划实现这一目标。但我仍然必须找到一种方法,以尽我所能利用我目前拥有的数据来获取这些数据,因此感谢所有帮助。

【问题讨论】:

  • 您是否关心将 DEV 迭代分开,或者您是否可以将所有 DEV 时间合并到每个项目的单个单元中?
  • 如果我正确理解您的问题,我认为我们需要将它们分开。原因是,每次都可能从 dev 到 prod 多次。
  • 我认为您可以真正受益于唯一标识管道的“pipeline_id”字段。据我所知,您无法快速确定哪些行与哪些管道一起使用,这似乎将成为您前进的一个持续的荆棘。您实际上是在尝试对您当前未在数据集中收集的数据执行计算。话虽如此,如果最终目标是“确定从开发到生产所需的平均持续时间”,那么我们可以只取 DEV 行的平均开发时间吗?
  • @LetEpsilonBeLessThanZero ,为简单起见,我修剪了数据集。数据中实际上有一个 pipeline_id 值(这最初是我分组的变量,而不是 Team_ID)。我遇到的问题是,团队正在创建仅用于 Dev 或仅用于 Prod 等的管道。因此,对于某些团队,如果我要按 pipeline_id 分组,则只有 Dev 的日期和时间pipeline_id 或只有该 pipeline_id 的产品的日期和时间等。
  • 我们可以轻松过滤掉至少没有一个 DEV 和至少一个 PROD 行的 pipeline_id。然后,我们可以按它们的 pipeline_id 对行进行分组,将它们的开发时间相加以获得每个 pipeline_id 的总开发时间,然后找到这些总开发时间量的平均值。这就是你想要达到的目标,是吗?如果您将 pipeline_id 字段添加到问题中的数据集,那么我可以快速让您编写代码来执行我刚才所说的操作。

标签: r dplyr purrr


【解决方案1】:

下面的代码怎么样?我修改了你的一个虚拟数据集,以便我可以测试一些不同的场景。

df dataframe 是未更改的虚拟数据集。

df_w_implied_proj_id 将向您展示我如何确定“proj_id”,这是我创建的一个字段。 proj_id 旨在表示“真正的”管道。

mean_dev_df 计算 proj_id 之间的平均总 diffTime。

library(dplyr)

df = data.frame(startTime = as.POSIXct(c("2018-08-01 12:00:00",
                                         "2018-08-02 10:00:00",
                                         "2018-08-02 14:00:00",
                                         "2018-08-02 16:00:00",
                                         "2018-08-01 12:00:00",
                                         "2018-08-02 12:00:00",
                                         "2018-08-02 16:00:00",
                                         "2018-08-05 12:00:00",
                                         "2018-08-06 12:00:00",
                                         "2018-08-06 14:00:00",
                                         "2018-08-06 16:00:00",
                                         "2018-08-06 18:00:00",
                                         "2018-08-01 12:00:00",
                                         "2018-08-02 12:00:00",
                                         "2018-08-02 14:00:00",
                                         "2018-08-02 16:00:00"), format="%Y-%m-%d %H:%M:%S"),
                endTime = as.POSIXct(c("2018-08-01 13:00:00",
                                       "2018-08-02 13:00:00",
                                       "2018-08-02 15:00:00",
                                       "2018-08-02 18:00:00",
                                       "2018-08-01 13:00:00",
                                       "2018-08-02 13:00:00",
                                       "2018-08-02 18:00:00",
                                       "2018-08-05 13:00:00",
                                       "2018-08-06 13:00:00",
                                       "2018-08-06 15:00:00",
                                       "2018-08-06 17:00:00",
                                       "2018-08-06 19:00:00",
                                       "2018-08-01 13:00:00",
                                       "2018-08-02 13:00:00",
                                       "2018-08-02 15:00:00",
                                       "2018-08-02 21:00:00"), format="%Y-%m-%d %H:%M:%S"),
                env_type = c("DEV","DEV","PROD","PROD","DEV","DEV","PROD","DEV","DEV","PROD","DEV","PROD","DEV","DEV","PROD","PROD"),
                Team_ID = c("A","A","A","A","B","B","B","C","C","C","C","C","D","D","D","D"))

df_w_implied_proj_id = df %>%
  arrange(Team_ID, startTime) %>%
  mutate(diffTimeSecs = difftime(endTime,startTime,units="secs"),
         proj_id = cumsum(env_type != lag(env_type, default = first(env_type))) %/% 2 + 1) %>%
  group_by(proj_id) %>%
  mutate(total_proj_diffTimeSecs = sum(diffTimeSecs))

mean_dev_df = df_w_implied_proj_id %>%
  group_by(proj_id) %>%
  summarise(temp_totals = sum(diffTimeSecs)) %>%
  ungroup() %>%
  summarise(mean_total_proj_diffTimeSecs = mean(temp_totals))

这段代码的主要工作蜂是这一行:

proj_id = cumsum(env_type != lag(env_type, default = first(env_type))) %/% 2 + 1

为了理解,我们看一下数据集中的env_type值:

env_type
DEV
DEV
PROD
PROD
DEV
DEV
PROD
DEV
DEV
PROD
DEV
PROD
DEV
DEV
PROD
PROD

lag 函数只返回前一行的值。因此,作为一个随机示例,lag(c("A","B","C"),default="BALLOON") 将返回 c("BALLOON","A","B")

所以env_type != lag(env_type, default = first(env_type)) 会返回这个:

env_type != lag(env_type, default = first(env_type))
0 (note: there's no row before the first row, so the lag statement defaults this to the first element of env_type vector, which is "DEV". And "DEV" != "DEV" evaluates to FALSE aka 0)
0 (note: "DEV" != "DEV" evaluates to FALSE aka 0)
1 (note: "PROD" != "DEV" evaluates to TRUE aka 1)
0 (note: "PROD != "PROD" evaluates to FALSE aka 0. By now you hopefully get the gist of what's going on.)
1
0
1
1
0
1
1
1
1
0
1
0

然后cumsum(...) 的 0 和 1 向量就变成了:

0 0 1 1 2 2 3 4 4 5 6 7 8 8 9 9

每增加 1 表示从“DEV”切换到“PROD”,反之亦然。

然后我们可以将每个偶数及其奇数后继数通过整数除以 2 然后加 1 得到:

1 1 1 1 2 2 2 3 3 3 4 4 5 5 5 5

这些是我们最终的 proj_id。

【讨论】:

  • 对我的帖子进行了编辑,试图澄清事情。不,这并不能满足我的要求。
  • 我提供的第一个数据样本实际上只是为了展示它的纯正性,以防有人有更好的方法来扩大数据范围。我只是把它们都做成了开发管道。
  • 哦...我现在看到了这个问题。本质上,没有字段可以可靠地指示管道,因为某些管道被赋予了多个 pipeline_id。我相信这意味着我们必须对数据做出假设才能继续。我们需要一些方法来将行分组为实际的“管道”,但我怀疑没有办法用可用的数据制作 100% 准确的方法。我可以假设以下两种情况吗? (1) 每个 DEV 周期后面跟着一个 PROD 周期 (2) 每个 DEV 和 PROD 周期,在同一天的开始和结束时间正好相隔 1 小时,并且具有相同的 team_id 链接?
  • 因为如果我们不能假设这些事情是真实的,那么我们就会遇到问题。例如,如果 A 团队昨天有 DEV 和 PROD 期,然后 A 队今天也有 DEV 和 PROD 期,那么我们无法知道他们是在同一天工作还是在不同的管道上工作.我们必须做一个假设。
  • 是的,我们必须对这些数据做出一些假设。但是我们需要做的主要假设是,对于每个 prod 管道,紧接在它之前的 dev 管道(按时间顺序)是我们想要 diffTime 的那个。我们必须从 prod 向后工作到 dev 才能工作。不幸的是,不,dev 并不总是紧跟着 prod,在 prod 执行之前可能有很多 dev 执行。此外,我认为实际上不会有任何恰好相隔 1 小时的开始和结束时间 - 我包含的时间戳只是一个示例 - 不是实际数据。
【解决方案2】:

答案真的归功于letepsilonbelessthanzero,因为他为我提供了一些关于 dplyr::lag() 的指导。但我已经测试了以下解决方案,它完全按照我的需要工作。

df %>% 
    group_by(Team_ID) %>% 
    arrange(Team_ID, startTime) %>% 
    mutate("Dev-Prod" = as.numeric(difftime(prod_end_time, lag(dev_start_time), units = "secs"))) %>%
    filter(!is.na(`Dev-Prod`))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-12-28
    • 1970-01-01
    • 2021-10-20
    • 2023-04-05
    • 1970-01-01
    • 2019-09-24
    • 2018-07-11
    相关资源
    最近更新 更多