簡體   English   中英

當最后一個和下一個非 NA 值相等時替換 NA

[英]Replace NA when last and next non-NA values are equal

我有一個示例表,其中包含需要替換的一些但不是全部NA值。

> dat
   id message index
1   1    <NA>     1
2   1     foo     2
3   1     foo     3
4   1    <NA>     4
5   1     foo     5
6   1    <NA>     6
7   2    <NA>     1
8   2     baz     2
9   2    <NA>     3
10  2     baz     4
11  2     baz     5
12  2     baz     6
13  3     bar     1
14  3    <NA>     2
15  3    <NA>     3
16  3     bar     4
17  3    <NA>     5
18  3     bar     6
19  3    <NA>     7
20  3     qux     8

我的目標是代替NA相同的“消息”所包圍的值使用所述消息的所述第一外觀(最低index值)和消息的最后外觀(使用max index按id值)

有時,NA 序列的長度僅為 1,有時它們可​​能很長。 無論如何,所有的NA被在‘消息’的相同值之前和之后的‘夾在’的NA應被填充。

上述不完整表的輸出將是:

 > output
   id message index
1   1    <NA>     1
2   1     foo     2
3   1     foo     3
4   1     foo     4
5   1     foo     5
6   1    <NA>     6
7   2    <NA>     1
8   2     baz     2
9   2     baz     3
10  2     baz     4
11  2     baz     5
12  2     baz     6
13  3     bar     1
14  3     bar     2
15  3     bar     3
16  3     bar     4
17  3     bar     5
18  3     bar     6
19  3    <NA>     7
20  3     qux     8

此處使用data.tabledplyr任何指導都會有所幫助,因為我什至不確定從哪里開始。

據我所知,是通過唯一消息進行子集化,但此方法不考慮id

#get distinct messages
messages = unique(dat$message)

#remove NA
messages = messages[!is.na(messages)]

#subset dat for each message
for (i in 1:length(messages)) {print(dat[dat$message == messages[i],]) }

數據:

 dput(dat)
structure(list(id = c(1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 
3, 3, 3, 3, 3, 3, 3), message = c(NA, "foo", "foo", NA, "foo", 
NA, NA, "baz", NA, "baz", "baz", "baz", "bar", NA, NA, "bar", 
NA, "bar", NA, "qux"), index = c(1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 
5, 6, 1, 2, 3, 4, 5, 6, 7, 8)), row.names = c(NA, -20L), class = "data.frame")

向前和向后執行na.locf0 ,如果它們相同,則使用公共值; 否則,使用 NA。 分組是用ave完成的。

library(zoo)

filler <- function(x) {
  forward <- na.locf0(x)
  backward <- na.locf0(x, fromLast = TRUE)
  ifelse(forward == backward, forward, NA)
}
transform(dat, message = ave(message, id, FUN = filler))

給予:

   id message index
1   1    <NA>     1
2   1     foo     2
3   1     foo     3
4   1     foo     4
5   1     foo     5
6   1    <NA>     6
7   2    <NA>     1
8   2     baz     2
9   2     baz     3
10  2     baz     4
11  2     baz     5
12  2     baz     6
13  3     bar     1
14  3     bar     2
15  3     bar     3
16  3     bar     4
17  3     bar     5
18  3     bar     6
19  3    <NA>     7
20  3     qux     8

使用一個選項na.approxzoo

首先,我們從不是NAmessage中提取唯一元素,並在dat$message找到這些位置

x <- unique(na.omit(dat$message))
(y <- match(dat$message, x))
# [1] NA  1  1 NA  1 NA NA  2 NA  2  2  2  3 NA NA  3 NA  3 NA  4

library(zoo)
library(dplyr)
out <- do.call(coalesce, 
               lapply(seq_along(x), function(i) as.double(na.approx(match(y, i) * i, na.rm = FALSE))))
