Markov Chains
University Project #Data Science#Mathematics

Markov Chains#

Custom markov chain functions along with computing absorbing states and graph regularity.

.rmd knitted with execution results available for download here

Set up

if (!require("markovchain")) {
install.packages("markovchain")
library(markovchain)
}
## Loading required package: markovchain
## Warning: package 'markovchain' was built under R version 4.3.2
0.9.5
## Package: markovchain
## Date: 2023-09-24 09:20:02 UTC
## BugReport: https://github.com/spedygiorgio/markovchain/issues
if (!require("igraph")) {
install.packages("igraph")
library(igraph)
}
## Loading required package: igraph
##
## Attaching package: 'igraph'
## The following objects are masked from 'package:stats':
##
## decompose, spectrum
## The following object is masked from 'package:base':
##
## union

Q1#

# set up helper functions
# limit of 'p^k' k->inf (actually, 1e5), is p^k positive
reg = function(p){
pk = p
for(i in c(1, 1e5)){
if(reg1(pk)){
# compute stationary probability
# normalize eigen vector associated w/ eigen value '1' (always e$vectors[,1])
e = eigen(p)
# normalize eigen vector
norm_e = abs(e$vectors[,1]) / sum(abs(e$vectors[,1]))
# return(c(norm_e, e$vectors[,j]))
return(norm_e)
}
pk = p %*% p
}
# didn't reach limit in reasonable time period
return(FALSE)
}
# if matrix positive, return true
reg1 = function(p_in){
p = p_in %*% p_in
# loop thru rows
for(i in c(1, length(p[1,]))){
# loop thru cols
for(j in c(1, length(p[,1]))){
# entry non-positve
if(p[i,j] <= 0){
# entire matrix non-positive -> non-regular markov-chain
return(FALSE)
}
}
}
return(TRUE)
}
# a markov chain has absorbing states if:
# some value on the main diagonal is '1' -> stuck at that state
# returns false if 'p' is not absorbing,
# returns the absorbing states otherwise
absorb = function(p, l=TRUE){
# assuming p is valid (cols sum up to 1)
val = FALSE
count = 1
for(i in c(1:length(p[,1]))){
if(p[i,i] == 1){
# absorbing state: 'i'
if(typeof(val) == "logical"){ # first absorbing state
val = c()
}
if(l){
val[count] = paste("s",toString(i),sep="")
} else {
val[count] = i
}
count = count + 1
}
}
return(val)
}
# Define matrices
p1 = matrix(c(c(1,0),c(0,1)), nrow=2, ncol=2)
p2 = matrix(c(c(0.2, 0.5, 0.3), c(0, 0.5, 0.5), c(0.5, 0.5, 0)), nrow=3, ncol=3)
p3 = matrix(c(c(0.1, 0.9, 0, 0), c(0.8, 0.1, 0.1, 0), c(0, 0.1, 0.3, 0.6), c(0, 0, 0.5, 0.5)), nrow=4, ncol=4)
p1
## [,1] [,2]
## [1,] 1 0
## [2,] 0 1
p2
## [,1] [,2] [,3]
## [1,] 0.2 0.0 0.5
## [2,] 0.5 0.5 0.5
## [3,] 0.3 0.5 0.0
p3
## [,1] [,2] [,3] [,4]
## [1,] 0.1 0.8 0.0 0.0
## [2,] 0.9 0.1 0.1 0.0
## [3,] 0.0 0.1 0.3 0.5
## [4,] 0.0 0.0 0.6 0.5

Plot the graphs.

G1 = make_graph(c(c(1,1), c(2,2)))
E(G1)$weights = c(1,1)
par(mar=c(0,0,0,0)+.1)
plot(G1)
G2 = make_graph(c(
c(1,1), c(1,2),
c(1,3), c(2,2), c(2,3),
c(3,1), c(3,2)))
E(G2)$weights = c(0.2, 0.5, 0.3, 0.5, 0.5, 0.5, 0.5)
par(mar=c(0,0,0,0)+.1)
plot(G2, edge.arrow.size=E(G2)$weights*10, layout=layout_in_circle(G2))
G3 = make_graph(c(
c(1,1), c(2,2), c(3,3), c(4,4),
c(1,2), c(2,1), c(2,3), c(3,2),
c(3,4), c(4,3)))
E(G3)$weights = c(0.1, 0.1, 0.3, 0.5, 0.9, 0.8, 0.1, 0.1, 0.6, 0.5)
par(mar=c(0,0,0,0)+.1)
plot(G3, edge.arrow.size=E(G3)$weights*10, layout=layout_in_circle(G3))
# is p1 regular?
reg(p1)
## [1] FALSE
# is p2 regular?
reg(p2)
## [1] 0.1923077 0.5000000 0.3076923
# is p3 regular?
reg(p3)
## [1] 0.2173913 0.2445652 0.2445652 0.2934783

So p1 is not regular, but p2 and p3 are, and their stationary distribution vectors are the normalized eigen vectors associated with the eigen value ‘1’.

# is p1 absorbing?
absorb(p1)
## [1] "s1" "s2"
# is p2 absorbing?
absorb(p2)
## [1] FALSE
# is p3 absorbing?
absorb(p3)
## [1] FALSE

So only ‘p1’ is absorbing, with the absorbing states: {1, 2}

Verify Q1 with ‘markovchain’ Library#

