Cadenas de Markov usando R

Ismael Ancona Téllez (CU: 171911)

12 de octubre, 2023

Introduccion al paquete markovchain.

A continuación se describe cómo instalar y cargar correctamente el paquete markovchain. Estas instrucciones funcionan para cualquier otro modulo de CRAN.

Instalacion del paquete.

Primero se debe cargar e instalar el paquete markovchain desde CRAN. Para hacerlo se usa la función install.packages(arg) como se muestra a continuacion:

install.packages('markovchain')

Alternativamente, si se trabaja con una IDE, como RStudio o Pycharm, es posible instalar el módulo usando el menu de paquetes de la aplicación. En el caso de RStudio, el IDE más popular para R, se deben completar los siguientes pasos

  1. Dar click en ToolsInstall Packages
  2. Seleccionar Repository (CRAN) en el espacio Install from:
  3. Ingresar el nombre del paquete (o varios nombres, separados por un espacio o coma), en este caso markovchain
  4. Dejar la opción Install dependencies seleccionada por default.
  5. Dar click en Install

Finalmente, cargamos el módulo usando la función library() o require(), como se muestra a continuación. Esto se puede hacer en la consola o directamente en el script o rmd.

library(markovchain)

Despues de correr el codigo, lo siquiente debería aparecer en la consola. En caso contrario, el paquete no fue instalado correctamente.

## Package:  markovchain
## Version:  0.8.6
## Date:     2021-05-17
## BugReport: https://github.com/spedygiorgio/markovchain/issues

En general se recomienda más el uso de library() sobre require(). Para más información sobre la diferencia entre estas dos funciones revisar este link.

Uso del paquete.

A continuación se muestra como hacer uso del paquete para crear un objeto markovchain, obtener un resumen de sus principales características, clasificar los estados, encontrar probabilidades de transición, obtener la matriz de trancisión en \(n\) pasos, obtener la distribución de la variable \(X_n\), calcular una distribución estacionaria y obtener el gráfo de la cadena. Para más información sobre el paquete revisar este link.

Para ejemplificar, se supondrá una cadena de markov \(X = \{X_1, X_2, ...\}\) definida en un espacio de estados \(S = \{a, b, c\}\) y cuya matriz de trancisión esta dada por: \[P = \begin{bmatrix} 0 & 0.5 & 0.5\\ 0.5 & 0 & 0.5\\ 0.5 & 0.5 & 0 \end{bmatrix}\]

Creación de una CM.

La primera forma de crear una instancia de markovchain es definir una matriz de trancisión P y un vector de estados S, esto se puede hacer usando las funciones matrix() y letters respectivamente como se muestra a continuación.

P <- matrix(c(0, 0.5, 0.5,
              0.5, 0, 0.5,
              0.5, 0.5, 0), nrow = 3, byrow = T);
S <- letters[1:3];

Aquí la función matrix() toma tres argumentos: El primero es un vector que contiene las entradas de la matriz, el segundo nrow es el numero deseado de columnas y byrow indica si la matriz se va a llenar por columnas (FALSE) o por filas (TRUE). En el caso de S la función letters nos da un vector con las letras empezando por la primera (a) hasta la n-esima letra.

Podemos usar la función show() para mostrar como se ve nuestra matriz y el conjunto de estados.

show(P)
##      [,1] [,2] [,3]
## [1,]  0.0  0.5  0.5
## [2,]  0.5  0.0  0.5
## [3,]  0.5  0.5  0.0
show(S)
## [1] "a" "b" "c"

Ahora podemos crear una instancia de markovchain usando la función new(). La clase markovchain tiene 4 atributos: transitionMatrix (la matriz de trancisión), states (los estados), name (el nombre del objeto) y byrows (Si la matriz es estocástica por filas (TRUE) o columnas (FALSE)). A continuación creamos la cadena de markov del ejemplo.

CM_1 <- new("markovchain", transitionMatrix = P, states = S, name = "Ejemplo 1", byrow = TRUE);
show(CM_1)
## Ejemplo 1 
##  A  3 - dimensional discrete Markov Chain defined by the following states: 
##  a, b, c 
##  The transition matrix  (by rows)  is defined as follows: 
##     a   b   c
## a 0.0 0.5 0.5
## b 0.5 0.0 0.5
## c 0.5 0.5 0.0

