根据日期之间的排名逐行选择值
Posted
技术标签:
【中文标题】根据日期之间的排名逐行选择值【英文标题】:Select values row-wise based on rank among dates 【发布时间】:2018-02-05 18:08:48 【问题描述】:假设我有一个包含如下几行的数据框:
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"))
对于每一行,我想根据dates
获得不是a
、b
和c
之间的NA
的最新值(所以我分别查看date1
, date2
或 date3
并选择最新的)。
基本上,date1
给出了与值a
对应的日期,
date2
给出与值 b
对应的日期,
date3
给出与值c
对应的日期。
如果date1 > date2
& date1 > date3
,我想取值a
但是,如果a
的值是NA
(在我的示例中就是这种情况),我将比较date2
和date3
。在我的示例中,date2 > date3
,由于值 b
不是 NA
而是 50
,我将采用 50
作为我的最终结果。
现在我想对数据框中的所有行执行此操作
由于我使用dplyr
,我尝试通过rank函数使用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
有没有人对这个问题有想法甚至完全不同的方法?
【问题讨论】:
【参考方案1】:我建议转换为长格式并计算相关值。如果需要,您可以将结果添加到原始 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
【讨论】:
谢谢!由于使用了 data.table 对象,我对算法的速度印象深刻【参考方案2】:这应该处理它。首先,我们将数据整理成整齐的形式(每个日期、值各 1 行,以及一个 row_num 以标识整齐的行属于哪个示例)。然后我们过滤掉NAs,group_by row_num,order by Date降序,取第一行。
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()
【讨论】:
【参考方案3】:这是一种方法...
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
【讨论】:
【参考方案4】:这里还有一种方法:
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
【讨论】:
【参考方案5】:另一个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
【讨论】:
【参考方案6】:聚会结束了,但我刚刚看到这个帖子并决定留下以下内容。我的想法是我宁愿创建一个数据框并完成这项工作。
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
【讨论】:
以上是关于根据日期之间的排名逐行选择值的主要内容,如果未能解决你的问题,请参考以下文章