Variable Nested for Loops

variable nested for loops

Here's an example in plain old C++. First I make a vector of the ranges for each dimension called maxes. if the sum of all indices are 2 then I print did something.
In the example I loop z from 0 to 1, y from 0 to 2, x from 0 to 3

You can for sure make this more neat.

Here goes:

#include <iostream>
#include <vector>
using namespace std;

int f(){
return 2 ;
}

void inner(int depth,vector<int> & numbers,vector<int> & maxes){
if (depth>0){
for(int i=0;i<maxes[depth-1];i++){
numbers[depth-1]=i;
inner(depth-1, numbers,maxes) ;
}
}else{
// calculate sum of x,y,z:
cout << "values are ";
for(int i=0;i<numbers.size();i++){
cout <<numbers[i]<<" ";
}
int thesum(0);
for(int i=0;i<numbers.size();i++){
thesum+=numbers[i];
}
if (thesum==f()){
cout << "did something! ";
}
cout<<endl;
}
}

void donest(){
vector<int> numbers;
numbers.resize(3);
vector<int> maxes;
maxes.push_back(4);
maxes.push_back(3);
maxes.push_back(2);
inner(numbers.size(),numbers,maxes);
}

int main(){
donest();
}

result:

values are 0 0 0 
values are 1 0 0
values are 2 0 0 did something!
values are 3 0 0
values are 0 1 0
values are 1 1 0 did something!
values are 2 1 0
values are 3 1 0
values are 0 2 0 did something!
values are 1 2 0
values are 2 2 0
values are 3 2 0
values are 0 0 1
values are 1 0 1 did something!
values are 2 0 1
values are 3 0 1
values are 0 1 1 did something!
values are 1 1 1
values are 2 1 1
values are 3 1 1
values are 0 2 1
values are 1 2 1
values are 2 2 1
values are 3 2 1

Variable amount of nested for loops

Recursion can solve this problem neatly:

function callManyTimes(maxIndices, func) {
doCallManyTimes(maxIndices, func, [], 0);
}

function doCallManyTimes(maxIndices, func, args, index) {
if (maxIndices.length == 0) {
func(args);
} else {
var rest = maxIndices.slice(1);
for (args[index] = 0; args[index] < maxIndices[0]; ++args[index]) {
doCallManyTimes(rest, func, args, index + 1);
}
}
}

Call it like this:

callManyTimes([2,3,5], doSomething);

Nested loop - analysis of one variable by subsetting on two others variables

Comments on your code

First, about your loop, it can't fill in the data frame because you're calling the wrong index. For example:

for(j in 1:3){
for(i in 1:4){
results[j] <- something[j]
}
}

In that case, j will only loop between 1 and 3, rewriting the previous result at each occurrence of the inner loop (in other words, you will write 3 times something in results[1], 3 times in results[2], ...). What you want to do is along those lines:

for(j in 0:2){
for(i in 0:3){
results[j*3 + i + 1] <- something[j]
}
}

so that when i=j=0, you write in result[1], when i=1,j=0, you write in results[2], ..., when i=0,j=1 you write in results[4], ..., when i=3,j=2 you write in results[12]. This could be enough to make the loop do what you want.

In addition, there are two small things that are not best practice but shouldn't affect the results: I think all your as.vector() are not useful and have no effect, and adding rows to a data frame during a loop is not a great idea.

For the second one, the idea is that a data frame is usually stored in a consecutive range in memory (same for a vector or matrix). When you add a row, you need to append something to where the data frame is already stored, if there is no space the whole data frame will get copied, which is slow and inefficient. When using a for loop, you always want to initialize your results variables with the right length:

N <- 12 #the length you want
results <- data.frame(n = rep(NA, N),
ameans = rep(NA, N),
CIameanslower = rep(NA, N),
CIameansupper = rep(NA, N))
# or an easier equivalent way:
results <- matrix(NA, nrow=N, ncol=4)
results <- as.data.frame(results)
names(results) <- c("n", "ameans", "CIameanslower", "CIameansupper")

But in R, that is rarely a concern since we can usually vectorize the operations.

How to vectorize

You can do everything with base R, but why not use the best tools available: here it'll be much easier with the tidyverse (in particular the package dplyr).

library(tidyverse)

Now we can transform the original data frame.

graphdata1 %>%
group_by(Gl, CS) %>%
summarize(mean_RC = mean(RC),
sd_RC = sd(RC),
n = n())

So we easily have the mean, sd, and number of observations; you could add any summary statistic here.
But you want to do a t test. If I understand correctly, you want a one-sample test, comparing the mean in your sample to 0. You could try simply adding it in summarize:

graphdata1 %>%
group_by(Gl, CS) %>%
summarize(mean_RC = mean(RC),
sd_RC = sd(RC),
n = n(),
t_test = t.test(RC))
# Error: Problem with `summarise()` input `t_test`.
# x Input `t_test` must be a vector, not a `htest` object.
# i Input `t_test` is `t.test(RC)`.
# i The error occurred in group 1: Gl = "c", CS = "1".