En este caso show() muestra la “forma” del objeto markovchain o bien, su definición como tipo str.

Formas alternativas.

Existen (que yo sepa), dos formas más de crear una cadena de Markov discreta como una instancia de la clase markovchain.

La primera forma consiste en crear la matriz de trancisión P y definir los estados como los nombres de las filas y columnas de la matriz usando colnames() y rownames() y luego crear la CM sin necesidad de pasar el atributo states.

P_alt1 <- matrix(c(0, 0.5, 0.5,
              0.5, 0, 0.5,
              0.5, 0.5, 0), nrow = 3, byrow = T);
colnames(P_alt1) <- rownames(P_alt1) <- letters[1:3];
CM_alt1 <- as(P_alt1, "markovchain")
show(CM_alt1)
## Unnamed Markov chain 
##  A  3 - dimensional discrete Markov Chain defined by the following states: 
##  a, b, c 
##  The transition matrix  (by rows)  is defined as follows: 
##     a   b   c
## a 0.0 0.5 0.5
## b 0.5 0.0 0.5
## c 0.5 0.5 0.0

Nota: Aqui se usó la funcion as() para crear una instancia de la clase, esta función convierte objetos a alguna clase. Generalmente es mejor usar new() pero aqui se uso as() para mostrar que tambien se puede de esta forma. La desventaja es que no se puede nombrar la CM.

La segunda forma consiste en pasar los estados directamente a la matriz P usando el argumento dimnames de la función matrix() y pasar la matriz como atributo de la instancia, sin pasar un conjunto de estados.

P_alt2 <- matrix(c(0, 0.5, 0.5,
              0.5, 0, 0.5,
              0.5, 0.5, 0),
                 nrow = 3,
                 byrow = T,
                 dimnames = list(letters[1:3], letters[1:3]))

CM_alt2 <- new("markovchain", transitionMatrix = P_alt2, name = "Instancia Alternativa", byrow = TRUE);
show(CM_alt2)
## Instancia Alternativa 
##  A  3 - dimensional discrete Markov Chain defined by the following states: 
##  a, b, c 
##  The transition matrix  (by rows)  is defined as follows: 
##     a   b   c
## a 0.0 0.5 0.5
## b 0.5 0.0 0.5
## c 0.5 0.5 0.0

Características principales de la CM.

La función summary() da un resumen de las principales características de la cadena de Markov. En este resumen podemos ver las clases cerradas, recurrentes y transitivas, asi como si la CM es irreducible y cuales son sus estados absorbentes.

summary(CM_1)
## Ejemplo 1  Markov chain that is composed by: 
## Closed classes: 
## a b c 
## Recurrent classes: 
## {a,b,c}
## Transient classes: 
## NONE 
## The Markov chain is irreducible 
## The absorbing states are: NONE

De igual forma, podemos solicitar las distintas partes de este resumen por separado usando las funciones recurrentClasses(), transientStates(), absorbingStates(), is.irreducible(), communicatingClasses().

  • Clases recurrentes
recurrentClasses(CM_1)
## [[1]]
## [1] "a" "b" "c"
  • Estados transitivos
transientClasses(CM_1)
## list()
  • Estados absorbentes
absorbingStates(CM_1)
## character(0)
  • Clases comunicativas
communicatingClasses(CM_1)
## [[1]]
## [1] "a" "b" "c"
  • Si la CM es irreducible
is.irreducible(CM_1)
## [1] TRUE
  • El periodo
period(CM_1)
## [1] 1

Tambien podemos revisar si un estado es accesible desde otro usando is.accesible(object, from, to) como se muestra a continuación.

is.accessible(CM_1, "a", "c")
## [1] TRUE

Gráfo de la CM

Para visualizar el diagrama de transición de la CM se puede usar la función plot(). El gráfo de la CM es un objeto tipo igraph asi que puede recibir como argumentos adicionales a la CM cualquier argumento que recibiría un gráfico de este tipo. Para más información sobre los parámetros que se pueden especificar revisar el manual del paquete igraph.

plot(CM_1,
    edge.arrow.size = 0.75,
    vertex.size = 20, edge.label.cex = 2,
    vertex.label.cex = 2)

Análisis probabilístico

