[英]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的每一行進行計數,即滿足以下條件的數據框中的行數:
我已經編寫了一個腳本來使用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
進行計數。
接下來的兩行將行分為偷竊和未偷竊的汽車。 關鍵是:
N
次,其中N
是未失竊的汽車行的數量, M
倍,其中M
是被盜汽車的行數,並且 (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_not
是is_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
行中:
telematic_trip_no = 526005880
違反了所有未盜用行的經度條件,因此其計數應為0
telematic_trip_no = 526006880
違反了所有未盜用行的緯度條件,因此其計數應為0
telematic_trip_no = 526007880
違反了所有未盜用行的telematic_trip_no
條件,因此其計數應為0
telematic_trip_no = 526006890
滿足未竊取的第4
行和第5
行的條件,因此其計數應為2
telematic_trip_no = 526106880
滿足第6
行未被盜的條件,因此其計數應為1
telematic_trip_no = 526017880
違反了所有未盜用行的time_of_day
條件,因此其計數應為0
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 = 7
第7
行開始。
如果改為使用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.