Introducción

Este laboratorio lleva a R el capítulo 4 de Oana, Schneider y Thomann (2021), que es el corazón de QCA: el análisis de suficiencia. Seguimos el orden del libro y usamos sus mismos ejemplos, con el script que los autores publicaron como referencia. Arrancamos con una condición a la vez. Después armamos la tabla de verdad y la minimizamos. Cerramos con el problema de la diversidad limitada, que el libro resuelve con el análisis estándar (SA) y el análisis estándar mejorado (ESA).

Necesitan los paquetes QCA y SetMethods. Si no los tienen:

install.packages("SetMethods")   # instala también QCA como dependencia
library(QCA)
library(SetMethods)

Los datos de la coach Susan

Todo el capítulo gira alrededor de una pregunta: ¿por qué algunos jugadores del equipo de la coach Susan llegan a tiempo al entrenamiento y otros no? El resultado es llegar a tiempo (Y). Las condiciones cambian de sección en sección, pero siempre salen de esta lista:

El libro usa tres versiones de los datos. Para la suficiencia de una sola condición trabaja con equipos pequeños e hipotéticos (SF1 a SF4). Para la tabla de verdad usa 13 jugadores con tres condiciones (SF5). Para la diversidad limitada usa 131 jugadores con las cinco condiciones, que vienen en SetMethods con el nombre STUF. Vamos a construir cada uno cuando lo necesitemos.

Resumen de lo que hacemos en este laboratorio

Evaluamos condiciones suficientes una por una con xy.plot() y QCAfit(), cuidando la consistencia, los casos DCK, el PRI y la cobertura. Construimos una tabla de verdad con truthTable() y la minimizamos con minimize(). Leímos la solución en los casos con pimplot() y vimos que a veces hay más de un modelo. Con los 131 jugadores aparecieron los remanentes lógicos: el análisis estándar da tres soluciones y el ESA, con LR.intersect() y esa(), bloquea los supuestos que contradicen lo que afirmamos en otras partes del análisis.

Una condición suficiente a la vez

Una condición \(X\) es suficiente para \(Y\) si siempre que aparece \(X\) aparece también \(Y\). En conjuntos, \(X\) es un subconjunto de \(Y\) (\(X \subseteq Y\)), y lo escribimos \(X \rightarrow Y\). Con conjuntos difusos eso quiere decir que la pertenencia de cada caso en \(X\) es menor o igual a su pertenencia en \(Y\).

El gráfico XY para suficiencia

Empezamos con siete jugadores. X es vivir cerca y Y es llegar a tiempo.

Name <- c("Amanda", "Bob", "Carl", "Cecilia", "Ana", "Alex", "Kim")
X <- c(0.6, 0.6, 0.2, 0.1, 0.9, 0.7, 0.3)
Y <- c(0.7, 0.6, 0.9, 0.3, 1, 0.8, 0.4)
SF1 <- data.frame(X, Y)
row.names(SF1) <- Name

La función xy.plot() de SetMethods pone la condición en el eje horizontal y el resultado en el vertical. El argumento clave es necessity = FALSE: le dice a R que evalúe suficiencia y que calcule los parámetros que corresponden.

xy.plot("X", "Y",
        data = SF1,
        main = "XY plot - suficiencia",
        ylab = "Llega a tiempo",
        xlab = "Vive cerca",
        necessity = FALSE,
        fontsize = 6)

Todos los jugadores quedan sobre o encima de la diagonal: ninguno vive más cerca de lo que llega a tiempo. Esa es la huella de la suficiencia. Fíjense que en necesidad buscábamos lo contrario, casos debajo de la diagonal.

Consistencia y casos desviados

Agregamos tres jugadores a SF1: Mark, Dee y Fiona.

SF2 <- rbind(SF1, c(0.75, 0.65), c(0.55, 0.45), c(0.35, 0.25))
row.names(SF2)[8:10] <- c("Mark", "Dee", "Fiona")

xy.plot("X", "Y",
        data = SF2,
        main = "XY plot - suficiencia",
        ylab = "Llega a tiempo",
        xlab = "Vive cerca",
        necessity = FALSE,
        fontsize = 6)

Los tres nuevos quedan debajo de la diagonal. La consistencia de suficiencia resume cuánto se aparta el patrón de un subconjunto perfecto:

\[\text{Cons}_{suf} = \frac{\sum \min(X_i, Y_i)}{\sum X_i}\]

El numerador es el mismo que en necesidad. Cambia el denominador: ahora sumamos la condición. QCAfit() la calcula si le pedimos necessity = FALSE:

QCAfit(SF2$X, SF2$Y, cond.lab = "X", necessity = FALSE)
## Cons.Suf  Cov.Suf      PRI 
##    0.941    0.785    0.885

Una consistencia de 0.941 está muy por encima del umbral usual de 0.8 (o 0.75). Pero el número solo no basta. Los tres casos se alejan de la diagonal por la misma distancia (0.1) y aun así Dee pesa más en contra. Dee vive cerca (\(X > 0.5\)) y llega tarde (\(Y < 0.5\)): es un caso desviado de consistencia en tipo (DCK). Mark y Fiona tienen \(X\) y \(Y\) del mismo lado de 0.5, así que solo se desvían en grado (DCD).

Relaciones de subconjunto simultáneas y PRI

Con conjuntos difusos, una misma condición puede verse suficiente para \(Y\) y para \(\sim Y\) al mismo tiempo. El libro lo muestra con un equipo donde casi nadie vive cerca.

Name <- c("Dan", "Claire", "Amanda", "Bob", "Carl", "Cecilia",
          "Ana", "Mark", "Jim", "Joe")
X <- c(0.45, 0.2, 0.1, 0.2, 0.1, 0.4, 0.6, 0.25, 0.65, 0.6)
Y <- c(0.55, 0.9, 0.7, 0.4, 0.3, 0.3, 0.7, 0.1, 0.65, 0.35)
SF3 <- data.frame(X, Y)
row.names(SF3) <- Name

Graficamos X contra Y y luego contra ~Y. En xy.plot() basta con escribir la tilde delante del nombre del resultado.

xy.plot("X", "Y",
        data = SF3,
        main = "Resultado Y",
        ylab = "Llega a tiempo",
        xlab = "Vive cerca",
        necessity = FALSE,
        fontsize = 6)

xy.plot("X", "~Y",
        data = SF3,
        main = "Resultado ~Y",
        ylab = "NO llega a tiempo",
        xlab = "Vive cerca",
        necessity = FALSE,
        fontsize = 6)