dat$new <- x[out]
dat
#    id message index  new
#1   1    <NA>     1 <NA>
#2   1     foo     2  foo
#3   1     foo     3  foo
#4   1    <NA>     4  foo
#5   1     foo     5  foo
#6   1    <NA>     6 <NA>
#7   2    <NA>     1 <NA>
#8   2     baz     2  baz
#9   2    <NA>     3  baz
#10  2     baz     4  baz
#11  2     baz     5  baz
#12  2     baz     6  baz
#13  3     bar     1  bar
#14  3    <NA>     2  bar
#15  3    <NA>     3  bar
#16  3     bar     4  bar
#17  3    <NA>     5  bar
#18  3     bar     6  bar
#19  3    <NA>     7 <NA>
#20  3     qux     8  qux

tl;博士

當我們打電話

match(y, 1) * 1
# [1] NA  1  1 NA  1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA

我們只在y中有1的地方得到元素。 因此,當我們做

match(y, 2) * 2
# [1] NA NA NA NA NA NA NA  2 NA  2  2  2 NA NA NA NA NA NA NA NA

2秒的結果相同。

12視為中的第一個和第二個元素

x
# [1] "foo" "baz" "bar" "qux"

那是"foo""baz"

現在對於每個match(y, i) * i我們可以從zoo調用na.approx來填充介於兩者之間的NA s( i稍后將變為seq_along(x) )。

na.approx(match(y, 2) * 2, na.rm = FALSE)
# [1] NA NA NA NA NA NA NA  2  2  2  2  2 NA NA NA NA NA NA NA NA

我們對seq_along(x)每個元素執行相同的操作,即使用lapply1:4 結果是一個列表

lapply(seq_along(x), function(i) as.double(na.approx(match(y, i) * i, na.rm = FALSE)))
#[[1]]
# [1] NA  1  1  1  1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
#
#[[2]]
# [1] NA NA NA NA NA NA NA  2  2  2  2  2 NA NA NA NA NA NA NA NA
#
#[[3]]
# [1] NA NA NA NA NA NA NA NA NA NA NA NA  3  3  3  3  3  3 NA NA
#
#[[4]]
# [1] NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA  4

