在R中,使用mutate()根据组

时间:2017-03-20 16:46:49

标签: r dplyr

对于每个人,有两种类型的访问,每次访问都有日期记录。数据集如下所示。

p <-c(1,1,1,2,2,2,2,3,3,3,4)
type <- c(15,20,20,15,20,15,20,20,15,15,15)
date <- as.Date.factor(c("2014-02-03","2014-02-04","2014-02-06","2014-01-28","2014-02-03","2014-03-03","2014-03-13","2014-04-03","2014-04-09","2014-12-03","2014-04-05"))
d <- data.frame(p,type,date)

所以现在数据集看起来像这样。

> d
   p type       date
1  1   15 2014-02-03
2  1   20 2014-02-04
3  1   20 2014-02-06
4  2   15 2014-01-28
5  2   20 2014-02-03
6  2   15 2014-03-03
7  2   20 2014-03-13
8  3   20 2014-04-03
9  3   15 2014-04-09
10 3   15 2014-12-03

现在,我想创建三个新列。

  1. 表示在15型访问后7天内是否发生20型访问,如果是,则指标为1,否则为0.(例如,对于p2,在第4行中,此值应为1 ,在第6行中,该值应为0)

  2. 15型访问后7天内第20次访问的第一次约会是什么时候。如果在类型15之后的7天内没有20型访问,则将其保持空白。 (例如,对于p1,值应为2014-02-04而不是2014-02-06)

  3. 15天访问和20型访问之间的天数是7天之内。如果在类型15之后的7天内没有类型20访问,则保持空白。(例如,第1行中的值应为1)

  4. 我是R的超级新手,基本上不知道该怎么做。我在组内尝试了一个for循环,但它永远不会起作用。

    group_by(p)%>%
    for(i in i:length(date)){
      *if(type[i]== 15 && date[i]+7 >= date[i+1:length(date)]){
      indicator = 1
      first_date = 
      days =* #Have no idea how to check in this part
    } else {
      indicator = 0
      first_date = NA
      days = NA
    }
    

    预期输出如下。

       p type       date ind first_date days
    1  1   15 2014-02-03   1 2014-02-04    1 # = 2014-02-04 - 2014-02-03  
    2  1   20 2014-02-04  NA       <NA>   NA
    3  1   20 2014-02-06  NA       <NA>   NA
    4  2   15 2014-01-28   1 2014-02-03    6 # = 2014-02-03 - 2014-01-28   
    5  2   20 2014-02-03  NA       <NA>   NA
    6  2   15 2014-03-03   0       <NA>   NA # since (2014-03-13 - 2014-03-03) > 7   
    7  2   20 2014-03-13  NA       <NA>   NA
    8  3   20 2014-04-03  NA       <NA>   NA #I don't care about the value for type 20 lines
    9  3   15 2014-04-09   0       <NA>   NA
    10 3   15 2014-12-03   0       <NA>   NA
    

    所以我提出了一个新想法。如果我们按p和= = 15对记录进行分组怎么办。然后我们可以在组内使用减法作为天数,其余的将很容易。

    我找到了一种方法:

     d[,group:= cumsum(type ==15)]
    

    但是,这会在遇到新的15类记录时对组进行计数。如何将p添加为另一个分组条件?

3 个答案:

答案 0 :(得分:1)

我抓住了这个。但有一点需要注意:我的回答是假设在第15次访问后,7天内的下次访问将是type_20访问。如果情况并非如此,即在7天内还有另一种类型15访问,则第一种类型15访问不会被考虑,只有第二种类型15访问很重要:

library(dplyr)
library(tidyr)
library(lubridate)

d %>% 
  mutate(rownum = 1:n()) %>%
  spread(type, date, sep="_")  %>% 
  group_by(p) %>%
  mutate(ind = ifelse(lead(type_20) - type_15 <= 7, 1, 0)) %>%
  mutate(ind = ifelse(is.na(ind), 0, ind)) %>%
  mutate(ind = ifelse(is.na(type_15), NA, ind)) %>%
  mutate(first_date = ifelse(ind == 1, lead(type_20), NA)) %>%
  mutate(first_date = as.Date(first_date, origin = lubridate::origin)) %>%
  mutate(days = first_date - type_15) %>%
  gather("type", "date", type_15, type_20) %>% 
  filter(!is.na(date)) %>% 
  arrange(p, date) %>%
  select(p, type, date, ind, first_date, days)

#       p    type       date   ind first_date    days
#   <dbl>   <chr>     <date> <dbl>     <date>  <time>
#1      1 type_15 2014-02-03     1 2014-02-04  1 days
#2      1 type_20 2014-02-04    NA       <NA> NA days
#3      1 type_20 2014-02-06    NA       <NA> NA days
#4      2 type_15 2014-01-28     1 2014-02-03  6 days
#5      2 type_20 2014-02-03    NA       <NA> NA days
#6      2 type_15 2014-03-03     0       <NA> NA days
#7      2 type_20 2014-03-13    NA       <NA> NA days
#8      3 type_20 2014-04-03    NA       <NA> NA days
#9      3 type_15 2014-04-09     0       <NA> NA days
#10     3 type_15 2014-12-03     0       <NA> NA days

让我试着解释一下我在做什么:

首先展开typedate列,以便类型和日期显示在不同的列中(这样可以更轻松地比较两种不同类型的日期)。接下来,几个变异。前三个应用问题中列出的条件如下:如果lead(type_20) - type_15 <= 7)表示在第15次访问的7天内有20型访问,那么我们将其标记为1,否则我们标记为0。在此之后,如果indNA,我们假设没有找到20型访问,因此我们也将其标记为0.在第三次变异中,我们将15种NA行标记为NA。

