1

I dont know the proper way of describing this so please bear with me. Basically, I have a function in R that reads through a data frame and pastes the contents in a specific order--its used to write a tex file for LaTeX so I can make hundreds of labels very quickly.

Ive attached a simplified version with just the for loop. What I am hoping to do is have the code loop through four rows of the data, do something different for the fifth, then return to the next four rows of data. In the example below, it would be pasting one phrase for most of the rows and on the fifth it would paste something else--each based on the data frame.

For my actual code I want to flip the label horizontally to use up the most amount of paper possible. See attached photo.photo of what Im actually trying to create though unrelated to example code, But in reality it all comes down to the for loop I think.

data <- data.frame(title = c("big Cactus", "little cactus", "bad cactus", "skinny cactus", 
                             "round cactus", "spiny cactus", "twisty cactus", "bizarre cactus"),
                   name = c("Franklin", "Stephanie", "Megan", "Mark", 
                            "Patricia", "KD", "Claude", "Audrey"),
                   needs = c("nothing", "some love", "too much", "some water", 
                             "some sunlight", "new soil", "transplanted", "a friend"))

for (this.label in 1:dim(data)[1]) {
#main function I want for every label
  line <- paste0(data$name[this.label], " the ", data$title[this.label], " needs ", data$needs[this.label])
  print(line)
  }

example of alternate function for every fourth row of the data frame:
line <- paste0(data$name[this.label], " is feeling left out!")
Tyler
  • 43
  • 3
  • Within your `for` loop, simply test your index `this.label` with the modulo operator [`%%`](https://rdrr.io/r/base/Arithmetic.html) like so: `if(this.label %% 5 == 0){execute alternate function} else {execute main function}`. Assuming you start counting at `1`, then this test will evaluate as `TRUE` every five loops (for `this.label` ∈ {`5`, `10`, `15`, ...}) and so execute the alternate "function" (code). – Greg Jan 11 '22 at 22:35
  • @Dion While the `*apply()` family would constitute better practice, I'm not sure it allows access to the index of iteration, so would there be a way to differentiate every fifth row? – Greg Jan 11 '22 at 22:46
  • @Dion Without digging into the source code, what is the name of the index variable that you test in that `if` statement? – Greg Jan 11 '22 at 23:06
  • @Dion Oh, sorry. I thought you meant using an `*apply()` on the `data.frame` *itself*. In that case, I think such usage of an `*apply()` function simply complicates things. – Greg Jan 11 '22 at 23:30
  • @Greg Seems that [you are right](https://stackoverflow.com/a/42440872/10852113). Didn't know that, thanks! – Dion Groothof Jan 11 '22 at 23:34

2 Answers2

1

Here's a solution in base R that requires minimal modification to your existing code.

Solution

Within your for loop, simply test your index this.label with the modulo operator %% like so:

for (this.label in seq_len(nrow(data))) {
  if (this.label %% 5 == 0) {
    # example of alternate function for every fourth row of the data frame:
    line <- paste0(data$name[this.label], " is feeling left out!")
  } else {
    # main function I want for every label
    line <- paste0(data$name[this.label], " the ", data$title[this.label], " needs ", data$needs[this.label])
  }
  
  print(line)
}

This test

this.label %% 5 == 0

will evaluate as TRUE every five loops (for this.label ∈ {5, 10, 15, ...}) and so execute the alternate "function" (technically "expression"); whereas the other four loops will simply execute the "main function" (expression).

Result

Given your example code, along with the data in your sample

data <- data.frame(
  title = c("big Cactus", "little cactus", "bad cactus", "skinny cactus", "round cactus", "spiny cactus", "twisty cactus", "bizarre cactus"),
  name = c("Franklin", "Stephanie", "Megan", "Mark", "Patricia", "KD", "Claude", "Audrey"),
  needs = c("nothing", "some love", "too much", "some water", "some sunlight", "new soil", "transplanted", "a friend")
)

this solution should yield the desired output:

[1] "Franklin the big Cactus needs nothing"
[1] "Stephanie the little cactus needs some love"
[1] "Megan the bad cactus needs too much"
[1] "Mark the skinny cactus needs some water"
[1] "Patricia is feeling left out!"
[1] "KD the spiny cactus needs new soil"
[1] "Claude the twisty cactus needs transplanted"
[1] "Audrey the bizarre cactus needs a friend"

Note

I've replaced the clunky 1:dim(data)[1] with seq_len(nrow(data)), which is the proper way to iterate over the rows of a table. In cases where the table is empty (0 rows), this prevents any looping over 1:0 (ie. c(1, 0)) that targets nonexistent rows.

Greg
  • 3,054
  • 6
  • 27
  • 1
    Okay amazing! This looks great! I will give it a shot! Also thanks for the update on the clunky part--being self taught leads to lots of clunky things. – Tyler Jan 13 '22 at 23:57
  • @Tyler _"being self taught leads to lots of clunky things."_ No worries! We're all here to learn. :) – Greg Jan 14 '22 at 00:21
  • 1
    So this works very smoothly! Though I need to redesign the LaTeX formatting to handle the changes--but it now implements the alternate function every 11th row! Thanks!! – Tyler Jan 14 '22 at 00:38
1
make_label_A <- function(this.label) {
  line <- paste0(data$name[this.label], " the ", data$title[this.label], " needs ", data$needs[this.label])
  print(line)
}

make_label_B <- function(this.label) {
  line <- paste0(data$name[this.label], " is feeling left out!")
  print(line)
}

for (i in seq(1,dim(data)[1], 5)) {
  sapply(1:4, \(x) make_label_A(i+x))
  make_label_B(i+4)
}

Hope this agrees well with the LaTeX engine you are using. Sometimes print statement require a little more care to show from within loops.

dbogdan
  • 66
  • 4