Los dos gráficos se parecen: como X es un conjunto tan chico, casi todos los casos quedan arriba de la diagonal en ambos. Veamos los números. Para ~Y le pasamos a QCAfit() el complemento, 1 - SF3$Y:

QCAfit(SF3$X, SF3$Y, cond.lab = "X -> Y", necessity = FALSE)
## Cons.Suf  Cov.Suf      PRI 
##    0.859    0.616    0.583
QCAfit(SF3$X, 1 - SF3$Y, cond.lab = "X -> ~Y", necessity = FALSE)
## Cons.Suf  Cov.Suf      PRI 
##    0.803    0.564    0.417

Las dos consistencias pasan 0.8. Afirmar que vivir cerca basta para llegar a tiempo y también para llegar tarde sería absurdo. El PRI (reducción proporcional de la inconsistencia) resuelve el empate:

\[\text{PRI} = \frac{\sum \min(X_i, Y_i) - \sum \min(X_i, Y_i, \sim Y_i)}{\sum X_i - \sum \min(X_i, Y_i, \sim Y_i)}\]

Para Y el PRI es 0.583 y para ~Y es 0.417. Sumen los dos: dan 1. El PRI es simétrico, así que mientras más se acerca a 0.5, menos podemos decidir. Una condición con PRI menor a 0.5 no debe declararse suficiente. Los casos cuentan la misma historia: para X -> Y hay un DCK (Joe) y para X -> ~Y hay dos (Ana y Jim).

Cobertura

Una vez que una condición pasa consistencia, casos DCK y PRI, preguntamos cuánto del resultado explica. Esa es la cobertura de suficiencia:

\[\text{Cov}_{suf} = \frac{\sum \min(X_i, Y_i)}{\sum Y_i}\]

Comparamos vivir cerca (SF3) con llegar en bicicleta (SF4). El resultado es el mismo en los dos casos.

X4 <- c(0, 0, 0.1, 0.05, 0.1, 0, 0.6, 0.25, 0.45, 0.1)
Y4 <- c(0.55, 0.9, 0.7, 0.4, 0.3, 0.3, 0.7, 0.1, 0.65, 0.35)
SF4 <- data.frame(X4, Y4)
row.names(SF4) <- Name
xy.plot("X", "Y",
        data = SF3,
        main = "Cobertura alta",
        ylab = "Llega a tiempo",
        xlab = "Vive cerca",
        necessity = FALSE,
        fontsize = 6)

xy.plot("X4", "Y4",
        data = SF4,
        main = "Cobertura baja",
        ylab = "Llega a tiempo",
        xlab = "Llega en bicicleta",
        necessity = FALSE,
        fontsize = 6)

QCAfit(SF3$X, SF3$Y, cond.lab = "Vive cerca", necessity = FALSE)
## Cons.Suf  Cov.Suf      PRI 
##    0.859    0.616    0.583
QCAfit(SF4$X4, SF4$Y4, cond.lab = "Bicicleta", necessity = FALSE)
## Cons.Suf  Cov.Suf      PRI 
##    0.909    0.303    0.727

La bicicleta es más consistente (0.909) pero cubre mucho menos (0.303 frente a 0.616). En el gráfico se nota porque los casos quedan lejos, arriba de la diagonal. Los más problemáticos son los de la esquina superior izquierda (\(X < 0.5\), \(Y > 0.5\)): Claire, Amanda, Dan y Jim llegan a tiempo, pero no en bicicleta. Son casos desviados de cobertura, puntualidad que esta condición no explica.

Con los 131 jugadores: QCAfit()

Ahora cargamos los datos grandes del libro. STUF trae las columnas con nombres genéricos, así que las renombramos una vez con las letras del capítulo. Así los nombres coinciden con los del resto del laboratorio.

data("STUF")
names(STUF) <- c("X", "A", "C", "D", "E", "Y")
head(STUF)

QCAfit() funciona igual que en necesidad. Para una condición:

QCAfit(STUF$X, STUF$Y, cond.lab = "X", necessity = FALSE)
## Cons.Suf  Cov.Suf      PRI 
##    0.800    0.885    0.724

Si le pasamos varias columnas, evalúa cada condición y su negación:

QCAfit(x = STUF[, 1:5], y = STUF$Y, necessity = FALSE)
##    Cons.Suf Cov.Suf    PRI
## X    0.8000  0.8853 0.7244
## A    0.7606  0.8577 0.6864
## C    0.7565  0.7896 0.6831
## D    0.3827  0.4333 0.1888
## E    0.7994  0.8975 0.7259
## ~X   0.2797  0.3382 0.0677
## ~A   0.2876  0.3417 0.0599
## ~C   0.3005  0.3822 0.0982
## ~D   0.5947  0.7039 0.5013
## ~E   0.2846  0.3396 0.0537

Ninguna condición sola destaca. X queda justo en 0.8 y E se queda un milésimo abajo. Comparen X con su negación:

xy.plot(x = "X", y = "Y",
        data = STUF,
        necessity = FALSE,
        jitter = TRUE,
        xlab = "X", ylab = "Y",
        main = "Suficiencia de X para Y")

xy.plot(x = "~X", y = "Y",
        data = STUF,
        necessity = FALSE,
        jitter = TRUE,
        xlab = "~X", ylab = "Y",
        main = "Suficiencia de ~X para Y")

Con 131 casos, jitter = TRUE mueve un poco las etiquetas para que no se encimen. Que ninguna condición alcance por sí sola nos empuja a mirar combinaciones de condiciones. Para eso está la tabla de verdad.

La tabla de verdad

Los datos: 13 jugadores

Susan junta datos de 13 jugadores nuevos con tres condiciones: vivir cerca (X), bicicleta (A) y familia puntual (C). Es la tabla 4.3 del libro.

Name <- c("Amanda", "Bob", "Carl", "Cecilia", "Ana", "Alex", "Mark",
          "Dee", "Fiona", "Kim", "Joe", "Jess", "Frank")
X <- c(0.6, 0.3, 0.8, 0, 0.9, 0.7, 0.75, 0.55, 0.35, 0.4, 0.4, 0.45, 0.6)
A <- c(0.8, 0.7, 1, 0.6, 0.1, 0.2, 0.7, 0.3, 0.7, 0.9, 0.4, 0.45, 0.35)
C <- c(0.7, 0.7, 0.3, 0.2, 0.2, 0.4, 0.6, 0.3, 0.2, 0.6, 0.4, 0.55, 0.6)
Y <- c(0.7, 0.8, 0.9, 0.3, 1, 0.8, 0.65, 0.45, 0.75, 0.6, 0.2, 0.2, 0.2)
SF5 <- data.frame(X, A, C, Y)
row.names(SF5) <- Name

