Новая переменная, указывающая на первое вхождение определенного значения

Я хочу создать новую переменную, которая указывает первое конкретное наблюдение значения переменной.

В следующем примере набора данных я хочу иметь новую переменную firstna, равную «1» для первого наблюдения «NA» для этого игрока.

game_data <- data.frame(player = c(1,1,1,1,2,2,2,2), level = c(1,2,3,4,1,2,3,4), points = c(20,NA,NA,NA,20,40,NA,NA))

game_data
  player level points
1      1     1     20
2      1     2     NA
3      1     3     NA
4      1     4     NA
5      2     1     20
6      2     2     40
7      2     3     NA
8      2     4     NA

Результирующий фрейм данных должен выглядеть так:

game_data_new <- data.frame(player = c(1,1,1,1,2,2,2,2), level = c(1,2,3,4,1,2,3,4), points = c(20,NA,NA,NA,20,40,NA,NA), firstna = c(0,1,0,0,0,0,1,0))

game_data_new
  player level points firstna
1      1     1     20       0
2      1     2     NA       1
3      1     3     NA       0
4      1     4     NA       0
5      2     1     20       0
6      2     2     40       0
7      2     3     NA       1
8      2     4     NA       0

Если честно, я не знаю, как это сделать. Было бы идеально, если бы для этого был вариант dplyr.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
0
252
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

Вот решение с data.table:

library("data.table")

game_data <- data.table(player = c(1,1,1,1,2,2,2,2), level = c(1,2,3,4,1,2,3,4), points = c(20,NA,NA,NA,20,40,NA,NA))

game_data[, firstna:=is.na(points) & !is.na(shift(points)), player][]
# > game_data[, firstna:=is.na(points) & !is.na(shift(points)), player][]
#    player level points firstna
# 1:      1     1     20   FALSE
# 2:      1     2     NA    TRUE
# 3:      1     3     NA   FALSE
# 4:      1     4     NA   FALSE
# 5:      2     1     20   FALSE
# 6:      2     2     40   FALSE
# 7:      2     3     NA    TRUE
# 8:      2     4     NA   FALSE

Вы можете сделать это, сгруппировав по игрокам, а затем изменив, чтобы проверить, имеет ли строка значение NA, а предыдущая строка - нет.

game_data %>%
  group_by(player) %>%
  mutate(firstna = ifelse(is.na(points) & lag(!is.na(points)),1,0)) %>%
  ungroup()

Результат:

# A tibble: 8 x 4
# Groups:   player [2]
  player level points firstna
   <dbl> <dbl>  <dbl>   <dbl>
1      1     1     20       0
2      1     2     NA       1
3      1     3     NA       0
4      1     4     NA       0
5      2     1     20       0
6      2     2     40       0
7      2     3     NA       1
8      2     4     NA       0

Это предполагает, что НА всегда все вместе в одной группе.

iod 26.11.2018 14:22
Ответ принят как подходящий
game_data %>% 
  group_by(player) %>% 
  mutate(firstna=as.numeric(is.na(points) & !duplicated(points)))

Сгруппируйте по игрокам, затем создайте логический вектор для случаев, которые одновременно являются NA и не дублируют предыдущие строки.

# A tibble: 8 x 4
# Groups:   player [2]
  player level points firstna
   <dbl> <dbl>  <dbl>   <dbl>
1      1     1     20       0
2      1     2     NA       1
3      1     3     NA       0
4      1     4     NA       0
5      2     1     20       0
6      2     2     40       0
7      2     3     NA       1
8      2     4     NA       0

Если вы хотите, чтобы единицы в последней строке, отличной от NA, перед NA, замените строку mutate следующим образом:

mutate(lastnonNA=as.numeric(!is.na(points) & is.na(lead(points))))

Первый ряд блока НА, идущий до конца группы игрока:

game_data %>% 
  group_by(player) %>% 
  mutate(firstna=as.numeric(is.na(points) & !duplicated(cbind(points,cumsum(!is.na(points))))))

Спасибо за помощь, код работает отлично, и мне нравится работать с dplyr. Что я могу добавить, если хочу, чтобы в строке перед первым появлением «NA» была цифра «1» для firstna (в примере кода в строках 1 и 6)?

Scijens 27.11.2018 17:33

Я добавил необходимый код в свой ответ выше. Рад слышать, что это помогло. Не забудьте проголосовать и принять!

iod 27.11.2018 17:36

Большое спасибо, это мне очень помогает!

Scijens 30.11.2018 15:39

Теперь мне нужно первое наблюдение NA, где все последующие строки для этого пользователя также NA. Мне это нужно для кодирования, чтобы пользователь вышел из игры

Scijens 03.12.2018 16:31

Что ж, вы можете начать с кода, который у вас есть, и посмотреть, сможете ли вы выяснить, как применить этот метод к вашим новым потребностям.

iod 03.12.2018 16:41

Я его мучил, но решения пока не нашел. Я буду продолжать попытки.

Scijens 03.12.2018 18:07