Para conocer la probabilidad de trancisión en 1 paso entre un estado y otro basta con utilizar la función transitionProbability(object, t0, t1), en donde object es la CM, t0 es el estado en el tiempo 0 y t1 el estado en el tiempo 1.

Por ejemplo, la probabilidad de transición en un paso del estado \(a\) al estado \(b\) sería:

transitionProbability(CM_1, 'a', 'c')
## [1] 0.5

El resultado también se puede obtener dado que se sabe que eso corresponde a la entrada \(P[1, 3]\) de la patriz \(P\).

show(CM_1[1, 3])
## [1] 0.5

Así mismo podemos convertir la markovchain a un dataframe que contenga las probabilidades de trancisión en un paso de cada estado a otro estado.

mcDf <- as(CM_1, "data.frame");
show(mcDf)
##   t0 t1 prob
## 1  a  a  0.0
## 2  a  b  0.5
## 3  a  c  0.5
## 4  b  a  0.5
## 5  b  b  0.0
## 6  b  c  0.5
## 7  c  a  0.5
## 8  c  b  0.5
## 9  c  c  0.0

Matriz de transición en \(n\) pasos

Es posible computar la matriz de transición en \(n\) pasos simplemente computando la \(n\)-ésima potencia de la matriz \(P\), por ejemplo, si se quisiera la matriz \(P_5\) (\(n=5\)) se puede calcular usando la siguiente línea de código.

CM_5 <- CM_1^5;
show(CM_5)
## Ejemplo 1^5 
##  A  3 - dimensional discrete Markov Chain defined by the following states: 
##  a, b, c 
##  The transition matrix  (by rows)  is defined as follows: 
##         a       b       c
## a 0.31250 0.34375 0.34375
## b 0.34375 0.31250 0.34375
## c 0.34375 0.34375 0.31250

Y de igual forma se puede hacer el análisis anterior a esta nueva matriz.

transitionProbability(CM_5, "a", "c")
## [1] 0.34375
show(CM_5[1, 3])
## [1] 0.34375
mcDf5 <- as(CM_5, "data.frame");
show(mcDf5)
##   t0 t1    prob
## 1  a  a 0.31250
## 2  a  b 0.34375
## 3  a  c 0.34375
## 4  b  a 0.34375
## 5  b  b 0.31250
## 6  b  c 0.34375
## 7  c  a 0.34375
## 8  c  b 0.34375
## 9  c  c 0.31250

Distribución de la variable \(X_n\)

Usando las ecuaciones de Chapman-Kolmogorov se sabe que \[\pi_n = \pi_0P_n = \pi_0P^n\] Así, si el vector inicial de la cadena de Markov CM_1 esta dado por \[\pi_0 = (0.5, 0.2, 0.3)\] entonces podemos calcular \(\pi_6\), por ejemplo, como se muestra a continuación

pi_0 <- c(0.5, 0.2, 0.3);
pi_6 <- pi_0*CM_1^6;
show(pi_6)
##              a       b         c
## [1,] 0.3359375 0.33125 0.3328125

Y podemos confirmar que en efecto el vector tiene entradas positivas y suma 1.

show(sum(pi_6))
## [1] 1
show(all(pi_6 >= 0))
## [1] TRUE

Para facilitar este tipo de análisis en un futuro, es conveniente hacer una función que tome como parametros una distribucion inicial \(\pi_0\) y una \(n\). A continuación se muestra una implementación de dicha función.

pi <- function(CM, pi_0, n = 1) {
    return(pi_0 * CM^n)
}

Asi pues, \(\pi_6\) sería

pi(CM_1, pi_0, 6)
##              a       b         c
## [1,] 0.3359375 0.33125 0.3328125

Distribución estacionaria

Una distribución es estacionaria si sucede que \[\pi^{*} = \pi^{*}P\] Se puede encontrar una distribución estacionaria para una CM utilizando la función steadyStates() como se muestra a continuación.

steadyStates(CM_1)
##              a         b         c
## [1,] 0.3333333 0.3333333 0.3333333

Ejercicios de Tarea.

Ejercicio 1

Para el primer ejercicio se pide determinar las clases comunicantes, diagrama de comunicación, los estados cerrados, recurrentes y transitivos y el periodo de varias cadenas de markov dadas por una matriz de trancisión distinta.