Construir la tabla con truthTable()

Todo lo que hicimos a mano en la sesión teórica (escribir las \(2^3 = 8\) filas, asignar cada caso a la fila donde tiene pertenencia mayor a 0.5, decidir qué filas son suficientes) lo hace truthTable() del paquete QCA en un solo paso.

TT_y <- truthTable(data = SF5,
                   outcome = "Y",
                   conditions = c("X", "A", "C"),
                   incl.cut = 0.86,
                   pri.cut = 0.51,
                   n.cut = 1,
                   sort.by = "incl",
                   show.cases = TRUE)
TT_y
## 
##   OUT: output value
##     n: number of cases in configuration
##  incl: sufficiency inclusion score
##   PRI: proportional reduction in inconsistency
## 
##     X  A  C    OUT    n  incl  PRI   cases        
## 4   0  1  1     1     2  0.862 0.625 Bob,Kim      
## 7   1  1  0     0     1  0.859 0.613 Carl         
## 8   1  1  1     0     2  0.857 0.613 Amanda,Mark  
## 5   1  0  0     0     3  0.830 0.643 Ana,Alex,Dee 
## 3   0  1  0     0     2  0.800 0.438 Cecilia,Fiona
## 2   0  0  1     0     1  0.773 0.286 Jess         
## 6   1  0  1     0     1  0.754 0.370 Frank        
## 1   0  0  0     0     1  0.753 0.269 Joe

Cómo leer la salida:

  • La primera columna es el número de fila. R numera en binario: la fila 1 es 000, la fila 8 es 111. Esta numeración no coincide con la de la tabla 4.6 del libro, que empieza por 111.
  • OUT dice si la fila es suficiente (1) o no (0).
  • n cuenta los casos en la fila. incl es la consistencia y PRI el PRI.

Cuatro filas son suficientes para llegar a tiempo. Miren la fila 3 (~X*A*~C): su consistencia es exactamente 0.800, pero su PRI (0.438) queda debajo de 0.51. R le asigna OUT = 0.

Casos desviados en cada fila

Con dcc = TRUE en vez de show.cases = TRUE, la tabla muestra solo los casos desviados de consistencia en tipo de cada fila:

truthTable(data = SF5,
           outcome = "Y",
           conditions = c("X", "A", "C"),
           incl.cut = 0.8,
           pri.cut = 0.51,
           sort.by = "incl",
           dcc = TRUE)
## 
##   OUT: output value
##     n: number of cases in configuration
##  incl: sufficiency inclusion score
##   PRI: proportional reduction in inconsistency
## 
##     X  A  C    OUT    n  incl  PRI  
## 4   0  1  1     1     2  0.862 0.625
## 7   1  1  0     1     1  0.859 0.613
## 8   1  1  1     1     2  0.857 0.613
## 5   1  0  0     1     3  0.830 0.643
## 3   0  1  0     0     2  0.800 0.438
## 2   0  0  1     0     1  0.773 0.286
## 6   1  0  1     0     1  0.754 0.370
## 1   0  0  0     0     1  0.753 0.269

Dee aparece en la fila 5 (X*~A*~C). Esa fila entra como suficiente con un DCK de tres casos. Cecilia es el DCK de la fila 3, la que acabamos de dejar fuera.

Guardar la tabla

Para exportar la tabla a un archivo, el libro sugiere dos caminos:

write.csv(TT_y$tt, "myTT.csv")                     # hoja de cálculo
stargazerTT(truthtable = TT_y, show.cases = TRUE,
            type = "text", out = "myTT.txt")       # texto, LaTeX o HTML

Minimización lógica

minimize()

La tabla nos dice que cuatro combinaciones bastan para llegar a tiempo. Escrita tal cual, la respuesta es larga y redundante. minimize() aplica la regla de una diferencia y elimina los implicantes primos que sobran.

sol_y <- minimize(input = TT_y,
                  details = TRUE)
sol_y
## 
## M1: ~X*A*C -> Y
## 
##            inclS   PRI   covS   covU   cases 
## ---------------------------------------------- 
## 1  ~X*A*C  0.862  0.625  0.497    -    Bob,Kim 
## ---------------------------------------------- 
##        M1  0.862  0.625  0.497

La solución es X*~C + A*C -> Y: llega a tiempo quien vive cerca y no viene de familia puntual, o quien llega en bicicleta y sí viene de familia puntual. Para cada término, R reporta:

  • inclS: consistencia de suficiencia.
  • PRI.
  • covS: cobertura bruta.
  • covU: cobertura única, la parte del resultado que solo ese término explica.
  • cases: casos con pertenencia mayor a 0.5 en el término. La coma separa casos de la misma fila y el punto y coma separa filas distintas.

La última línea (M1) da los parámetros de la solución completa. La cobertura de 0.834 nos dice que queda algo de puntualidad sin explicar.

La tabla de implicantes primos

minimize() guarda la tabla de implicantes primos dentro del objeto:

sol_y$PIchart
## 
##          4 
## ~X*A*C   x

Las columnas son las filas suficientes de la tabla de verdad. Las filas son los implicantes primos. X*A cubre las filas 7 y 8, pero X*~C ya cubre la 7 y A*C ya cubre la 8. Por eso X*A es redundante y no aparece en la solución.

Ver la solución en gráficos XY

QCA es un método orientado a casos, y la solución hay que devolverla a los casos. pimplot() de SetMethods hace gráficos XY de filas de la tabla, de cada término suficiente y de la solución completa. Necesita los datos, el resultado y el objeto que produjo minimize().

Primero, la fila 5 (X*~A*~C), que el libro usa para ilustrar el paso 4 del algoritmo:

pimplot(data = SF5,
        outcome = "Y",
        results = sol_y,
        all_labels = TRUE,
        ttrows = "5",
        jitter = TRUE)

Ahora la fila 3 (~X*A*~C), la que quedó fuera por su PRI:

pimplot(data = SF5,
        outcome = "Y",
        results = sol_y,
        all_labels = TRUE,
        ttrows = "3",
        jitter = TRUE,
        fontsize = 6)

Fiona confirma la suficiencia de esa fila y Cecilia la contradice. La decisión de dejarla fuera se sostiene.

