8  ESTRUCTURAS DE DATOS

R es un lenguaje con características compatibles con la programación orientada a objetos. Un objeto es una entidad sobre la cual puede operarse. Así, un número es un objeto, también son objetos una cadena de caracteres, una fórmula o un resultado.

Aunque es evidente la posibilidad de operar sobre un número, no lo es tanto la posibilidad de operar sobre una cadena de caracteres, sobre una fórmula o sobre un resultado. No obstante, si se considera, por ejemplo, la función print, tomando tales objetos como argumento para mostrarlos en la consola, la posibilidad de operar sobre los mismos se hace clara1.

Un grupo de objetos de gran interés es el de los que actúan como contenedores de información, permitiendo agruparla o estructurarla.

Nota 8.1: Atómicas y Recursivas

Los vectores, las matrices y los arreglos están restringidos a contener elementos de un único tipo. A tales estructuras se les denomina atómicas.

Los data frames y las listas son estructuras que admiten elementos de diferentes tipos. A tales estructuras se les denomina recursivas.

En resumen…

Las estructuras contenedoras de datos en R, en orden de complejidad son:

  • Vector
  • Matriz
  • Arreglo
  • Data frame
  • Lista

Las tres primeras son estructuras atómicas; las dos últimas, recursivas.

8.1 Vectores

La manera más común de crear un vector es mediante el uso de la función de concatenación c y el operador de asignación <-, separando los diferentes elementos con comas.

Para crear, por ejemplo, un vector llamado a, que contenga los elementos 3, 5 y 9, se usa la siguiente instrucción:

a <- c(3, 5, 9)

En la anterior expresión, la función c es la que define el vector (nemotécnicamente, puede recordarse como combinar); c(3, 5, 9) es un vector.

El paso correspondiente al almacenamiento de dicho objeto en el ambiente de trabajo, es decir, la asignación, se requeriría únicamente si se quisiera usar dicho vector en un proceso posterior.

Si no se realizara el paso de asignación, se generaría un vector que a pesar de su existencia efímera podría bastar para un propósito dado, como servir de argumento en una función determinada.

La siguiente instrucción crea un vector tipo carácter. Al definir este tipo de vectores, es necesario entrecomillar cada uno de los elementos constituyentes, siendo posible usar comillas dobles o sencillas.

nombres <- c("Gabriela", 'Carlina', "Luz", 'Marina')
¿Es un vector?

Para verificar si un objeto es un vector se usa la función is.vector(objeto), la cual genera un resultado lógico (falso o verdadero).

En la sección 10.4 se discuten algunas salvedades.

8.2 Matrices

Las matrices son estructuras bidimensionales (filas y columnas) que agrupan objetos del mismo tipo.

Supóngase que se quiere crear una matriz numérica con dos filas y tres columnas, para alojar la siguiente información:

2 0 5
-1 9 1

Para tal efecto, se usa la siguiente instrucción.

matrix(c(2, 0, 5, -1, 9, 1), nrow = 2, ncol = 3, byrow = TRUE)

matrix es la función que define la creación de la matriz. Su primer argumento es un vector con la información que se distribuirá en filas y columnas. Los argumentos nrow y ncol especifican el número de filas y de columnas respectivamente.

¿Por filas o por columnas?

El cuarto argumento de la función matrix especifica la manera en la que se distribuyen los elementos. Su valor por defecto es byrow = FALSE.

Por tanto, si dicho argumento fuera omitido (o se definiera explícitamente como byrow = F), los elementos ingresados se ordenarían por columnas.

Así, si se hubiera omitido este argumento en la instrucción anterior —en lugar de la matriz objetivo— se obtendría la siguiente matriz:

     [,1] [,2] [,3]
[1,]    2    5    9
[2,]    0   -1    1
¿Pueden concatenarse varios vectores?

Considérense nuevamente los siguientes elementos, con base en los cuales se quiere definir una matriz con dos filas y tres columnas:

