How to select non-consecutive values from an array, or generate non-consecutive values?

Hello all,
I have the following question. I am creating trials where participants listen to a series of 50 ms tones in a classic oddball paradigm, with 114 total tones in a trial. For those unfamiliar with oddball paradigms, the large majority of these tones will be the same tone, called the 'standards.' A minority of these tones will be 'deviant' tones, which the participant will be asked to respond to. I want these deviant tones to occur randomly, but they cannot occur consecutively. So if the fourth tone is a deviant, the fifth (or the third tone) cannot be deviants.
If it helps, i have included some code that randomly selects which tones will be deviants, but I don't know how to make sure that no deviants occur consecutively. Randperm doesn't seem to have this capability
num_deviants=29; %arbitrary number, each trial will have a different number of deviants
%% 3. Initialize static variable (stay the same across trials)
num_stim_total=114; %total number of tones
stim_times=[0,50]; %this matrix will have start and stop times for the stimuli, in columns 1 and 2 respectively
%% 4. Create interstimulus intervals and stimulus onset/offset times
ISI_generator=randi([700 1000],1,113); %creates 113 random samplings from 700 to 1000
for i=1:113
stim_times(i+1,1)=stim_times(i,2)+ISI_generator(i); %adds ISI to the end time of the last stimulus to create start time of next stim
stim_times(i+1,2)=stim_times(i+1,1)+50; %adds 50ms to stim start time to signify the stimulus stop time
end
%% 5. Randomly decides deviants vs standards
deviant_decider=randperm(num_stim_total,num_deviants)'; %randomly decides which start times will become deviant sounds (designated by a '1')
stim_times(deviant_decider,3)= 1;
standard_array=1:114; %create an array of 1 to 114
standard_decider=setdiff(standard_array,deviant_decider'); %returns all the numbers 1-114 that aren't in deviant_decider, and makes them standards

 Accepted Answer

I'd argue that requiring that the outliers/deviants NOT be consecutive biases your results.
But this is easy enough to do.
Start by deciding how many deviants you need. In the example, you had 29 of them.
Now, insert one standard tones between every deviant. That insures you will not have anything consecutive. You may decide to insert a standard tone before the first, and after the last. I chose that as the desired behavior.
Finally, you now have a total of 29 + 30 tones in place. You want a total of 114 tones, so you need to choose random places to insert new tones. There are several ways you can do that, but even if you just pick a random spot one at a time, who cares? There will be only 114 - 59 = 55 new tones to insert. The code I wrote below is fully vectorized however.
In the example I'll give, the final result will have a 1 in it, where a deviant tone will live. There will ALWAYS be at least one standard tone between every deviant tone. (Even though I disagree with that. Your experiment however, so your choice.)
ndev = 29;
ntot = 114;
seq = zeros(1,ndev*2 + 1);
seq(2:2:end) = 1;
seqfinal = zeros(1,ntot);
seqfinal(sort(randperm(ntot,ndev*2 + 1))) = seq;
So the final sequence chosen is:
seqfinal =
Columns 1 through 21
0 1 0 0 1 0 0 0 0 1 0 1 0 0 0 0 1 0 0 0 1
Columns 22 through 42
0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0
Columns 43 through 63
0 1 0 0 0 0 0 0 0 0 1 0 1 0 1 0 0 0 0 0 1
Columns 64 through 84
0 0 0 0 1 0 0 1 0 0 1 0 0 0 0 1 0 0 1 0 1
Columns 85 through 105
0 0 0 0 0 1 0 0 0 0 1 0 1 0 0 1 0 0 0 1 0
Columns 106 through 114
1 0 0 1 0 1 0 1 0
There are exactly 29 ones in the vector, with no pair of consecutuve ones. I set it up so that the first and last point are not 1, but that behavior could easily be changed.

7 Comments

HI John,
agree completely about your point that this creates bias. I made my script originally to allow for consecutive deviants. But i am but a humble research tech and do not make the decisions!
I accepted your answer and the code works great! I also modified it slightly so that the first five tones are always standards. However, would you mind explaining what is going on in the last line of your code?
Hi Matthieu, I agree with John (and you) that the design has some concerns. If this is being done on humans where the number of trials is less than a few dozen, they may not learn the hidden rule that there won't be consecutive oddballs. If this is being done on animals that will experience hundreds or thousands of trials, they absolutely will learn the rule which means 1) every time they correctly identify an oddball, they will very rarely have a 'false alarm' on the next trial and 2) every time they incorrectly identify an oddball they will be much less likely to correctly respond to an oddball on the following trial even if they perceived it.
Another problem this design introduces is how the data will be analyzed. Basic statistical approaches assume random trials and equal probability of responding within the same condition. These assumptions are no longer valid under this design and that's true no matter who the subjects are. I suppose one way around that would be to removed every trial from the analysis that followed an oddball detection (whether the trial was an oddball or not) but you'll lose a lot of data, including some of the oddball trials which are already infrequent.
A much better solution is to use non-zero probabilities to determine whether the next trial will be an oddball. You could even use different probability based on how long it's been since the previous oddball. That also introduces bias but it will be much more difficult for the subject to learn and for editors to accept.
About John's last line of code, randperm(x,y) chooses y unique, random values from 1:x (without replacement). Sort() puts those values in order and they will act as indices to seqfinal. Note that y (from randperm(x,y)) is the same length as seq. Also note that seq is a vector of alternating 0s and 1s. So, even if the indices produced by sort(randperm()) produce consecutive integers (and, they will), the values being placed at those indices will never have consecutive 1s. That's probably still confusing so dissect it from the inside-out.
A valid question that.
First, what does randperm do?
randperm(10,2)
ans =
5 4
Thus randperm(10,2) selects two random integers from the set 1:10, without replacement. So you need never worry about repeats. How did I use it?
seqfinal(sort(randperm(ntot,ndev*2 + 1))) = seq;
Whenever you have a question about a line of code, I suggest to start in the middle. Yes, the middle. Take the code apart one piece at a time, but start in the middle. So what does this mean, in context of what I just said about randperm?
randperm(ntot,ndev*2 + 1)
that code fragment generates random integers from the set [1:114] here. It chooses ndev*2+1 of them. In the example above, that means I generated 59 random integers from the set.
Why 59 of them? Because we originally set up a vector that describes a sequence of 59 events.
Next, move out a level. What does the sort do?
sort(randperm(ntot,ndev*2 + 1))
That part is easy. It sorts a vector in increasing order. So, while randperm returns random integers, the sort puts them in increasing order. One such set might be...
sort(randperm(ntot,ndev*2 + 1))
ans =
Columns 1 through 21
1 4 6 7 10 11 13 14 17 18 22 23 26 27 29 30 34 35 36 40 41
Columns 22 through 42
42 43 50 51 52 54 56 59 61 62 65 66 70 71 72 73 74 75 76 77 84
Columns 43 through 59
87 91 93 94 97 98 99 100 102 103 107 108 109 110 111 112 113
But what do they mean in context? Actually a vector of numbers is just a list of numbers. They mean nothing, not until you decide how they will be used.
What was seqfinal before that?
seqfinal = zeros(1,ntot);
seqfinal is just a vector of zeros. I'm using the sorted ranperm to decide where to stuff the vector in seq into that vector of zeros. Because my integers are sorted, this insertion into seqfinal does not change the sequence of what was in seq. Effectively, it inserts extra zeros into random positions in that vector.
As I said, start in the middle, and work outwards, effectively one set of parens at a time is a good idea.
If your goals are to force extra zeros at the beginning, you can do that.
Ironically i understood best what you were doing on the left side of the assignment, ie the stuff with the randperm and sort since i did essentially just that in my original script. the part i was a bit hung up on was th '=seq' part since seq is a vector and it seemed like you were setting individual indices of seqfinal equal to a vector. but now i realize that if you said a=seqfinal(sort(randperm(ntot,ndev*2 + 1)), a would be a row vector of zeros, indexed from seqfinal, that is the same size as seq. so when you say seqfinal(sort(randperm(num_stim_total,num_targets*2 + 1)))=seq, it becomes identical to seq. but the other values in seqfinal remain zeros, and i get how that ensures there are no consecutive 1's. what i just wrote might not have been the best written explanation but i get visually and procedurally what happened.
It is always difficult to know online how to explain something. I find it far easier to explain a topic like that in person, because I can watch the student's eyes as I explain a concept. You know when someone understands what you are saying because yuou can see the light go on, so you can then go on to the part of the problem they don't understand. That does force me to tend to over-explain. :)
Viewed as I want to view what I did, this was a vectorized scheme to essentially insert zeros into the vector we already had, by implicitly spreading out the elements in the vector seq. The sort forced them to stay in the same order, and the randperm implicitly chose random places for the spreading/zero insertion to happen.
A trick with using MATLAB effectively in vectorized schemes is always to visualize your data as a whole. You get used to working with vectors and arrays, and how the elements in that vectors and arrays live in memory, where they live, how you can move them around. Then you can use the tools of MATLAB to their fullest potential. This is what makes a language like MATLAB a great one.

Sign in to comment.

More Answers (0)

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!