It doesn't work. But look at the error message: the test worked, but you can't just put the result of a test in a data frame. A magic trick is to use a "list-column": one of the columns of our data frame will be a list, that can contain anything, even whole test results.

graphdata1 %>%
group_by(Gl, CS) %>%
summarize(mean_RC = mean(RC),
sd_RC = sd(RC),
n = n(),
res = list(t.test(RC)),
.groups="drop")

I also added .groups="drop" to avoid having a grouping afterwards that may affect subsequent operations.

All we're left to do is extract the values of interest from the test results that are stored. There is again a trick: we need to specify that we want to do the computations row by row and not column by column, with rowwise().

graphdata1 %>%
group_by(Gl, CS) %>%
summarize(mean_RC = mean(RC),
sd_RC = sd(RC),
n = n(),
res = list(t.test(RC)),
.groups="drop") %>%
rowwise() %>%
mutate(lower.ci = res$conf.int[1],
upper.ci = res$conf.int[2])

And we're done! We can use select() to remove the columns that are not interesting anymore and rename and order the ones to keep, and arrange() to sort the rows by 1 or more variables.

graphdata1 %>%
group_by(Gl, CS) %>%
summarize(mean_RC = mean(RC),
sd_RC = sd(RC),
n = n(),
res = list(t.test(RC)),
.groups="drop") %>%
rowwise() %>%
mutate(lower.ci = res$conf.int[1],
upper.ci = res$conf.int[2]) %>%
select(Gl, CS, mean_RC,
conf_low = lower.ci, conf_high = upper.ci) %>%
arrange(rev(Gl), CS)
# Gl CS mean_RC conf_low conf_high
# <fct> <fct> <dbl> <dbl> <dbl>
# 1 a 1 213. 181. 245.
# 2 a 2 225. 190. 260.
# 3 a 3 257. 229. 285.
# 4 a 4 221. 184. 257.
# 5 b 1 242. 214. 270.
# 6 b 2 255. 222. 288.
# 7 b 3 225. 196. 255.
# 8 b 4 236. 207. 264.
# 9 c 1 248. 218. 277.
# 10 c 2 257. 224. 291.
# 11 c 3 258. 226. 289.
# 12 c 4 245. 207. 283.

How to put variable on nested loop?

Here's a modified version of your code (the comments in the code point out the changes I've made):

int i;
for(i=0;i<h;i++){ //changed <= to < (since i is starting from 0) or it would've printed an extra row of L's
//also, it should be i<h not i<w (considering h is the height of the rectangle)
//removed printf("%c ",c); from here as well to avoid printing an extra L at the beginning of each row
int j;
for(j=0;j<w;j++){ //changed <= to < in this case too or it would've printed an extra L at the end of each row
//changed j<h to j<w as well (considering w is the width of the rectangle)
printf("%c ",c);
}
printf("\n");
}

Nested for-loop: error variable already defined

generate is for generating new variables. The second time your code reaches a generate statement, the code fails for the reason given.

One answer is that you need to generate your variable outside the loops and then replace inside.

For other reasons your code can be rewritten in stages.

First, integer sequences can be more easily and efficiently specified with forvalues, which can be abbreviated: I tend to write forval.

gen strata = . 
forval x = 1/40 {
forval r = 1/5 {
forval s = 1/2 {
forval a = 1/4 {
replace strata = `x' if race==`r' & sex==`s' & age==`a'
}
}
}
}

Second, the code is flawed any way. Everything ends up as 40!

Third, you can do allocations much more directly, say by

gen strata = 8 * (race - 1) + 4 * (sex - 1) + age  

This is a self-contained reproducible demonstration:

clear
set obs 5
gen race = _n
expand 2
bysort race : gen sex = _n
expand 4
bysort race sex : gen age = _n
gen strata = 8 * (race - 1) + 4 * (sex - 1) + age
isid strata

Clearly you can and should vary the recipe for a different preferred scheme.

Syntax and Variable Declaration in Nested Loops: How Does an Inner Declaration take Precedence?

You are absolutely right about result being reset to 1 in each iteration of the for loop. It does get reset.

"But why doesn't printing result give me 2 every time" you ask.

After result is being set to 1, and before that iteration of the for loop ends, the while loop runs. How many times does the while loop runs? It depends on i. In the first iteration of the for loop, the while loop loops once, in the second iteration of the for loop, the while loop loops twice, in the third iteration of the for loop, the while loop loops three times, and so on.

At the end of each iteration of the for loop, result will contain the value of 2 to the power <however many times the while loop looped for>. So at the end of the first iteration of the for loop, result is 2 because the while loop looped only once. At the end of the second iteration, result is 4 because the while loop ran twice and so result *= 2 is ran twice.

If result isn't reset, it would have become 8 by the end of the second iteration of the for loop: it got multiplied once in the first iteration, and twice in the second iteration.

Can I split this two-variable for loop into two nested loops?

it is wrong conversion, correct one will be:

int j = s.length - 1;
for (int i = 0; i < j; i++) {
// inside logic
j--;
}


Related Topics



Leave a reply



Submit