SAS Viya Copilot for Code Assistance in SAS Data and AI Studio
Recent Library Articles
In this post, we’ll take a look at how SAS Viya Copilot for Code Assistance can help with some of those everyday tasks: understanding code, improving it, and making changes more confidently—all without leaving the SAS Data and AI Studio environment.
I have a categorical variable, var 1, that has missing data, and I am trying to impute using PROC MI in SAS. Var1 can take on the 0, 1, 2, and 3, but I know that the missing values cannot take on the value of 0 because of substantive knowledge of the data, where I know the people that are missing can only take on the values of 1-3. Below is some dummy code example of what my code looks like now, but is there a way to tell PROC MI that the missing values can only take on 1-3? Or any other ways to do this? Thanks! proc mi data=have nimpute=10 out=final;
class var1 var2 var3;
var var1 var2 var3;
fcs logistic(var1) nbiter=100;
run;
... View more
Overview of the article series of the Monopoly board game simulation in SAS Viya
Monopoly #1 - Basic Example for the Visit Frequency of the Fields: Gives a basic introduction into the simulation architecture using a SAS datastep and shows how you can generate first simulations and visualization of the visit distributions.
Monopoly #2 - Consider "Go-to-Jail" for the Visit Frequency of the Fields: Extends the simulation by considering the Go-to-Jail directive in your simulation
Monopoly #3 - Create a visualization dashboard in SAS Visual Analytics: Shows how you can store the simulation results in a repository to compare the results from different simulations settings and how to create a visualization dashboard in SAS Visual Analytics
Monopoly #4 - Consider Chance Cards and Community Chest Cards for the Visit Frequency: Explains how you can additionally consider the instructions from chance and community chest cards for the simulation by extending the SAS datastep
This article: Monopoly #5 - Using Stationary Probabilities and SAS/IML to Calculate the Fields' Visit Frequency: Shows how you can use SAS IML software to calculate stationary probabilities for the visit distribution
Monopoly #6 - Calculate the Profit per Field and per Player: Extends the simulation by considering purchase options of properties and houses, as well as rent payment obligations. This article also explains how you can extend your vizualisation dashboard in SAS Visual Analytics to show the profitability per field and per player number.
Introduction and Basic Idea
If the Monopoly game continues for a very long time, what proportion of visits will occur on each Monopoly field? The answer can be obtained by Monte Carlo Simulation (as shown it the other articles of this series) or by finding the stationary distribution of the Markov chain. In this article you seen an implementation using SAS IML software. You can download the SAS programs for this article from my GIT repo.
Johannes Palmetshofer, who was my students at Johannes Kepler University in Linz, Austria in 2025, deserves credit for suggesting the idea to perform the calculation using stationary probabilities. He created a first version in R as a voluntary additional work in the class. I translated and augmented his program in SAS IML.
The transition matrix
A Monopoly board contains 40 fields. You can represent each field as a state in a stochastic process. After rolling the dice, the player moves to another state. Consequently, the movement from one field to another can be represented using a transition probability matrix.
An example for such a 40x40 matrix, the upper left 9x9 cells, is shown below. The from-to matrix reads as: Rows= FROM, Columns = TO.
Because the player must move to one of the possible fields after every dice roll, the probabilities associated with each starting field sum to 1, consequently the row sums must all be 1. To receive stationary probabilities, the instructions and the logic of the Monopoly Board game must be packed in the definition of the transition matrix.
In its 3 subsections, this article shows how the transition matrix is defined for 3 different scenarios
Basic Example just by considering the 2 dice
Considering the go-to-jail directive
Consider the Chance and Community Chest cards
Calculating the stationary probabilities
The calculation of the stationary probabilities involves the following steps:
Calculate the eigenvalues and eigenvectors of the transition matrix.
Find eigenvalue closest to 1
Extracting the stationary distribution (normalize)
1. Basic Example for Stationary Probabilities (just consider the 2 dice)
1.1 Start IML and Initialize the Transition Matrix
The PROC IML statements starts SAS IML and the j-function in IML create a 40x40 matrix with 0.
proc iml;
/* Initialize a 40x40 zero matrix */
mat = j(40, 40, 0);
1.2 Defining the probabilities
The sum two dice can contain values from 2 to 12. The probabilities follow a triangle distribution (2 and 12 being least frequent, 7 being most frequent). Note that the vector starts with probabilities = 0 as you cannot roll a "1" with two dice.
/* Define dice probabilities */
probs = {0 1 2 3 4 5 6 5 4 3 2 1} / 36;
1.3 Filling the transition probabilities matrix
The PROBS vector is used to fill the matrix with the transition probabilities. Note that there is an OUTER loop for the 40 rows where the player is actually located and an inner loop for the 12 fields that can be reached from each position after rolling the dice once.
/* Fill transition probabilities */
do row = 1 to 40;
do i = 1 to 12;
mat[row, mod(row + i - 1, 40)+1] = t(probs[i]);
end;
end;
The values of the transition matrix can be shows using the PRINT statement
print mat;
or by created a heatmap using the CALL HEATMAP function.
call HeatmapCont(mat) colorramp="ThreeColor" range={-0.3 0.3}
In the resulting heatmap you see the distribution of the transition probabilities in the matrix.
If you are located at field #1 you will land on fields 6,7,8,9,10 with higher probability.
if you are located at field #34 your will land on fields 39,40,1,2,3 with higher probability.
Note that for better illustration of "FROM in rows, TO in columns" nature of the transition matrix, some of the labels of the rows and columns in the heatmap have been overwritten using the following statements. Vectors of numbers and FROM/TO-labels are concatenated in XVAL and YVAL and finally used in the heatmap.
xval = char(T(1:18)) // "," // "T" // "O" // "." // char(t(23:40));
yval = char(T(1:17)) // "," // "F" // "R" // "O" // "M" // "." // char(t(24:40));
call HeatmapCont(mat) colorramp="ThreeColor" range={-0.3 0.3} xvalues = xval
yvalues = yval;
1.5 Calculate the eigenvalues and eigenvectors of the transition matrix
You calculate the eigenvalues and eigenvectors of the transition matrix using the following statement:
call eigen(evals, evecs, t(mat));
EVALS contains the Eigenvalues of matrix MAT.
Next you find the eigenvalue closest to 1. You calculate the distance from 1 using the following expression.
/* Find eigenvalue closest to 1 */
diffs = abs(evals - 1);
And you locate the position of the eigenvalue which has the minimum distance using the LOC function.
idx = loc(diffs = min(diffs));
After you have identified the position you extract the eigenvector from the matrix of eigenvectors at this position.
/* Get stationary distribution (normalize) */
stationary = abs(evecs[, idx]);
stationary = stationary / sum(stationary);
You use the abs() function to make all elements non-negative. This is useful because a stationary probability distribution must contain non-negative values. Eigenvectors themselves can be returned with an arbitrary sign, meaning that the same eigenvector might be represented with either positive or negative values.
Finally you normalize the vector. The resulting vector sums to 1 and can therefore be interpreted as a probability distribution.
stationary = stationary / sum(stationary);
The resulting stationary vector contains the long-run probability of visiting each field at the Monopoly board game.
Finally you add an index vector 1:40 as the first column.
/* Add index (field number) as first column */
index = t(1:40);
result = index || stationary;
The first 10 rows of this result matrix are shown below
You can finally export this matrix to a SAS dataset using the following statement.
/* Create dataset with index and probability */
create stationary_prob from result[colname={"Field" "Probability"}];
append from result;
close stationary_prob;
And use the SGLOT procedure to create a barchart for each field.
%let ScenarioName = "5. Stationary Probabilities: Dice Only";
proc sgplot data=stationary_prob;
title Scenario: &scenarioname.;
vbar field / response=Probability;
xaxis label="Field";
yaxis label="Probability";
run;
The full code can be found in the attached program 10.5.1.
You see that you get equal probabilities for each field. This is not surprising as you have just used the two dice and not considered any relocation instruction (jail, chance cards, ...) so far. The next section explains how this can be done.
2. Considering the Go-To-Jail Directive in the Calculation of the Stationary Probabilities
In this section you consider the fact that field 31 relocates the token to field 11 (go-to-jail). You consider this fact in the definition of the transition matrix after filling the transition probabilities of the 2 dice. You do this with the statements
/* Fill transition probabilities */
do row = 1 to 40;
do i = 1 to 12;
mat[row, mod(row + i - 1, 40)+1] = t(probs[i]);
end;
end;
** ADDED: ;
/* Go to jail: add probabilities from 31 to 11, zero out 31 */
mat[,11 ] = mat[,11 ] + mat[,31 ];
mat[,31 ] = 0;
/* Go to jail: add probabilities from 31 to 11, zero out 31 */
mat[,11 ] = mat[,11 ] + mat[,31 ];
mat[,31 ] = 0;
The first line adds the probability of ending up at field 31 to field 11.
The second line sets the probability of remaining on field 31 to 0 (as you are always sent to jail from this field).
In the heatmap you see that
the probability of ending up at field 11 (column 11) not only comes from rows where the token starts from fields 1-9.
You see that there are also transition probabilities coming from fields 19-29, where the token lands at field 31 and is relocated to 11.
You also see that the probability of landing at field 31 (column 31) is zero.
Performing the calculation of the stationary probabilities as described in section 1 provides a vector with probabilities which is displayed in the barchart below. You see the higher probability at field 11, and 0 probabilities at field 31. Note that there is also a change in probabilities of other fields fields after: higher probabilities after field 11, and lower probabilities after fields 31.
The full code can be found in the attached program 10.5.2.
3. Considering Chance and Community Chest Fields in the Calculation
In the next step you also consider the chance and community chest fields in the calculation of the visit probability.
The "Chance" fields are located at fields 8, 23, and 37. 10 out of 16 cards relocate the token:
3.1 Cards 1, 2, 3, 4, 11, and 14 relocate the token to a specific field
The target fields of this relocation are 40, 1, 25, 12, 11, and 6. You need to increase the probability of each of these fields with probability that the respective cards is picked.
The probability for picking up one of the 16 community chest cards is 1/16.
You multiply this with the cumulative visit probabilities of fields 8, 23, and 37 where you have to pick up a community chest card.
Note that a vector of the target fields is defined, which you process in a DO LOOP.
/* Increase probability for target fields */
target = {40 1 25 12 11 6};
do t_idx = 1 to ncol(target);
t = target[t_idx];
mat[,t] = mat[,t] + (mat[,8] + mat[,23] + mat [,37])* 1/16;
end;
3.2 Card 10 relocates you 3 spaces backward.
The target fields of this relocation are 5, 20, 34, because the community chest cards are picked up at fields 8, 23 and 37.
Again need to increase the probability of each of these fields with probability that the respective cards is picked.
space3 = {5 20 34};
do t_idx = 1 to ncol(space3);
t = space3[t_idx];
mat[,t] = mat[,t] + (mat[,8] + mat[,23] + mat [,37])/3 * 1/16;
end;
3.3 Cards 5,6, and 7 relocate to the "nearest" utility or railroad.
Note that in the simulation "nearest" is interpreted as the next utility (railroad) in the direction how tokens move in the game. Here you need to take into account where the token is currently located to determine the target.
Eg. the first line in the code below considers that the nearest railroad after field 8 is located at field 16.
/* Advance to nearest Railroad */
mat[,16] = mat[,16] + mat[,8] * 2/16 ;
mat[,26] = mat[,26] + mat[,23] * 2/16 ;
mat[,6] = mat[,6] + mat[,37] * 2/16 ;
/* Advance to nearest Utility */
mat[,13] = mat[,13] + (mat[,8] + mat[,37]) * 1/16 ;
mat[,29] = mat[,29] + mat[,23] * 1/16 ;
3.4 Decrease the probabilities of the source fields
Finally you need to decrease the probability for the source fields 8, 23, and 37.
You loop over the vector of these field numbers and set the probability to 6/16, which is the number of cards that do not contain a relocation.
/* Decrease probability for source fields */
source = {8 23 37};
do s_idx = 1 to ncol(source);
s = source[s_idx];
mat[,s] = mat[,s] * 6/16;
end;
3.5 Considering the community chest cards
The community chest fields are located at fields 3, 18, and 34. Only 2 of the 16 cards relocate the token:
Card 1 relocates you to the start field (field 1)
Cards 6 sends you to jail.
You consider this in the same ways as for the chance cards. You increase the probabilities of the target fields.
/* Community Chest: 3,18 and 34 visits are moved to 1 in 1/16 and to 11 in 1/16 of the cases*/
/* Increase probability for target fields */
mat[,1] = mat[,1] + (mat[,3] + mat[,18] + mat[,34])/16;
mat[,11] = mat[,11] + (mat[,3] + mat[,18] + mat[,34])/16;
You decrease the probability for the source fields
/* Decrease probability for source fields */
mat[,3] = mat[,3] * 14/16;
mat[,18] = mat[,18] * 14/16;
mat[,34] = mat[,34] * 14/16;
3.6 Full Code
The full code of considering the chance and community chest cards is shown here:
/* Fill transition probabilities */
do row = 1 to 40;
do i = 1 to 12;
mat[row, mod(row + i - 1, 40)+1] = t(probs[i]);
end;
end;
/* Go to jail: add probabilities from 31 to 11, zero out 31 */
mat[,11] = mat[,11] + mat[,31];
mat[,31] = 0;
*** Added: ;
/* Increase probability for target fields */
target = {40 1 25 12 11 6};
do t_idx = 1 to ncol(target);
t = target[t_idx];
mat[,t] = mat[,t] + (mat[,8] + mat[,23] + mat [,37])* 1/16;
end;
space3 = {5 20 34};
do t_idx = 1 to ncol(space3);
t = space3[t_idx];
mat[,t] = mat[,t] + (mat[,8] + mat[,23] + mat [,37])/3 * 1/16;
end;
/* Advance to nearest Railroad */
mat[,16] = mat[,16] + mat[,8] * 2/16 ;
mat[,26] = mat[,26] + mat[,23] * 2/16 ;
mat[,6] = mat[,6] + mat[,37] * 2/16 ;
/* Advance to nearest Utility */
mat[,13] = mat[,13] + (mat[,8] + mat[,37]) * 1/16 ;
mat[,29] = mat[,29] + mat[,23] * 1/16 ;
/* Decrease probability for source fields */
source = {8 23 37};
do s_idx = 1 to ncol(source);
s = source[s_idx];
mat[,s] = mat[,s] * 6/16;
end;
/* Community Chest: 3,18 and 34 visits are moved to 1 in 1/16 and to 11 in 1/16 of the cases*/
/* Increase probability for target fields */
mat[,1] = mat[,1] + (mat[,3] + mat[,18] + mat[,34])/16;
mat[,11] = mat[,11] + (mat[,3] + mat[,18] + mat[,34])/16;
/* Decrease probability for source fields */
mat[,3] = mat[,3] * 14/16;
mat[,18] = mat[,18] * 14/16;
mat[,34] = mat[,34] * 14/16;
3.7 Displaying the Results
You see that the transition matrix now shows increased and decreased probabilities for various fields.
The probabilities to remain on fields 8, 23, 37 and 3, 18, 34 is lower and the probabilities of some of the target fields (railroads, utilities, ...) is increased.
Performing the calculation of the stationary probabilities as described in section 1 provides a vector with probabilities which is displayed in the barchart below.
You see the higher probability at at the target fields (e.g. 1, 6, 13, 16, 26, 29, 40) and lower probabilities at the source fields ( e.g. 8, 23, 37).
3.8 Optional: Displaying the Monopoly Board Game
You can also (partially) display the board of the Monopoly Game in a heatmap.
First you initialize the field with a small negative number, which is displayed as blue.
/* Display Board */
board = j(11, 11, -0.08);
Next you assign the appropriate values from the stationary distribution to the margins of the board.
board[11,2:11] =t(stationary[10:1]);
board[2:11,1]=stationary[20:11];
board[1,1:10] =t(stationary[21:30]);
board[1:10,11]=stationary[31:40];
Finally you plot the heat map.
call HeatmapCont(board) colorramp="ThreeColor" range={-0.08 0.08} ;
You see the board with field 1 in the lower right corner. Note that the labels are incorrect here and do not represent the true field numbers. However the display gives you some impression about the how/low visit probabilities: high in the lower left corner which is field 11 (jail), low in the upper right corner which is fields 31 (go-to-jail).
Related Articles and Links
You can download the SAS programs for this article from my GIT repo.
DO LOOP Blog https://blogs.sas.com/content/iml/ and SAS IML Software https://www.sas.com/en_us/software/iml.html
Ask-the-Expert On-Demand - Implementing a Digital Twin for the Monopoly Board Game Using SAS® Viya®
More SAS Community articles from Gerhard
Case Study 8 (Chapter 26, 27 and 28) in my SAS Press Book Applying Data Science - Business Case Studies Using SAS
Youtube:
Studying Complex Systems – Simulating the Monopoly Board Game
Analytics mal anders: E-Werk, Bahnhof oder Schlossallee? Der Monopoly-Check | SAS Forum Digital 2021
... View more
Team Name Randoms Track Energy Use Case PeakShift is a proposed application that helps small and midsize manufacturers reduce electricity costs while meeting production deadlines. Technology React + TypeScript, Python + FastAPI, PostgreSQL, and SAS Viya Region NA Team lead Daniel Flores Elizondo, @dflores2 Team members @mameraka @sboyana @gpsubash @Brodym @jmmicha4 @S00 @machepur @hyerram @aida_ @SaiG06 @jsawyer0 Social media handles https://www.linkedin.com/in/gaurinathsubash/ https://www.linkedin.com/in/manish-chepuri/ https://www.linkedin.com/in/dflorese/ https://www.linkedin.com/in/sidhanth-boyanapalli-66208021b/ https://www.linkedin.com/in/brody-moore77/ https://www.linkedin.com/in/jonathansawyer3/ https://www.linkedin.com/in/sundayoo/ https://www.linkedin.com/in/joshua-michael28/ https://www.linkedin.com/in/sairaj-gunda-a12052264/ https://www.linkedin.com/in/aidafatima/ https://www.linkedin.com/in/hruday-yerramareddy-680923231/ https://www.linkedin.com/in/mannaseh-merakanapalli-87b875332/ Is your team interested in participating in an interview? Y Optional: Expand on your technology expertise
... View more
Team Name M&R Track Public Sector Use Case We are building a SAS-based simulation and analysis pipeline that models how public agency messaging (health guidance, emergency instructions) spreads and shifts opinion through a population of LLM agents, so a public sector communicator can test a messaging strategy statistically before it goes live. Technology SAS Viya for the modeling and visualization, Python for the agent simulation that generates the data, and open source LLMs run locally via Ollama for the agents themselves. Region Americas Team lead Rehan Mohammed Team members Mahdiya Ahsan Social media handles https://www.linkedin.com/in/rehan-mohammed-/, https://www.linkedin.com/in/mahdiya-ahsan/ Is your team interested in participating in an interview? Y Optional: Expand on your technology expertise
... View more