0

I'm generating and plotting multiple ggplots based on data from two lists, therefore I'm using mapply. One of the lists has named elements, which I would like to use as ggtitle. But it only takes the first element for all the plots

> names(sample_subset_list)
[1] "water after day 43 dna min reads per OTU 5"
[2] "biofilm after day 43 dna min reads per OTU 5"
[3] "water after day 43 cdna min reads per OTU 5"
[4] "biofilm after day 43 cdna min reads per OTU 5"
[5] "water after day 44 dna min reads per OTU 5"
[6] "biofilm after day 44 dna min reads per OTU 5"
[7] "water after day 44 cdna min reads per OTU 5"
[8] "biofilm after day 44 cdna min reads per OTU 5"

and this is the plotting function:

ordination_plots <- list()
counter <- 0
ordination_plots <- mapply(function(x,y,counter) {
                        counter <- counter + 1 
                        plot_ordination(x, y, type = "sample") + 
                            ggtitle(names(sample_subset_list)[counter]) +
}, x = sample_subset_list, y = ordination_nmds, counter = 0, SIMPLIFY = FALSE)

this will give me plots where the title is always the first element of

names(sample_subset_list).

The same happens calling ggtitle(names(sample_subset_list)[]) +

If I use counter <<- (suggested here: Using a counter inside an apply structured loop in R) or call ggtitle like

ggtitle(names(sample_subset_list)) +

or

ggtitle(names(sample_subset_list)[[]]) +

I get no title at all.

I started without a counter, which also gave me the same title for all plots. Could someone explain to me how I can iterate over the names of the list elements to use them for the ggplots?

crazysantaclaus
  • 613
  • 5
  • 19
  • you passed `counter` as a parameter and are only modifying it locally. To modify it in the parent scope you need to use `<<-`. I'd remove it from the function def and mapply parameter list, too. – hrbrmstr Nov 23 '18 at 14:56
  • @hrbrmstr that worked, thanks! But I have to admit that I don't really understand, why it worked. Do you want to add this as answer and make it a little bit clearer? – crazysantaclaus Nov 23 '18 at 15:01

1 Answers1

2

Let's reduce the complexity of the example:

counter <- 0

invisible(mapply(function(letter, counter) {

  counter <- counter + 1
  cat("Letter: ", letter, "; Counter: ", counter, "\n", sep="")

}, letters[1:10], counter))

NOTE: I only used invisible() to stop printing the result of mapply().

letters[1:10] is a 10-element vector of lower-case letter (built in data).

You define counter outside of mapply(). Unlike for or while, functions in mapply() do not — by default — create or modify variables in the parent scope (outside of mapply(), so the result is this:

Letter: a; Counter: 1
Letter: b; Counter: 1
Letter: c; Counter: 1
Letter: d; Counter: 1
Letter: e; Counter: 1
Letter: f; Counter: 1
Letter: g; Counter: 1
Letter: h; Counter: 1
Letter: i; Counter: 1
Letter: j; Counter: 1

It's fine to pass in a second parameter with info to the function argument of mapply() but if the intent is to have a side-effect of incrementing something outside the scope of the function in mapply() then you really shouldn't pass it to in as parameter and just modify it using the <<- operator, which — according to the help page:

"The operators <<- and ->> are normally only used in functions, and cause a search to be made through parent environments for an existing definition of the variable being assigned. If such a variable is found (and its binding is not locked) then its value is redefined, otherwise assignment takes place in the global environment."

So, we can just do this:

# TO MY FUTURE SELF AND TEAM MEMBERS
# `counter` is modified as a side-effect of operations in the `mapply()`
# that follows the object declaration
counter <- 0

invisible(mapply(function(letter) {

  counter <<- counter + 1
  cat("Letter: ", letter, "; Counter: ", counter, "\n", sep="")

}, letters[1:10]))

to get this:

Letter: a; Counter: 1
Letter: b; Counter: 2
Letter: c; Counter: 3
Letter: d; Counter: 4
Letter: e; Counter: 5
Letter: f; Counter: 6
Letter: g; Counter: 7
Letter: h; Counter: 8
Letter: i; Counter: 9
Letter: j; Counter: 10

The comment was not meant for snark. You are using a side-effect that may be non-obvious to your future self or folks you share the code with so noting it will help you re-figure out and them figure out what's happening.

hrbrmstr
  • 77,368
  • 11
  • 139
  • 205
  • alright, so I mixed defining a parameter inside the function called `counter` with updating a counter which was not part of the function. Thank you very much for the detailed explanation. – crazysantaclaus Nov 23 '18 at 16:43