m1 = new('markovchain',
transitionMatrix=p1,
states=c('s1','s2'), byrow=FALSE)
m1
## Unnamed Markov chain
## A 2 - dimensional discrete Markov Chain defined by the following states:
## s1, s2
## The transition matrix (by cols) is defined as follows:
## s1 s2
## s1 1 0
## s2 0 1
is.regular(m1)
## [1] FALSE
absorbingStates(m1)
## [1] "s1" "s2"
# so P1 is not regular, but is absorbing, with the absorbing states {1, 2}, so my Q1 is right
m2 = new('markovchain',
transitionMatrix=p2,
states=c('s1','s2', 's3'), byrow=FALSE)
m2
## Unnamed Markov chain
## A 3 - dimensional discrete Markov Chain defined by the following states:
## s1, s2, s3
## The transition matrix (by cols) is defined as follows:
## s1 s2 s3
## s1 0.2 0.0 0.5
## s2 0.5 0.5 0.5
## s3 0.3 0.5 0.0
is.regular(m2)
## [1] TRUE
steadyStates(m2)
## [,1]
## s1 0.1923077
## s2 0.5000000
## s3 0.3076923
absorbingStates(m2)
## character(0)
# so P2 is regular w/ same stationary probability, and isn't absorbing, so my Q1 is right
m3 = new('markovchain',
transitionMatrix=p3,
states=c('s1','s2', 's3', 's4'), byrow=FALSE)
m3
## Unnamed Markov chain
## A 4 - dimensional discrete Markov Chain defined by the following states:
## s1, s2, s3, s4
## The transition matrix (by cols) is defined as follows:
## s1 s2 s3 s4
## s1 0.1 0.8 0.0 0.0
## s2 0.9 0.1 0.1 0.0
## s3 0.0 0.1 0.3 0.5
## s4 0.0 0.0 0.6 0.5
is.regular(m3)
## [1] TRUE
steadyStates(m3)
## [,1]
## s1 0.2173913
## s2 0.2445652
## s3 0.2445652
## s4 0.2934783
absorbingStates(m3)
## character(0)
# so P3 is regular w/ same stationary probability, and isn't absorbing, so my Q1 is right

So all my Q1 answers are correct.

Q2#

Create standard form function

# converts markov chain matrix to standard form
sForm = function(p){
states = absorb(p, FALSE) # list of absorbing states
if(typeof(states) == "logical"){ # no absorbing states
return(FALSE)
}
# col swap
# move the 'states' columns into the front
M1 = p[,states]
# now add non-absorbing columns
M1 = cbind(M1, p[,-states])
# print(M1)
# row swap
# move the '1's in the absorbing state columns to the top to create an identity matrix
M2 = M1[states,] # grab row from M1
# now add non-absorbing rows
M2 = rbind(M2, M1[-states,])
return(M2)
}
val_comp = function(sf, a){
sf_l = length(sf[1,])
x = sf_l - length(a)
# print(x)
# print(sf_l)
I = diag(nrow=x ,ncol=x)
Q = sf[(x+1):sf_l, (x+1):sf_l]
N = solve(I-Q) # (I-Q)^-1
Ti = list()
for(r in c(1:length(N[,1]))){ # for each row
Ti[r] = sum(N[r,])
}
R = sf[1:length(a), (x+1):sf_l] # gets sf: rows [1,length(a)), cols [length(a), length(sf[,1]))
B = R %*% N
print(R)
print(N)
# print('---- N is ---- ')
# print(N)
# print('---- Ti is ---- ')
# print(Ti)
# print('---- B is ---- ')
# print(B)
return(c(as.data.frame(N), as.data.frame(Ti), as.data.frame(B)))
}
P = matrix(
c(0.2, 0.3, 0.4, 0.1, 0, 1, 0, 0, 0.5, 0.2, 0.2, 0.1, 0, 0, 0, 1),
nrow=4, ncol=4)
P
## [,1] [,2] [,3] [,4]
## [1,] 0.2 0 0.5 0
## [2,] 0.3 1 0.2 0
## [3,] 0.4 0 0.2 0
## [4,] 0.1 0 0.1 1
a = absorb(P)
a
## [1] "s2" "s4"
sf = sForm(P)
sf
## [,1] [,2] [,3] [,4]
## [1,] 1 0 0.3 0.2
## [2,] 0 1 0.1 0.1
## [3,] 0 0 0.2 0.5
## [4,] 0 0 0.4 0.2
vc = val_comp(sf, a)
## [,1] [,2]
## [1,] 0.3 0.2
## [2,] 0.1 0.1
## [,1] [,2]
## [1,] 1.8181818 1.136364
## [2,] 0.9090909 1.818182
# N
as.data.frame(vc[1:2])
## V1 V2
## 1 1.8181818 1.136364
## 2 0.9090909 1.818182
# Ti
as.data.frame(vc[3:4])
## X2.95454545454545 X2.72727272727273
## 1 2.954545 2.727273
# B
as.data.frame(vc[5:6])
## V1 V2
## 1 0.7272727 0.7045455
## 2 0.2727273 0.2954545
M = new("markovchain",
transitionMatrix=P,
states=c('s1','s2','s3','s4'), byrow=FALSE)
M
## Unnamed Markov chain
## A 4 - dimensional discrete Markov Chain defined by the following states:
## s1, s2, s3, s4
## The transition matrix (by cols) is defined as follows:
## s1 s2 s3 s4
## s1 0.2 0 0.5 0
## s2 0.3 1 0.2 0
## s3 0.4 0 0.2 0
## s4 0.1 0 0.1 1
canonicForm(M)
## Unnamed Markov chain
## A 4 - dimensional discrete Markov Chain defined by the following states:
## s2, s4, s1, s3
## The transition matrix (by cols) is defined as follows:
## s2 s4 s1 s3
## s2 1 0 0.3 0.2
## s4 0 1 0.1 0.1
## s1 0 0 0.2 0.5
## s3 0 0 0.4 0.2

Which is the same as my standard form matrix, so my Q2 computes the right standard form matrix.

← Back to Projects