【问题标题】:Calculate the mean between several columns of df2 that can vary according to the variable `var1` of df1 and add the value to a new variable in df1计算可以根据 df1 的变量“var1”变化的 df2 的几列之间的平均值,并将该值添加到 df1 中的新变量
【发布时间】:2019-05-17 18:00:07
【问题描述】:

我有一个数据框df1,它总结了不同地方不同鱼的深度。

另一方面,我有df2,它总结了从地表到 39 米深度以 8 米为间隔(m0-7m8-15、@987654325)随时间(每三个小时)的电流强度@、m24-31m32-39) 在特定位置。举个例子:

df1<-data.frame(Datetime=c("2016-08-01 15:34:07","2016-08-01 16:25:16","2016-08-01 17:29:16","2016-08-01 18:33:16","2016-08-01 20:54:16","2016-08-01 22:48:16"),Site=c("BD","HG","BD","BD","BD","BD"),Ind=c(16,17,19,16,17,16), Depth=c(5.3,24,36.4,42,NA,22.1))
df1$Datetime<-as.POSIXct(df1$Datetime, format="%Y-%m-%d %H:%M:%S",tz="UTC")


> df1
             Datetime Site Ind Depth
1 2016-08-01 15:34:07   BD  16   5.3
2 2016-08-01 16:25:16   HG  17  24.0
3 2016-08-01 17:29:16   BD  19  36.4
4 2016-08-01 18:33:16   BD  16  42.0
5 2016-08-01 20:54:16   BD  17    NA
6 2016-08-01 22:48:16   BD  16  22.1

df2<-data.frame(Datetime=c("2016-08-01 12:00:00","2016-08-01 15:00:00","2016-08-01 18:00:00","2016-08-01 21:00:00","2016-08-02 00:00:00"), Site=c("BD","BD","BD","BD","BD"),var1=c(2.75,4,6.75,2.25,4.3),var2=c(3,4,4.75,3,2.1),var3=c(2.75,4,5.75,2.25,1.4),var4=c(3.25,3,6.5,2.75,3.4),var5=c(3,4,4.75,3,1.7))
df2$Datetime<-as.POSIXct(df2$Datetime, format="%Y-%m-%d %H:%M:%S",tz="UTC")
colnames(df2)<-c("Datetime","Site","m0-7","m8-15","m16-23","m24-31","m32-39")

> df2
             Datetime Site m0-7 m8-15 m16-23 m24-31 m32-39
1 2016-08-01 12:00:00   BD 2.75  3.00   2.75   3.25   3.00
2 2016-08-01 15:00:00   BD 4.00  4.00   4.00   3.00   4.00
3 2016-08-01 18:00:00   BD 6.75  4.75   5.75   6.50   4.75
4 2016-08-01 21:00:00   BD 2.25  3.00   2.25   2.75   3.00
5 2016-08-02 00:00:00   BD 4.30  2.10   1.40   3.40   1.70

我想在df1 中创建一个变量,它反映鱼不在的深度层的平均电流。例如,如果鱼在 20 米深,对应于层 m16-23,我想知道层 m0-7m8-15m24-31m32-39 的平均电流。

注意 1:如果我的鱼的深度超过 39 米,我认为它好像在最深处(m32-39)。 df1 的第 4 行中的一个示例。

注2:由于当前记录是每三个小时,df2$Datetime中的每个小时表示多一个半小时,少一个半小时。也就是说,df2 中在21:00:00 处指出的电流强度反映了19:30:0022:30:00 之间的电流。其余时间也一样。

我希望这样:

> df1
             Datetime Site Ind Depth current.Mean
1 2016-08-01 15:34:07   BD  16   5.3         3.75
2 2016-08-01 16:25:16   HG  17  24.0           NA
3 2016-08-01 17:29:16   BD  19  36.4         5.94
4 2016-08-01 18:33:16   BD  16  42.0         5.94
5 2016-08-01 20:54:16   BD  17    NA           NA
6 2016-08-01 22:48:16   BD  16  22.1         2.87

有人知道怎么做吗?

