如何找到值低于 0 的 5 行或更多行(日期)系列的第一行(日期)
How to find the first row (date) of the series of 5 or more rows (date) with below 0 value
我有以下日期的平均温度数据。我想找到连续至少 5 天低于或高于 0 摄氏度的开始日期。
date_short mean.temp
1 2018-05-18 17.54
2 2018-05-19 19.45
3 2018-05-20 22.31
4 2018-05-21 13.26
5 2018-05-22 10.29
6 2018-05-23 15.06
我使用了以下脚本并找出有多少天低于 0 以及哪些行符合低于 0 温度的标准。显示0度以下的天数共147天,观测到0度以下的天数在哪一行。从那里我可以看到第 161 个日期是低于 0 温度的第一天,但这不是我想要的,因为它不是至少 5 天低于或高于 0 度的系列的第一个日期。相反,我希望 R 到 return 第 170 天,因为它是低于或高于 0 度的至少 5 天系列的开始。
length(which(d.mean$mean.temp <= 0))
[1] 147
which(d.mean$mean.temp <= 0)
[1] 161 162 166 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
[30] 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
[59] 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
[88] 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
[117] 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 307 308 309 310 313 314 315 316 317
[146] 318 324
如何在 R 中完成。我可以手动完成,但我必须为许多列找到这样的日期。
在 excel 中,函数如下所示。
IF(B2<0, IF(B3<0, IF(B4<0, IF(B5<0, IF(B6<0,A2,""),""),""),""),"")
提前致谢
tidyversere
和 zoo
的一个解决方案如下。您可以使用 rollapply
找到 5 个小于零的温度,将它们设置为 TRUE
。作为滚动 window,它将标记那些后面跟着温度低于零的另外四个(连续)日期的日期。要过滤掉这些系列发生的日期,请查看从 FALSE 到 TRUE 的转换。
编辑:如果您有多列温度,并且想将此方法应用于每一列温度,您可以使用pivot_longer
和group_by
第一的。示例现在包括 3 列温度。
set.seed(126)
library(tidyverse)
library(zoo)
df %>%
pivot_longer(cols = -date, names_to = "temp", values_to = "value") %>%
group_by(temp) %>%
mutate(start = rollapply(value < 0, width = 5, all, align = "left", fill = FALSE)) %>%
dplyr::filter(start & !lag(start, default = FALSE)) %>%
dplyr::select(date, temp) %>%
arrange(temp, date)
输出
# A tibble: 7 x 2
# Groups: temp [3]
date temp
<date> <chr>
1 2020-01-10 temp_A
2 2020-01-16 temp_A
3 2020-01-22 temp_A
4 2020-01-05 temp_B
5 2020-01-22 temp_B
6 2020-01-01 temp_C
7 2020-01-23 temp_C
数据
df <- data.frame(
date = seq(as.Date("2020/01/01"), as.Date("2020/02/01"), "days"),
temp_A = sample(c(-10:2), 32, replace = TRUE),
temp_B = sample(c(-10:2), 32, replace = TRUE),
temp_C = sample(c(-10:2), 32, replace = TRUE)
)
date temp_A temp_B temp_C
1 2020-01-01 -9 -8 -6
2 2020-01-02 -1 1 -9
3 2020-01-03 -6 -7 -4
4 2020-01-04 0 1 -9
5 2020-01-05 2 -8 -3
6 2020-01-06 -4 -3 0
7 2020-01-07 -1 -3 1
8 2020-01-08 2 -3 0
9 2020-01-09 1 -6 1
10 2020-01-10 -1 -7 -1
11 2020-01-11 -2 -4 -6
12 2020-01-12 -8 -2 1
13 2020-01-13 -7 1 -5
14 2020-01-14 -3 -2 -7
15 2020-01-15 0 0 -8
16 2020-01-16 -1 -4 -10
17 2020-01-17 -4 -1 2
18 2020-01-18 -6 1 -9
19 2020-01-19 -5 -7 -5
20 2020-01-20 -4 -6 0
21 2020-01-21 2 0 -6
22 2020-01-22 -1 -3 0
23 2020-01-23 -4 -7 -3
24 2020-01-24 -2 -7 -5
25 2020-01-25 -10 -1 -10
26 2020-01-26 -5 -6 -6
27 2020-01-27 -3 -10 -1
28 2020-01-28 -8 -5 1
29 2020-01-29 0 1 -2
30 2020-01-30 2 -9 -6
31 2020-01-31 -10 -4 -1
32 2020-02-01 2 -10 -4
或者(可能不那么优雅)您可以将 data.table
中的 rleid
函数与 dplyr
包结合使用。
简而言之,您将温度转换为 0 和 1,具体取决于它们是低于还是高于 0。然后,rleid
将计算 1 或 0 的每个连续序列的长度,并为每个序列分配一个数字.您可以按此数字分组并查看每个序列的长度,找到每个序列的最短日期并过滤大于 4 的序列,然后您将获得低于或高于 0 的温度列表以及它们开始的时间。
library(lubridate)
library(data.table)
library(dplyr)
Result_DF <- df %>%
mutate(Above0 = ifelse(temp > 0,1,0)) %>% # Compute temperature above 0
mutate(Seq_ID = rleid(Above0)) %>%
group_by(Seq_ID) %>%
mutate(Length_seq = n()) %>%
filter(Length_seq > 4) %>%
mutate(Date_Min = min(date)) %>%
distinct(Date_Min, Above0, Length_seq, Seq_ID)
# A tibble: 18 x 4
# Groups: Seq_ID [18]
Date_Min Above0 Length_seq Seq_ID
<date> <dbl> <int> <int>
1 2018-02-04 1 6 23
2 2018-02-14 1 6 25
3 2018-02-28 1 6 31
4 2018-03-09 1 9 33
5 2018-04-06 1 5 47
6 2018-04-30 1 5 59
7 2018-06-19 1 5 83
8 2018-06-30 1 6 87
9 2018-07-14 1 6 93
10 2018-07-25 1 9 97
11 2018-08-21 1 5 107
12 2018-09-08 1 6 117
13 2018-09-25 1 10 125
14 2018-10-15 1 7 131
15 2018-10-23 1 7 133
16 2018-11-23 0 5 148
17 2018-12-05 1 6 155
18 2018-12-24 1 5 163
可能有更快更优雅的方法来做到这一点(@Ben 的回答非常简单)但这只是另一种选择。
编辑:改进代码(感谢@allistaire 的评论)
感谢@allistaire 的评论,您可以通过以下方式快速走得更远:
df %>%
group_by(run = data.table::rleid(temp > 0)) %>%
filter(n() >= 5) %>%
slice(1)
可重现的例子
set.seed(123)
df <- data.frame(date = seq(ymd("2018-01-01"), ymd("2018-12-31"), by = "day"),
temp = sample(-15:25, 365, replace = TRUE))
base R的rle
(运行长度编码)函数就足够了,例如
# sample data
set.seed(47)
df <- data.frame(
date = seq(as.Date("1970-01-01"), length = 500, by = "days"),
temp = rnorm(500)
)
runs <- rle(df$temp < 0)
df[(cumsum(runs$lengths) - runs$lengths + 1)[runs$values & runs$lengths >= 5], ]
#> date temp
#> 25 1970-01-25 -0.3264668
#> 270 1970-09-27 -0.5443173
#> 350 1970-12-16 -0.8436569
#> 356 1970-12-22 -1.2768785
#> 370 1971-01-05 -1.4122783
#> 431 1971-03-07 -0.4711361
#> 454 1971-03-30 -0.9901146
要稍微分解一下,请查看组成部分:
runs
#> Run Length Encoding
#> lengths: int [1:235] 3 1 1 2 1 3 2 1 2 1 ...
#> values : logi [1:235] FALSE TRUE FALSE TRUE FALSE TRUE ...
# start index of each run
head((cumsum(runs$lengths) - runs$lengths + 1), 20)
#> [1] 1 4 5 6 8 9 12 14 15 17 18 19 22 23 24 25 30 33 34 37
# runs where temp < 0 and length >= 5
head(runs$values & runs$lengths >= 5, 20)
#> [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [13] FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE
# together, indices of first rows which satisfy the conditions
(cumsum(runs$lengths) - runs$lengths + 1)[runs$values & runs$lengths >= 5]
#> [1] 25 270 350 356 370 431 454
你可以使用 rle
.
LEN <- 5
rrl <- rle(+(dat$temp < 0))
(bel.0 <-
which(c(NA, diff(rep(suppressWarnings(rrl$lengths*(1:0)), rrl$lengths) >= LEN)) == 1))
# [1] 4 21 306 384 417 427
我们抑制了由于 1-0
句点可能不完整而引起的警告。
检查:
dat$minus <- 0
dat$minus[bel.0] <- 1
head(dat, 30)
# date temp minus
# 1 2017-12-01 -14.03 0
# 2 2017-12-02 17.33 0
# 3 2017-12-03 20.02 0
# 4 2017-12-04 -21.28 1
# 5 2017-12-05 -23.49 0
# 6 2017-12-06 -13.04 0
# 7 2017-12-07 -19.27 0
# 8 2017-12-08 -18.76 0
# 9 2017-12-09 26.44 0
# 10 2017-12-10 10.14 0
# 11 2017-12-11 -6.05 0
# 12 2017-12-12 -19.10 0
# 13 2017-12-13 -4.88 0
# 14 2017-12-14 -19.19 0
# 15 2017-12-15 6.95 0
# 16 2017-12-16 -19.07 0
# 17 2017-12-17 -2.02 0
# 18 2017-12-18 4.96 0
# 19 2017-12-19 -15.18 0
# 20 2017-12-20 5.80 0
# 21 2017-12-21 -24.17 1
# 22 2017-12-22 -23.51 0
# 23 2017-12-23 -10.26 0
# 24 2017-12-24 -7.91 0
# 25 2017-12-25 -7.65 0
# 26 2017-12-26 8.66 0
# 27 2017-12-27 -9.71 0
# 28 2017-12-28 -15.09 0
# 29 2017-12-29 -28.49 0
# 30 2017-12-30 -22.01 0
数据
set.seed(666)
temp <- sample(-(3e3):3e3, 5e2, replace=TRUE) / 1e2
dat <- data.frame(date=as.Date(seq(temp) + 1.75e4), temp)
我有以下日期的平均温度数据。我想找到连续至少 5 天低于或高于 0 摄氏度的开始日期。
date_short mean.temp
1 2018-05-18 17.54
2 2018-05-19 19.45
3 2018-05-20 22.31
4 2018-05-21 13.26
5 2018-05-22 10.29
6 2018-05-23 15.06
我使用了以下脚本并找出有多少天低于 0 以及哪些行符合低于 0 温度的标准。显示0度以下的天数共147天,观测到0度以下的天数在哪一行。从那里我可以看到第 161 个日期是低于 0 温度的第一天,但这不是我想要的,因为它不是至少 5 天低于或高于 0 度的系列的第一个日期。相反,我希望 R 到 return 第 170 天,因为它是低于或高于 0 度的至少 5 天系列的开始。
length(which(d.mean$mean.temp <= 0))
[1] 147
which(d.mean$mean.temp <= 0)
[1] 161 162 166 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
[30] 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
[59] 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
[88] 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
[117] 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 307 308 309 310 313 314 315 316 317
[146] 318 324
如何在 R 中完成。我可以手动完成,但我必须为许多列找到这样的日期。 在 excel 中,函数如下所示。
IF(B2<0, IF(B3<0, IF(B4<0, IF(B5<0, IF(B6<0,A2,""),""),""),""),"")
提前致谢
tidyversere
和 zoo
的一个解决方案如下。您可以使用 rollapply
找到 5 个小于零的温度,将它们设置为 TRUE
。作为滚动 window,它将标记那些后面跟着温度低于零的另外四个(连续)日期的日期。要过滤掉这些系列发生的日期,请查看从 FALSE 到 TRUE 的转换。
编辑:如果您有多列温度,并且想将此方法应用于每一列温度,您可以使用pivot_longer
和group_by
第一的。示例现在包括 3 列温度。
set.seed(126)
library(tidyverse)
library(zoo)
df %>%
pivot_longer(cols = -date, names_to = "temp", values_to = "value") %>%
group_by(temp) %>%
mutate(start = rollapply(value < 0, width = 5, all, align = "left", fill = FALSE)) %>%
dplyr::filter(start & !lag(start, default = FALSE)) %>%
dplyr::select(date, temp) %>%
arrange(temp, date)
输出
# A tibble: 7 x 2
# Groups: temp [3]
date temp
<date> <chr>
1 2020-01-10 temp_A
2 2020-01-16 temp_A
3 2020-01-22 temp_A
4 2020-01-05 temp_B
5 2020-01-22 temp_B
6 2020-01-01 temp_C
7 2020-01-23 temp_C
数据
df <- data.frame(
date = seq(as.Date("2020/01/01"), as.Date("2020/02/01"), "days"),
temp_A = sample(c(-10:2), 32, replace = TRUE),
temp_B = sample(c(-10:2), 32, replace = TRUE),
temp_C = sample(c(-10:2), 32, replace = TRUE)
)
date temp_A temp_B temp_C
1 2020-01-01 -9 -8 -6
2 2020-01-02 -1 1 -9
3 2020-01-03 -6 -7 -4
4 2020-01-04 0 1 -9
5 2020-01-05 2 -8 -3
6 2020-01-06 -4 -3 0
7 2020-01-07 -1 -3 1
8 2020-01-08 2 -3 0
9 2020-01-09 1 -6 1
10 2020-01-10 -1 -7 -1
11 2020-01-11 -2 -4 -6
12 2020-01-12 -8 -2 1
13 2020-01-13 -7 1 -5
14 2020-01-14 -3 -2 -7
15 2020-01-15 0 0 -8
16 2020-01-16 -1 -4 -10
17 2020-01-17 -4 -1 2
18 2020-01-18 -6 1 -9
19 2020-01-19 -5 -7 -5
20 2020-01-20 -4 -6 0
21 2020-01-21 2 0 -6
22 2020-01-22 -1 -3 0
23 2020-01-23 -4 -7 -3
24 2020-01-24 -2 -7 -5
25 2020-01-25 -10 -1 -10
26 2020-01-26 -5 -6 -6
27 2020-01-27 -3 -10 -1
28 2020-01-28 -8 -5 1
29 2020-01-29 0 1 -2
30 2020-01-30 2 -9 -6
31 2020-01-31 -10 -4 -1
32 2020-02-01 2 -10 -4
或者(可能不那么优雅)您可以将 data.table
中的 rleid
函数与 dplyr
包结合使用。
简而言之,您将温度转换为 0 和 1,具体取决于它们是低于还是高于 0。然后,rleid
将计算 1 或 0 的每个连续序列的长度,并为每个序列分配一个数字.您可以按此数字分组并查看每个序列的长度,找到每个序列的最短日期并过滤大于 4 的序列,然后您将获得低于或高于 0 的温度列表以及它们开始的时间。
library(lubridate)
library(data.table)
library(dplyr)
Result_DF <- df %>%
mutate(Above0 = ifelse(temp > 0,1,0)) %>% # Compute temperature above 0
mutate(Seq_ID = rleid(Above0)) %>%
group_by(Seq_ID) %>%
mutate(Length_seq = n()) %>%
filter(Length_seq > 4) %>%
mutate(Date_Min = min(date)) %>%
distinct(Date_Min, Above0, Length_seq, Seq_ID)
# A tibble: 18 x 4
# Groups: Seq_ID [18]
Date_Min Above0 Length_seq Seq_ID
<date> <dbl> <int> <int>
1 2018-02-04 1 6 23
2 2018-02-14 1 6 25
3 2018-02-28 1 6 31
4 2018-03-09 1 9 33
5 2018-04-06 1 5 47
6 2018-04-30 1 5 59
7 2018-06-19 1 5 83
8 2018-06-30 1 6 87
9 2018-07-14 1 6 93
10 2018-07-25 1 9 97
11 2018-08-21 1 5 107
12 2018-09-08 1 6 117
13 2018-09-25 1 10 125
14 2018-10-15 1 7 131
15 2018-10-23 1 7 133
16 2018-11-23 0 5 148
17 2018-12-05 1 6 155
18 2018-12-24 1 5 163
可能有更快更优雅的方法来做到这一点(@Ben 的回答非常简单)但这只是另一种选择。
编辑:改进代码(感谢@allistaire 的评论)
感谢@allistaire 的评论,您可以通过以下方式快速走得更远:
df %>%
group_by(run = data.table::rleid(temp > 0)) %>%
filter(n() >= 5) %>%
slice(1)
可重现的例子
set.seed(123)
df <- data.frame(date = seq(ymd("2018-01-01"), ymd("2018-12-31"), by = "day"),
temp = sample(-15:25, 365, replace = TRUE))
base R的rle
(运行长度编码)函数就足够了,例如
# sample data
set.seed(47)
df <- data.frame(
date = seq(as.Date("1970-01-01"), length = 500, by = "days"),
temp = rnorm(500)
)
runs <- rle(df$temp < 0)
df[(cumsum(runs$lengths) - runs$lengths + 1)[runs$values & runs$lengths >= 5], ]
#> date temp
#> 25 1970-01-25 -0.3264668
#> 270 1970-09-27 -0.5443173
#> 350 1970-12-16 -0.8436569
#> 356 1970-12-22 -1.2768785
#> 370 1971-01-05 -1.4122783
#> 431 1971-03-07 -0.4711361
#> 454 1971-03-30 -0.9901146
要稍微分解一下,请查看组成部分:
runs
#> Run Length Encoding
#> lengths: int [1:235] 3 1 1 2 1 3 2 1 2 1 ...
#> values : logi [1:235] FALSE TRUE FALSE TRUE FALSE TRUE ...
# start index of each run
head((cumsum(runs$lengths) - runs$lengths + 1), 20)
#> [1] 1 4 5 6 8 9 12 14 15 17 18 19 22 23 24 25 30 33 34 37
# runs where temp < 0 and length >= 5
head(runs$values & runs$lengths >= 5, 20)
#> [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [13] FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE
# together, indices of first rows which satisfy the conditions
(cumsum(runs$lengths) - runs$lengths + 1)[runs$values & runs$lengths >= 5]
#> [1] 25 270 350 356 370 431 454
你可以使用 rle
.
LEN <- 5
rrl <- rle(+(dat$temp < 0))
(bel.0 <-
which(c(NA, diff(rep(suppressWarnings(rrl$lengths*(1:0)), rrl$lengths) >= LEN)) == 1))
# [1] 4 21 306 384 417 427
我们抑制了由于 1-0
句点可能不完整而引起的警告。
检查:
dat$minus <- 0
dat$minus[bel.0] <- 1
head(dat, 30)
# date temp minus
# 1 2017-12-01 -14.03 0
# 2 2017-12-02 17.33 0
# 3 2017-12-03 20.02 0
# 4 2017-12-04 -21.28 1
# 5 2017-12-05 -23.49 0
# 6 2017-12-06 -13.04 0
# 7 2017-12-07 -19.27 0
# 8 2017-12-08 -18.76 0
# 9 2017-12-09 26.44 0
# 10 2017-12-10 10.14 0
# 11 2017-12-11 -6.05 0
# 12 2017-12-12 -19.10 0
# 13 2017-12-13 -4.88 0
# 14 2017-12-14 -19.19 0
# 15 2017-12-15 6.95 0
# 16 2017-12-16 -19.07 0
# 17 2017-12-17 -2.02 0
# 18 2017-12-18 4.96 0
# 19 2017-12-19 -15.18 0
# 20 2017-12-20 5.80 0
# 21 2017-12-21 -24.17 1
# 22 2017-12-22 -23.51 0
# 23 2017-12-23 -10.26 0
# 24 2017-12-24 -7.91 0
# 25 2017-12-25 -7.65 0
# 26 2017-12-26 8.66 0
# 27 2017-12-27 -9.71 0
# 28 2017-12-28 -15.09 0
# 29 2017-12-29 -28.49 0
# 30 2017-12-30 -22.01 0
数据
set.seed(666)
temp <- sample(-(3e3):3e3, 5e2, replace=TRUE) / 1e2
dat <- data.frame(date=as.Date(seq(temp) + 1.75e4), temp)