-3

I ran into an issue when trying to generate jittered interstimulus intervals for an fMRI project and I couldn't find any resources online to generate these times. For those that don't know, within fMRI research, we often use fixation crosses between stimuli as baseline measures and we need these to be of varying lengths so that the presentation is surprising, but of a reliable mean, so that we can predict the length of the task.

As such, how can I get Python to generate arrays of float values of a given length in a given range and of a pre-defined mean value? As a relatively new python users, I didn't find any accessible solutions, so I figured one out and wanted to share it.

EDIT: The purpose of this post is to share a solution to the above issue. I searched for awhile and didn't find any solutions that worked for me, so I wanted to post the question with the solution I worked out incase others stumble upon it. Michael Szczesny was nice enough to point me towards an alternative solution which is more sound. While perhaps fine for the very specific purpose of generating ISIs, it is not sound for generating arrays for more general purposes. I flagged this post as a duplicate, so you should be able to find the better solution through that.

  • 2
    Please read [How to ask.](https://stackoverflow.com/help/how-to-ask). As it stands this question isn't answerable. What was the issue you had? What was the incorrect code that you tried? What are expected outputs for given inputs? – Eli Harold Feb 17 '22 at 16:18
  • Very aware of how to ask a question. The purpose of this post was to share my solution to a question I hadn't found an accessible answer to, should others find themselves in my position. – William Mitchell Feb 18 '22 at 01:40
  • I understand, but you should still put forth the effort to write a good question, because a solution to a poor question is nearly useless hence the downvotes of your post and the upvotes of my comment. – Eli Harold Feb 18 '22 at 13:42
  • For example, I don't know what any of your question means since I do not use fMRI machines, and I also don't even understand the question "how can I get Python to generate arrays of float values of a given length in a given range and of a pre-defined mean value?" because you provided to additional information to this prompt. So a future reader looking for an answer to that question may not understand the question or the answer. – Eli Harold Feb 18 '22 at 13:45
  • Since the question is poorly written I can't even understand your code. – Eli Harold Feb 18 '22 at 13:45
  • Don't get me wrong, I am all for sharing knowledge Q&A-style, but there still needs to be a well-formulated question, and properly explained answer, neither of which you have provided here. This shows my that you, in fact, do not know how to ask a question, or an answer for that matter. – Eli Harold Feb 18 '22 at 13:48

1 Answers1

-1

I wanted to share my solution, should anyone else run into this issue and not find any publicly available solutions.

## Dependencies ----
import numpy as np
import random

## Conveniently modifiable variables ----
# Completely arbitrary; just choosing values to demonstate
seed = 101
ISI_n = 100
ISI_mean = 2.000
ISI_lower = 0.500
ISI_upper = 4.000

## Function -----
def generate_jitter(seed, ISI_n, ISI_mean, ISI_lower, ISI_upper):
    # A tracker to track which iteration we are currently on
    tracker = 1
    
    # A seed to be able to create the same simulation reliably
    random.seed(seed)

    # Creating an array to house the jitter values
    jitters = []

    # Iterate through this loop ISI_n times
    while tracker <= ISI_n:

        # If this is the first iteration
        if tracker == 1:

            # Add a random value with 3 digits that's between ISI_lower and ISI_upper to the array.
            jitters.append(round(random.uniform(ISI_lower, ISI_upper), 3))

        # If this is any other iteration    
        if tracker > 1:

            # Calculate the value that would make the mean of array Jitters equal to ISI_mean
            balance = (ISI_mean * tracker) - sum(jitters)

            # Calculate how many iterations are left in this loop
            progress = ((ISI_n - tracker)/ISI_n)

            # Calculate an upper bound centered around the balance value, but not the balance value.
            # As the loop grows closer to the final iteration, this value will be closer to the balance value.
            upper = balance + ((ISI_upper - balance) * progress)

            # If on the off chance upper is greater than ISI_upper
            if upper > ISI_upper:

                # Make upper equivalent to ISI_upper
                upper = ISI_upper
            lower = balance - ((balance - ISI_lower) * progress)

            # If on the off chance upper is greater than ISI_upper            
            if lower < ISI_lower:

                # Make upper equivalent to ISI_upper
                lower = ISI_lower

            # Then generate a new float between upper and lower     
            jitters.append(round(random.uniform(lower, upper), 3))

        # Add 1 to the tracker in preparation for the next iteration
        tracker += 1

    # Randomly shuffle the array 
    random.shuffle(jitters)

    return jitters

# Demonstrating that the function works using the values defined above.
array = generate_jitter(seed = seed, 
                        ISI_n = ISI_n, 
                        ISI_mean = ISI_mean, 
                        ISI_lower = ISI_lower, 
                        ISI_upper = ISI_upper)

print(np.mean(array))
print(np.min(array))
print(np.max(array))
print(len(array))

My output:

# Slightly off, but our MRI clock won't register 
# 0.0000000000000002 seconds. This will get rounded to 2.000
# so close enough for my purposes
>>> print(np.mean(array))
1.9999999999999998
>>> print(np.min(array))
0.702
>>> print(np.max(array))
3.83
>>> print(len(array))
100
>>> 
  • Your algorithm doesn't work in general. If you change *ISI_n=10, ISI_mean=3.9* the requirements for *ISI_mean* and *ISI_upper* aren't met in almost all of the generated samples. Btw: You're setting the seed for `np.random` but never use `np.random`. – Michael Szczesny Feb 17 '22 at 16:35
  • There are several [questions](https://stackoverflow.com/q/68285142/14277722) with better [approaches](https://stackoverflow.com/a/68288621/14277722) using a dirichlet or triangular distribution on stackoverflow. – Michael Szczesny Feb 17 '22 at 17:04
  • Hadn't seen any of these appear in my search but I appreciate you sharing them. – William Mitchell Feb 18 '22 at 01:43
  • Ahh, yes my mistake on the seed; forgot to remove that. Regarding the functionality, though, I'm not sure what you mean. I've generated over 50 samples using different parameters every time and in no case have I generated any value above the respective upper limit, or an array that deviated from the defined mean by more than 0.0000001. – William Mitchell Feb 18 '22 at 01:57
  • `generate_jitter(seed = 10, ISI_n = 10, ISI_mean = 3.9, ISI_lower = 0.5, ISI_upper = 4.0)` yields [**5.328**, **4.465**, **4.447**, 3.302, **4.027**, 2.549, **4.59**, 2.5, **4.14**, 3.365] with mean **3.8713**. All values ​​that do not meet the requirements are marked in bold. – Michael Szczesny Feb 18 '22 at 07:32
  • Okay, I see, thanks so much. That's very helpful to see. – William Mitchell Feb 18 '22 at 11:11