使用dplyr基于条件分组计算行

时间:2016-07-12 11:09:09

标签: r dplyr

我的数据框如下:

        position_time telematic_trip_no  lat_dec lon_dec
1 2016-06-05 00:00:01         526132109 -26.6641 27.8733
2 2016-06-05 00:00:01         526028387 -26.6402 27.8059
3 2016-06-05 00:00:01         526081476 -26.5545 28.3263
4 2016-06-05 00:00:04         526140512 -26.5310 27.8704
5 2016-06-05 00:00:05         526140518 -26.5310 27.8704
6 2016-06-05 00:00:19         526006880 -26.5010 27.8490 
  is_stolen hour_of_day time_of_day day_of_week  lat_min
1         0           0           0      Sunday -26.6651
2         0           0           0      Sunday -26.6412
3         0           0           0      Sunday -26.5555
4         0           0           0      Sunday -26.5320
5         0           0           0      Sunday -26.5320
6         0           0           0      Sunday -26.5020
   lat_max lon_max lon_min 
1 -26.6631 27.8743 27.8723     
2 -26.6392 27.8069 27.8049    
3 -26.5535 28.3273 28.3253    
4 -26.5300 27.8714 27.8694      
5 -26.5300 27.8714 27.8694      
6 -26.5000 27.8500 27.8480     

现在我要做的是计算 is_stolen = 1的每一行,数据框中满足以下条件的行数:

  • lat_dec lon_dec 位于 lat_max lat_min lon_max 之间和 lon_min (即适合GPS点周围的“方框”)
  • time_of_day day_of_week 与感兴趣的行相同
  • 行的 telematic_trip_no 需要与感兴趣的行的 telematic_trip_no
  • 最后匹配行的 is_stolen 标记需要等于0

我已经使用 for 循环编写了一个脚本来执行此操作,但它慢慢地非常,这让我想到是否有一种有效的方法来执行复杂的行计数有很多条件使用像dplyr或data.table这样的东西?

ps如果你很好奇,我确实试图计算一辆偷车在一次典型行程中经过多少辆汽车:)

2 个答案:

答案 0 :(得分:1)

鉴于您对问题的描述,以下内容应该有效

library(dplyr)
library(stats)
# df is the data.frame (see below)
df <- cbind(ID=seq_len(nrow(df)),df)
r.stolen <- which(df$is_stolen == 1)
r.not <- which(df$is_stolen != 1)
print(df[rep(r.not, times=length(r.stolen)),] %>%
  setNames(.,paste0(names(.),"_not")) %>%
    bind_cols(df[rep(r.stolen, each=length(r.not)),], .) %>% 
      mutate(in_range = as.numeric(telematic_trip_no != telematic_trip_no_not & time_of_day == time_of_day_not & day_of_week == day_of_week_not & lat_dec >= lat_min_not & lat_dec <= lat_max_not & lon_dec >= lon_min_not & lon_dec <= lon_max_not)) %>%
        group_by(ID) %>%
          summarise(count = sum(in_range)) %>% 
            arrange(desc(count)))

第一行只是将一个名为ID的列添加到df,该列按行号标识行,我们稍后可以dplyr::group_by进行计数。

