3
\$\begingroup\$

The Function

"Persistence Rate" is also sometimes referred to as "retention rate". It is the number of ID's in a given term/period that are also found in the next term/period. (e.g., I have 10 customers in October but only 3 of those customers return in November. My persistence rate is then 30%.)

I have written a function to calculate persistence from a dataframe containing a list of ID's and the rank of the term/period (e.g., October is ranked 10 out of all the 12 months). The function uses tidyeval. It has all of the necessary arguments -- data, id, and rank. I also included a period argument so that the output is easily interpreted. (e.g., output "October" instead of 10). I also included a grouping argument (...) (e.g., persistence rates by gender). The rmax assignment is so I can filter out the last term, since you wouldn't be able to calculate the last terms persistence rate. All of this fits pretty neatly into one dplyr chain.

But I wanted to add an "overall" argument. The overall argument would be a logical argument, indicating whether or not to include the entire population's persistence rate (not considering the term or grouping variables).

Here is what I came up with:

  persist_sum <- function(df, id, period, rank, ..., overall = FALSE){

  rmax <- max(df[deparse(substitute(rank))])

  enq_period <- enquo(period)
  enq_id <- enquo(id)
  enq_rank <- enquo(rank)
  enq_group_var <- quos(...)

   out <- df %>%
          group_by(UQ(enq_id)) %>%   
          arrange(UQ(enq_rank)) %>% 
          mutate(nextrank = lead(UQ(enq_rank))) %>% 
          group_by(UQS(enq_group_var), UQ(enq_period), UQ(enq_rank)) %>% 
          summarize(persistence_rate=sum(nextrank == UQ(enq_rank) + 1, na.rm = TRUE)/n()) %>%
          filter(!! enq_rank != rmax) %>%
          select(- UQ(enq_rank))

   if(overall == TRUE){
    total <- df %>%
              group_by(UQ(enq_id)) %>%   
              arrange(UQ(enq_rank)) %>% 
              mutate(nextrank = lead(UQ(enq_rank))) %>% 
              ungroup()%>%
              filter(UQ(enq_rank)!= rmax)%>%
              summarize(persistence_rate=sum(nextrank == (UQ(enq_rank) + 1), na.rm = TRUE)/n())%>%
              as.numeric()

    out <- out %>%
            mutate(overall = total)
     }

  return(out)
}

The Problem/Question

Any suggestions for how to improve this code would be welcome, but my main concern is that calculating the overall persistence rate using the if() statement uses a lot of code. I feel like I should be able to more efficiently incorporate the logical argument into my dplyr chain.

Any recommendations for reducing the amount of text in this code? Also looking for best practices when it comes to incorporating arguments into the code.

Practice Data

Here is the practice data that I have been using to test this function. It also works on the real data that it is intended for.

dataFrame <- data.frame(id = as.character(c(1, 2, 3, 4, 1, 2)), 
                        period = c("A", "A", "A", "A", "B", "B"), 
                        rank = c(1, 1, 1, 1, 2, 2), 
                        group = c(1, 2, 1, 2, 1, 2), 
                        stringsAsFactors = FALSE)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.