Я пытаюсь найти наиболее близкое соответствие значению из lastseen из ряда других столбцов при посещении сайта (d1, d2, d3, d4, d5), чтобы создать новый столбец nextvisit со значением из d1 , d2, d3, d4 или d5, который является следующим по величине значением в lastseen (т.е. следующее посещение после того, как человека видели в последний раз).
Воспроизводимый пример:
indiv lastseen d1 d2 d3 d4 d5
A 2 2 4 5 8 10
B 5 2 3 5 7 9
C 9 1 6 9 11 15
Итак, ответ, который я ищу:
indiv lastseen d1 d2 d3 d4 d5 nextvisit
A 2 2 4 5 8 10 4
B 5 2 3 5 7 9 7
C 9 1 6 9 11 15 11
Например, 4 - это следующее по величине число в столбцах d1:d5 над 2 для индивидуума A.
Я пробовал использовать tidyr и dplyr, но не могу эффективно найти следующее по величине совпадение.
Спасибо





Используя tidyverse, мы gather столбцы от 'd1' до 'd5' в 'длинный' формат, сгруппированные по 'indiv', создаем столбец разницы между 'val' и 'last seen', slice строку, которая имеет минимальное положительное значение. , select интересующие столбцы и выполнить соединение с исходным набором данных
library(tidyverse)
df1 %>%
gather(key, val, d1:d5) %>%
group_by(indiv) %>%
mutate(Diff = val -lastseen,
Diff = replace(Diff, Diff <=0, NA)) %>%
slice(which.min(Diff)) %>%
select(indiv, val) %>%
right_join(df1) %>%
select(names(df1), everything())
# A tibble: 3 x 8
# Groups: indiv [3]
# indiv lastseen d1 d2 d3 d4 d5 val
# <chr> <int> <int> <int> <int> <int> <int> <int>
#1 A 2 2 4 5 8 10 4
#2 B 5 2 3 5 7 9 7
#3 C 9 1 6 9 11 15 11
Другой вариант - использовать max.col от base R. Получите разницу столбцов 'd' с столбцами 'последнего посещения' в объекте ('m1'), замените значения, которые меньше или равны 0, на очень большое число, используйте max.col, чтобы получить индекс столбца каждая строка, имеющая максимальное значение (обратная логика - изменила его на отрицательное), cbind с индексом строки и извлекает значение из столбцов 'd', которые ему соответствуют.
m1 <- df1[3:7] -df1$lastseen
m1[m1 <=0] <- 999
df1$val <- df1[3:7][cbind(seq_len(nrow(df1)), max.col(-m1, 'first'))]
df1$val
#[1] 4 7 11
df1 <- structure(list(indiv = c("A", "B", "C"), lastseen = c(2L, 5L,
9L), d1 = c(2L, 2L, 1L), d2 = c(4L, 3L, 6L), d3 = c(5L, 5L, 9L
), d4 = c(8L, 7L, 11L), d5 = c(10L, 9L, 15L)),
class = "data.frame", row.names = c(NA, -3L))
Считайте, что df - это ваш data.frame. Вот подход с полной базой R
> ind <- (df[, -c(1,2)]- df[, 2])>0
> df$nextvisit <- apply(df[, -c(1,2)]*ind, 1, function(x) min(x[x!=0]))
> df
indiv lastseen d1 d2 d3 d4 d5 nextvisit
1 A 2 2 4 5 8 10 4
2 B 5 2 3 5 7 9 7
3 C 9 1 6 9 11 15 11
df = read.table(text = "
indiv lastseen d1 d2 d3 d4 d5
A 2 2 4 5 8 10
B 5 2 3 5 7 9
C 9 1 6 9 11 15
", header=T)
library(tidyverse)
df %>%
group_by(indiv, lastseen) %>% # for each combination
nest() %>% # nest data
mutate(nextvisit = map2(lastseen, data, ~{vec = unlist(.y); min(vec[vec > .x])})) %>% # get the minimum value higher than the corresponding lastseen value
unnest() # unnest data
# # A tibble: 3 x 8
# indiv lastseen nextvisit d1 d2 d3 d4 d5
# <fct> <int> <int> <int> <int> <int> <int> <int>
# 1 A 2 4 2 4 5 8 10
# 2 B 5 7 2 3 5 7 9
# 3 C 9 11 1 6 9 11 15
Спасибо. Искал функцию "получше", но, похоже, мне нужен unlist.
Другой вариант base R
idx <- which(DF[-c(1, 2)] == DF$lastseen, arr.ind = TRUE)
idx[, "col"] <- idx[, "col"] + 1 # lastseen + 1 = next visit (in terms of column positions)
DF$nextvisit <- DF[-c(1, 2)][idx]
DF
# indiv lastseen d1 d2 d3 d4 d5 nextvisit
#1 A 2 2 4 5 8 10 4
#2 B 5 2 3 5 7 9 7
#3 C 9 1 6 9 11 15 11
данные
DF <- structure(list(indiv = c("A", "B", "C"), lastseen = c(2L, 5L,
9L), d1 = c(2L, 2L, 1L), d2 = c(4L, 3L, 6L), d3 = c(5L, 5L, 9L
), d4 = c(8L, 7L, 11L), d5 = c(10L, 9L, 15L)), .Names = c("indiv",
"lastseen", "d1", "d2", "d3", "d4", "d5"), class = "data.frame", row.names = c(NA,
-3L))
другой базовый способ R:
df$lastvisit <- apply(df[,-1], 1, function(x) min(tail(x,5)[tail(x,5)>head(x,1)]))
или менее читаемый, но короче:
df$lastvisit <- apply(df[,-1], 1, function(x) min(x[-1][x[-1]>x[1]]))
Вот решение, которое сочетает в себе функции data.table и Find():
library(data.table)
setDT(df1)[, nextvisit := Find(function(x) x > lastseen, .SD), .SDcols = d1:d5, by = indiv]
df1[]
indiv lastseen d1 d2 d3 d4 d5 nextvisit 1: A 2 2 4 5 8 10 4 2: B 5 2 3 5 7 9 7 3: C 9 1 6 9 11 15 11
Find() применяет функцию фильтрации function(x) x > lastseen к столбцам .SD слева направо в каждой строке и возвращает первый элемент, который удовлетворяет условию. Столбцы .SD определены .SDcols = d1:d5.
Обратите внимание, что значения в каждой строке должны быть уже отсортированы в порядке возрастания слева направо. Если не уверен, .SD. можно заменить на sort(.SD).
Решение tidyverse, полностью векторизованное и без преобразования в матрицу здесь:
library(tidyverse)
df1 %>%
transmute_at(vars(starts_with("d")), ~ ifelse(.x>.y, .x, Inf), .$lastseen) %>%
invoke(pmin,.) %>%
bind_cols(df1,nextvisit=.)
# indiv lastseen d1 d2 d3 d4 d5 nextvisit
# 1 A 2 2 4 5 8 10 4
# 2 B 5 2 3 5 7 9 7
# 3 C 9 1 6 9 11 15 11
Хорошее использование map2