2 0 5
-1 9 1

Considérense ahora los vectores a, b y c, definidos con base en las columnas de la matriz objetivo:

a <- c(2, -1)
b <- c(0, 9)
c <- c(5, 1)

Podría pensarse en concatenar dichos vectores para obtener la matriz objetivo, así:

c(a, b, c) 
[1]  2 -1  0  9  5  1

Se observa que, en lugar de la matriz objetivo, se obtiene un vector. Esto es debido a que la función c siempre da lugar a vectores.

Existen, sin embargo, un par de funciones de concatenación que sí dan lugar a matrices:

  • rbind, que permite combinar vectores por filas (rows)

  • cbind, que permite concatenar vectores por columnas (columns)

Para crear la matriz objetivo a partir de los vectores a, b y c definidos anteriormente, se usaría cbind:

m1 <- cbind(a, b, c)

Para ver la matriz resultante en consola, se usa la función print:

print(m1)
      a b c
[1,]  2 0 5
[2,] -1 9 1

La concatenación por filas sería así:

a <- c(2, 0, 5)
b <- c(-1, 9, 1)
m2 <- rbind(a, b)
print(m2)
  [,1] [,2] [,3]
a    2    0    5
b   -1    9    1
¿Es una matriz?

Para verificar si un objeto es una matriz se usa la función is.matrix(objeto), la cual genera un resultado lógico (falso o verdadero).

is.matrix(m2)
[1] TRUE

8.2.1 Álgebra matricial

Las matrices en R no son solo contenedores de información, sino que comparten las características de las matrices como concepto matemático. Esto hace posible su participación en operaciones de álgebra matricial.

¿¡Es necesario pasar por esto para aprender R!?

Si usted está iniciándose con R, tratando de entender su estructura básica, y de momento no está interesado en su uso como herramienta de álgebra matricial, le recomedamos que se salte esta sección y pase directamente a la sección 8.3.

Sin embargo, no olvide que esta sección está aquí… Podría serle muy útil a futuro.


Entre las operaciones matriciales más comunes se destacan las siguientes:

  • Suma de matrices: operador +

  • Diferencia de matrices: operador -

  • Producto matricial: operador %*%

  • Trasposición: función t

  • Determinante: función det

  • Inversión: función solve

  • Obtención de la diagonal: función diag

  • Descomposición espectral: función eigen

  • Descomposición en valores singulares: función svd

Precaución 8.1: ¡No es producto matricial!

El operador * entre dos matrices no realiza su producto matricial, sino el producto elemento a elemento.

Considere las siguientes matrices:

m1 <- matrix(c(4, -3,
               -7, 0), nrow = 2, byrow = TRUE)
print(m1)
     [,1] [,2]
[1,]    4   -3
[2,]   -7    0
m2 <- matrix(c(5, 1,
               2, 3), nrow = 2, byrow = TRUE)
print(m2)
     [,1] [,2]
[1,]    5    1
[2,]    2    3

Observe el efecto de multiplicarlas mediante el operador *

print(m1 * m2)  # Producto elemento a elemento
     [,1] [,2]
[1,]   20   -3
[2,]  -14    0

Observe ahora el efecto de multiplicarlas mediante el operador %*%

print(m1 %*% m2)  # Producto matricial
     [,1] [,2]
[1,]   14   -5
[2,]  -35   -7
¡Facilítese la vida!

Al definir las matrices m1 y m2 en el llamado de precaución 8.1, aparecen un par de aspectos que vale la pena resaltar:

  1. Se omite el argumento ncol, el cual se calcula a partir de tamaño del vector. La únicamente manera de alojar cuatro elementos en una matriz de 2 filas (nrow = 2 sí se especifica) es que también tenga 2 columnas. Esta misma estrategia puede usarse definiendo únicamente el número de columnas (ncol).

  2. En cada una de las definiciones, se introdujo un salto de línea entre los elementos de la primera y la segunda fila. Este salto tiene por objeto facilitar la visualización. Al escribir el vector de esta manera inmediatamente se observa cómo quedará conformada la matriz. No obstante, podría haberse escrito en una sola línea (como se ilustró anteriormente) o introduciendo un salto entre cualquier otro par de elementos.

