Help with automated ggplot2 in a nested loop

Hello,

I'm trying to automate an exploratory analysis as suggested here: Automating exploratory plots with ggplot2 and purrr

i wrote a function that would help me plot relationships between explanatory variables and response variables grouped by demographic variables in color (geom_smooth).

jitter_fun <- function(x, y, z, ...) {
  
  require(tidyverse)
  
  x <- enquo(x)
  y <- enquo(y)
  z <- enquo(z)
  
  p <- ggplot(data = df, aes(x = !!x, y = !!y)) +
    geom_jitter() +
    geom_smooth(aes(color = !!z), se = FALSE)
  
  print(p)
}

expl <- c("HH", "A", "empathy", "Extraversion", "Emotionality", "O")
response <- c("courtesy", "noncourtesy", "family", "friend", "selfish", "prosocial")
demo <- c("race", "Gender", "Religion", "Region")


# The function works fine when I enter arguments myself. First image down below.
jitter_fun(HH, overalllie, race)

However, when I run a call that would give me all the plots at once, the regression lines don't appear; in fact, there are no scales indicated on the x and y axes. Second image below.

all_plots = map(demo, ~map(response, ~map(expl, jitter_fun, y = .x)), z = .x)

What can I do to fix this error?

This post was flagged by the community and is temporarily hidden.

I expect your issues are due to mixing standard and non standard evaluation.
I.e. you wrote code so that it's convenient to manually use it, and your functions use enqou etc to support that but then when your params come from other function calls as strings not symbols, this approach breaks down. I would follow the path of least resistance and change the function to expect character params that don't require enquo, then params can be passed by other functions without extra work.

1 Like

Thank you for your suggestion! I looked up what standard and non-standard evaluation was, and I revised my function code to this:

jitter_fun <- function(x, y, z, ...) {
    
    require(tidyverse)
    
    
    p <- ggplot(data = df, aes(x = .data[[x]], y = .data[[y]])) +
    geom_jitter() +
    geom_smooth(aes(color = .data[[z]]), se = FALSE)

    print(p)
}


However, now I have an error with the way my mapping function is written:

all_plots = map(demo, ~map(response, ~map(expl, jitter_fun, y = .x)), z = .x)

Error in splice(dot_call(capture_dots, frame_env = frame_env, named = named,  : 
  argument "z" is missing, with no default

How can I fix this? Thank you!

There are is a short thread of comments to the post you linked to that are relevant here. I think you could start here and then see the next in the thread for an example of working with three variables.

Essentially, I was being a bit tricky with that nested loop and being able to refer to .x. That trick quickly fails with more nesting.

I'd switch to using anonymous functions if you want more nested maps. Nested loops in general are a little hard to follow, but that could look something like:

map(demo, function(demo) {
    map(response,
        function(response) {
            map(expl, jitter_fun, y = response, z = demo)
            })
    })

It can be reasonable to forgo the complexity of nesting in some cases. The main reason I like nested loops (in some cases) is if I want to take advantage of the organization and list names in the output. If you don't need those, switch to crossing variables plus pmap() will make the code easier to read.

Like:

allvar = tidyr::expand_grid(expl, response, demo)
pmap(allvar, ~jitter_fun(x = ..1, y = ..2, z = ..3))

Use anonymous functions if you have more than 3 variables to pass into pmap() or if your variables are not in the same order as your function arguments.

I couldn't test these extensively without your dataset, but the code examples hit on the general idea. In the future, try including a minimal reproducible example when asking a question. :slight_smile:

Thank you so much @aosmith!!!

I'm so pleasantly surprised to find you here on this thread--as the author of the blog posting I was basing my code on!

I read the comments you suggested and they made sense to me. Thank you for pointing that out. I wasn't really familiar with nesting, so it's helpful to learn that complex nested maps may be better written out with anonymous functions. The anonymous function code you shared with me worked perfectly! I had been working on this for a couple of days, and I'm so grateful that I found a solution.

I'm sorry I didn't have a reproducible data set--I'll make sure I'll include one the next time I post a question in this community. :sweat_smile:

Thank you so much for your blog posting (it is immensely helpful), and thank you for your suggestions! :blush: