BookmarkSubscribeRSS Feed

Don’t Be Scared of Time-Based Arrays: User Variable Arrays in SAS Fraud Management Part 4

Started a month ago by
Modified a month ago by
Views 182

In this post, you'll learn how to create and use time based arrays in your SAS Fraud Management rules. This post is the latest in a series of posts on user variable arrays in SAS Fraud Management.

 

Check out the other installments if you missed them or for a brief review:

 

Part 1: User variable arrays in SAS Fraud Management

 

Part 2: Use arrays to detect scoring trends and reduce false positives 

 

Part 3: Using arrays and distance-based rules

 

User variable arrays allow rule-writers to store sets of data from transaction to transaction, enabling it to be utilized in rule logic. Two of the most common use cases for arrays are events-based arrays and time-series arrays. In this October installment of the series on user variable arrays in SAS Fraud Management, we'll explore time-based arrays to write rules for a haunted hayride.

 

Previously, we discussed event-based arrays, which store information about the most recent events by shifting the array for each relevant transaction. As an example of this type of array, we might create a 3 item array that stores how many tickets were sold per transaction. Every time a visitor buys tickets, the array is shifted. Over time this array will contain the number of tickets purchased in the 3 most recent transactions. This information might be useful if we want to understand how many visitors are in the average group, but what about if we want to know how many total visitors are in each wagon? A full wagon could be more groups or fewer groups than the number of spaces in the array. Instead, we will use time-series arrays.

 

 

Using Time-Series Arrays

 

Let's say hayrides leave every 30 minutes: on the hour or at half past. The hayride is pulled by a very large tractor so weight not a limiting factor, but the wagon only accommodates 24 guests at a time. If the number of guests exceeds 24, the farmer would like an alert to be issued so that she knows to add a second wagon.

 

Each position in the array will represent a haunted hayride departure time. The transaction information will be added to the appropriate ticket time without necessarily shifting the array. Now, you can track the total number of tickets sold at each time.

 

When working with time-series arrays, each position in the array is a length of time. You can use different size buckets of time depending on your needs: for example, you might want a 7 position array tracking daily buckets of activity, representing the past week. You might want hourly buckets in a 24 item array capturing the last day of some type of activity. Time series arrays are calculated on the number of seconds since January 1, 1960.

 

Let’s consider an array with 30 minute buckets of time, and 6 spaces. If each bucket is 30 minutes, the 6 item array will cover the most recent 3 hours of hayrides.

 

To begin, we create the user variables that make up our array. We will use variables called entryTime1 - entryTime6 to store the departure times for the hayride. They’ll have default values of 0. I'll also create user variables to put in an array of the same dimensions that will store the total number of seats sold. They’ll have default values of 0 since we start with no guests registered.

 

I'll also need a user variable to track the datetime of the most recent element in the array. I'll call it _H_TICKET_TIME, and use this variable with the Shift History Array macro later.

 

I’ll begin the rule code by declaring the user variable array for seats sold:

 

%DeclareArray(&rule.tickets_sold , _H_SEATS_SOLD_1 - _H_SEATS_SOLD_6);

 

Table 1: tickets_sold array initial value

 

_H_SEATS_SOLD_1 _H_SEATS_SOLD_2 _H_SEATS_SOLD_3 _H_SEATS_SOLD_4 _H_SEATS_SOLD_5 _H_SEATS_SOLD_6
0 0 0 0 0 0

 

I might add rule code describing other features of the transaction - for example, to only add the tickets if the transaction type indicates a hayride ticket was purchased, and exclude other purchase events like corn maze entry.

 

One of the most important parts a time-based array is that my rule code will use the entry time to determine which bucket the tickets sold should be added to, instead of simply shifting the array with every transaction.

 

This requires specifying additional arguments with the %ShiftHistoryArray macro besides one or more arrays. We identify the array in the first argument, and the second argument is the user variable we defined earlier. The datetime of the latest element in the array is stored here by the ShiftHistoryArray macro. This variable should only be modified by the macro, and you should not modify this user variable elsewhere in rule code. The third argument is the period size of the time based array. In this case, it is 30 minutes, formatted as a SAS datetime. The fourth argument is the result variable, and will be used to store the index the current element is to be placed in. It is a local variable called hhc, short for half hour index.

 

