EMA: Random Schedule (Automatic)
Overview
Problem: You need random or semi-random sampling within defined time windows, but want schedules generated automatically within REDCap without external tools.
Solution: Use calculated fields implementing a pseudo-random number generator (PRNG) to compute sample times at enrollment, create one event per sample, and use Automated Survey Invitations (ASIs) to deliver surveys at the calculated times.
Random numbers in REDCap?
We’re using a Linear Congruential Generator (LCG): a pseudo-random number generator we chose because it’s straightforward to build with the tools REDCap gives you.
This generator relies on four parameters: A multiplier a, an increment c, a modulus m, and a starting value seed. It uses a chained operation – every time we generate a new value, we use the previous value as the seed. See the Wikipedia page for more details.
Prerequisites
- A longitudinal REDCap project
- An email field for participants
- Ability to generate and upload CSV files (for events, event mappings, instruments, and ASIs)
- Project Setup and Design rights
- Alerts & Notifications rights
When to Use This Approach
This approach works well when:
- You need unpredictably-timed sampling within defined time windows, with different schedules per participant
- The schedule can be fully determined before sampling begins
- You want everything self-contained in REDCap (no external scheduling or API)
- You’re willing to invest significant upfront setup effort
- You can define explicit sampling periods (e.g., 9:15-10:45 AM, 11:15-12:45 PM, 1:15-2:45 PM)
- Your total number of samples is bounded and not immense — things get trickier if you need to exceed about 100 samples.
See the EMA overview for other approaches.
Trade-offs
Advantages:
- Fully automated within REDCap
- Uses built-in ASI features (reminders, expiration, completion tracking)
- No external scheduling tools or API access required
- Reproducible randomization based on seed values
- Schedule is visible immediately after generation
Drawbacks:
- Very complex initial setup many — possibly hundreds — of calculated fields
- Requires one event per sample (e.g., 35 events for 5 samples/day × 7 days)
- Requires one ASI per sample
- REDCap calculation limits constrain total sample count
- Performance considerations with many calculated fields
- Debugging calculation chains is challenging
Is this the most complex thing you can do in REDCap? We’re not sure, but it’s close. Do not add complexity beyond what you need. Consider the flexible approach if it can work for you.
How It Works
This approach uses three key techniques:
- Pseudo-random number generator: Generates unpredictable numbers, unique to each participant, using only calculated fields. Each field calculates the next number from the previous one.
- Time window mapping: Each sampling period (e.g., “morning slot”) has a start time and duration. The generated numbers are used to pick a time within each period.
- Event-per-sample architecture: Each potential sample gets its own event with an ASI that triggers at the calculated time.
Why so many events?
For some designs, it might be possible to do this with repeating instruments. There are some tricky bugs to figure out, though. This approach is bulky but reliable.
Conceptual Example
Suppose you want 2 samples per day for 3 days (6 total samples):
- Days: 3 (call these day 1, day 2, day 3)
- Sampling periods per day: 2 (morning: 9:15-10:45 AM, afternoon: 1:15-2:45 PM)
- Total samples: 3 days × 2 periods = 6 samples
You’ll create: - 6 LCG fields (rand_01 through rand_06) - 6 “minutes from midnight” fields (min_from_midnight_d01_s01 through min_from_midnight_d03_s02) - 6 “delivery time” fields (deliver_at_d01_s01 through deliver_at_d03_s02) - 7 events (enrollment plus ema_d01_s01 through ema_d03_s02) - 6 ASIs (one per EMA event)
Download Complete Example
Rather than building this project manually, you can download a complete working example:
Option 1: Import the entire project
- Download REDCap Project XML - Import this via Project Setup > Other Functionality > Project XML
This project was exported from REDCap 15.6.1. After importing, verify the ASI email addresses and survey settings match your needs.
Option 2: Build manually using CSV files
Download the individual components and upload them:
- Data Dictionary CSV - For Designer > Data Dictionary > Upload your Data Dictionary file
- Events CSV - For Events > Define My Events
- Event Mappings CSV - For Events > Designate Instruments for My Events
- ASI List CSV - For Designer > Auto Invitation options > Upload…
Steps
Set Up Core Configuration Fields
Create an instrument called ema_config to hold your study parameters and random number generator.
Add the following fields:
ema_start_on:- Field Type: Text Box
- Validation: Date (M-D-Y)
- Variable Name:
ema_start_on - Field Note: “First day of EMA sampling”
seed_input:- Field Type: Text Box
- Validation: Integer
- Variable Name:
seed_input - Action Tags:
@DEFAULT='[record-name]' - Field Note: “Initial seed value (defaults to record ID)”
seed:- Field Type: Calculated Field
- Variable Name:
seed - Calculation:
mod((([a] * mod((([a] * mod((([a] * [seed_input]) + [c]), [m])) + [c]), [m])) + [c]), [m]) - Field Note: “Processed seed (runs LCG a few times to avoid similar early values for similar seeds)”
Add constant fields for the LCG parameters:
a:- Field Type: Calculated Field
- Calculation:
1664525
c:- Field Type: Calculated Field
- Calculation:
1013904223
m:- Field Type: Calculated Field
- Calculation:
4294967296
Similar seed values (like sequential record IDs) produce similar early LCG values. You don’t want all your first samples to happen at about the same time of day! Running the LCG a few times on seed_input creates much more varied initial random numbers.
Add fields for your sampling periods. For each period, you’ll need a start time (minutes from midnight) and duration (in minutes).
For example, for two periods per day:
period_start_01:- Field Type: Calculated Field
- Calculation:
555(9:15 AM = 9×60 + 15 = 555 minutes from midnight)
period_len_01:- Field Type: Calculated Field
- Calculation:
90(90 minutes = 9:15 AM to 10:45 AM)
period_start_02:- Field Type: Calculated Field
- Calculation:
795(1:15 PM = 13×60 + 15 = 795 minutes from midnight)
period_len_02:- Field Type: Calculated Field
- Calculation:
90(90 minutes)
Generate the Random Number Fields
For each sample you want to collect, create one LCG field. If you’re collecting 6 total samples, you’ll create rand_01 through rand_06.
Each field follows this pattern:
rand_01:- Field Type: Calculated Field
- Calculation:
mod((([a] * [seed]) + [c]), [m])
rand_02:- Field Type: Calculated Field
- Calculation:
mod((([a] * [rand_01]) + [c]), [m])
rand_03:- Field Type: Calculated Field
- Calculation:
mod((([a] * [rand_02]) + [c]), [m])
- (continue this pattern…)
Use Excel, R, or Python to generate the instrument CSV with all these calculated fields. Manually creating hundreds of fields is error-prone. You can also download the complete data dictionary CSV from the example project.
Generate the Sample Time Fields
For each sample, create two fields:
- Minutes from midnight for that sample
- Delivery datetime for that sample
The pattern depends on which period the sample falls into. For a 3-day, 2-samples-per-day study:
Day 1, Sample 1 (period 1): - min_from_midnight_d01_s01: - Calculation: [period_start_01] + mod([rand_01], [period_len_01]) - deliver_at_d01_s01: - Calculation: @CALCDATE([ema_start_on], (0 * (24*60)) + [min_from_midnight_d01_s01], 'm')
Day 1, Sample 2 (period 2): - min_from_midnight_d01_s02: - Calculation: [period_start_02] + mod([rand_02], [period_len_02]) - deliver_at_d01_s02: - Calculation: @CALCDATE([ema_start_on], (0 * (24*60)) + [min_from_midnight_d01_s02], 'm')
The (0 * (24*60)) term is included so the pattern repeats more easily.
Day 2, Sample 1 (period 1): - min_from_midnight_d02_s01: - Calculation: [period_start_01] + mod([rand_03], [period_len_01]) - deliver_at_d02_s01: - Calculation: @CALCDATE([ema_start_on], (1 * (24*60)) + [min_from_midnight_d02_s01], 'm')
Day 2, Sample 2 (period 2): - min_from_midnight_d02_s02: - Calculation: [period_start_02] + mod([rand_04], [period_len_02]) - deliver_at_d02_s02: - Calculation: @CALCDATE([ema_start_on], (1 * (24*60)) + [min_from_midnight_d02_s02], 'm')
Notice the pattern: - The day number multiplier increases: 0 * (24*60), 1 * (24*60), 2 * (24*60), etc. - The period alternates: period 1, period 2, period 1, period 2, etc. - The random number increments: rand_01, rand_02, rand_03, etc.
Generate these fields programmatically. The pattern is systematic but error-prone to create manually.
Create Your EMA Survey Instrument
- Create an instrument called
emawith your data collection fields - Enable it as a survey
- Configure survey settings (timeout, completion message, etc.)
Upload Events
Create one event per sample. Using the CSV file upload in Project Setup > Define My Events (download events.csv):
Map Instruments to Events
Use the CSV upload in Project Setup > Designate Instruments for My Events (download event_mappings.csv):
The ema_config form should only be mapped to the enrollment event. This ensures calculations only trigger once when you save that form.
Create ASIs for Each Event
For each EMA event, create an ASI via Designer > Automated invitations for the ema instrument, or use the CSV upload feature if your REDCap version supports it.
Each ASI should: - Trigger when: The ema_config form is complete (e.g., [ema_config_complete] = 2) - Send at: The corresponding deliver_at_dXX_sYY field - Send to: [email] - How often: Only once - Expiration: Set as appropriate (e.g., 2 hours after delivery) - Include the survey link in the message body
Example for the first sample: - When to send: At the time specified by a date/time field: deliver_at_d01_s01 - Ensure conditions are still true: Check this box - Email body: Include [event-url] or similar piping for the survey link
Use a script to generate the ASI configuration if your REDCap version supports CSV upload for ASIs. You can also download the complete ASI list CSV from the example project (remember to update the email address).
Testing
- Create a test record
- Set
ema_start_onto today or tomorrow - Set
seed_input(or let it default to record ID) - Save the
ema_configform - Check that all
deliver_at_dXX_sYYfields are populated with reasonable datetimes - Go to Designer > Survey Distribution Tools > Survey Invitation Log to verify ASIs are scheduled
While developing and testing, leave all the calculated fields visible on the ema_config survey so you can watch the random numbers and schedule populate in real time. This makes debugging much easier. You can hide them from participants later with @HIDDEN-SURVEY once everything works.
During the time between when a survey is scheduled and when it is sent, it will not appear in the Survey Invitation Log.
Notes
- Calculation limits: With 500+ calculated fields on a form, save times can exceed 6-10 seconds and may hit server timeouts. Stay well under 500 calculations per form.
- Trigger cascade: Setting
seed_inputorema_start_ontriggers all calculations that depend on those fields. Be certain you want to generate the schedule before saving these values. - Seed stability: If using
[record-name]as your seed, save it with@DEFAULTintoseed_inputso renaming the record doesn’t regenerate the schedule. - Consolidate calculations: Breaking calculations into steps (like
min_from_midnight_dXX_sYY) makes debugging easier but is slower. Once tested, you can consolidate into single nested calculations for performance. This is fairly easy if you’re generating your fields using R or Python. - Multiple arms: If your event list is too cluttered, you can put EMA events in a separate arm. ASIs won’t trigger until the participant has a record in that arm, so redirect them through the EMA arm somewhere in your enrollment flow (use Survey Settings > Redirect to a URL). REDCap will warn about participants in multiple arms, but this is not a problem.
- Extracting additional random values: If you need more than 100 random numbers and hit calculation limits, you can extract multiple values from each LCG output. For example,
mod([rand_01], 256)gives a value 0-255, andmod(floor([rand_01] / 256), 256)gives another independent 0-255 value. But this adds complexity. Don’t do it unless necessary. - Computing additional things: You can also split calculations across multiple instruments (or page sections) if needed. Again, don’t do this unless you need to.
- Non-constant samples per day: You can have different numbers of samples per day, but it makes the field generation script more complex.
- Repeating events: This approach doesn’t work with repeating events. The event-per-sample architecture is required for ASIs to work correctly.
- Alerts instead of Events: An alternate design would be to use one Alert per sample instead of one Event per sample. The main drawbacks here are that you can’t use survey tools (reminders, expiration) and that you can’t hide the UI clutter for Alerts like you can with Events.
Performance Considerations
With many calculated fields:
- Form saves can take 6+ seconds with 500 calculations
- Don’t generate the schedule until you’re ready to start EMA sampling
- Test your design before going live
Troubleshooting
Calculations aren’t populating
- Verify the field dependencies are correct (each
rand_XXdepends on the previous one) - Check for typos in field names
- Ensure
seed_inputandema_start_onare filled - Try saving the
ema_configform again - If you have more than 100 LCG fields (
rand_01throughrand_100+) on a form that’s not currently visible (e.g., on a different instrument from where you’re triggering the calculation), only the first ~100 will calculate. Keep your calculated fields on a visible survey or form, or use the technique in Extracting additional random values above
ASIs aren’t scheduling
- Verify the ASI condition logic evaluates to true
- Check that
deliver_at_dXX_sYYfields contain valid datetimes - Confirm the ASI is enabled and saved
- Check Designer > Survey Distribution Tools > Survey Invitation Log
Random times are invalid or outside periods
- Verify
period_start_XXandperiod_len_XXvalues are correct - Check that the LCG fields are producing reasonable values (should be large positive integers)
- Verify the modulo operations in
min_from_midnight_dXX_sYYfields
Forms are saving very slowly
- Count your calculated fields—you may have too many
- Consider consolidating calculations
- Consider the flexible approach instead