Sin ttrows, pimplot() grafica cada término y la solución completa:

pimplot(data = SF5,
        outcome = "Y",
        results = sol_y,
        all_labels = TRUE,
        jitter = TRUE,
        fontsize = 6)

En los gráficos buscamos tres tipos de casos:

  • Típicos: arriba de 0.5 en el término y en el resultado. Amanda, Bob, Mark y Kim para A*C. Alex, Ana y Carl para X*~C.
  • Desviados de consistencia en tipo: arriba de 0.5 en el término, debajo en el resultado. Dee, en X*~C.
  • Desviados de cobertura: en la esquina superior izquierda del gráfico de la solución. Fiona llega a tiempo por razones que ninguno de los dos términos captura.

Ambigüedad de modelos

A veces la minimización produce más de una fórmula igual de corta. El libro lo ilustra con 47 jugadores de varios equipos y el resultado no llegar a tiempo (~Y), en la tabla 4.9. Los datos originales de esos 47 jugadores no vienen en SetMethods. Reconstruimos solo lo que la minimización necesita: las ocho combinaciones de X, A y C, cada una con el OUT de la tabla 4.9.

TT47 <- data.frame(
  X = c(0, 0, 0, 0, 1, 1, 1, 1),
  A = c(0, 0, 1, 1, 0, 0, 1, 1),
  C = c(0, 1, 0, 1, 0, 1, 0, 1),
  Y = c(0, 0, 0, 1, 1, 0, 0, 1)   # Y = 0 en las filas suficientes para ~Y
)

tt_ny <- truthTable(TT47,
                    outcome = "~Y",
                    conditions = c("X", "A", "C"),
                    incl.cut = 0.8)
tt_ny
## 
##   OUT: output value
##     n: number of cases in configuration
##  incl: sufficiency inclusion score
##   PRI: proportional reduction in inconsistency
## 
##     X  A  C    OUT    n  incl  PRI  
## 1   0  0  0     1     1  1.000 1.000
## 2   0  0  1     1     1  1.000 1.000
## 3   0  1  0     1     1  1.000 1.000
## 4   0  1  1     0     1  0.000 0.000
## 5   1  0  0     0     1  0.000 0.000
## 6   1  0  1     1     1  1.000 1.000
## 7   1  1  0     1     1  1.000 1.000
## 8   1  1  1     0     1  0.000 0.000

Como cada fila tiene un solo caso crisp, las consistencias salen 1 o 0. Los valores de incl y PRI no son los del libro. Las filas suficientes (1, 2, 3, 6 y 7) sí lo son, y eso es lo que decide la solución.

sol_ny <- minimize(tt_ny)
sol_ny
## 
## M1: ~A*C + A*~C + (~X*~A) <-> ~Y 
## M2: ~A*C + A*~C + (~X*~C) <-> ~Y

R devuelve dos modelos, M1 y M2. Coinciden en ~A*C y A*~C. El término entre paréntesis es el que cambia: ~X*~A en uno, ~X*~C en el otro. La flecha doble (<->) aparece porque en nuestra reconstrucción la solución cubre todo el resultado sin inconsistencias; con los datos del libro la flecha es simple.

sol_ny$PIchart
## 
##         1  2  3  6  7 
## ~X*~A   x  x  -  -  - 
## ~X*~C   x  -  x  -  - 
## ~A*C    -  x  -  x  - 
## A*~C    -  -  x  -  x

~X*~A cubre las expresiones 1 y 2; ~X*~C cubre la 1 y la 3. La 2 ya la cubre ~A*C y la 3 ya la cubre A*~C. Cualquiera de los dos sirve y ninguno hace falta junto al otro. Los datos no pueden decidir entre M1 y M2: esa decisión viene de la teoría o de evidencia dentro de los casos. Y la ambigüedad siempre se reporta.

Diversidad limitada: el análisis estándar

131 jugadores y cinco condiciones

Volvemos a STUF. Susan y otros entrenadores quieren explicar ahora por qué algunos jugadores llegan tarde (~Y). Con cinco condiciones hay \(2^5 = 32\) filas posibles.

conds <- c("X", "A", "C", "D", "E")
TT_ny <- truthTable(data = STUF,
                    outcome = "~Y",
                    conditions = conds,
                    incl.cut = 0.8,
                    n.cut = 2,
                    pri.cut = 0.5,
                    sort.by = c("OUT", "incl"),
                    complete = TRUE)
TT_ny
## 
##   OUT: output value
##     n: number of cases in configuration
##  incl: sufficiency inclusion score
##   PRI: proportional reduction in inconsistency
## 
##      X  A  C  D  E    OUT    n   incl  PRI  
## 19   1  0  0  1  0     1     2   0.960 0.827
##  5   0  0  1  0  0     1     4   0.954 0.892
## 11   0  1  0  1  0     1     7   0.954 0.908
## 25   1  1  0  0  0     1     2   0.953 0.803
##  3   0  0  0  1  0     1     30  0.951 0.929
##  1   0  0  0  0  0     1     16  0.950 0.926
##  7   0  0  1  1  0     1     2   0.947 0.849
## 20   1  0  0  1  1     1     2   0.946 0.691
## 22   1  0  1  0  1     1     2   0.935 0.791
##  4   0  0  0  1  1     1     4   0.934 0.781
## 12   0  1  0  1  1     1     6   0.912 0.752
## 24   1  0  1  1  1     0     2   0.894 0.490
## 28   1  1  0  1  1     0     4   0.806 0.405
## 32   1  1  1  1  1     0     5   0.650 0.175
## 30   1  1  1  0  1     0     36  0.285 0.055
## 17   1  0  0  0  0     ?     1   0.969 0.890
## 21   1  0  1  0  0     ?     1   0.963 0.855
## 13   0  1  1  0  0     ?     1   0.950 0.752
## 15   0  1  1  1  0     ?     1   0.940 0.755
##  6   0  0  1  0  1     ?     1   0.927 0.772
## 26   1  1  0  0  1     ?     1   0.910 0.611
## 14   0  1  1  0  1     ?     1   0.898 0.565
##  2   0  0  0  0  1     ?     0     -     -  
##  8   0  0  1  1  1     ?     0     -     -  
##  9   0  1  0  0  0     ?     0     -     -  
## 10   0  1  0  0  1     ?     0     -     -  
## 16   0  1  1  1  1     ?     0     -     -  
## 18   1  0  0  0  1     ?     0     -     -  
## 23   1  0  1  1  0     ?     0     -     -  
## 27   1  1  0  1  0     ?     0     -     -  
## 29   1  1  1  0  0     ?     0     -     -  
## 31   1  1  1  1  0     ?     0     -     -