【问题讨论】:

    标签: r dplyr tidyverse


    【解决方案1】:

    我会分两步解决这个问题:

    1. 使用 avg_speed_elsewhere 为 df2 中的每个日期时间、站点和深度创建一个查找表。
    2. 加入 df1。

    这是一个查找表:

    library(tidyverse)
    df2_long <- df2 %>%
      gather(depth_rng, speed, `m0-7`:`m32-39`) %>%
      separate(depth_rng, c("min_depth", "max_depth")) %>%
      mutate_at(vars(matches("depth")), parse_number) %>%
      # EDIT -- added to make deep category cover >39 too
      mutate(max_depth = if_else(max_depth == 39, 10000, max_depth)) %>%
      group_by(Datetime, Site) %>%
      # Avg Speed elsewhere is the sum of all speeds, minus this speed, all divided by 4.
      mutate(avg_speed_elsewhere = (sum(speed) - speed) / 4)
    
    > df2_long
    # A tibble: 25 x 6
    # Groups:   Datetime, Site [5]
       Datetime            Site  min_depth max_depth speed avg_speed_elsewhere
       <dttm>              <fct>     <dbl>     <dbl> <dbl>               <dbl>
     1 2016-08-18 12:00:00 BD            0         7  2.75                3   
     2 2016-08-18 15:00:00 BD            0         7  4                   3.75
     3 2016-08-18 18:00:00 BD            0         7  6.75                5.44
     4 2016-08-18 21:00:00 BD            0         7  2.25                2.75
     5 2016-08-19 00:00:00 BD            0         7  4.3                 2.15
     6 2016-08-18 12:00:00 BD            8        15  3                   2.94
     7 2016-08-18 15:00:00 BD            8        15  4                   3.75
     8 2016-08-18 18:00:00 BD            8        15  4.75                5.94
     9 2016-08-18 21:00:00 BD            8        15  3                   2.56
    10 2016-08-19 00:00:00 BD            8        15  2.1                 2.7 
    # ... with 15 more rows
    

    我希望这会起作用,但您提供的数据不会重叠,所以我不确定:

    df1 %>%
      # EDIT - replaced floor_date with round_date
      mutate(Datetime_3hr = lubridate::round_date(Datetime, "3 hour")) %>%
      left_join(df2_long, by = c("Site", "Datetime_3hr" = "Datetime")) %>%
      filter(Depth >= min_depth & Depth < max_depth + 1 | is.na(Depth))
    

    【讨论】:

    • 艾乔恩!!谢谢你的时间。我正在检查你的脚本,我发现了一些我不知道你是否知道如何解决的问题。一个问题是从df2df1 的时间分配。例如,在17:29:16 记录的鱼的电流分配对应于时间15:00:00,而不是时间18:00:00。我需要为每个df1$Datetime 分配最接近df2 的时间。另一个问题是代码删除了 6 行中的 3 行。删除了属于其他Site的行,删除了Depth中的NA的行和鱼深超过39米的行。
    • 我不想删除其中的任何行,因为在我的真实数据中,这些行包含我想保留的其他有价值的信息。此外,正如我在帖子中所说,对于大于 39 米的深度,例如在第 4 行 (==42) 的情况下,我想考虑它们是否位于我们拥有的最深层 (m32-39)。如果您找到解决这些问题的方法,我期待找到您的新贡献。非常感谢您再次光临 :)
    • 编辑为使用round_date 而不是floor_date,并更改了 df2_long 以便最后一个类别涵盖超过 39 的深度。
    • 你最后的改动几乎完美了!你知道怎么做才能不删除鱼深度为NA 的行和对应于不同站点的行吗?感谢您的贡献!!!
    • 不确定对应于不同站点的行,但我认为调整后的过滤器将包括 NA 深度以及适合的深度。您将获得 NA 深度的多个匹配项,因为不知道哪个是合适的。
    【解决方案2】:

    这个问题包含有趣的挑战:

    1. OP 要求 “部分反连接”,即 OP 希望在 df2 中聚合当前数据,其中 DatetimeSite 匹配但深度层不是
    2. 当前数据df2 在查找表中给出,其中每个值都与一个深度范围(深度层)和一个时间范围为3小时。因此,df1 中测量的DepthDatetime 需要映射到各自的范围。

    我尝试了不同的方法,但最终得到了以下一种方法,它没有对聚合函数做出假设。所以,可以直接调用mean()

    library(data.table)
    library(magrittr)
    
    # reshape df2 from wide to long format
    currents <- melt(setDT(df2), id.vars = c("Datetime", "Site"),
                     variable.name = "layer", value.name = "current")
    
    # create columns to join on
    labels <- names(df2) %>% stringr::str_subset("^m")
    breaks <- c(seq(0, 32, 8), Inf)
    setDT(df1)[, layer := cut(Depth, breaks = breaks, labels = labels)]
    df1[, current.dt := df2[df1, on = .(Site, Datetime), 
                          roll = "nearest", x.Datetime]]
    
    # "partial anti-join" to compute mean of other layers
    currents_other_layers <- 
      currents[df1, on = .(Site, Datetime = current.dt)][
        layer != i.layer, mean(current), by = .(i.Datetime, Site)]
    
    # append result column
    df1[currents_other_layers, on = .(Site, Datetime = i.Datetime), current.mean := i.V1]
    df1
    
                   Datetime Site Ind Depth  layer          current.dt current.mean
    1: 2016-08-01 15:34:07   BD  16   5.3   m0-7 2016-08-01 15:00:00       3.7500
    2: 2016-08-01 16:25:16   HG  17  24.0 m16-23                <NA>           NA
    3: 2016-08-01 17:29:16   BD  19  36.4 m32-39 2016-08-01 18:00:00       5.9375
    4: 2016-08-01 18:33:16   BD  16  42.0 m32-39 2016-08-01 18:00:00       5.9375
    5: 2016-08-01 20:54:16   BD  17    NA   <NA> 2016-08-01 21:00:00           NA
    6: 2016-08-01 22:48:16   BD  16  22.1 m16-23 2016-08-02 00:00:00       2.8750
    

    这再现了 OP 的预期结果。

    说明

    df2 从宽格式改成长格式。这允许在layer 列上加入/反加入。

    currents
    
                   Datetime Site  layer current
     1: 2016-08-01 12:00:00   BD   m0-7    2.75
     2: 2016-08-01 15:00:00   BD   m0-7    4.00
     3: 2016-08-01 18:00:00   BD   m0-7    6.75
     4: 2016-08-01 21:00:00   BD   m0-7    2.25
     5: 2016-08-02 00:00:00   BD   m0-7    4.30
     6: 2016-08-01 12:00:00   BD  m8-15    3.00
     7: 2016-08-01 15:00:00   BD  m8-15    4.00
     8: 2016-08-01 18:00:00   BD  m8-15    4.75
     9: 2016-08-01 21:00:00   BD  m8-15    3.00
    10: 2016-08-02 00:00:00   BD  m8-15    2.10
    11: 2016-08-01 12:00:00   BD m16-23    2.75
    12: 2016-08-01 15:00:00   BD m16-23    4.00
    13: 2016-08-01 18:00:00   BD m16-23    5.75
    14: 2016-08-01 21:00:00   BD m16-23    2.25
    15: 2016-08-02 00:00:00   BD m16-23    1.40
    16: 2016-08-01 12:00:00   BD m24-31    3.25
    17: 2016-08-01 15:00:00   BD m24-31    3.00
    18: 2016-08-01 18:00:00   BD m24-31    6.50
    19: 2016-08-01 21:00:00   BD m24-31    2.75
    20: 2016-08-02 00:00:00   BD m24-31    3.40
    21: 2016-08-01 12:00:00   BD m32-39    3.00
    22: 2016-08-01 15:00:00   BD m32-39    4.00
    23: 2016-08-01 18:00:00   BD m32-39    4.75
    24: 2016-08-01 21:00:00   BD m32-39    3.00
    25: 2016-08-02 00:00:00   BD m32-39    1.70
                   Datetime Site  layer current
    

    现在,必须修改 df1 以在 currents 中包含与 layerDatetime 对应的列。

    对于Depth,使用cut() 函数。最后一层级别m32-39 扩展到Inf,因此所有大于 32 m 的深度都按照 OP 的要求包含在此级别中。

    对于Datetime,使用滚动连接到df2最近的 Datetime。这是可能的,因为df2$Datetime 表示 3 小时时间范围的中点。

    df1准备好后,我们就可以进行“部分反连接”了。不幸的是,data.table 的非 equi 连接不接受 != 运算符。所以,我们不能写

    currents[df1, on = .(Datetime = current.dt, Site, layer != layer)]
    

    直接但必须使用一种变通方法,我们首先选择我们期望匹配的行,然后执行反连接:

     currents[df1, on = .(Datetime = current.dt, Site)][
        !df1, on = .(Datetime = current.dt, Site, layer)]
    
                   Datetime Site  layer current          i.Datetime Ind Depth i.layer
     1: 2016-08-01 15:00:00   BD  m8-15    4.00 2016-08-01 15:34:07  16   5.3    m0-7
     2: 2016-08-01 15:00:00   BD m16-23    4.00 2016-08-01 15:34:07  16   5.3    m0-7
     3: 2016-08-01 15:00:00   BD m24-31    3.00 2016-08-01 15:34:07  16   5.3    m0-7
     4: 2016-08-01 15:00:00   BD m32-39    4.00 2016-08-01 15:34:07  16   5.3    m0-7
     5: 2016-08-01 18:00:00   BD   m0-7    6.75 2016-08-01 17:29:16  19  36.4  m32-39
     6: 2016-08-01 18:00:00   BD  m8-15    4.75 2016-08-01 17:29:16  19  36.4  m32-39
     7: 2016-08-01 18:00:00   BD m16-23    5.75 2016-08-01 17:29:16  19  36.4  m32-39
     8: 2016-08-01 18:00:00   BD m24-31    6.50 2016-08-01 17:29:16  19  36.4  m32-39
     9: 2016-08-01 18:00:00   BD   m0-7    6.75 2016-08-01 18:33:16  16  42.0  m32-39
    10: 2016-08-01 18:00:00   BD  m8-15    4.75 2016-08-01 18:33:16  16  42.0  m32-39
    11: 2016-08-01 18:00:00   BD m16-23    5.75 2016-08-01 18:33:16  16  42.0  m32-39
    12: 2016-08-01 18:00:00   BD m24-31    6.50 2016-08-01 18:33:16  16  42.0  m32-39
    13: 2016-08-01 21:00:00   BD   m0-7    2.25 2016-08-01 20:54:16  17    NA    <NA>
    14: 2016-08-01 21:00:00   BD  m8-15    3.00 2016-08-01 20:54:16  17    NA    <NA>
    15: 2016-08-01 21:00:00   BD m16-23    2.25 2016-08-01 20:54:16  17    NA    <NA>
    16: 2016-08-01 21:00:00   BD m24-31    2.75 2016-08-01 20:54:16  17    NA    <NA>
    17: 2016-08-01 21:00:00   BD m32-39    3.00 2016-08-01 20:54:16  17    NA    <NA>
    18: 2016-08-02 00:00:00   BD   m0-7    4.30 2016-08-01 22:48:16  16  22.1  m16-23
    19: 2016-08-02 00:00:00   BD  m8-15    2.10 2016-08-01 22:48:16  16  22.1  m16-23
    20: 2016-08-02 00:00:00   BD m24-31    3.40 2016-08-01 22:48:16  16  22.1  m16-23
    21: 2016-08-02 00:00:00   BD m32-39    1.70 2016-08-01 22:48:16  16  22.1  m16-23
    22:                <NA>   HG   <NA>      NA 2016-08-01 16:25:16  17  24.0  m16-23
                   Datetime Site  layer current          i.Datetime Ind Depth i.layer
    

    这可以通过任意聚合函数根据需要聚合(无需手动选择性地添加单个列):

    currents_other_layers <- 
      currents[df1, on = .(Datetime = current.dt, Site)][
        !df1, on = .(Datetime = current.dt, Site, layer)][
          !is.na(Depth), mean(current), by = .(i.Datetime, Site)]
    
    currents_other_layers
    
                i.Datetime Site     V1
    1: 2016-08-01 15:34:07   BD 3.7500
    2: 2016-08-01 17:29:16   BD 5.9375
    3: 2016-08-01 18:33:16   BD 5.9375
    4: 2016-08-01 22:48:16   BD 2.8750
    5: 2016-08-01 16:25:16   HG     NA
    

    此结果包含除观察层之外的所有其他层的平均电流。请注意,分组是由i.Datetime 指代df1$DatetimeSite。省略df1 中缺少Depth 的行以满足OP 的预期结果。

    最后的更新连接将结果列附加到df1

    【讨论】:

      【解决方案3】:

      使用data.table,您可以在两个数据库之间进行滚动连接,以便将深度变量与当前变量关联起来,即使时间不匹配。滚动连接的作用是将一个表与另一个表关联,此时时间最接近(根据您选择的选项)。我更改了您的一些数据,以便日期匹配

      library(data.table)
      
      df1<-data.frame(Datetime=c("2016-08-01 15:34:07","2016-08-01 16:25:16","2016-08-01 17:29:16","2016-08-01 18:33:16","2016-08-01 20:54:16","2016-08-01 22:48:16"),Site=c("BD","HG","BD","BD","BD","BD"),Ind=c(16,17,19,16,17,16), Depth=c(5.3,24,36.4,42,NA,22.1))
      df1$Datetime<-as.POSIXct(df1$Datetime, format="%Y-%m-%d %H:%M:%S",tz="UTC")
      
      df2<-data.frame(Datetime=c("2016-08-01 12:00:00","2016-08-01 15:00:00","2016-08-01 18:00:00","2016-08-01 21:00:00","2016-08-02 00:00:00"), Site=c("BD","BD","BD","BD","BD"),var1=c(2.75,4,6.75,2.25,4.3),var2=c(3,4,4.75,3,2.1),var3=c(2.75,4,5.75,2.25,1.4),var4=c(3.25,3,6.5,2.75,3.4),var5=c(3,4,4.75,3,1.7))
      df2$Datetime<-as.POSIXct(df2$Datetime, format="%Y-%m-%d %H:%M:%S",tz="UTC")
      colnames(df2)<-c("Datetime","Site","m0-7","m8-15","m16-23","m24-31","m32-39")
      
      setDT(df1)
      setDT(df2)
      
      setkey(df1, Site, Datetime)
      setkey(df2, Site, Datetime)
      
      df_merge = df2[df1, roll = Inf]
      

      然后我使用 dplyr 的 case_when 来计算其他深度的电流

      library(dplyr)
      
      df_merge[, current_elsewhere := case_when(
        is.na(Depth) ~ NA_real_,
        Depth < 7 ~ (`m8-15` + `m16-23` + `m24-31` + `m32-39`)/4,
        Depth < 15 ~ (`m0-7` + `m16-23` + `m24-31` + `m32-39`)/4,
        Depth < 23 ~ (`m0-7` + `m8-15` + `m24-31` + `m32-39`)/4,
        Depth < 31 ~ (`m0-7` + `m8-15` + `m16-23` + `m32-39`)/4,
        T ~ (`m0-7` + `m8-15` + `m16-23` + `m24-31`)/4)]
      
      df_merge
                    Datetime Site m0-7 m8-15 m16-23 m24-31 m32-39 Ind Depth current_elsewhere
      1: 2016-08-01 15:34:07   BD 4.00  4.00   4.00   3.00   4.00  16   5.3            3.7500
      2: 2016-08-01 17:29:16   BD 4.00  4.00   4.00   3.00   4.00  19  36.4            3.7500
      3: 2016-08-01 18:33:16   BD 6.75  4.75   5.75   6.50   4.75  16  42.0            5.9375
      4: 2016-08-01 20:54:16   BD 6.75  4.75   5.75   6.50   4.75  17    NA                NA
      5: 2016-08-01 22:48:16   BD 2.25  3.00   2.25   2.75   3.00  16  22.1            2.7500
      6: 2016-08-01 16:25:16   HG   NA    NA     NA     NA     NA  17  24.0                NA
      

      【讨论】:

      • Ei @Luis,感谢您的回复!我检查了df1 的输出,它似乎工作得很好......虽然有一个问题我不知道你是否知道如何解决:从df2df1 的正确时间分配.例如,df1[1, ] 位于15:34:07,但来自df2 的电流分配对应于时间18:00:00,而不是时间15:00:00df1[3 , ] 也是如此。注意:我刚刚更改了数据框 df2 的日期,因为我犯了一个错误,并且我在 df1 中写了不同的日期时间。
      • 另一个问题,我还发现对于没有深度的行 (df1[5 , ]),您的脚本仍然会计算 current_elsewhere。例如,在这种情况下,它根据df221:00:00 中的当前记录估计20:54:16 的电流为 2.5625。但我不知道它是如何得到这个 2.5625 的平均值的,因为它不是五列中的平均值。你知道当Depth 列中有NA 时它是如何计算current_elsewhere 的吗?
      • @Luis,您的结果与 OP 的预期结果不符。这是因为您使用的是roll = -Inf,它不与最近的连接,而是与前面的Datetime 连接。使用roll = "nearest"
      • 谢谢 Uwe。我所做的不是roll="nearest",而是用round_date(df1$Datetime, "3 hour") 创建一个圆角列。之后,我运行了df_merge = df2[df1, roll = -Inf]。对吗?
      • @Dekike 您可以通过选择适当的“滚动”选项(“最近”、-Inf 和 Inf、分别)。如果在 df2 表中 15:00 行表示 12:00-15:00,则应使用 -Inf,如果表示 15:00-18:00,则应使用 Inf。至于没有深度的线的计算,你是对的,当深度为NA时没有考虑到。看一下编辑(只需将 case_when 中的 is.na(Depth) 更改为您认为最好的)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-04-19
      • 1970-01-01
      • 1970-01-01
      • 2020-07-27
      • 2016-01-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多