8.2.1.1 Sumas de Cuadrados y Productos Cruzados

Una suma de cuadrados es esencialmente una suma de valores elevados al cuadrado.

Considérese el siguiente vector:

a <- c(2, 3, -2, 5)

En notación algebraica, la suma de cuadrados de \(a\) es:

\[ \sum\limits_{i=1}^4 a_i^2=(2)^2+(3)^2+(-2)^2+(5)^2=42 \] En álgebra matricial, se asume que los vectores son matrices de una columna (vector columna) y que, por tanto, su transpuesta son matrices de una fila (vector fila). Luego, la suma de cuadrados de \(a\), puede expresarse como el producto matricial \(a^ta\), así:

\[ a^ta=\begin{pmatrix} 2&3&-2&5 \end{pmatrix} \begin{pmatrix} 2\\ 3\\ -2\\5 \end{pmatrix} =(2)^2+(3)^2+(-2)^2+(5)^2=42 \]

Esta operación podría realizarse en R, usando el producto matricial (operador %*%), así:

1a.t <- matrix(c(2, 3, -2, 5), nrow = 1)
2a   <- matrix(c(2, 3, -2, 5), ncol = 1)
suma.cuad <- a.t %*% a
1
Vector fila (matriz \(1\times4\))
2
Vector columna (matriz \(4\times1\))

Equivalentemente, puede obtenerse la suma de cuadrados, mediante la función crossprod, así:

crossprod(a, a)

La anterior instrucción puede escribirse de manera simplificada, usando un solo argumento. En tal caso, la función crossprod interpreta que se quiere realizar el producto del vector \(a\) contra sí mismo, es decir, la suma de cuadrados de \(a\), así:

crossprod(a)

Las instrucciones anteriores puede usarse no solo con vectores, sino con matrices que tengan más de una fila y más de una columna.

Considérese la siguiente matriz:

\[ X= \begin{pmatrix} 0&3&1&7\\ 8&1&4&3\\ 1&0&1&2\\ 9&4&7&3\\ \end{pmatrix} \]

El producto \(X^tX\) exige realizar el producto matricial entre las siguiente matrices:

\[ \begin{pmatrix} 0&8&1&9\\ 3&1&0&4\\ 1&4&1&7\\ 7&3&2&3\\ \end{pmatrix} \begin{pmatrix} 0&3&1&7\\ 8&1&4&3\\ 1&0&1&2\\ 9&4&7&3\\ \end{pmatrix} \] En R, puede realizarse usando el operador %*%, así:

X <- matrix(c(0, 3 ,1, 7,
              8, 1, 4, 3,
              1, 0, 1, 2,
              9, 4, 7 ,3), nrow = 4, byrow = T)
XtX <- t(X) %*% X
print(XtX)
     [,1] [,2] [,3] [,4]
[1,]  146   44   96   53
[2,]   44   26   35   36
[3,]   96   35   67   42
[4,]   53   36   42   71

Esta operación es equivalente a la ilustrada anteriormente para obtener la suma de cuadrados del vector \(a\).

El producto \(X^tX\) es común en análisis multivariante, donde la matriz \(X\) suele estar conformada por variables en las columnas e individuos en filas.

En este contexto, los valores de la diagonal de la matriz \(X^tX\) contienen la suma de cuadrados de cada una de las variables y reflejan su variabilidad.

Equivalentemente, puede usarse la función crossprod para el cálculo de \(X^tX\), asi:

crossprod(X, X)

O así:

crossprod(X)

Así como la matrix \(X^tX\) contiene información sobre las varianzas y las covarianzas de las variables que conforman la matriz \(X\), la matriz \(XX^t\) contiene información sobre la similitud/disimilitud de los individuos.

La matriz \(XX^t\) se calcula en R así:

(X) %*% t(X)

O también puede calcularse mediante la función tcrossprod, con cualquiera de las siguientes instrucciones:

tcrossprod(X, X)
tcrossprod(X)

El producto de una matriz transpuesta consigo misma (por ejemplo, \(a^ta\) o \(X^tX\)) se denomina suma de cuadrados. A una operación equivalente realizada entre diferentes matrices, se le denomina suma de productos cruzados.

¡Tienen que ser conformables!

Para realizar el producto entre dos matrices, estas tienen que ser conformables.

Esto implica que el número de columnas de la primera matriz tiene que corresponder con el número de filas de la segunda.


Para ilustrar los anteriores operadores y funciones, considérese el estimador minimocuadrático de los parámetros en los modelos de regresión lineal:

\[ \widehat{\beta}=\left(X'X\right)^1X´y \]

Suponiendo que X y y han sido definidas, el estimador \(\widehat\beta\) se calcula así:

solve(t(X) %*% X) %*% t(X) %*% y

O, equivalentemente, así:

solve(crossprod(X)) %*% crossprod(X, y)
En resumen…


La matriz \(X^tX\) puede calcularse mediante cualquiera de las siguientes expresiones:

t(X) %*% X
crossprod(X, X)
crossprod(X)

La matriz \(XX^t\) puede calcularse mediante cualquiera de las siguientes expresiones:

X %*% t(X)
tcrossprod(X, X)
tcrossprod(X)

La matriz \(X^ty\) puede calcularse mediante cualquiera de las siguientes expresiones:

t(X) %*% y
crossprod(X, y)

La matriz \(Xy^t\) puede calcularse mediante cualquiera de las siguientes expresiones:

X %*% t(y)
tcrossprod(X, y)

En adición a las funciones básicas presentadas anteriormente, que forman parte del paquete base, el paquete Matrix contiene funciones adicionales para realizar operaciones avanzadas de álgebra lineal y muy particularmente para trabajar con matrices dispersas (sparse matrices).

¿!Y los vectores qué!?

Aunque los vectores en R son simples contenedores unidimensionales que no guardan correspondencia con el concepto del álgebra lineal, también admiten operaciones matriciales, adaptándose, cuando sea del caso, a matrices fila o matrices columna según corresponda.

Considérense los siguientes objetos:

a  <- c(3, 5, 9)
b  <- c(7, 2)
m <- matrix(c(2, 0, 5,
              -1, 9, 1), nrow = 2, byrow = TRUE)
  • m es una matriz \(2\times3\)
  • a es un vector de tamaño 3
  • b es un vector de tamaño 2

Aunque inicialmente, ni a ni b son matrices (puede verificarse mediante la función is.matrix), se convierten en matrices cuando participan en una operación que incluya el operador %*%.

Por tanto, la siguiente operación estaría adecuadamente definida:

b %*% m

En este caso, el vector b se adapta a una matriz fila \((1 \times 2)\). El resultado es una matriz \(1 \times 3\).

Esta otra operación también está adecuadamente definida:

m %*% a

En este caso, el vector a se adapta a una matriz columna \((3 \times 1)\). El resultado es una matriz \(2 \times 1\).

Obsérvese que los vectores iniciales a y b no son ni vectores fila ni vectores columna, sino que adquieren la correspondiente modalidad, de acuerdo con el contexto en el que se usen como operandos.


Cuando se usa el operador de trasposición t sobre un vector, este se convierte en un vector fila.

Luego, la siguiente operación queda adecuadamente definida:

t(a) %*% a

En este caso, puesto que t(a) es un vector fila \((1 \times 3)\), el vector a se adecúa a un vector columna \((3 \times 1)\). El resultado es una matriz \(1 \times 1\).

Este mismo resultado se obtiene mediante la siguiente instrucción:

a %*% a

El primer factor se adecúa a una matriz fila \((1 \times 3)\). El segundo factor se adecúa a una matriz columna \((3 \times 1)\). El resultado es una matriz \(1 \times 1\).

Considérese ahora la siguiente instrucción.

a %*% t(a)


Al usar el operador de trasposición (t) sobre el segundo factor, este se convierte en una matriz fila (\(1 \times 3\), en este caso). Por tanto, el primer factor se adecúa a una matriz columna (\(3 \times 1\), en este caso). En consecuencia, el resultado es una matriz \(3 \times 3\).

8.3 Arreglos

Cuando se tienen más de dos dimensiones con elementos del mismo tipo, es posible estructurar la información en arreglos. Considérese una situación hipotética en la que se evalúan tres variables sobre cuatro unidades muestrales, en dos tiempos, pudiendo obtenerse como resultado cualquiera de las letras del alfabeto.

Tiempo1
v1 v2 v3
unidad1 a x d
unidad2 c m b
unidad3 f j u
unidad4 d l y



Tiempo2
v1 v2 v3
unidad1 b h r
unidad2 j o s
unidad3 h w n
unidad4 l p q


La anterior información podría estructurase en un arreglo de tres dimensiones, con las unidades en la primera dimensión, las variables en la segunda y el tiempo en la tercera, lo cual puede visualizarse como un cubo, en el que las tres dimensiones representan altura, ancho y profundidad, respectivamente.



Aunque la analogía entre un cubo y un arreglo de tres dimensiones puede facilitar el entendimiento del arreglo, la inexistencia de este tipo de relaciones en arreglos con mayor número de dimensiones no impide la existencia de estos. Si se tuviera otro factor, como localidad, por ejemplo, podría incorporarse en la cuarta dimensión. De igual manera, podrían generarse dimensiones adicionales.

Aunque podría usarse una única instrucción para construir el arreglo en cuestión, se ilustrará en dos pasos, con el fin de hacer más claro el proceso.

Inicialmente, se construye un vector de 24 elementos con toda la información.

vector <- c('a', 'c', 'f', 'd', 'x', 'm', 'j', 'l',
            'd', 'b', 'u', 'y', 'b', 'j', 'h', 'i',
            'h', 'o', 'w', 'p', 'r', 's', 'n', 'q')

A continuación, se genera el arreglo:

letras <- array(vector, dim = c(4, 3, 2))

La función array concatena los objetos contenidos en vector, por columnas (no existe la opción para concatenar por filas). El valor del argumento dim debe ser un vector de enteros con la longitud de cada una de las dimensiones.

¿Es un arreglo?

Para verificar si un objeto es un arreglo, se utiliza la función is.array(objeto).

is.array(letras)
[1] TRUE

Todas las matrices son a su vez arreglos; sin embargo, solo los arreglos de dos dimensiones son matrices.

8.4 Data frames

El data frame es una estructura rectangular (dos dimensiones), que puede estar conformada por objetos de diferente tipo en cada columna (pero del mismo tipo dentro de cada columna). Es decir que se trata de una estructura recursiva.

Esto contrasta con las estructuras atómicas ilustradas en las secciones anteriores (vectores, matrices y arreglos), que exigen que todos los elementos sean del mismo tipo (cf. nota 8.1).

¿Atómico?

Para verificar si un objeto es atómico, se usa la función is.atomic(objeto).

is.atomic(letras)
[1] TRUE

Considérense los siguientes vectores:

1id <- c("a23", "f31", "j33", "m54")
2v1 <- c(2.4, 7.9, 1.1, 8.5)
3v2 <- c(4+3i, 2-0.8i, 1+1.1i, 3-5i)
1
Vector de caracteres
2
Vector numérico
3
Vector complejo

Una manera de generar un data frame es a través de la agregación de vectores, sin importar que estos sean de diferente tipo. La siguiente instrucción genera un data frame concatenando por columnas los vectores definidos anteriormente.

df <- data.frame(id, v1, v2)
print(df)
   id  v1     v2
1 a23 2.4 4+3.0i
2 f31 7.9 2-0.8i
3 j33 1.1 1+1.1i
4 m54 8.5 3-5.0i

Esta capacidad de combinar objetos de diferentes tipos hace del data frame la estructura ideal para alojar bases de datos en R. Los data frames se organizan con las observaciones o individuos en filas, y variables en columnas.

No es necesario que los objetos que se concatenan para conformar un data frame sean vectores; también pueden concatenarse matrices, arreglos u otros data frames, exigiéndose únicamente que todos tengan el mismo número de filas.

¿¡Concatenar arreglos en un data frame!?

Puesto que el data frame es una estructura bidimensional, al incluir un arreglo en la definición de un data frame, este se “aplana” a dos dimensiones.

Así, por ejemplo, el arreglo letras generado en la sección 8.3 generaría un data frame de cuatro filas (las 4 unidades muestrales, que están en la primera dimensión) y seis columnas en las que aparecen concatenadas las 3 variables de cada uno de los 2 tiempo: primero las 3 variables del tiempo 1 y a continuación las 3 variables del tiempo 2.

df2 <- data.frame(letras)
print(df2)
  X1 X2 X3 X4 X5 X6
1  a  x  d  b  h  r
2  c  m  b  j  o  s
3  f  j  u  h  w  n
4  d  l  y  i  p  q

Cuando se usan las funciones read.table, read.delim, read.delim2, read.csv, read.csv2 o read_excel (cf. capítulo 6), los objetos que se generan son data frames.

¿Es un data frame?

Para verificar si un objeto es un data frame se usa la función is.data.frame (objeto), la cual genera un resultado lógico (falso o verdadero).

is.data.frame(df2)
[1] TRUE

8.5 Listas

La lista constituye la estructura más flexible, pudiendo estar conformada por objetos de cualquier tipo y clase, incluso por otras listas.

Dada la diversidad de objetos que pueden conformar una lista, en estas no existe el concepto de filas, columnas ni de ninguna otra dimensión. Las listas son objetos unidimensionales.

Vectores genéricos

Puesto que las listas son objetos unidimensionales, en ocasiones se hace referencia a las mismas como vectores genéricos, es decir, vectores que pueden contener elementos de distintos tipos, a diferencia de los vectores básicos definidos en la sección 8.1, que están restringidos a contener objetos de un único tipo.

A los vectores básicos se les denomina vectores atómicos cuando se desea diferenciarlos de las listas.

La figura 8.1 ilustra el concepto de las listas como vectores genéricos. La figura 8.1 (a) representa dos vectores, cada uno de los cuales está restringido a contener elementos de un mismo tipo, los cuales se ubican a lo largo de una única dimensión. La figura 8.1 (b) representa una lista, la cual, a pesar de manejar también una única dimensión, puede contener elementos de cualquier tipo.

(a) Vectores atómicos
(b) Vector recursivo: lista
Figura 8.1: Representación esquemática de una lista

Considérense los siguientes objetos de diferente naturaleza:

1nombre <- c('Iván', 'Rosa', 'Diana')
2edad <- c(34, 43)
3aprobado <- c(TRUE, TRUE, FALSE)
4score <- matrix(c(2, 0, 5, -1, 9, 1), nrow = 2, ncol = 3)
1
Vector de caracteres
2
Vector numérico
3
Vector lógico
4
Matriz numérica

Los anteriores objetos pueden concatenarse en una lista, así:

lista1 <- list(nombre, edad, aprobado, score)

La lista es el contenedor más general, pudiendo contener incluso otras listas. Para ilustrarlo, generaremos una nueva lista, usando lista1 y el arreglo letras presentado en la sección 8.3:

lista2 <- list(letras, lista1)
¿Es una lista y un vector?

Para verificar si un objeto es una lista se usa la función is.list(objeto), la cual genera un resultado lógico (falso o verdadero).

is.list(lista2)
[1] TRUE

Asimismo, puede verificarse que las listas también son vectores (vectores genéricos):

is.vector(lista2)
[1] TRUE
En resumen…

El lenguaje R proporciona una variada gama de contenedores que resulta suficiente para satisfacer las necesidades de organización de la información.

El vector constituye la manera más básica de agrupar datos; es el objeto más utilizado para la definición de argumentos secundarios en las funciones de R; es la estructura que se utilizaría siempre que los datos fueran del mismo tipo y no exigieran un arreglo multidimensional.

Cuando se requiera realizar operaciones matriciales, los objetos deben ser matrices (o eventualmente vectores; cf. sección 8.2.1).

El arreglo resulta útil para organizar datos del mismo tipo en más de dos dimensiones.

El data frame es la estructura en la que se organizan las bases de datos en R. La mayoría de los procedimientos estadísticos implementados en R exigen que el objeto de entrada sea un data frame. Cuando se importan datos, se genera un data frame.

La lista resulta útil cuando se requiere tener información de diversa índole (tamaño, dimensionalidad, tipo…) en un mismo contenedor. Este es el objeto que suele usarse para alojar los resultados de las funciones.

El anterior resumen deja claro que casi todas las estructuras contenedoras de datos tienen un nicho muy definido… excepto los arreglos, que no son muy comunes y cuyas posibilidades de uso no quedan muy claras.

Si está interesado en ver una aplicación práctica de un arreglo como contenedor tetradimensional, puede explorarla a continuación. No obstante, si usted viene estudiando este libro en orden, le sugerimos posponer esta revisión para después de haber estudiado la sección 13 (indexación), la sección 9.4 (reciclaje de elementos), el capítulo 18 (funciones por grupos) y la sección 20.1 (ciclos).


A continuación se ejemplifica el uso de un arreglo como contenedor multidimensional de información.

Aunque, desde luego, esta no es la única opción posible, sí se considera muy práctica, por permitir organizar todos los datos en un solo objeto.

Considérese el coeficiente general de similitud de Gower (Gower 1971)

\[ \text{SG}=\frac{\sum\limits_{j= 1}^{p}{W_{ii'j}S_{ii'j}}}{\sum_{j=1}^{p}{W_{ii'j}}} \tag{8.1}\]

donde:

\(\text{SG}\) : Coeficiente general de similitud de Gower.
\(W_{ii'j}\) : \(j\)-ésima ponderación para la comparación entre \(i\)-ésimo y el \(i'\)-ésimo individuo.
\(S_{ii'j}\) : Similitud parcial entre el \(i\)-ésimo y el \(i'\)-ésimo individuo, a partir de la \(j\)-ésima variable.

Este coeficiente es, en esencia, un promedio ponderado de las similitudes parciales que se calculan para cada par de individuos a partir de cada una de las variables.

El aspecto que le agrega complejidad a este cálculo es el hecho de que ni las ponderaciones ni las similitudes parciales pueden definirse de manera general; cada una de ellas se determina al momento de comparar un par de observaciones. Estos cálculos deberán generar finalmente una matriz simétrica de similitudes de tamaño \(n \times n\), siendo \(n\) el número de observaciones.

Para calcular la matriz de similitudes, resulta conveniente colectar la información en un arreglo de cuatro dimensiones, en el que las dos primeras dimensiones correspondan a las observaciones que se compararan; la tercera dimensión, a las similitudes parciales, y la cuarta, a las ponderaciones.

Este arreglo podría visualizarse como un cubo doble, en el que el primer cubo contiene las similitudes parciales, mientras que el segundo contiene las ponderaciones.

Para ilustrarlo gráficamente, supóngase una situación sencilla en la que se tienen 3 observaciones y 5 variables. La figura 8.2 representa esta situación.


Figura 8.2: Representación de un arreglo tetradimensional para organizar la información requerida para el cálculo del coeficiente de similitud general de Gower

La cara frontal de cada uno de los cubos (2 primeras dimensiones del arreglo) relaciona cada observación con cada una de las demás observaciones. En este ejemplo, la cara en cuestión es \(3 \times 3\).

La tercera dimensión (profundidad del primer cubo) contiene las similitudes parciales, \(S_{ii'j}\), entre cada par de observaciones, para cada una de las variables consideradas (5 variables en este ejemplo).

La cuarta dimensión (profundidad del segundo cubo) contiene las ponderaciones, \(W_{ii'j}\), para la comparación de cada par de observaciones, a través de cada una de las variables.

Para acceder a cada una de las posiciones, basta con usar los correspondientes descriptores de acceso, acorde con lo ilustrado en la sección 13.

Puesto que el objetivo de este apartado es ilustrar la utilidad del arreglo como contenedor multidimensional, más que detallar el coeficiente general de similitud de Gower, nos limitaremos a señalar que hay una serie de reglas que determinan tanto las similitudes parciales (s en el script), como las ponderaciones (w en el script), dependiendo del tipo de variable (presencia/ausencia, categórica o numérica), de los valores observados y de que los datos estén presentes o no.

Suponiendo una situación en la que se tienen \(n\) observaciones y \(p\) variables, la parte central de una función que organice esta información en un arreglo, dejándola preparada para calcular la matriz de similitudes podría tener el siguiente aspecto:

SW <- array(0, dim = c(n, n, p, 2))
for (j in 1:p) {
  for (i1 in 1:n) {
    for (i2 in 1:n) {
      SW[i1, i2, j, 1] <- s
      SW[i1, i2, j, 2] <- w
    }
  }
}

La línea 1 genera un arreglo tetradimensional de tamaño \(n \times n \times p \times 2\), conformado inicialmente por ceros. Aunque podría pensarse que es necesario usar \(n \times n \times p \times 2\) ceros, no es así, pues dicho valor se reclicla (cf. sección 9.4).

Seguidamente se escriben tres ciclos anidados, mediante los cuales se realizan todas los posibles comparaciones entre pares de observaciones a través de todas las variables.

La primera instrucción del ciclo más interno (línea 5) irá llenando el primer cubo con las similitudes parciales, \(S_{ii'j}\) (cf. figura 8.2), mientras que la segunda instrucción del mismo ciclo (línea 6) llenará el segundo cubo con las correspondientes ponderaciones, \(W_{ii'j}\) (cf. figura 8.2).

El cálculo final del coeficiente de similitud general de Gower podría realizarse, utilizando las funciones para cálculos por dimensiones ilustradas en el capítulo 18:

SW[, , , 1] <- SW[, , , 1] * SW[, , , 2] 
SG <- apply(SW[ , , , 1], c(1, 2), sum)/ 
      apply(SW[ , , , 2], c(1, 2), sum)

La primera línea calcula los productos —elemento a elemento— entre las similitudes parciales (cubo frontal de la figura 8.2) y sus correspondientes ponderaciones (cubo posterior de la figura 8.2), dando lugar a una serie de sumandos que quedan condensados en un cubo \(n \times n \times p\). Esta información se sobrescribe en el primer cubo (SW[, , , 1]). El segundo cubo (SW[, , , 2]) sigue conservando los ponderadores.

Las líneas 2 y 3 corresponden a una instrucción, que genera el coeficiente general de similitud de Gower en una matriz simétrica \(n \times n\). La línea 2 suma el producto \(W_{ii'j}S_{ii'j}\) para cada una de las celdas de las dos primeras dimensiones, es decir, el numerador de la expresión 8.1. La línea 3 suma las ponderaciones para cada una de las celdas de las dos primeras dimensiones, es decir, el denominador de la expresión 8.1.

Referencias Bibliográficas

Gower, J. C. 1971. «A general coefficient of similarity and some of its properties». Biometrics 27 (4): 857-71.

  1. La función print se presenta en el capítulo 14↩︎