Dos argumentos nuevos:

  • n.cut = 2: con 131 jugadores, los entrenadores exigen al menos dos casos por fila. Una fila con un solo caso pasa a ser remanente.
  • complete = TRUE: imprime también las filas sin casos.

Las filas con OUT = ? son remanentes lógicos. Hay 17: siete con un caso y diez sin ninguno. No sabemos si esas combinaciones bastarían para llegar tarde. El análisis estándar produce tres soluciones que se distinguen por qué hacen con esos remanentes.

Solución conservadora

sol_nyc <- minimize(TT_ny,
                    details = TRUE)
sol_nyc
## 
## M1: ~X*~A*~E + ~X*~C*D + ~A*~C*D + X*~A*C*~D*E + X*A*~C*~D*~E -> ~Y
## 
##                  inclS   PRI   covS   covU  
## ------------------------------------------- 
## 1      ~X*~A*~E  0.942  0.922  0.674  0.182 
## 2       ~X*~C*D  0.913  0.881  0.541  0.065 
## 3       ~A*~C*D  0.905  0.862  0.504  0.020 
## 4   X*~A*C*~D*E  0.935  0.791  0.125  0.022 
## 5  X*A*~C*~D*~E  0.953  0.803  0.114  0.008 
## ------------------------------------------- 
##              M1  0.900  0.868  0.823

Cinco caminos para llegar tarde, algunos con cinco condiciones. Es la descripción más detallada y la más difícil de interpretar. Guarden el término 5 (X*A*~C*~D*~E): contiene X*A y va a volver en la sección 7.

Solución más parsimoniosa y supuestos simplificadores

Con include = "?", R puede tratar como suficiente cualquier remanente que ayude a simplificar. De los 17 remanentes, R elige solo los que acortan la fórmula.

sol_nyp <- minimize(TT_ny,
                    details = TRUE,
                    include = "?",
                    row.dom = TRUE)
sol_nyp
## 
## M1: ~X + ~E + ~A*~C + ~A*~D -> ~Y
## 
##           inclS   PRI   covS   covU  
## ------------------------------------ 
## 1     ~X  0.905  0.877  0.832  0.033 
## 2     ~E  0.914  0.886  0.829  0.024 
## 3  ~A*~C  0.905  0.870  0.676  0.007 
## 4  ~A*~D  0.927  0.897  0.398  0.021 
## ------------------------------------ 
##       M1  0.875  0.839  0.929

row.dom = TRUE evita aquí la ambigüedad de modelos; no tiene que ver con los remanentes. Los remanentes que R usó son los supuestos simplificadores:

sol_nyp$SA
## $M1
##    X A C D E
## 2  0 0 0 0 1
## 6  0 0 1 0 1
## 8  0 0 1 1 1
## 9  0 1 0 0 0
## 10 0 1 0 0 1
## 13 0 1 1 0 0
## 14 0 1 1 0 1
## 15 0 1 1 1 0
## 16 0 1 1 1 1
## 17 1 0 0 0 0
## 18 1 0 0 0 1
## 21 1 0 1 0 0
## 23 1 0 1 1 0
## 27 1 1 0 1 0
## 29 1 1 1 0 0
## 31 1 1 1 1 0

Dieciséis supuestos. Cada uno afirma que un tipo de jugador que no observamos llegaría tarde.

Solución intermedia y contrafácticos fáciles

La solución intermedia usa solo los supuestos simplificadores que coinciden con nuestras expectativas direccionales. Los entrenadores esperan que todas las condiciones contribuyan a llegar tarde cuando están ausentes, salvo D: creen que los jugadores con experiencia llegan tarde porque saben que no hay sanciones. Eso se escribe en dir.exp:

sol_nyi <- minimize(TT_ny,
                    details = TRUE,
                    include = "?",
                    dir.exp = "~X, ~A, ~C, ~D, ~E",
                    row.dom = TRUE)
sol_nyi
## 
## From C1P1: 
## 
## M1:    ~X*~C + ~A*~C + ~A*~D + ~X*~A*~E + ~C*~D*~E -> ~Y 
## 
##              inclS   PRI   covS   covU  
## --------------------------------------- 
## 1     ~X*~C  0.911  0.884  0.730  0.089 
## 2     ~A*~C  0.905  0.870  0.676  0.022 
## 3     ~A*~D  0.927  0.897  0.398  0.043 
## 4  ~X*~A*~E  0.942  0.922  0.674  0.018 
## 5  ~C*~D*~E  0.937  0.904  0.312  0.011 
## --------------------------------------- 
##          M1  0.892  0.860  0.889

Los supuestos que pasaron el filtro son los contrafácticos fáciles:

sol_nyi$i.sol$C1P1$EC

Siete de los 16. C1P1 es el nombre que R le da a la combinación de la solución conservadora 1 con la parsimoniosa 1. Las tres soluciones están en una relación de subconjuntos: la conservadora está dentro de la intermedia, y la intermedia dentro de la más parsimoniosa.

El resultado Y

El análisis de suficiencia es asimétrico, así que Y y ~Y se analizan por separado. Necesitamos las soluciones para Y antes de pasar al ESA.

TT_y <- truthTable(data = STUF,
                   outcome = "Y",
                   conditions = conds,
                   incl.cut = 0.8,
                   n.cut = 2,
                   pri.cut = 0.5,
                   sort.by = c("OUT", "incl"),
                   complete = TRUE,
                   show.cases = FALSE)
TT_y
## 
##   OUT: output value
##     n: number of cases in configuration
##  incl: sufficiency inclusion score
##   PRI: proportional reduction in inconsistency
## 
##      X  A  C  D  E    OUT    n   incl  PRI  
## 30   1  1  1  0  1     1     36  0.929 0.906
## 32   1  1  1  1  1     1     5   0.925 0.824
## 28   1  1  0  1  1     1     4   0.846 0.525
## 24   1  0  1  1  1     0     2   0.890 0.470
## 20   1  0  0  1  1     0     2   0.873 0.281
## 19   1  0  0  1  0     0     2   0.809 0.173
## 25   1  1  0  0  0     0     2   0.807 0.192
##  4   0  0  0  1  1     0     4   0.765 0.219
## 22   1  0  1  0  1     0     2   0.753 0.209
## 12   0  1  0  1  1     0     6   0.734 0.248
##  7   0  0  1  1  0     0     2   0.703 0.151
##  5   0  0  1  0  0     0     4   0.617 0.100
## 11   0  1  0  1  0     0     7   0.537 0.083
##  1   0  0  0  0  0     0     16  0.361 0.060
##  3   0  0  0  1  0     0     30  0.332 0.036
## 14   0  1  1  0  1     ?     1   0.868 0.435
## 26   1  1  0  0  1     ?     1   0.855 0.375
## 13   0  1  1  0  0     ?     1   0.850 0.248
## 15   0  1  1  1  0     ?     1   0.806 0.215
## 21   1  0  1  0  0     ?     1   0.785 0.145
##  6   0  0  1  0  1     ?     1   0.755 0.228
## 17   1  0  0  0  0     ?     1   0.747 0.110
##  2   0  0  0  0  1     ?     0     -     -  
##  8   0  0  1  1  1     ?     0     -     -  
##  9   0  1  0  0  0     ?     0     -     -  
## 10   0  1  0  0  1     ?     0     -     -  
## 16   0  1  1  1  1     ?     0     -     -  
## 18   1  0  0  0  1     ?     0     -     -  
## 23   1  0  1  1  0     ?     0     -     -  
## 27   1  1  0  1  0     ?     0     -     -  
## 29   1  1  1  0  0     ?     0     -     -  
## 31   1  1  1  1  0     ?     0     -     -

El PRI de corte baja a 0.45 para que la fila 24 entre. Los autores lo hacen con fines de presentación: con 0.5 la solución para Y queda demasiado simple.

sol_yc <- minimize(TT_y,
                   details = TRUE)
sol_yc
## 
## M1: X*A*C*E + X*A*D*E -> Y
## 
##             inclS   PRI   covS   covU  
## -------------------------------------- 
## 1  X*A*C*E  0.921  0.894  0.724  0.503 
## 2  X*A*D*E  0.882  0.742  0.290  0.070 
## -------------------------------------- 
##         M1  0.905  0.870  0.794
sol_yp <- minimize(TT_y,
                   details = TRUE,
                   include = "?",
                   row.dom = TRUE)
sol_yp
## 
## M1: X*A*E -> Y
## 
##           inclS   PRI   covS   covU  
## ------------------------------------ 
## 1  X*A*E  0.901  0.864  0.800    -   
## ------------------------------------ 
##       M1  0.901  0.864  0.800
sol_yp$SA
## $M1
##    X A C D E
## 26 1 1 0 0 1
sol_yi <- minimize(TT_y,
                   details = TRUE,
                   include = "?",
                   dir.exp = c(1, 1, 1, 0, 1))
sol_yi
## 
## From C1P1: 
## 
## M1:    X*A*E -> Y 
## 
##           inclS   PRI   covS   covU  
## ------------------------------------ 
## 1  X*A*E  0.901  0.864  0.800    -   
## ------------------------------------ 
##       M1  0.901  0.864  0.800

Aquí las expectativas direccionales van como vector de unos y ceros, en el orden de conds. Es otra forma de escribir lo mismo. Revisamos los contrafácticos fáciles de los dos caminos:

sol_yi$i.sol$C1P1$EC
sol_yi$i.sol$C1P2$EC
## NULL

Hay uno solo, la fila 26.

Análisis estándar mejorado (ESA)

El análisis estándar no impide que hagamos supuestos insostenibles sobre los remanentes. El ESA los identifica y los bloquea antes de minimizar. Hay tres fuentes, y las vamos a ir sumando una por una.

Supuestos simplificadores contradictorios

Un remanente usado como supuesto simplificador para Y y para ~Y a la vez implica que esa combinación basta para llegar a tiempo y para llegar tarde. LR.intersect() busca los remanentes compartidos entre dos soluciones:

CSA <- LR.intersect(sol_nyp, sol_yp)
CSA
## character(0)

Es lo mismo que intersectar a mano los nombres de las filas:

intersect(rownames(sol_nyp$SA$M1), rownames(sol_yp$SA$M1))
## character(0)

Las filas 23 y 31 son supuestos simplificadores contradictorios (CSA). Para las soluciones intermedias revisamos los contrafácticos fáciles:

LR.intersect(sol_nyi, sol_yi)
## character(0)

Ninguno. La intermedia para Y usa un solo contrafáctico fácil (la fila 26) y no aparece en la de ~Y.

esa() de SetMethods crea una tabla nueva donde las filas bloqueadas pasan a OUT = 0. Como nos interesa sobre todo ~Y, bloqueamos los CSA ahí:

TT_nyesa <- esa(oldtt = TT_ny,
                contrad_rows = c(CSA))
TT_nyesa
## 
##   OUT: output value
##     n: number of cases in configuration
##  incl: sufficiency inclusion score
##   PRI: proportional reduction in inconsistency
## 
##      X  A  C  D  E    OUT    n   incl  PRI  
## 19   1  0  0  1  0     1     2   0.960 0.827
##  5   0  0  1  0  0     1     4   0.954 0.892
## 11   0  1  0  1  0     1     7   0.954 0.908
## 25   1  1  0  0  0     1     2   0.953 0.803
##  3   0  0  0  1  0     1     30  0.951 0.929
##  1   0  0  0  0  0     1     16  0.950 0.926
##  7   0  0  1  1  0     1     2   0.947 0.849
## 20   1  0  0  1  1     1     2   0.946 0.691
## 22   1  0  1  0  1     1     2   0.935 0.791
##  4   0  0  0  1  1     1     4   0.934 0.781
## 12   0  1  0  1  1     1     6   0.912 0.752
## 24   1  0  1  1  1     0     2   0.894 0.490
## 28   1  1  0  1  1     0     4   0.806 0.405
## 32   1  1  1  1  1     0     5   0.650 0.175
## 30   1  1  1  0  1     0     36  0.285 0.055
## 17   1  0  0  0  0     ?     1   0.969 0.890
## 21   1  0  1  0  0     ?     1   0.963 0.855
## 13   0  1  1  0  0     ?     1   0.950 0.752
## 15   0  1  1  1  0     ?     1   0.940 0.755
##  6   0  0  1  0  1     ?     1   0.927 0.772
## 26   1  1  0  0  1     ?     1   0.910 0.611
## 14   0  1  1  0  1     ?     1   0.898 0.565
##  2   0  0  0  0  1     ?     0     -     -  
##  8   0  0  1  1  1     ?     0     -     -  
##  9   0  1  0  0  0     ?     0     -     -  
## 10   0  1  0  0  1     ?     0     -     -  
## 16   0  1  1  1  1     ?     0     -     -  
## 18   1  0  0  0  1     ?     0     -     -  
## 23   1  0  1  1  0     ?     0     -     -  
## 27   1  1  0  1  0     ?     0     -     -  
## 29   1  1  1  0  0     ?     0     -     -  
## 31   1  1  1  1  0     ?     0     -     -

