Select 根据日期之间的排名按行值

Select values row-wise based on rank among dates

假设我有一个包含多行的数据框,如下所示:

df <- data.frame(a = c(NA,20,NA),
                 date1 = c("2016-03-01", "2016-02-01", "2016-02-01"),
                 b = c(50,NA, NA),
                 date2 = c("2016-02-01", "2016-03-01", "2016-03-01"),
                 c = c(10,10, 10),
                 date3 = c("2016-01-01","2016-01-01", "2016-01-01"))

对于每一行,我想根据 [=18] 在 abc 之间获取不是 NA 的最新值=](所以我分别看date1date2date3,然后选择最近的那个)。

基本上,date1给出值a对应的日期, date2给出值b对应的日期, date3给出值c对应的日期。

如果date1 > date2&date1 > date3,我会想取值a 但是,如果值 aNA(在我的示例中就是这种情况),我将比较 date2date3。在我的示例中 date2 > date3 ,并且由于值 b 不是 NA 而是 50,因此我将 50 作为我的最终结果。

现在我想对数据框中的所有行执行此操作

因为我使用的是 dplyr,所以我尝试使用排名函数来使用 case_when 函数(在我的示例中,我查看了排名第一的日期,然后查看链接的值。如果是 NA,我会看排名第二的,等等...)

但是,我不能像我想做的那样:

df <- df %>%
        mutate(result = case_when(is.na(a) & is.na(b) & is.na(c) ~ NA_integer_,
                                  rev(rank(date1, date2, date3))[1] == 3 & !is.na(a) ~ a,
                                  rev(rank(date1, date2, date3))[2] == 3 & !is.na(b) ~ b,
                                  rev(rank(date1, date2, date3))[3] == 3 & !is.na(a) ~ c,
                                  rev(rank(date1, date2, date3))[1] == 2 & !is.na(a) ~ a,
                                  rev(rank(date1, date2, date3))[2] == 2 & !is.na(b) ~ b,
                                  rev(rank(date1, date2, date3))[3] == 2 & !is.na(a) ~ c,
                                  rev(rank(date1, date2, date3))[1] == 1 & !is.na(a) ~ a,
                                  rev(rank(date1, date2, date3))[2] == 1 & !is.na(b) ~ b,
                                  rev(rank(date1, date2, date3))[3] == 1 & !is.na(a) ~ c))

因为 rank 函数需要一个唯一的向量作为参数(但我不能输入 c(date1, date2, date3) ,因为它会给我这个向量的整个顺序而不是每一行的排名)

在我的示例中,我想要的结果是

res

a    date1         b      date2       c    date3       result
NA   2016-03-01    50     2016-02-01  10   2016-01-01  50
20   2016-02-01    NA     2016-03-01  10   2016-01-01  20
NA   2016-02-01    NA     2016-03-01  10   2016-01-01  10

有没有人有解决这个问题的想法,甚至是完全不同的方法?

这是一种方法...

df$result <- apply(df, 1, function(x){
  dates <- as.Date(x[seq(2, length(x), 2)])
  values <- x[seq(1,length(x),2)]
  return(values[!is.na(values)][which.max(dates[!is.na(values)])])
})

df
   a      date1  b      date2  c      date3 result
1 NA 2016-03-01 50 2016-02-01 10 2016-01-01     50
2 20 2016-02-01 NA 2016-03-01 10 2016-01-01     20
3 NA 2016-02-01 NA 2016-03-01 10 2016-01-01     10

我建议转换为长格式并计算相关值。如果需要,您可以将结果添加到原始 data.frame。下面是如何使用 data.table:

library(data.table)
setDT(df)                     # convert to data.table object
df[, row := .I]               # add a row-id
dflong <- melt(df, id = "row", measure = patterns("^date", "^(a|b|c)"),
               na.rm = TRUE) # convert to long format
setorder(dflong, value1)      # reorder by date value
dflong <- unique(dflong, by = "row", fromLast = TRUE) # get the latest dates
df[dflong, result := i.value2, on = "row"]  # add result to original data