接下来的两行将行划分为被盗和未被盗的汽车。关键是:

  1. 复制每一排被盗车辆N次,其中N是未被盗车辆的行数,
  2. 复制未被盗汽车的行(作为一个街区)M次,其中M是被盗汽车行的数量,
  3. 将(2)的结果附加到(1)作为新列,并更改这些新列的名称,以便我们可以在条件中引用它们
  4. (3)的结果具有从原始数据框中枚举所有被盗和未被盗行的行,以便您的条件可以以数组方式应用。作为代码第四行的dplyr管道R工作流程(包含在print()中)执行此操作:

    • 第一个命令使用times
    • 复制未被盗的汽车行
    • 第二个命令将_not附加到列名称,以便在绑定列时将它们与被盗的汽车列区分开来。感谢this SO answer获得该宝石。
    • 第三个命令使用each复制被盗车辆行,并使用dplyr::bind_cols
    • 将上一个结果附加为新列
    • 第四个命令使用dplyr::mutate创建一个名为in_range的新列,该列是应用条件的结果。布尔结果转换为{0,1}以便于累积
    • 管道中的其余命令执行in_range的计数,按ID分组,并按计数的递减顺序排列结果。请注意,现在ID是标识is_stolen = 1原始数据框行的列,而ID_notis_stolen = 0
    • 行的列

    这假定您希望计算原始数据框中is_stolen = 1的每一行,这就是您在问题中所说的内容。如果您真的想要被盗的每个telematic_trip_no的计数,那么您可以使用

    group_by(telematic_trip_no) %>%
    
    而在管道中

    我已使用以下数据片段对此进行了测试

    df <- structure(list(position_time = structure(c(1L, 1L, 1L, 2L, 3L, 
                    4L, 4L, 5L, 6L, 7L, 8L, 9L, 10L), .Label = c("2016-06-05 00:00:01", 
                    "2016-06-05 00:00:04", "2016-06-05 00:00:05", "2016-06-05 00:00:19", 
                    "2016-06-05 00:00:20", "2016-06-05 00:00:22", "2016-06-05 00:00:23", 
                    "2016-06-05 00:00:35", "2016-06-05 00:09:34", "2016-06-06 01:00:06"
                    ), class = "factor"), telematic_trip_no = c(526132109L, 526028387L, 
                    526081476L, 526140512L, 526140518L, 526006880L, 526017880L, 526027880L, 
                    526006880L, 526006890L, 526106880L, 526005880L, 526007880L), 
                    lat_dec = c(-26.6641, -26.6402, -26.5545, -26.531, -26.531, 
                    -26.501, -26.5315, -26.5325, -26.501, -26.5315, -26.5007, 
                    -26.5315, -26.5315), lon_dec = c(27.8733, 27.8059, 28.3263, 
                    27.8704, 27.8704, 27.849, 27.88, 27.87, 27.849, 27.87, 27.8493, 
                    27.87, 27.87), is_stolen = c(0L, 0L, 0L, 0L, 0L, 0L, 1L, 
                    1L, 1L, 1L, 1L, 1L, 1L), hour_of_day = c(0L, 0L, 0L, 0L, 
                    0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L), time_of_day = c(0L, 
                    0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 9L, 0L), day_of_week = structure(c(2L, 
                    2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 1L), .Label = c("Monday", 
                    "Sunday"), class = "factor"), lat_min = c(-26.6651, -26.6412, 
                    -26.5555, -26.532, -26.532, -26.502, -26.532, -26.532, -26.502, 
                    -26.532, -26.502, -26.532, -26.532), lat_max = c(-26.6631, 
                    -26.6392, -26.5535, -26.53, -26.53, -26.5, -26.53, -26.53, 
                    -26.5, -26.53, -26.5, -26.53, -26.53), lon_max = c(27.8743, 
                    27.8069, 28.3273, 27.8714, 27.8714, 27.85, 27.8714, 27.8714, 
                    27.85, 27.8714, 27.85, 27.8714, 27.8714), lon_min = c(27.8723, 
                    27.8049, 28.3253, 27.8694, 27.8694, 27.848, 27.8694, 27.8694, 
                    27.848, 27.8694, 27.848, 27.8694, 27.8694)), .Names = c("position_time", 
                    "telematic_trip_no", "lat_dec", "lon_dec", "is_stolen", "hour_of_day", 
                    "time_of_day", "day_of_week", "lat_min", "lat_max", "lon_max", 
                    "lon_min"), class = "data.frame", row.names = c(NA, -13L))
    

    在此,我将7个新行is_stolen = 1添加到原始6is_stolen = 0

    1. telematic_trip_no = 526005880添加的第一行违反了所有未被盗行的经度条件,因此其计数应为0
    2. telematic_trip_no = 526006880添加的第二行违反了所有未被盗行的纬度条件,因此其计数应为0
    3. telematic_trip_no = 526007880的第三个添加的行违反了所有未被盗行的telematic_trip_no条件,因此其计数应为0
    4. telematic_trip_no = 526006890的第四个添加行符合未被盗的行45的条件,因此其计数应为2
    5. telematic_trip_no = 526106880个第五行符合行6未被盗的条件,因此其计数应为1
    6. 包含telematic_trip_no = 526017880的第六行添加了违反所有未被盗行的time_of_day条件,因此其计数应为0
    7. 包含telematic_trip_no = 526027880的第七行添加的行违反了所有未被盗行的day_of_week条件,因此其计数应为0
    8. 在此数据上运行代码会产生:

      # A tibble: 7 x 2
           ID count
        <int> <dbl>
      1    10     2
      2    11     1
      3     7     0
      4     8     0
      5     9     0
      6    12     0
      7    13     0
      

      正如预期的那样回忆起is_stolen = 1附加的行从7开始ID = 7

      如果要按telematic_trip_no进行分组,我们会得到结果:

      # A tibble: 7 x 2
        telematic_trip_no count
                    <int> <dbl>
      1         526006890     2
      2         526106880     1
      3         526005880     0
      4         526006880     0
      5         526007880     0
      6         526017880     0
      7         526027880     0
      

      作为一个警告,上述方法确实会耗费内存。最坏的情况是行数增加到N^2/4,其中N是原始数据帧中的行数,并且用于评估条件的数据帧的列数加倍。与大多数阵列处理技术一样,速度和内存之间存在交易。

      希望这有帮助。

答案 1 :(得分:1)

The current development version of data.table, v1.9.7有一个新功能非equi 连接,这使得条件连接非常简单。使用@ aichao的数据:

require(data.table) # v1.9.7+
setDT(df)[, ID := .I] # add row numbers
not_stolen = df[is_stolen == 0L]
is_stolen  = df[is_stolen == 1L]

not_stolen[is_stolen, 
    .(ID = i.ID, N = .N - sum(telematic_trip_no == i.telematic_trip_no)), 
    on = .(time_of_day, day_of_week, lat_min <= lat_dec, 
          lat_max >= lat_dec, lon_min <= lon_dec, lon_max >= lon_dec), 
    by=.EACHI][, .(ID, N)]
#    ID  N
# 1:  7 NA
# 2:  8 NA
# 3:  9  0
# 4: 10  2
# 5: 11  1
# 6: 12 NA
# 7: 13 NA

部分not_stolen[is_stolen,执行子集式连接操作..即,对于is_stolen中的每一行,匹配行索引(基于在提供给on=参数的条件下提取。

by = .EACHI确保i(第一个)参数中的每一行,is_stolen,在相应的匹配行索引上,j中提供的表达式,第二个参数,.(ID = i.ID, N = .N-sum(telematic_trip_no==i.telematic_trip_no)),被评估。返回上面显示的结果。

HTH。