簡體   English   中英

基於dplyr的條件分組對行進行計數

[英]Counting rows based upon conditional grouping with 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_declon_declat_maxlat_minlon_maxlon_min之間 (即適合該GPS點周圍的“框”)
  • time_of_dayday_of_week與感興趣行的相同
  • 行的telematic_trip_no必須與感興趣的行不同
  • 最后,匹配行的is_stolen標記必須等於0

我已經編寫了一個腳本來使用for循環來執行此操作但是它運行非常緩慢,這讓我開始思考是否有一種有效的方法可以使用dplyr或data.table之類的條件在許多條件下進行復雜的行計數?

ps:如果您很好奇,我的確是在計算一次典型旅行中被盜車通過了多少輛車:)

根據您對問題的描述,以下應該可以工作

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)))

第一行只是在df中添加了一個名為ID的列,該列通過其行號來標識該行,稍后我們可以使用dplyr::group_by進行計數。

接下來的兩行將行分為偷竊和未偷竊的汽車。 關鍵是:

  1. 將每行失竊的汽車復制N次,其中N是未失竊的汽車行的數量,
  2. 將未偷竊的汽車行(作為一個塊)復制M倍,其中M是被盜汽車的行數,並且
  3. 將(2)的結果附加到(1)作為新列,並更改這些新列的名稱,以便我們可以在條件中引用它們

(3)的結果具有一些行,這些行枚舉了原始數據幀中所有被盜和未被盜的行對,因此您的條件可以以數組的方式應用。 dplyr管道式R工作流是代碼的第四行(包裝在print() ),它是這樣做的:

  • 第一條命令使用times復制未偷走的汽車行
  • 第二個命令將_not附加到列名,以在綁定列時將它們與被盜的car列區分開。 感謝這個寶石的答案
  • 第三個命令使用each復制行復制被盜的汽車行,並使用dplyr::bind_cols將先前的結果附加為新列
  • 第四個命令使用dplyr::mutate創建一個名為in_range的新列,該列是應用條件的結果。 布爾結果被轉換為{0,1}以便於累加
  • 管道中的其余命令對按ID分組的in_range進行計數, in_range按計數的in_range排列。 請注意,現在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))

在這里,我將is_stolen = 1 7新行追加到全部為is_stolen = 0原始6行中:

  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滿足未竊取的第4行和第5行的條件,因此其計數應為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

在此數據上運行代碼將得到:

# 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的附加行從ID = 77行開始。

如果改為使用telematic_trip_no進行分組, 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 2/4,其中N是原始數據幀中的行數,而用於評估條件的數據幀的列數則增加了一倍。 與大多數陣列處理技術一樣,在速度和內存之間也要進行權衡。

希望這可以幫助。

data.table的當前開發版本v1.9.7具有 等額聯接的新功能,這使條件聯接變得非常簡單。 使用@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每一行, is_stolen匹配的行索引 (基於提供給on=參數的條件)。

by = .EACHI確保在第一個參數i (第一個)中的每一行is_stolen上對應的匹配行索引上第二個參數j提供的表達式.(ID = i.ID, N = .N-sum(telematic_trip_no==i.telematic_trip_no)),被評估。 返回上面顯示的結果。

HTH。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM