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## Package: markovchain## Date: 2023-09-24 09:20:02 UTC## BugReport: https://github.com/spedygiorgio/markovchain/issuesif (!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':#### unionQ1
# set up helper functions
# limit of 'p^k' k->inf (actually, 1e5), is p^k positivereg = 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 truereg1 = 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 otherwiseabsorb = 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 matricesp1 = 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 1p2## [,1] [,2] [,3]## [1,] 0.2 0.0 0.5## [2,] 0.5 0.5 0.5## [3,] 0.3 0.5 0.0p3## [,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.5Plot 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.2934783So 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] FALSESo 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 1is.regular(m1)## [1] FALSEabsorbingStates(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.0is.regular(m2)## [1] TRUEsteadyStates(m2)## [,1]## s1 0.1923077## s2 0.5000000## s3 0.3076923absorbingStates(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.5is.regular(m3)## [1] TRUEsteadyStates(m3)## [,1]## s1 0.2173913## s2 0.2445652## s3 0.2445652## s4 0.2934783absorbingStates(m3)## character(0)# so P3 is regular w/ same stationary probability, and isn't absorbing, so my Q1 is rightSo all my Q1 answers are correct.
Q2
Create standard form function
# converts markov chain matrix to standard formsForm = 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 1a = 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.2vc = 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# Nas.data.frame(vc[1:2])## V1 V2## 1 1.8181818 1.136364## 2 0.9090909 1.818182# Tias.data.frame(vc[3:4])## X2.95454545454545 X2.72727272727273## 1 2.954545 2.727273# Bas.data.frame(vc[5:6])## V1 V2## 1 0.7272727 0.7045455## 2 0.2727273 0.2954545M = 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 1canonicForm(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.2Which is the same as my standard form matrix, so my Q2 computes the right standard form matrix.