Call the same function by varying parameters with tidyverse and apply family functions

Viewed 10036

This is my first question here on Stack Overflow, so I apologize in advance if I won't be clear enough. I searched for similar questions, but I didn't find anything (I probably didn't search enough!)

Given a data.frame (or a data.table or a tibble) consisting of four sets of points divided into two groups:

df_points <- tibble(
    x = c(rnorm(10000, mean = 0), rnorm(10000, mean = 1), 
          rnorm(10000, mean = 0), rnorm(10000, mean = 4)), 
    dist = c(rep("d1", 10000), rep("d2", 10000), 
             rep("d1", 10000), rep("d2", 10000)), 
    overlap = c(rep("o1", 20000), rep("o2", 20000))
)

my goal is to apply the density function, using different values of bw, from and to for the "o1" and "o2" groups.

I would like to solve this problem in an elegant way with both a tidyverse and a R-base-data.table approach (apply family functions).

For now I have managed to do this via tidyverse:

  1. I define a common_dens function which applies density and returns a tibble of the x and y of the distribution

    common_dens <- function(df, Bw, lower, upper) {
      d <- density(df, n = 2048, bw = Bw, from = lower, to = upper)
      df_d <- tibble(x = d$x, y = d$y)
      return(df_d)
    }
    
  2. assuming that the values of upper, lower and bws are the following:

    lower <- c(-5.050, -4.705)
    upper <- c(6.445, 9.070)
    bws <- c(0.1427, 0.1417)
    

    I get the desired dataframe through the following for loop:

    df_dens <- NULL
    for (i in 1:2) {
      df_t <- df_points %>% 
          filter(overlap == unique(df_points$overlap)[[i]]) %>% 
          group_by(dist, overlap) %>%
          summarise(common_dens(x, bws[i], lower[i], upper[i]))
      df_dens <- rbind(df_dens, df_t)
    }
    

Is there any way to remove the for loop?

Is there a way to do the same with apply family functions and data.table?

Thanks for your help!

1 Answers

The purrr::pmap function allows you to apply an arbitrary number of parameters to a function in succession. The pmap_dfr returns a data.frame bound by row:

Consider your parameters provided as a data.frame:

params <- data.frame(group = c("o1","o2"), bws, lower, upper)
  group    bws  lower upper
1    o1 0.1427 -5.050 6.445
2    o2 0.1417 -4.705 9.070

The paramters are automatically assigned to the special symbols ..1, ..2, and so on:

library(purrr)
pmap_dfr(params, ~ df_points %>%
           filter(overlap == ..1) %>%
           group_by(dist, overlap) %>%
           summarise(common_dens(x,Bw = ..2, lower = ..3, upper = ..4)))

It can get confusing which ..# is which, so a trick is to use with(list(...), ):

pmap_dfr(params, ~ with(list(...), df_points %>%
           filter(overlap == group) %>% 
           group_by(dist, overlap) %>% 
           summarise(common_dens(x,Bw = bws, lower = lower, upper = upper))))

You could of course do the same with base R apply:

apply(params, 1, function(y){ df_points %>%
        filter(overlap == y[1]) %>% 
        group_by(dist, overlap) %>% 
        summarise(common_dens(x, Bw = as.numeric(y[2]), lower = as.numeric(y[2]),
                              upper = as.numeric(y[4])))}) %>%
   bind_rows()

However, because apply converts the types, you'll need to use as.numeric.

Related