df
#    a      date1  b      date2  c      date3 row result
#1: NA 2016-03-01 50 2016-02-01 10 2016-01-01   1     50
#2: 20 2016-02-01 NA 2016-03-01 10 2016-01-01   2     20
#3: NA 2016-02-01 NA 2016-03-01 10 2016-01-01   3     10

这应该可以解决。首先,我们将数据整理成整齐的形式(每个日期、值对应 1 行,并用 row_num 标识整齐的行属于哪个示例)。然后我们过滤掉NA,group_by row_num,按日期降序排列,取第一行。

df %>%
  mutate(row_num = row_number()) %>%
  unite(a, a, date1) %>%
  unite(b, b, date2) %>%
  unite(c, c, date3) %>%
  gather(key, value, -row_num) %>%
  select(-key) %>%
  separate(value, into=c("Value", "Date"), sep = "_") %>%
  mutate(Date = as.Date(Date)) %>%
  filter(Value != "NA") %>%
  group_by(row_num) %>%
  top_n(1, Date) %>%
  ungroup()

还有一种方法:

df$row <- 1:nrow(df)

gather(df, key, date_val, date1, date2, date3, -row) %>% 
   select(-key) %>% 
   gather(key, val, a,b,c) %>% 
   filter(!is.na(val)) %>% 
   group_by(row) %>% 
   mutate(max_date = max(date_val)) %>% 
   filter(date_val == max_date) %>% summarise(result = max(val)) %>% 
   left_join(df, by="row") %>% select(-row)

# A tibble: 3 × 7
  result     a      date1     b      date2     c      date3
   <dbl> <dbl>     <fctr> <dbl>     <fctr> <dbl>     <fctr>
1     50    NA 2016-03-01    50 2016-02-01    10 2016-01-01
2     20    20 2016-02-01    NA 2016-03-01    10 2016-01-01
3     10    NA 2016-02-01    NA 2016-03-01    10 2016-01-01

另一个base备选方案:

df$id <- 1:nrow(df)
d2 <- reshape(df, varying = list(seq(1, by = 2, len = (ncol(df) - 1)/2),
                                 seq(2, by = 2, len = (ncol(df) - 1)/2)),
              direction = "long")

d2 <- with(d2, d2[order(-id, date1, decreasing = TRUE), ])

cbind(df, res = tapply(d2$a[!is.na(d2$a)], d2$id[!is.na(d2$a)], `[`, 1)) 
#    a      date1  b      date2  c      date3 id res
# 1 NA 2016-03-01 50 2016-02-01 10 2016-01-01  1  50
# 2 20 2016-02-01 NA 2016-03-01 10 2016-01-01  2  20
# 3 NA 2016-02-01 NA 2016-03-01 10 2016-01-01  3  10

派对结束了,但我刚刚看到这个 post 并决定留下以下内容。我的想法是我更想创建一个数据框并完成这项工作。

out <- data.frame(group = 1:nrow(df),
                  date = as.Date(unlist(df[, grep(x = names(df), "[1-9]")]),
                                 "%Y-%m-%d"),
                  result = unlist(df[nchar(names(df)) == 1])) %>%
       filter(complete.cases(.)) %>%
       group_by(group) %>%
       slice(which.max(date)) %>%
       ungroup

cbind(df, result = out$result)

#   a      date1  b      date2  c      date3 result
#1 NA 2016-03-01 50 2016-02-01 10 2016-01-01     50
#2 20 2016-02-01 NA 2016-03-01 10 2016-01-01     20
#3 NA 2016-02-01 NA 2016-03-01 10 2016-01-01     10

如果我使用data.table,我会做下面的事情,这是基于docendo的回答。

setDT(df)[, row := .I]

out <- melt(df, id = "row", measure = patterns("^date", "^(a|b|c)"), 
            value.name = c("date", "result"), na.rm = TRUE) [, 
                 date := as.Date(date, "%Y-%m-%d")][,
                     .SD[which.max(date)], by = row][, c("row", "result")]

df[out, on = "row"]

#    a      date1  b      date2  c      date3 row result
#1: 20 2016-02-01 NA 2016-03-01 10 2016-01-01   2     20
#2: NA 2016-03-01 50 2016-02-01 10 2016-01-01   1     50
#3: NA 2016-02-01 NA 2016-03-01 10 2016-01-01   3     10