(這里需要as.double因為否則coalesce會抱怨“參數 4 必須是 double 類型,而不是整數類型”

我們就快到了。 我們接下來需要做的是找到每個位置的第一個非缺失值,這就是從dplyr coalescedplyr ,結果是

out <- do.call(coalesce, 
               lapply(seq_along(x), function(i) as.integer(na.approx(match(y, i) * i, na.rm = FALSE))))
out
# [1] NA  1  1  1  1 NA NA  2  2  2  2  2  3  3  3  3  3  3 NA  4

我們可以使用這個向量從x提取所需的值作為

x[out]
# [1] NA    "foo" "foo" "foo" "foo" NA    NA    "baz" "baz" "baz" "baz" "baz" "bar" "bar" "bar" "bar" "bar" "bar" NA    "qux"

希望這可以幫助。

這是一種沒有分組的方法來填充值,如果它們填充不正確,然后用NA替換回來。

tidyr::fill默認用前一個值填充缺失值,因此它會溢出一些值。 不幸的是,它不尊重分組,所以我們必須使用if_else條件來修復它的錯誤。

首先,我們捕獲原始缺失值位置並計算每個idmessage的最大和最小index 填充后,我們在這些index邊界上加入。 如果不匹配,則id改變; 如果匹配,要么是正確的替換,要么index在邊界之外。 因此,我們檢查具有這些條件的原始缺失值的位置,如果滿足,則用NA替換。

編輯:這可以在其他輸入上被破壞,試圖修復

library(tidyverse)
dat <- structure(list(id = c(1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3), message = c(NA, "foo", "foo", NA, "foo", NA, NA, "baz", NA, "baz", "baz", "baz", "bar", NA, NA, "bar", NA, "bar", NA, "qux"), index = c(1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8)), row.names = c(NA, -20L), class = "data.frame")

indices <- dat %>%
  group_by(id, message) %>%
  summarise(min = min(index), max = max(index)) %>%
  drop_na

dat %>%
  mutate(orig_na = is.na(message)) %>%
  fill(message) %>%
  left_join(indices, by = c("id", "message")) %>% 
  mutate(
    message = if_else(
      condition = orig_na &
        (index < min | index > max | is.na(min)),
      true = NA_character_,
      false = message
    )
  )
#>    id message index orig_na min max
#> 1   1    <NA>     1    TRUE  NA  NA
#> 2   1     foo     2   FALSE   2   5
#> 3   1     foo     3   FALSE   2   5
#> 4   1     foo     4    TRUE   2   5
#> 5   1     foo     5   FALSE   2   5
#> 6   1    <NA>     6    TRUE   2   5
#> 7   2    <NA>     1    TRUE  NA  NA
#> 8   2     baz     2   FALSE   2   6
#> 9   2     baz     3    TRUE   2   6
#> 10  2     baz     4   FALSE   2   6
#> 11  2     baz     5   FALSE   2   6
#> 12  2     baz     6   FALSE   2   6
#> 13  3     bar     1   FALSE   1   6
#> 14  3     bar     2    TRUE   1   6
#> 15  3     bar     3    TRUE   1   6
#> 16  3     bar     4   FALSE   1   6
#> 17  3     bar     5    TRUE   1   6
#> 18  3     bar     6   FALSE   1   6
#> 19  3    <NA>     7    TRUE   1   6
#> 20  3     qux     8   FALSE   8   8

reprex 包(v0.2.1) 於 2019 年 2 月 15 日創建

另一個使用 case_when 的 tidyverse 解決方案。 編輯以避免在系列結束后填充。

library(dplyr)

dfr <- data.frame(
  index =  c(1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8),
  message = c(NA, "foo", "foo", NA, "foo", NA, NA, "baz", NA, "baz", "baz", "baz", "bar", NA, NA, "bar", NA, "bar", NA, "qux"),
  id =  c(1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3)
)

dfrFilled <- dfr %>% 
  group_by(id) %>% 
  mutate(
    endSeries = max( # identify end of series
      index[message == na.omit(message)[1]],
      na.rm = T
      ),
    filledValues = case_when(
      min(index) == index ~ message,
      max(index) == index ~ message,
      index < endSeries ~ na.omit(message)[1], # fill if index is before end of series.
      TRUE ~ message
    )
  )

如果您填寫兩種方式並檢查應該有效的相等性,只要您考慮分組和索引:

整理宇宙:

library(tidyverse)

dat %>%
  arrange(id, index) %>%
  mutate(msg_down = fill(group_by(., id), message, .direction = 'down')$message,
         msg_up   = fill(group_by(., id), message, .direction = 'up')$message,
         message = case_when(!is.na(message) ~ message,
                             msg_down == msg_up ~ msg_down,
                             TRUE ~ NA_character_)) %>%
  select(-msg_down, -msg_up)

   id message index
1   1    <NA>     1
2   1     foo     2
3   1     foo     3
4   1     foo     4
5   1     foo     5
6   1    <NA>     6
7   2    <NA>     1
8   2     baz     2
9   2     baz     3
10  2     baz     4
11  2     baz     5
12  2     baz     6
13  3     bar     1
14  3     bar     2
15  3     bar     3
16  3     bar     4
17  3     bar     5
18  3     bar     6
19  3    <NA>     7
20  3     qux     8

數據表

library(data.table)
library(zoo)

setDT(dat)[order(index),
           message := ifelse(na.locf(message, na.rm = FALSE) == na.locf(message, na.rm = FALSE, fromLast = TRUE),
                             na.locf(message, na.rm = FALSE),
                             NA),
           by = "id"][]

    id message index
 1:  1    <NA>     1
 2:  1     foo     2
 3:  1     foo     3
 4:  1     foo     4
 5:  1     foo     5
 6:  1    <NA>     6
 7:  2    <NA>     1
 8:  2     baz     2
 9:  2     baz     3
10:  2     baz     4
11:  2     baz     5
12:  2     baz     6
13:  3     bar     1
14:  3     bar     2
15:  3     bar     3
16:  3     bar     4
17:  3     bar     5
18:  3     bar     6
19:  3    <NA>     7
20:  3     qux     8

暫無
暫無

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

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