【问题标题】:R: Keep first values in time series after NA and set 3 consecutive values to NAR:在 NA 之后保留时间序列中的第一个值并将 3 个连续值设置为 NA
【发布时间】:2018-12-02 19:18:01
【问题描述】:

我有一个数据框,列中有许多时间序列(例如 col1 中的一个序列)。我想遍历每个时间序列并保留出现在 NA 之后的第一个值,并将 仅连续 3 个值设置为 NA(col2 中所需结果的示例)。理想情况下,这应该适用于数据框中的所有或指定(例如 [2:30])列。

This answer 不考虑仅 3 个连续值到 NA 的条件。

样本数据

df <- data.frame(
col1 = c(7.00, NA, NA, 1.00, 2.00, 5.00, NA, 5.00, 7.00, NA, NA, 1.00, NA, 2.00, NA, NA, 1.00, 3.00, 4.00, 5.00, 6.00, 7.00, NA, 7.00, NA),
col2 = c(7.00, NA, NA, 1.00, NA, NA, NA, 5.00, NA, NA, NA, 1.00, NA, 2.00, NA, NA, 1.00, NA, NA, NA, 6.00, NA, NA, 7.0, NA),
                     stringsAsFactors = FALSE)

感谢您的帮助。

【问题讨论】:

  • 第 22 行是否正确?我期望一个7 输出,但你有一个NA。这似乎是自col1 中的最后一个NA 以来的第六行。
  • @JonSpring。谢谢你,乔恩。很好的解决方案。但是,确实,我的例子是“正确的”。我在我的问题中说得不够清楚:如果在 NA 之后有超过 4 个有效值,则应将第 5 个视为 NA 之后的第一个。
  • 啊哈。不,这还不清楚。从措辞看来,NA 之后的每组中只有第 2:4 行应该是NA,但听起来您想将相同的模式应用于第 6:8、10:12 等行。相应地修改了我的答案。
  • @JonSpring:太棒了。这就是诀窍。非常感谢你。是否可以对数据框中的所有列或指定列运行此操作?
  • # / NA 逻辑会为每一列单独工作,还是它们都基于一个“关键”列?如果原始数据看起来像这样,您会期望什么输出? df &lt;- data.frame(colA = c(7.0, NA, NA, 1.0, 2.0, 3.0, 4.0, 5.0), colB = c(2.0, 2.0, NA, NA, 6.0, 7.0, 8.0, 9.0), stringsAsFactors = FALSE)

标签: r time-series na


【解决方案1】:

这是一种使用dplyr的方法。

首先,每当我们有一个跟随NA 的有效值时,我都会创建一个新组。然后在每个组中,我将第 2 到第 4 行设置为NA,否则使用col1

library(dplyr)

df %>%
  mutate(new_grp = if_else(!is.na(col1) & is.na(lag(col1)), 1, 0),
         grp = cumsum(new_grp)) %>%
  group_by(grp) %>%
  # Modified below per OP clarification: treat each group of 4 entries
  #   following an NA like the first four, with one value and then 3 NAs.
  #   Uses modulo 4, where any row with a remainder of 2, 3, or 0 (ie row 4, etc.) will get NA
  mutate(col2b = case_when(row_number() %% 4 %in% c(2:3, 0)  ~ NA_real_,
                          TRUE ~ col1)) %>%
  ungroup() 

输出:

row col1 col2 col2b
1   7   7   7
2   NA  NA  NA
3   NA  NA  NA
4   1   1   1
5   2   NA  NA
6   5   NA  NA
7   NA  NA  NA
8   5   5   5
9   7   NA  NA
10  NA  NA  NA
11  NA  NA  NA
12  1   1   1
13  NA  NA  NA
14  2   2   2
15  NA  NA  NA
16  NA  NA  NA
17  1   1   1
18  3   NA  NA
19  4   NA  NA
20  5   NA  NA
21  6   6   6
22  7   NA  NA   # Modified per OP clarification
23  NA  NA  NA
24  7   7   7
25  NA  NA  NA

编辑:应用于多个/所有列

如果您的所有列都属于同一类型,这应该可以一次转换所有列。它通过使用tidyr 将数据从宽格式收集到“长”格式,然后执行与以前相同的计算,然后传播回宽格式。

df %>%
  mutate(row = row_number()) %>%
  tidyr::gather(col, value, -row) %>%
  group_by(col) %>%

  mutate(new_grp = if_else(!is.na(value) & is.na(lag(value)), 1, 0),
         grp = cumsum(new_grp)) %>%
  group_by(col, grp) %>%
  mutate(value = case_when(row_number() %% 4 %in% c(2:3, 0)  ~ NA_real_,
                           TRUE ~ value)) %>%
  ungroup() %>%
  tidyr::spread(col, value) %>%
  select(-row, -new_grp, -grp)

如果您的数据框使用不同的类型,我认为它会变得更加复杂,除非我缺少更简单的替代方案。我从一种使用“tidyeval”的方法开始,让您使用函数以编程方式更改一个指定的列。之后的最后一步可以使用purrr 将函数应用于所有列。

NA_2to4 <- function(df_name, col_to_change) {
  col_quo <- enquo(col_to_change)
  df_name %>%
    mutate(new_grp = if_else(!is.na(!!col_quo) & is.na(lag(!!col_quo)), 1, 0),
         grp = cumsum(new_grp)) %>%
    group_by(grp) %>%
    mutate(!!col_quo := case_when(row_number() %% 4 %in% c(2:3, 0)  ~ NA_real_,
                             TRUE ~ !!col_quo)) %>%
    ungroup() %>%
    select(-new_grp, -grp)
}

您可以通过以下方式将其应用于特定列:

df %>% 
  NA_2to4(colA) %>%
  NA_2to4(colB)

我认为有一种方法可以使用 purr::map 来应用于所有列,但我目前不确定语法。

【讨论】:

  • 是的,列中的所有数据都属于同一类型。如果我使用你的第一个代码,我会在第一行得到一个“0”,然后每第四行得到一个“0”。这适用于所有列。请参阅此输入数据为您的代码: df
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-12-03
  • 2014-01-08
  • 2018-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-12
相关资源
最近更新 更多