Comparen con TT_ny: las filas 23 y 31 pasaron de ? a 0.

Relaciones de subconjunto simultáneas

Lo mismo puede pasar con filas observadas: una fila con OUT = 1 para Y y para ~Y. Lo revisamos:

SSR <- intersect(rownames(TT_ny$tt)[TT_ny$tt$OUT == 1],
                 rownames(TT_y$tt)[TT_y$tt$OUT == 1])
SSR
## character(0)

No hay ninguna, gracias al corte de PRI. Si las hubiera, se bloquean también con contrad_rows.

Supuestos que contradicen la necesidad

Los entrenadores hicieron antes el análisis de necesidad para ~Y, con superSubset() (capítulo 3):

SUIN_ny <- superSubset(data = STUF,
                       outcome = "~Y",
                       conditions = conds,
                       incl.cut = 0.906,
                       cov.cut = 0.8,
                       ron.cut = 0.8,
                       depth = 2)
SUIN_ny
## 
##             inclN   RoN   covN  
## ------------------------------- 
## 1  ~X + ~A  0.910  0.839  0.869 
## -------------------------------

~X + ~A es necesaria para llegar tarde. Las dos condiciones son SUIN y las interpretamos como un concepto más amplio: la cancha no queda a mano.

pimplot(data = STUF,
        outcome = "~Y",
        results = SUIN_ny,
        necessity = TRUE)

Si ~X + ~A es necesaria, ningún camino suficiente hacia ~Y puede prescindir de ella. Las filas que la contradicen son las que tienen X*A. Entre los supuestos simplificadores hay tres así (27, 29 y 31), y la fila 27 aparece incluso entre los contrafácticos fáciles. Además, la fila observada 25 (X*A*~C*~D*~E) tiene OUT = 1: es el término 5 de la solución conservadora.

El argumento nec_cond pone en OUT = 0 todas las filas con X*A, remanentes y observadas:

TT_nyesa <- esa(TT_ny,
                contrad_rows = c(CSA),
                nec_cond = "~X+~A")
TT_nyesa
## 
##   OUT: output value
##     n: number of cases in configuration
##  incl: sufficiency inclusion score
##   PRI: proportional reduction in inconsistency
## 
##      X  A  C  D  E    OUT    n   incl  PRI  
## 19   1  0  0  1  0     1     2   0.960 0.827
##  5   0  0  1  0  0     1     4   0.954 0.892
## 11   0  1  0  1  0     1     7   0.954 0.908
## 25   1  1  0  0  0     0     2   0.953 0.803
##  3   0  0  0  1  0     1     30  0.951 0.929
##  1   0  0  0  0  0     1     16  0.950 0.926
##  7   0  0  1  1  0     1     2   0.947 0.849
## 20   1  0  0  1  1     1     2   0.946 0.691
## 22   1  0  1  0  1     1     2   0.935 0.791
##  4   0  0  0  1  1     1     4   0.934 0.781
## 12   0  1  0  1  1     1     6   0.912 0.752
## 24   1  0  1  1  1     0     2   0.894 0.490
## 28   1  1  0  1  1     0     4   0.806 0.405
## 32   1  1  1  1  1     0     5   0.650 0.175
## 30   1  1  1  0  1     0     36  0.285 0.055
## 17   1  0  0  0  0     ?     1   0.969 0.890
## 21   1  0  1  0  0     ?     1   0.963 0.855
## 13   0  1  1  0  0     ?     1   0.950 0.752
## 15   0  1  1  1  0     ?     1   0.940 0.755
##  6   0  0  1  0  1     ?     1   0.927 0.772
## 26   1  1  0  0  1     0     1   0.910 0.611
## 14   0  1  1  0  1     ?     1   0.898 0.565
##  2   0  0  0  0  1     ?     0     -     -  
##  8   0  0  1  1  1     ?     0     -     -  
##  9   0  1  0  0  0     ?     0     -     -  
## 10   0  1  0  0  1     ?     0     -     -  
## 16   0  1  1  1  1     ?     0     -     -  
## 18   1  0  0  0  1     ?     0     -     -  
## 23   1  0  1  1  0     ?     0     -     -  
## 27   1  1  0  1  0     0     0     -     -  
## 29   1  1  1  0  0     0     0     -     -  
## 31   1  1  1  1  0     0     0     -     -

Remanentes imposibles

Algunas combinaciones no pueden existir. Para el ejemplo, el libro supone que es imposible un jugador que no vive cerca, llega en bicicleta, no viene de familia puntual y no tiene experiencia (~X*A*~C*~D). El argumento untenable_LR bloquea esas filas:

TT_nyesa <- esa(TT_ny,
                contrad_rows = c(CSA),
                nec_cond = "~X+~A",
                untenable_LR = "~X*A*~C*~D")
TT_nyesa
## 
##   OUT: output value
##     n: number of cases in configuration
##  incl: sufficiency inclusion score
##   PRI: proportional reduction in inconsistency
## 
##      X  A  C  D  E    OUT    n   incl  PRI  
## 19   1  0  0  1  0     1     2   0.960 0.827
##  5   0  0  1  0  0     1     4   0.954 0.892
## 11   0  1  0  1  0     1     7   0.954 0.908
## 25   1  1  0  0  0     0     2   0.953 0.803
##  3   0  0  0  1  0     1     30  0.951 0.929
##  1   0  0  0  0  0     1     16  0.950 0.926
##  7   0  0  1  1  0     1     2   0.947 0.849
## 20   1  0  0  1  1     1     2   0.946 0.691
## 22   1  0  1  0  1     1     2   0.935 0.791
##  4   0  0  0  1  1     1     4   0.934 0.781
## 12   0  1  0  1  1     1     6   0.912 0.752
## 24   1  0  1  1  1     0     2   0.894 0.490
## 28   1  1  0  1  1     0     4   0.806 0.405
## 32   1  1  1  1  1     0     5   0.650 0.175
## 30   1  1  1  0  1     0     36  0.285 0.055
## 17   1  0  0  0  0     ?     1   0.969 0.890
## 21   1  0  1  0  0     ?     1   0.963 0.855
## 13   0  1  1  0  0     ?     1   0.950 0.752
## 15   0  1  1  1  0     ?     1   0.940 0.755
##  6   0  0  1  0  1     ?     1   0.927 0.772
## 26   1  1  0  0  1     0     1   0.910 0.611
## 14   0  1  1  0  1     ?     1   0.898 0.565
##  2   0  0  0  0  1     ?     0     -     -  
##  8   0  0  1  1  1     ?     0     -     -  
##  9   0  1  0  0  0     0     0     -     -  
## 10   0  1  0  0  1     0     0     -     -  
## 16   0  1  1  1  1     ?     0     -     -  
## 18   1  0  0  0  1     ?     0     -     -  
## 23   1  0  1  1  0     ?     0     -     -  
## 27   1  1  0  1  0     0     0     -     -  
## 29   1  1  1  0  0     0     0     -     -  
## 31   1  1  1  1  0     0     0     -     -

