Balanced Latin Squares in Python
When conducting within-subjects experiments where you want to expose each participant to all conditions, possible problems might arise from order or carry-over effects: the order of the conditions might influence the participants’ responses, for example due to fatigue or practice. To counter this to some extent, we can change the order of the conditions for every participant, record it, and later analyze its effects.
Full counter balancing would need n! different ordering sequences, which can become impractical very quickly. If we have 4 conditions or stimuli, we can arrange them in 24 different ways. The number of participants should then be a multiple of this, so 24, 48, … This number grows exponentially.
A Latin square is another method to arrange the conditions. It is a matrix that arranges n number of items in n sequences, so it’s much easier to handle. Still, we can do better, with something that’s called a Balanced Latin Square. This version optimizes the balance so that every item appears the same number of times after each other item.
This is how the algorithm works. In a n x n table, you fill out the first row with 1, 2, n, 3, n-1, 4 and so on. The following row just adds 1 to every cell. Of course, the numbers wrap around at n, so n+1 will be 1 again. I figured out that this can be easily done with a modulo operation: i % n + 1, so for n=6, 7 translates to 1. For odd numbers, we have to include some additional measures.
I wrote some concise Python code to calculate these sequences for arbitrary numbers of n. Here’s the code, and I will go over it to explain. It uses a lot of list comprehensions, which are a beautiful albeit slightly hard to read way in Python to express these calculations.
For n items, we want to end up with n sequences. That is the outermost loop, for i in range(n). Each of these sequences will contain n items. This is the inner loop, for j in range(n). To determine each item, we alternate (j%2) between counting up (j/2) and counting down (n-j/2). We add in i so every row is offset by one compared to the previous row. Finally, we use the modulo operator to “wrap around” n as described before. We also added 1, so that the ordering starts with 1 and not 0, but that’s not really necessary.
For an odd number of items, this isn’t enough. We have to repeat the table once more to achieve the desired balance. The second version is simply a reversed version from the first table. This can also be achieved by a little not well-known list comprehension, as you can see in the code.
To test if the code is correct, I ran it for different n and compared it with the tables published here.
That’s it! Now we can continue designing our experiment.