En este caso las funciones que se utilizarán son: - communicatingClasses() para obtener las clases comunicantes. - summary() para obtener los estados cerrados, recurrentes y transitivos. - period() para obtener el periodo. - plot() para obtener el diagrama.

Para no estar repitiendo los mismos comandos una y otra vez, conviene definir una función que nos permita obtener toda esta información de las distintas CM.

ejercicio1 <- function(P){
    cm <- as(P, "markovchain")
    print("The communicating classes are:")
    show(communicatingClasses(cm))
    summary(cm)
    if (is.irreducible(cm)){
        print("The period is:")
        show(period(cm))
    }

    plot(cm, edge.arrow.size = 0.75,
        vertex.size = 20, edge.label.cex = 1,
        vertex.label.cex = 1)
}

A continuación se muestran los ejercicios y su solución.

Soluciones

1.1

\[ P = \begin{bmatrix} 1 & 0 & 0 & 0\\ 0.5 & 0 & 0.5 & 0\\ 0 & 0.3 & 0.7 & 0\\ 0.2 & 0 & 0.8 & 0 \end{bmatrix} \]

P1 <- matrix(c(1, 0, 0, 0,
                0.5, 0, 0.5, 0,
                0, 0.3, 0.7, 0,
                0.2, 0, 0.8, 0),
                nrow = 4,
                byrow = T,
                dimnames = list(letters[1:4], letters[1:4]))
ejercicio1(P1)
## [1] "The communicating classes are:"
## [[1]]
## [1] "a"
## 
## [[2]]
## [1] "b" "c"
## 
## [[3]]
## [1] "d"
## 
## Unnamed Markov chain  Markov chain that is composed by: 
## Closed classes: 
## a 
## Recurrent classes: 
## {a}
## Transient classes: 
## {b,c},{d}
## The Markov chain is not irreducible 
## The absorbing states are: a

1.2

\[P = \begin{bmatrix} 0.3 & 0 & 0.7 \\ 0.4 & 0.6 & 0 \\ 0.5 & 0 & 0.5 \end{bmatrix} \]

P2 <- matrix(c(0.3, 0, 0.7,
                0.4, 0.6, 0,
                0.5, 0, 0.5),
                nrow = 3,
                byrow = T,
                dimnames = list(letters[1:3], letters[1:3]))
ejercicio1(P2)
## [1] "The communicating classes are:"
## [[1]]
## [1] "a" "c"
## 
## [[2]]
## [1] "b"
## 
## Unnamed Markov chain  Markov chain that is composed by: 
## Closed classes: 
## a c 
## Recurrent classes: 
## {a,c}
## Transient classes: 
## {b}
## The Markov chain is not irreducible 
## The absorbing states are: NONE

1.3

\[P = \begin{bmatrix} 0 & 0.2 & 0 & 0.8 \\ 0.4 & 0.6 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0.3 & 0.7 \end{bmatrix} \]

P3 <- matrix(c(0, 0.2, 0, 0.8,
                0.4, 0.6, 0, 0,
                0, 0, 1, 0,
                0, 0, 0.3, 0.7),
                nrow = 4,
                byrow = T,
                dimnames = list(letters[1:4], letters[1:4]))
ejercicio1(P3)
## [1] "The communicating classes are:"
## [[1]]
## [1] "a" "b"
## 
## [[2]]
## [1] "c"
## 
## [[3]]
## [1] "d"
## 
## Unnamed Markov chain  Markov chain that is composed by: 
## Closed classes: 
## c 
## Recurrent classes: 
## {c}
## Transient classes: 
## {a,b},{d}
## The Markov chain is not irreducible 
## The absorbing states are: c

1.4

\[P = \begin{bmatrix} 0.25 & 0.25 & 0.25 & 0.25 \\ 0 & 0.5 & 0.5 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \end{bmatrix} \]

P4 <- matrix(c(0.25, 0.25, 0.25, 0.25,
                0, 0.5, 0.5, 0,
                0, 0, 0, 1,
                0, 0, 1, 0),
                nrow = 4,
                byrow = T,
                dimnames = list(letters[1:4], letters[1:4]))

ejercicio1(P4)
## [1] "The communicating classes are:"
## [[1]]
## [1] "a"
## 
## [[2]]
## [1] "b"
## 
## [[3]]
## [1] "c" "d"
## 
## Unnamed Markov chain  Markov chain that is composed by: 
## Closed classes: 
## c d 
## Recurrent classes: 
## {c,d}
## Transient classes: 
## {a},{b}
## The Markov chain is not irreducible 
## The absorbing states are: NONE