De los 17 remanentes originales quedan 10 disponibles. Con stargazerTT() esta tabla se exporta igual que cualquier otra.

Las soluciones mejoradas

Empezamos por la conservadora. Parece extraño, porque la conservadora no usa remanentes, pero nec_cond también apagó la fila observada 25.

sol_nycesa <- minimize(TT_nyesa,
                       details = TRUE)
sol_nycesa
## 
## M1: ~X*~A*~E + ~X*~C*D + ~A*~C*D + X*~A*C*~D*E -> ~Y
## 
##                 inclS   PRI   covS   covU  
## ------------------------------------------ 
## 1     ~X*~A*~E  0.942  0.922  0.674  0.191 
## 2      ~X*~C*D  0.913  0.881  0.541  0.067 
## 3      ~A*~C*D  0.905  0.862  0.504  0.024 
## 4  X*~A*C*~D*E  0.935  0.791  0.125  0.024 
## ------------------------------------------ 
##             M1  0.901  0.868  0.815

Quedan cuatro términos y todos contienen ~X o ~A. El término con X*A desapareció.

sol_nypesa <- minimize(TT_nyesa,
                       details = TRUE,
                       include = "?",
                       row.dom = TRUE)
sol_nypesa
## 
## M1: ~X*D + ~A*~C + ~A*~D -> ~Y
## 
##           inclS   PRI   covS   covU  
## ------------------------------------ 
## 1   ~X*D  0.908  0.874  0.571  0.092 
## 2  ~A*~C  0.905  0.870  0.676  0.072 
## 3  ~A*~D  0.927  0.897  0.398  0.085 
## ------------------------------------ 
##       M1  0.893  0.860  0.858
sol_nypesa$SA
## $M1
##    X A C D E
## 2  0 0 0 0 1
## 6  0 0 1 0 1
## 8  0 0 1 1 1
## 15 0 1 1 1 0
## 16 0 1 1 1 1
## 17 1 0 0 0 0
## 18 1 0 0 0 1
## 21 1 0 1 0 0

Ningún supuesto simplificador contiene X*A, ninguno es contradictorio y ninguno cae en ~X*A*~C*~D.

sol_nyiesa <- minimize(TT_nyesa,
                       details = TRUE,
                       include = "?",
                       dir.exp = "~X, ~A, ~C, D, ~E")
sol_nyiesa
## 
## From C1P1: 
## 
## M1:    ~A*~C + ~A*~D + ~X*~A*~E + ~X*~C*D -> ~Y 
## 
##              inclS   PRI   covS   covU  
## --------------------------------------- 
## 1     ~A*~C  0.905  0.870  0.676  0.026 
## 2     ~A*~D  0.927  0.897  0.398  0.043 
## 3  ~X*~A*~E  0.942  0.922  0.674  0.018 
## 4   ~X*~C*D  0.913  0.881  0.541  0.067 
## --------------------------------------- 
##          M1  0.894  0.862  0.851
sol_nyiesa$i.sol$C1P1$EC

Para ver qué contrafácticos fáciles sobrevivieron al ESA, intersectamos la intermedia estándar con la mejorada:

tenable_EC <- LR.intersect(sol_nyi, sol_nyiesa)
tenable_EC
## [1] "2"  "6"  "17" "18" "21"

Las filas 2, 6, 17, 18 y 21 eran contrafácticos fáciles y sostenibles. Las filas 9 y 27 eran fáciles, pero insostenibles: la 27 contradice la necesidad y la 9 es un remanente imposible. El ESA las sacó.

Tarea #1 (4 pts.)

  1. Con SF5, suban incl.cut a 0.86. ¿Qué filas dejan de ser suficientes? ¿Cómo cambia la solución y qué pasa con su cobertura?
  2. Con SF5, analicen ~Y con los mismos umbrales. ¿Qué fila es suficiente para llegar tarde y no lo era para llegar a tiempo? ¿Qué casos tiene?
  3. Con STUF, repitan TT_y con pri.cut = 0.5. ¿Qué fila sale y cómo queda la solución más parsimoniosa?
  4. Cambien la expectativa de D a ~D en sol_nyi. ¿Qué contrafácticos fáciles aparecen o desaparecen? ¿Alguno contradice la necesidad de ~X + ~A?

Referencias

Oana, I.-E., Schneider, C. Q., y Thomann, E. (2021). Qualitative Comparative Analysis Using R: A Beginner’s Guide, cap. 4. Cambridge University Press.

tt_tarde <- truthTable(
  data = SF5,
  outcome = "~Y",
  conditions = c("X", "A", "C"),
  incl.cut = 0.86,
  pri.cut = 0.51,
  show.cases = TRUE
)

tt_tarde
## 
##   OUT: output value
##     n: number of cases in configuration
##  incl: sufficiency inclusion score
##   PRI: proportional reduction in inconsistency
## 
##     X  A  C    OUT    n  incl  PRI   cases        
## 1   0  0  0     1     1  0.909 0.731 Joe          
## 2   0  0  1     1     1  0.909 0.714 Jess         
## 3   0  1  0     0     2  0.844 0.562 Cecilia,Fiona
## 4   0  1  1     0     2  0.770 0.375 Bob,Kim      
## 5   1  0  0     0     3  0.693 0.357 Ana,Alex,Dee 
## 6   1  0  1     0     1  0.855 0.630 Frank        
## 7   1  1  0     0     1  0.776 0.387 Carl         
## 8   1  1  1     0     2  0.774 0.387 Amanda,Mark