*! 1.0.0 Ariel Linden 05Jul2025
program markovfutureprob_ind, rclass
version 11.0
syntax anything , CURRent(string) [ PERiod(real 2) FORmat(string) TITle(string) ]
// only one matrix may be specified
local matcount : word count `anything'
if (`matcount' > 1) {
di as err "only one matrix may be specified"
exit = 103
}
// only one current state may be specified
local currcount : word count `current'
if (`currcount' > 1) {
di as err "only one current state may be specified"
exit = 103
}
local nrows = rowsof(`anything')
local ncols = colsof(`anything')
if `nrows' < 2 {
di as err "the matrix must have at least 2 rows"
exit 198
}
// check that matrix is symmetrical
if `nrows' != `ncols' {
di as err "the matrix must be symmetrical (equal number of rows and columns)"
exit 198
}
// loop through each row to check if the sum equals 1
forval i = 1/`nrows' {
* Calculate the row sum
scalar row_sum = 0
forval j = 1/`nrows' {
scalar row_sum = row_sum + `anything'[`i', `j']
}
* Check if the row sum is not equal to 1
if abs(row_sum - 1) > 1e-6 {
di as err "row `i' of matrix `anything' does not sum to 1. Sum = " row_sum
exit 198
}
}
local rownames : rownames `anything'
local colnames : colnames `anything'
// determine if current state is a number or letter or combo //
// Check for integer
if regexm("`current'", "^[0-9]+$") {
local nrows = rowsof(`anything')
if `current' > `nrows' {
di as err "`current' is not a valid row number"
exit 198
}
else {
local holdout = `current'
}
}
// Check for alpha
else if regexm("`current'", "^[a-zA-Z]+$") {
if !`: list destination in rownames' {
di as err "{bf:`current'} is not in the list of matrix rownames: " "{bf:`rownames'}"
exit 198
}
else {
local i : list posof "`current'" in rownames
local holdout = `i'
}
}
// else it's mixed alpha and numeric
else if regexm("`current'", "^(?=.*[a-zA-Z])(?=.*[0-9]).+$") {
if !`: list destination in rownames' {
di as err "{bf:`current'} is not in the list of matrix rownames: " "{bf:`rownames'}"
exit 198
}
else {
local i : list posof "`current'" in rownames
local holdout = `i'
}
}
else {
di as err "`current' is not in the list of matrix rownames or a row number"
exit 198
}
mata: futureprob(`holdout', `period', "`anything'")
if "`format'" != "" {
confirm numeric format `format'
}
else local format %6.3f
local title title(Probabilities for `period' periods into the future)
// assign original rownames and colname that corresponds to the current state
matrix colnames futureprobs_full = `colnames'
local rowfind : word `holdout' of `rownames'
matrix rownames futureprobs_full = `rowfind'
matlist futureprobs_full, border(top bottom) lines(oneline) tindent(1) aligncolnames(ralign) twidth(8) format(`format') `title'
// save matrix
return matrix futureprobs = futureprobs_full
end
version 11.0
mata:
mata clear
void function futureprob(real holdout, real period, string scalar stata_matrixname)
{
real matrix A
// Convert Stata matrix to Mata
A = st_matrix(stata_matrixname)
nrows = rows(A)
// create matrix to identify current state
curr = J(1, nrows, 0)
curr[holdout] = 1
// Initialize matrix A as identity matrix
ident = I(rows(A))
// Multiply A by period (X times)
for (i = 1; i <= period; i++) {
ident = ident * A
}
futureprobs_full = curr * ident
// save as Stata matrix
st_matrix("futureprobs_full", futureprobs_full)
}
end