ошибка при замене NAs последовательностью чисел различной длины для переменной в фрейме данных

r #loops #dplyr #tidyverse

#r #циклы #dplyr #tidyverse

Вопрос:

У меня есть dataframe, который содержит много переменных, но я бы хотел сосредоточиться только на одной, ‘X’, как показано ниже:

 df <- data.frame("x" = c("NA", "NA", "NA", "NA", "NA", "NA", "NA", "NA", 1, "NA", "NA", "NA", "NA", "NA", "NA", "NA", "NA", 1, "NA", "NA", "NA", "NA", "NA", "NA", "NA", 1, "NA", "NA", "NA", "NA", "NA", "NA", 1,"NA", "NA", "NA", "NA", "NA", 1, "NA", "NA", "NA", "NA", "NA", "NA", "NA", "NA", 1))
 

Что я хочу сделать, так это заполнить числа в последовательности, начинающейся с 2, таким образом:

  1. когда есть x== 1, следующий NA должен занимать 2, 3, 4 и так далее, пока снова не появится x == 1, а затем следующий NA должен начать заполняться 2,3,4 и так далее.

Пример вывода:

 x = 2,3,4,5,1,2,3,4,5,6,7,1,2,3,4,5,6,7,8,9,1,2,3,4,1,2,3,4,5,1 ......
 

То, что я пытаюсь сделать, это:

 df$Sequence <- ifelse(df1$x!="1" amp; is.na(df1$x), seq(2,100), df1$x)
 

Но это не возвращает мне ожидаемый результат, почему?

Комментарии:

1. Символьная строка "NA" не совпадает с NA логической константой, которая указывает на пропущенное значение. Поэтому с вашими данными is.na() всегда будет возвращаться FALSE .

Ответ №1:

Поскольку x начинается с 8 последовательных NA , все еще неясно, как вменять самые первые строки. Почему ваш вывод примера начинается с 2? Вот как вменить все, начиная с первого вхождения 1. Я изменил пример, разрешив ему начинаться с 1:

 library(tidyverse)
df <- data.frame("x" = c(1, "NA", "NA", "NA", "NA", "NA", "NA", "NA", 1, "NA", "NA", "NA", "NA", "NA", "NA", "NA", "NA", 1, "NA", "NA", "NA", "NA", "NA", "NA", "NA", 1, "NA", "NA", "NA", "NA", "NA", "NA", 1, "NA", "NA", "NA", "NA", "NA", 1, "NA", "NA", "NA", "NA", "NA", "NA", "NA", "NA", 1))

df <-
  df %>%
  na_if("NA") %>%
  as_tibble() %>%
  mutate(id = row_number())

y <-
  df %>%
  filter(x == 1) %>%
  # calculate block sizes
  transmute(
    from = id,
    to = lead(id),
    diff = to - from
  ) %>%
  # vector ends with a 1 and there is no n 1 th element
  replace_na(list(diff = 1)) %>%
  pull(diff) %>%
  map(seq) %>%
  flatten() %>%
  as.numeric()
y
#>  [1] 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 1 2 3 4 5 6
#> [39] 1 2 3 4 5 6 7 8 9 1

df %>% transmute(x, y = y)
#> # A tibble: 48 x 2
#>    x         y
#>    <chr> <dbl>
#>  1 1         1
#>  2 <NA>      2
#>  3 <NA>      3
#>  4 <NA>      4
#>  5 <NA>      5
#>  6 <NA>      6
#>  7 <NA>      7
#>  8 <NA>      8
#>  9 1         1
#> 10 <NA>      2
#> # … with 38 more rows
 

Создано 2021-10-20 пакетом reprex (v2.0.1)

Ответ №2:

Тот же подход, что и @danlooo, но с использованием data.table вместо tidyverse

 df <- data.table(x = as.integer(c(1, "NA", "NA", "NA", "NA", "NA", "NA", "NA", 1, "NA", "NA", "NA", "NA", "NA", "NA", "NA", "NA", 1, "NA", "NA", "NA", "NA", "NA", "NA", "NA", 1, "NA", "NA", "NA", "NA", "NA", "NA", 1,"NA", "NA", "NA", "NA", "NA", 1, "NA", "NA", "NA", "NA", "NA", "NA", "NA", "NA", 1)))

df[, id := .I][!is.na(x), diff := lead(id) - id][is.na(diff), diff := 1L][, y := unlist(mapply(seq, df[x == 1L]$diff))][, c("id", "diff") := NULL]
 

Если вы хотите узнать, что это такое, вам нужно узнать, как data.table связывается и работает по ссылке.
Я советую вам посмотреть шаг за шагом, чтобы снять цепочку с моего кода следующим образом.
Если вы теперь запустите построчно, вы сможете увидеть, как выглядит таблица df после каждого шага

 ## tidyverse
# mutate(id = row_number())

## data.table
df[, id := .I] # adds a column id where .I is the row number

## tidyverse
# y <-
#  df %>%
#  filter(x == 1) %>%
#  transmute(
#    from = id,
#    to = lead(id),
#    diff = to - from
#  )

## data.table
# instead of creating a new object y first I calculate where available the difference
df[!is.na(x), diff := lead(id) - id] # if x is not NA (x==1 would give same result) create diff column (does the same as the transmute()

## tidyverse
# replace_na(list(diff = 1))

## data.table
df[is.na(diff), diff := 1L] # if diff is NA, assign the value 1 (1L stands for 1 as an integer) to diff

## tidyverse
# pull(diff) %>%
# map(seq) %>%
# flatten()
# as.numeric()
# df %>% transmute(x, y = y)

## data.table
# here I directly add the column y where df[x == 1L]$diff "pulls" the diff column with the rows where x == 1, mapply replaces the map() and in this context I need unlist() instead of flatten()
# no need to make numeric, as I made all integers at start (could have chosen numeric as well)
# I did not leave df, so I did not have to recreate df
df[, y := unlist(mapply(seq, df[x == 1L]$diff))]
# as I did not have to recreate df, I need to clean the "help" columns
df[, c("id", "diff") := NULL]
 

Комментарии:

1. Спасибо. Но не могли бы вы также включить некоторые пояснения к коду для таких читателей, как я, которым было бы немного сложно понять. Я уверен, что это тоже должно быть отличной работой, но я не смог понять, как работает этот код.

2. Я добавил более подробное объяснение и сравнил шаги с методом tidyverse.

3. вау. это тоже здорово … спасибо, что поделились советами и обошли. потрясающе!