%ShiftHistoryArray( &rule.tickets_sold, _H_TICKET_TIME, '00:30't, &rule.hhi );

 

These four arguments are required when using the shift history array macro on time-series arrays, but we will add two more optional arguments for this example. They specify which transaction values should be used for the date and the time. If none are specified, the default is rqo_tran_date and rqo_tran_time. In our example, we would like to use the same day as the transaction, but use the ticket time instead of the exact purchase time for managing our array. For those last two optional arguments, I'll add rqo_tran_date, and a fictional field that corresponds to the entry time of the ticket.

 

%ShiftHistoryArray( &rule.tickets_sold, _H_TICKET_TIME, '00:30't, &rule.hhi, rqo_tran_date, aqm_sas_haunted_hayride_time  );

 

Next, I need to add the quantity of tickets sold to the appropriate bucket of the array, using the index from earlier in the set macro with the tickets_sold array.

 

%set( &rule.tickets_sold, &rule.hhi ) = %get( &rule.tickets_sold, &rule.hhi ) + aqm_sas_haunted_hayride_quant;

 

Now, my rule code is ready to prime and maintain the user variable array.

 

Just in time, the first customers approach: a group of students from the local middle school. There are 18 students and 4 chaperones for a total of 22 tickets. They buy tickets at 5:45 PM for the 6:00 haunted hayride.

 

I’ll use the entry time and the number of tickets sold to add to the appropriate bucket. As each transaction is executed, the total is added to the correct array.

 

Date: 10/30/2025

Transaction Time: 5:45 PM

Entry Time: 6:00 PM

Tickets: 22

 

We would want our rule logic to add the 22 tickets to the first bucket, while also indicating that bucket corresponds to 6:00-6:30 PM.

 

After the rule code runs, the array looks like this:

 

Table 2: tickets_sold array after initial transaction

 

_H_SEATS_SOLD_1 _H_SEATS_SOLD_2 _H_SEATS_SOLD_3 _H_SEATS_SOLD_4 _H_SEATS_SOLD_5 _H_SEATS_SOLD_6
22 0 0 0 0 0

 

Next, another transaction occurs. This time a guest buys tickets in advance for 7:00, planning to meet her book club later.

 

Date: 10/30/2025

Transaction Time: 5:50 PM

Entry Time: 7:00 PM

Tickets: 8

 

If she buys 8 tickets, these 8 tickets need to be placed in the corresponding position in the array, but currently, the most recent spot in the array is 6:00 PM. The rule logic detects that the entry time is greater than or equal to 30 minutes after that time, so both arrays will be shifted and the new most recent position would be 6:30. The entry time is still greater than or equal to 6:30 (in this case the latter), so the arrays are shifted again before the tickets are added. The new arrays are as follows:

 

Table 3: tickets_sold array after 2nd transaction

 

_H_SEATS_SOLD_1 _H_SEATS_SOLD_2 _H_SEATS_SOLD_3 _H_SEATS_SOLD_4 _H_SEATS_SOLD_5 _H_SEATS_SOLD_6
8 0 22 0 0 0

 

For the next transaction, let's say someone would like to buy 4 tickets for the 6:00 departure time. The ShiftHistoryArray macro won't shift the array this time, but this transaction causes the array to exceed 24 at that position.

 

Table 4: tickets_sold array after 3rd transaction

 

_H_SEATS_SOLD_1 _H_SEATS_SOLD_2 _H_SEATS_SOLD_3 _H_SEATS_SOLD_4 _H_SEATS_SOLD_5 _H_SEATS_SOLD_6
8 0 26 0 0 0

 

We can create an action rule that generates an alert when the values in this array exceed 24, indicating that another wagon is needed. But we could also use this information a number of other ways depending on the use case. For example, we might want a rule that detects when the last 3 consecutive departure times have all exceeded the normal capacity of the hayride, or have on average exceeded capacity. We could create a rule that identifies large increases or decreases in tickets sales, or a rule that declines transactions where the visitors exceed 48, assuming only 1 second wagon can be added.

 

You can use time-series arrays with rules in a myriad of different ways to detect and prevent fraud. For example, you could detect trends in deposits and withdrawals over days or weeks. You can create arrays with an appropriate size and period for your business needs, whether you want to track activity on an hour by hour basis over days, or on a week by week basis over months. Then, you can use the user variable information in rule logic to make decisions or generate alerts, informed by a robust transaction history.

 

 

