Я хочу создать новую переменную, которая указывает первое конкретное наблюдение значения переменной.
В следующем примере набора данных я хочу иметь новую переменную 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.





Вот решение с 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
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)?
Я добавил необходимый код в свой ответ выше. Рад слышать, что это помогло. Не забудьте проголосовать и принять!
Большое спасибо, это мне очень помогает!
Теперь мне нужно первое наблюдение NA, где все последующие строки для этого пользователя также NA. Мне это нужно для кодирования, чтобы пользователь вышел из игры
Что ж, вы можете начать с кода, который у вас есть, и посмотреть, сможете ли вы выяснить, как применить этот метод к вашим новым потребностям.
Я его мучил, но решения пока не нашел. Я буду продолжать попытки.
тогда рассмотрите возможность публикации этого вопроса как нового. Вы привлечете к нему больше внимания.
черт возьми, да ладно. Это решение тоже добавлено.
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. Отлично сделано! (и это тоже сверх быстрый)
да, какого черта, это не имеет отношения к средним значениям ... почему это так описано в документации?
один вопрос: почему возвращается числовое, а не логическое (как я ожидал от ==)?
ave приводит возвращаемый тип к типу своего первого аргумента. Итак, будучи game_data$pointsnumeric, возвращается числовой вектор. Об этом следует помнить при использовании ave. На этот раз эта функция работает хорошо, но в других случаях она может быть препятствием.
это объясняет, почему в случае game_data$points <- NA возвращается логический. Очень хорошо! кое-что узнал.
Другой вариант 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
тоже очень хорошо. Возможно немного быстрее оригинала :)
Прохладный! Но ... я думаю, что это не так с ведущим NA ...: cumsum(is.na(c(NA, 1, NA))). Возможно, этого никогда не происходит с OP, но я просто хотел упомянуть об этом. Ваше здоровье
Другой способ использования базы:
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
Это предполагает, что НА всегда все вместе в одной группе.