7
votes

I have an extensive block of code that I've written using dplyr syntax in R. However, I am trying to put that code in a loop, so that I can ultimately create multiple output files as opposed to just one. Unfortunately, I appear unable to do so.

For illustration purposes regarding my problem, let's refer to the commonly used "iris" dataset in R:

      > data("iris")
      > str(iris)
      'data.frame': 150 obs. of  5 variables:
      $ Sepal.Length: num  
      $ Sepal.Width : num  
      $ Petal.Length: num  
      $ Petal.Width : num  
      $ Species     : Factor w/ 3 levels "setosa","versicolor","virginica"

Let's say that I want to save the average Petal.Length of the species "versicolor". The dplyr code could look like the following:

    MeanLength2 <- iris %>% filter(Species=="versicolor")
                       %>% summarize(mean(Petal.Length)) %>% print()

Which would give the following value:

      mean(Petal.Length)
    1               4.26

Lets attempt to create a loop to get the average petal length for all of the species.

From what little I know of loops, I would want to do something like this:

     for (i in unique(iris$Species))
      {
       iris %>% filter(iris$Species==unique(iris$Species)[i]) %>%
        summarize(mean(iris$Petal.Length)) %>% print()
        print(i) 
       }

For some reason, I had to specify the data frame and the column inside the loop, which is generally not the case while using the piping functionality of dplyr. I'm assuming that this is indicative of the problem.

Anyways, the above code gives the following output:

          mean(iris$Petal.Length)
     1                   3.758
     [1] "setosa"
          mean(iris$Petal.Length)
     1                   3.758
     [1] "versicolor"
          mean(iris$Petal.Length)
     1                   3.758
     [1] "virginica"  

So the code is outputting 3.758 three times, which is the average petal length across all species in the dataset. This indicates that the "filter" code did not work as expected. From what I can tell, it appears that the loop itself functioned as intended, as all three unique species names were printed in the eventual output.

How can one go about doing something like this with the use of for loops? I understand that this particular exercise does not require the use of fancy loops as one can easily get the average petal length of all the species by using, for example, the "group_by" function in dplyr, but I am looking to output close to a 100 unique table and PDF files with the dataset that I am working with and knowing how to use for loops would really help for that purpose.

2
If you really need separate objects for each group it would probably be simpler to just use group_by and then split() the result into a list with an element for each piece that you want.joran
Thanks for the code below. Even though my dataset is more complex than the iris data that I referenced for this question, I do believe that I can utilize your recommended workflow to do what I intend to.Naj S
The inside of the for-loop should be iris %>% filter(Species == i) %>% summarize(mean(Petal.Length)) %>% print(). That will make it produce different numbers for each species.TJ Mahr

2 Answers

6
votes

It is unfortunate that your code didn't raise any errors. If you run your code line by line you'll understand what I'm saying. For this example I will choose the first iteration of your loop, let's replace i for "setosa":

> iris  %>% filter(iris$Species == unique(iris$Species)["setosa"])
[1] Sepal.Length Sepal.Width  Petal.Length Petal.Width  Species     
<0 rows> (or 0-length row.names)

Your filter yields a data frame with no observations, so no point in going ahead, but for this example, let's run the rest of the code:

> iris  %>% filter(iris$Species == unique(iris$Species)["setosa"]) %>%  
+ summarize(mean(iris$Petal.Length))
  mean(iris$Petal.Length)
1                   3.758

What happened is that you're calling the iris dataset from within your code, a more obvious example would be:

> filter(iris, iris$Species == unique(iris$Species)["setosa"]) %>% 
+ summarize(mean(mtcars$cyl))
  mean(mtcars$cyl)
1           6.1875

That's why you don't get the answer you expected, your filter didn't work and you got a summary statistic from another dataset.

As TJ Mahr mentioned, your code without specifying the dataset runs fine:

> for (i in unique(iris$Species))
+ {
+     iris %>% filter(Species==i) %>%
+         summarize(mean(Petal.Length)) %>% print()
+     print(i) 
+ }
  mean(Petal.Length)
1              1.462
[1] "setosa"
  mean(Petal.Length)
1               4.26
[1] "versicolor"
  mean(Petal.Length)
1              5.552
[1] "virginica"

I hope this helps

6
votes

As I mentioned in my comment, if you really need the results separated, it will probably be easier to just use group_by and then split() the results:

iris %>% 
  group_by(Species) %>% 
  summarise(mn = mean(Petal.Length)) %>% 
  split(.,.$Species)

$setosa
# A tibble: 1 × 2
  Species    mn
   <fctr> <dbl>
1  setosa 1.462

$versicolor
# A tibble: 1 × 2
     Species    mn
      <fctr> <dbl>
1 versicolor  4.26

$virginica
# A tibble: 1 × 2
    Species    mn
     <fctr> <dbl>
1 virginica 5.552