Manually shifting time-series arrays: a hybrid approach

 

What if we only want to shift the array and store data when there are actively transactions? A hybrid approach lets us still group activity by discrete date ranges, and each position in the array corresponds to a date range and one or more activities. However, in the absence of activity, the array is not shifted.

 

Let's say our haunted hayride closes at midnight (even ghosts have to sleep!) and reopens every evening at 6:00 PM. It is the day after we implemented the new rule logic, and the first customers approach and buy 4 tickets. The array now looks like this:

 

Table 5: tickets_sold array has been cleared out the next day. 

 

_H_SEATS_SOLD_1 _H_SEATS_SOLD_2 _H_SEATS_SOLD_3 _H_SEATS_SOLD_4 _H_SEATS_SOLD_5 _H_SEATS_SOLD_6
4 0 0 0 0 0

 

All the activity from the previous evening has been displaced out of the array, since hay rides were not running from midnight until the attraction reopened. ShiftHistoryArray shifts the array systematically according to the period size you set, so it will keep the 6 most recent buckets regardless of whether there was activity or not. This might be suitable or desirable, depending on your rule logic. However, if you would like to use time-based buckets to store information in arrays, but still only shift the array when an event occurs, you can use a hybrid approach and manually shift your time-series array.

 

To do this, you need to use a user variable to track the most recent entry time. For our example, we will use _H_RECENT_TICKET_TIME. In the case where the array has not yet been primed, it will have the default value of 0 until the first transaction.

 

if _H_RECENT_TICKET_TIME = 0
then do;
%set(_H_RECENT_TICKET_TIME ) = AQM_SAS_HAUNTED_HAYRIDE_TIME;
end;

 

If the array has been primed, then the date is used to check whether or not the current transaction fits in the most recent bucket. If it is the same entry time as the most recent entry time in the array, the quantity of tickets in the order is added to that bucket.

 

if _H_RECENT_TICKET_TIME > 0 
and AQM_SAS_HAUNTED_HAYRIDE_TIME = _H_RECENT_TICKET_TIME 
then do;
%set(_&rule.tickets_sold, 1 ) = sum( %get(_&rule.tickets_sold, 1 ), AQM_SAS_HAUNTED_HAYRIDE_QUANT);
end;

 

Otherwise, the array is shifted by 1 position using the shift history array macro with only the array itself as an argument. The user variable storing the most recent ticket time is also reset to the current transaction's entry time, to reset the logic for the next rule.

 

else do;
%ShiftHistoryArray(&rule.tickets_sold);
%set( __H_RECENT_TICKET_TIME  ) = AQM_SAS_HAUNTED_HAYRIDE_TIME;
%set( &rule.tickets_sold,1 ) = sum( %get( &rule.tickets_sold,1), AQM_SAS_HAUNTED_HAYRIDE_QUANT);
end;

 

Note that if you were interested in knowing the last time the events happened other than for the first position in the array, you would need to capture additional date fields for each bucket of the array, depending on use case and logic of the rule.  This would also be required if we wanted to account for customers buying tickets in advance, because currently, the logic of the rule assumes that if the times do not match, the array needs to be shifted later.

 

For example, in this case you could create an array of the same dimensions to store the entry time corresponding to each bucket.

 

Hopefully, this example has inspired you to use time-based user variable arrays in your SAS Fraud Management environment for the first time, or in new ways to drive fraud detection and prevention.

 

For further reading about SAS Fraud Management, check out the user guides and communities.

 

 

Find more articles from SAS Global Enablement and Learning here.

Contributors
Version history
Last update:
a month ago
Updated by:

sas-innovate-2026-white.png



April 27 – 30 | Gaylord Texan | Grapevine, Texas

Registration is open

Walk in ready to learn. Walk out ready to deliver. This is the data and AI conference you can't afford to miss.
Register now and lock in 2025 pricing—just $495!

Register now

SAS AI and Machine Learning Courses

The rapid growth of AI technologies is driving an AI skills gap and demand for AI talent. Ready to grow your AI literacy? SAS offers free ways to get started for beginners, business leaders, and analytics professionals of all skill levels. Your future self will thank you.

Get started

Article Tags