1.5

\[P = \begin{bmatrix} 0 & 0 & 0 & 0 & 1\\ 0 & 0 & 1 & 0 & 0\\ 0 & 0 & 0 & 0.5 & 0.5\\ 0.2 & 0.2 & 0.2 & 0.2 & 0.2 \end{bmatrix}\]

P5 <- matrix(c(0, 0, 0, 0, 1,
                0, 0, 1, 0, 0,
                0, 0, 0, 0.5, 0.5,
                0, 0, 1, 0, 0,
                0.2, 0.2, 0.2, 0.2, 0.2),
                nrow = 5,
                byrow = T,
                dimnames = list(letters[1:5], letters[1:5]))

ejercicio1(P5)
## [1] "The communicating classes are:"
## [[1]]
## [1] "a" "b" "c" "d" "e"
## 
## Unnamed Markov chain  Markov chain that is composed by: 
## Closed classes: 
## a b c d e 
## Recurrent classes: 
## {a,b,c,d,e}
## Transient classes: 
## NONE 
## The Markov chain is irreducible 
## The absorbing states are: NONE
## [1] "The period is:"
## [1] 1

Ejercicio 2

Suponga que un mexicano puede estar en 1 de 4 estados: Rico, Clase media, Pobre o Pobreza extrema. Suponga las siquientes probabilidades de transición:

  • Si un mexicano es Rico, su siguiente generación será:
    • Rico: \(0.0\)
    • Clase media: \(0.75\)
    • Pobre: \(0.2\)
    • Pobreza extrema: \(0.05\)
  • Si un mexicano es de Clase media, su siguiente generación será:
    • Rico: \(0.05\)
    • Clase media: \(0.2\)
    • Pobre: \(0.3\)
    • Pobreza extrema: \(0.45\)
  • Si un mexicano es Pobre, su siguiente generación será:
    • Rico: \(0.1\)
    • Clase media: \(0.4\)
    • Pobre: \(0.3\)
    • Pobreza extrema: \(0.2\)
  • Si un mexicano está en Pobreza extrema, su siguiente generación será:
    • Rico: \(0.27\)
    • Clase media: \(0.15\)
    • Pobre: \(0.03\)
    • Pobreza extrema: \(0.55\)

Solución

Matriz de transición en 1 paso

\[P = \begin{bmatrix} 0 & 0.75 & 0.2 & 0.05\\ 0.05 & 0.2 & 0.3 & 0.45\\ 0.1 & 0.4 & 0.3 & 0.2\\ 0.27 & 0.15 & 0.03 & 0.55 \end{bmatrix}\]

S <- c("Rico", "Clase Media", "Pobre", "Pobreza extrema");
P <- matrix(c(0, 0.75, 0.2, 0.05,
            0.05, 0.2, 0.3, 0.45,
            0.1, 0.4, 0.3, 0.2,
            0.27, 0.15, 0.03, 0.55),
            nrow = 4,
            byrow = T,
            dimnames = list(S, S));

CM <- new("markovchain", transitionMatrix = P, name = "CM de Riqueza en México", byrow = T);

Probabilidad de pasar de Clase Media a Rico.

Después de una generación:

transitionProbability(CM, "Clase Media", "Rico")
## [1] 0.05

Después de dos generación:

transitionProbability(CM^2, "Clase Media", "Rico")
## [1] 0.1615

Después de tres generación:

transitionProbability(CM^3, "Clase Media", "Rico")
## [1] 0.1386

Probabilidad de pasar de Pob. extrema a Clase Media.

Después de una generación:

transitionProbability(CM, "Pobreza extrema", "Clase Media")
## [1] 0.15

Después de dos generación:

transitionProbability(CM^2, "Pobreza extrema", "Clase Media")
## [1] 0.327

Después de tres generación:

transitionProbability(CM^3, "Pobreza extrema", "Clase Media")
## [1] 0.292875

Distribución invariante

La distribución invariante de esta CM es:

steadyStates(CM)
##           Rico Clase Media     Pobre Pobreza extrema
## [1,] 0.1376442   0.2925528 0.1813545       0.3884486

Diagrama de comunicación