接下来的三个mutate行添加了问题中2和3中列出的列。

最后,将列回收到之前的格式,过滤掉冗余行,按p和日期排列数据框,并选择所需的列。

我希望这很清楚。逐行运行代码,停止在每行之后查看转换后的数据框以查看转换如何对数据框起作用可能会有所帮助。

答案 1 :(得分:1)

这是基础R方式。通常,我更喜欢创建一个执行任务的函数,然后可以在其他部分上重复这些函数,并在似乎无效的测试用例上进行调试。

第一步是定义各个部分:

d <- structure(list(p = c(1, 1, 1, 2, 2, 2, 2, 3, 3, 3),
                    type = c(15, 20, 20, 15, 20, 15, 20, 20, 15, 15),
                    date = structure(c(16104, 16105, 16107, 16098, 16104, 16132, 16142, 16163, 16169, 16407), class = "Date")),
               .Names = c("p", "type", "date"),
               row.names = c(NA, -10L), class = "data.frame")

id <- with(d, {
  id <- ave(type, p, FUN = function(x) cumsum(x == 15))
  factor(paste0(p, id), unique(paste0(p, id)))
})

sp <- split(d, id)

因此,sp创建了一个我们将应用函数的数据框列表。每件作品都是一个唯一的p,最多只有一个type == 15(加上type == 20sp[1:2] # $`11` # p type date # 1 1 15 2014-02-03 # 2 1 20 2014-02-04 # 3 1 20 2014-02-06 # # $`21` # p type date # 4 2 15 2014-01-28 # 5 2 20 2014-02-03

前两部分是

first_date(sp[[1]])

#   p type       date ind first_date days
# 1 1   15 2014-02-03   1 2014-02-04    1
# 2 1   20 2014-02-04  NA       <NA>   NA
# 3 1   20 2014-02-06  NA       <NA>   NA

first_date(sp[[2]])

#   p type       date ind first_date days
# 4 2   15 2014-01-28   1 2014-02-03    6
# 5 2   20 2014-02-03  NA       <NA>   NA

我们可以在每个

上应用以下功能
(sp1 <- lapply(sp, first_date))
`rownames<-`(do.call('rbind', sp1), NULL)

#    p type       date ind first_date days
# 1  1   15 2014-02-03   1 2014-02-04    1
# 2  1   20 2014-02-04  NA       <NA>   NA
# 3  1   20 2014-02-06  NA       <NA>   NA
# 4  2   15 2014-01-28   1 2014-02-03    6
# 5  2   20 2014-02-03  NA       <NA>   NA
# 6  2   15 2014-03-03   0       <NA>   NA
# 7  2   20 2014-03-13  NA       <NA>   NA
# 8  3   20 2014-04-03  NA       <NA>   NA
# 9  3   15 2014-04-09   0       <NA>   NA
# 10 3   15 2014-12-03   0       <NA>   NA

或者一次性使用循环

window

您可以利用(sp2 <- lapply(sp1, first_date, window = 14)) `rownames<-`(do.call('rbind', sp2), NULL) # p type date ind first_date days ind first_date days # 1 1 15 2014-02-03 1 2014-02-04 1 1 2014-02-04 1 # 2 1 20 2014-02-04 NA <NA> NA NA <NA> NA # 3 1 20 2014-02-06 NA <NA> NA NA <NA> NA # 4 2 15 2014-01-28 1 2014-02-03 6 1 2014-02-03 6 # 5 2 20 2014-02-03 NA <NA> NA NA <NA> NA # 6 2 15 2014-03-03 0 <NA> NA 1 2014-03-13 10 # 7 2 20 2014-03-13 NA <NA> NA NA <NA> NA # 8 3 20 2014-04-03 NA <NA> NA NA <NA> NA # 9 3 15 2014-04-09 0 <NA> NA 0 <NA> NA # 10 3 15 2014-12-03 0 <NA> NA 0 <NA> NA 等参数或您添加的任何其他参数,而无需更改功能,例如,更改窗口

first_date <- function(data, window = 7) {
  nr <- nrow(data)

  ## check at least one type 15 and > 1 row
  ty15 <- data$type == 15
  dt15 <- data$date[ty15]

  if (!any(ty15) | nr == 1L)
    return(cbind(data, ind = ifelse(any(ty15), 0, NA),
                 first_date = NA, days = NA))

  ## first date vector
  dts <- rep(min(data$date[!ty15]), nr)
  dts[!ty15] <- NA

  ## days from the type 15 date
  days <- as.numeric(data$date[!ty15] - min(dt15))
  days <- c(days, rep(NA, nr - length(days)))

  ## convert to NA if criteria not met
  to_na <- days > window | is.na(dts)
  days[to_na] <- dts[to_na] <- NA

  ## ind vector -- 1 or 0 if type 15, NA otherwise
  ind <- rep(NA, nr)
  ind[ty15] <- as.integer(!is.na(dts[ty15]))

  ## combine
  cbind(data, ind = ind, first_date = dts, days = days)
}
{{1}}

答案 2 :(得分:1)

如果您愿意使用purrr包中的某些功能并使用一些自定义功能,这是另一种选择...

您需要的套餐

library(dplyr)
library(purrr)

设置数据(根据问题)

p <-c(1,1,1,2,2,2,2,3,3,3)
type <- c(15,20,20,15,20,15,20,20,15,15)
date <- as.Date.factor(c("2014-02-03","2014-02-04","2014-02-06","2014-01-28","2014-02-03","2014-03-03","2014-03-13","2014-04-03","2014-04-09","2014-12-03"))
d <- data.frame(cbind(p,type,date))
d$date = as.Date(date)

创建可与purrr map_*函数配合使用的自定义函数,以迭代数据框并创建indfirst_date

# Function to manage ind
ind_manager <- function(type, date, dates_20) {
  if (type == 20)
    return (NA_integer_)

  checks <- map_lgl(dates_20, between, date, date + 7)
  return (as.integer(any(checks)))
}

# Function to manage first_date
first_date_manager <- function(ind, date, dates_20) {
  if (is.na(ind) || ind != 1)
    return (NA_character_)

  dates_20 <- dates_20[order(dates_20)]
  as.character(dates_20[which.max(date < dates_20)])
}

保存日期向量,其中type == 20用作比较

dates_20 <- d$date[d$type == 20]

最后mutate()来电

# mutate() call to create variables
d %>% 
  mutate(
    ind = map2_int(type, date, ind_manager, dates_20),
    first_date = as.Date(map2_chr(ind, date, first_date_manager, dates_20)),
    days = as.integer(first_date - date)
  )
#>    p type       date ind first_date days
#> 1  1   15 2014-02-03   1 2014-02-04    1
#> 2  1   20 2014-02-04  NA       <NA>   NA
#> 3  1   20 2014-02-06  NA       <NA>   NA
#> 4  2   15 2014-01-28   1 2014-02-03    6
#> 5  2   20 2014-02-03  NA       <NA>   NA
#> 6  2   15 2014-03-03   0       <NA>   NA
#> 7  2   20 2014-03-13  NA       <NA>   NA
#> 8  3   20 2014-04-03  NA       <NA>   NA
#> 9  3   15 2014-04-09   0       <NA>   NA
#> 10 3   15 2014-12-03   0       <NA>   NA