тогда рассмотрите возможность публикации этого вопроса как нового. Вы привлечете к нему больше внимания.

iod 03.12.2018 18:13

черт возьми, да ладно. Это решение тоже добавлено.

iod 03.12.2018 18:24
library(tidyverse)
library(data.table)

data.frame(
  player = c(1,1,1,1,2,2,2,2),
  level = c(1,2,3,4,1,2,3,4),
  points = c(20,NA,NA,NA,20,40,NA,NA)
) -> game_data

game_data_base1 <- game_data
game_data_dt <- data.table(game_data)

microbenchmark::microbenchmark(

  better_base = game_data$first_na <- ave(
    game_data$points,
    game_data$player,
    FUN=function(x) seq_along(x)==match(NA,x,nomatch=0)
  ),

  brute_base = do.call(
    rbind.data.frame,
    lapply(
      split(game_data, game_data$player),
      function(x) {
        x$firstna <- 0
        na_loc <- which(is.na(x$points))
        if (length(na_loc) > 0) x$firstna[na_loc[1]] <- 1
        x
      }
    )
  ),

  tidy = game_data %>%
    group_by(player) %>%
    mutate(firstna=as.numeric(is.na(points) & !duplicated(points))) %>%
    ungroup(),

  dt = game_data_dt[, firstna:=as.integer(is.na(points) & !is.na(shift(points))), player]

)
## Unit: microseconds
##         expr     min       lq      mean    median        uq        max neval
##  better_base 125.188  156.861  362.9829  191.6385  355.6675   3095.958   100
##   brute_base 366.642  450.002 2782.6621  658.0380 1072.6475 174373.974   100
##         tidy 998.924 1119.022 2528.3687 1509.0705 2516.9350  42406.778   100
##           dt 330.428  421.211 1031.9978  535.8415 1042.1240   9671.991   100

Базовое решение R:

ave(game_data$points, game_data$player,
    FUN = function(x) seq_along(x) == match(NA, x, nomatch = 0))

Черт, ave снова наносит удар. Этой функции нужен лучший PR. Отлично сделано! (и это тоже сверх быстрый)

hrbrmstr 26.11.2018 14:28

да, какого черта, это не имеет отношения к средним значениям ... почему это так описано в документации?

iod 26.11.2018 14:30

один вопрос: почему возвращается числовое, а не логическое (как я ожидал от ==)?

Andre Elrico 26.11.2018 14:31
ave приводит возвращаемый тип к типу своего первого аргумента. Итак, будучи game_data$pointsnumeric, возвращается числовой вектор. Об этом следует помнить при использовании ave. На этот раз эта функция работает хорошо, но в других случаях она может быть препятствием.
nicola 26.11.2018 14:33

это объясняет, почему в случае game_data$points <- NA возвращается логический. Очень хорошо! кое-что узнал.

Andre Elrico 26.11.2018 14:37

Другой вариант ave для определения первого NA по группе (player).

game_data$firstna <- ave(game_data$points, game_data$player, 
                         FUN = function(x) cumsum(is.na(x)) == 1)

game_data
#  player level points firstna
#1      1     1     20       0
#2      1     2     NA       1
#3      1     3     NA       0
#4      1     4     NA       0
#5      2     1     20       0
#6      2     2     40       0
#7      2     3     NA       1
#8      2     4     NA       0

тоже очень хорошо. Возможно немного быстрее оригинала :)

Andre Elrico 26.11.2018 14:38

Прохладный! Но ... я думаю, что это не так с ведущим NA ...: cumsum(is.na(c(NA, 1, NA))). Возможно, этого никогда не происходит с OP, но я просто хотел упомянуть об этом. Ваше здоровье

Henrik 26.11.2018 16:02

Другой способ использования базы:

game_data$firstna <-
unlist(
tapply(game_data$points, game_data$player, function(x) {i<-which(is.na(x))[1];x[]<-0;x[i]<-1;x})
)

или как другой клон ?ave:

ave(game_data$points, game_data$player, FUN = function(x) {
    i<-which(is.na(x))[1];x[]<-0;x[i]<-1;x
})

Вариант с использованием diff

transform(game_data, firstna = ave(is.na(points), player, FUN = function(x) c(0,diff(x))))
#   player level points firstna
# 1      1     1     20       0
# 2      1     2     NA       1
# 3      1     3     NA       0
# 4      1     4     NA       0
# 5      2     1     20       0
# 6      2     2     40       0
# 7      2     3     NA       1
# 8      2     4     NA       0

И его аналог для dplyr:

library(dplyr)
game_data %>% group_by(player) %>% mutate(firstna = c(0,diff(is.na(points))))
# # A tibble: 8 x 4
# # Groups:   player [2]
#   player level points firstna
#    <dbl> <dbl>  <dbl>   <dbl>
# 1      1     1     20       0
# 2      1     2     NA       1
# 3      1     3     NA       0
# 4      1     4     NA       0
# 5      2     1     20       0
# 6      2     2     40       0
# 7      2     3     NA       1
# 8      2     4     NA       0

Другие вопросы по теме