10  CLASIFICACIÓN DE OBJETOS

En R los objetos pueden clasificarse con base en tres criterios:

  1. Clase (class)

  2. Tipo (typeof)

  3. Modo (mode)

10.1 Clase

La clase indica, en sentido amplio, la naturaleza del objeto. En R, todos los objetos son de una clase determinada (en ocasiones pueden pertenecer a dos o más clases).

Para averiguar la clase de un objeto, se usa la función class. Las clases más comunes de los objetos contenedores de datos que se presentan en el capítulo 8 son:

  • entera (integer)
  • numérica (numeric)
  • compleja (complex)
  • lógica (logical)
  • carácter (character)
  • matriz (matrix)
  • arreglo (array)
  • data frame (data.frame)
  • lista (list)

Los nombres de las clases coinciden con los de los contendedores de información para las matrices, los arreglos, los data frames y las listas. Sin embargo, no hay coincidencia para el caso de los vectores, pues no existe una una clase denominada vector.

Los vectores pueden ser de alguna de las cinco primeras clases, según la naturaleza de sus elementos.

¡Ayuda nemotécnica!

Para establecer la relación entre clases y contenedores, puede pensarse cada una de las primeras cinco clases enumeradas anteriormente como si llevaran antepuesta la palabra vector, así: vector numérico, vector complejo, vector lógico, etc.

¡Hay que tener clase!

Como parte de la programación orientada a objetos, R maneja funciones genéricas, que derivan a funciones particulares, tomando en consideración la clase del argumento principal de la función.

Es posible —con ciertas restricciones— modificar la clase de un objeto, mediante el uso de funciones tales como as.matrix, as.data.frame, as.list, as.numeric y as.character, que permiten guardar objetos como matrices, data frames, listas, vectores numéricos y vectores de caracteres, respectivamente.

La siguiente instrucción genera una lista completa de tales funciones:

apropos("as")
¡No descuide la clase!

La clase de un objeto es un aspecto de particular relevancia, pues cada función exige que sus argumentos sean de una clase particular.

Así, por ejemplo, las operaciones matriciales exigen objetos de la clase matriz (eventualmente, también vectores; cf. sección 8.2.1).

10.2 Tipo

Aunque la clase define la naturaleza de los elementos que conforman un vector, no informa sobre los elementos que conforman los demás objetos atómicos. Para conocer la naturaleza de tales elementos en matrices y arreglos, debe evaluarse su tipo, mediante la función typeof.

Los tipos son cuasicoincidentes con las clases de vectores:

  • entero (integer)
  • doble (double)
  • complejo (complex)
  • lógico (logical)
  • carácter (character)
¿doble?

Se habrá notado que con excepción de double, los tipos coinciden con las clases de los vectores.

El nombre double es herencia de la computación de bajo nivel, donde surgió de la necesidad de representar números reales con mayor precisión que la de los flotantes de precisión simple (single-precision), que usaban 32 bits para el almacenamiento y permitían almacenar cada número con aproximadamente 7 u 8 dígitos de precisión.

En contraste, el almacenamiento de doble precisión (double-precision) usaba 64 bits y permitía entre 15 y 16 dígitos.

Al evaluar el tipo de datos que conforman un objeto atómico se obtiene información inequívoca (entero, lógico, carácter, etc.), puesto que todos sus elementos son del mismo tipo.

Sin embargo, cuando se pretende evaluar el tipo de datos que conforman un objeto recursivo, esto es, un data frame o una lista (cf. nota 8.1), el resultado obtenido es list, como un recordatorio de que los elementos alojados en dichos contenedores pueden ser de diferentes tipos. Este resultado es siempre el mismo, aun si se evalúan data frames o listas conformados por elementos del mismo tipo.

No obstante, de ser necesario, puede evaluarse el tipo de cada uno de los objetos que conforman un contenedor recursivo, mediante el uso de los descriptores de acceso apropiados (sección 13.1.3.1).

Naturaleza atómica

A nivel de usuario, la naturaleza de los elementos constituyentes de un objeto atómico (vector, matriz o arreglo) se evalúa a través de su tipo (typeof).

10.3 Modo

El modo, que se verifica con la función mode, tiene que ver con la manera en la que los objetos son almacenados en memoria, siendo cuasicoincidente con el tipo.

El concepto de modo es una herencia de las versiones antiguas de R. En la actualidad es poco usado, prefiriéndose el tipo.

No obstante, el modo cobraría relevancia si se importara o exportara código desde o hacia otros lenguajes tales como S, C, C++ o Fortran. Asimismo, si se requiriera compatibilidad con funciones antiguas.

10.4 Clase factor

En adición a las clases que se presentan en la sección 10.1 como las más populares para los vectores atómicos (integer, numeric, logical, character y complex), está la clase factor.

Los vectores de la clase factor están conformados por los diferentes niveles de una variable categórica.

Considérese el siguiente vector:

a <- c("j", "m", "f", "m", "m", "f")

Aunque el vector a aparentemente está conformado por una serie de niveles de una variable categórica, esto no lo convierte automáticamente en un miembro de la clase factor. El vector a es de la clase character1. Para que el vector a sea de la clase factor, es necesario definirlo explícitamente como tal, usando la función factor (o también as.factor), así:

a2 <- factor(a)

Aunque el contenido de los vectores a y a2 es en esencia el mismo, la definición de a2 como un vector de la clase factor, le agrega un atributo de niveles (levels) que lo hace apto como insumo de ciertos procesos.

La función levels permite recuperar los niveles de un vector de la clase factor:

levels(a2)
[1] "f" "j" "m"

Nótese que el anterior resultado no enumera todos los elementos del vector a2, sino únicamente los que son diferentes. Estos son los niveles del factor a2.

El resultado de la función levels es un vector con cada una de las categorías o niveles ordenados del factor. El orden por defecto es alfabético; no el orden de aparición en el factor.

Por otra parte, la función nlevels da como resultado el número de niveles de un vector de la clase factor. En este caso, no se enumeran los niveles; simplemente se obtiene su número .

nlevels(a2)
[1] 3
¿Porque aparece un número ineseperado de niveles?

Si al evaluar el número de niveles de un factor aparece un número mayor de niveles que el esperado, puede deberse a un uso descuidado de las mayúsculas y minúsculas.

Recuerde que R es case-sensitive. Consecuentemente, “enero” y “Enero” se registrarían como dos niveles diferentes.

No solamente los vectores de la clase character pueden convertirse a la clase factor. Cualquier vector, sin importar su clase original, puede convertirse en un factor, usando la función factor.

Considérense los siguientes vectores:

b <- c(9, 5, 3, 6, 10, 5, 5, 3)
b2 <- factor(b)

b es un vector de la clase numeric, mientras que b2 es un vector de la clase factor. Estos son su niveles:

levels(b2)
[1] "3"  "5"  "6"  "9"  "10"

Aunque siguen usándose los mismos símbolos para identificar los niveles del vector b2, estos pierden su concepto como valores numéricos, no siendo posible usarlos, por ejemplo, en una operación aritmética. En un vector de la clase factor, “3”, “5”, “6”, “9” y “10” son símbolos de la misma naturaleza que “f”, “j” y “m”.

Nótese que cuando el factor proviene de un vector numérico, el orden de los niveles respeta el orden numérico original.

¡Suelen sobreescribirse!

Puesto que la definición de un vector como miembro de la clase factor únicamente conlleva un cambio en sus atributos, sin que se altere el contenido del vector, lo que usualmente se hace es sobreescribir el vector original, en lugar de generar un nuevo vector. Con esto se evita la acumulación innecesaria de objetos en el entorno de trabajo.

Acorde con esto, en lugar de los vectores a y a2 del ejemplo anterior2 se sobreescribiría el vector a —usándolo como argumento de la función factor y como resultado— así:

a <- c("j", "m", "f", "m", "m", "f")
a <- factor(a)
class(a)
[1] "factor"

Al final se tiene un único vector a de la clase factor.

10.4.1 Redefinición del orden de los niveles de un factor

En ocasiones, puede requerirse cambiar el orden que por defecto se les asigna a los niveles de un factor.

Considérese el siguiente vector.

mes <- c("mar", "feb", "ene", "abr")
mes <- factor(mes)
levels(mes)
[1] "abr" "ene" "feb" "mar"

El orden alfabético que se obtiene por defecto para los niveles del factor mes podría no ser el más conveniente si se quisiera, por ejemplo, construir un gráfico con los meses en la abscisa.

Para generar un orden personalizado, acorde con su secuencia temporal de aparición durante el año, se incorpora un vector con los niveles en el orden deseado, a través del argumento levels, así:

mes <- c("mar", "feb", "ene", "abr")
mes <- factor(mes, levels = c("ene", "feb", "mar", "abr"))
levels(mes)
[1] "ene" "feb" "mar" "abr"

Igualmente, podría obtenerse un orden personalizado, usando la función levels en una línea aparte, en lugar de hacerlo como argumento de la función factor, así:

mes <- c("mar", "feb", "ene", "abr")
mes <- factor(mes)
levels(mes) <-  c("ene", "feb", "mar", "abr")
levels(mes)
[1] "ene" "feb" "mar" "abr"

La línea 1 genera el vector mes, de la clase character.

En la línea 2 se sobreescribe el vector mes con otro vector del mismo nombre, pero de la clase factor. El orden de los niveles del factor mes es el alfabético.

Mediante la instrucción de la línea 3 se redefine el orden de los niveles del factor mes.

¡Tienen que ser los mismos niveles!

Las estrategias anteriores permiten reordenar los niveles del factor, pero no permiten modificar sus nombres. Consecuentemente, el siguiente fragmento de código genera un resultado inesperado:

mes <- c("mar", "feb", "ene", "abr")
mes <- factor(mes, levels = c("enero", "febrero", "marzo", "abril"))
levels(mes)
[1] "enero"   "febrero" "marzo"   "abril"  

Hasta aquí todo pareciera ir bien. El factor mes reconoció los nuevos nombres para las etiquetas de sus niveles.

Obsérvese, sin embargo, lo que sucede con el vector mes.

print(mes)
[1] <NA> <NA> <NA> <NA>
Levels: enero febrero marzo abril

En la sección 11.3 se detalla el significado de la etiqueta NA.

De manera breve podemos decir que cuando se auscultan los elementos del vector mes y no se encuentra correspondencia entre estos y los niveles del factor, se interpretan como información faltante.

10.4.2 Redefinición del primer nivel

Un caso particular de reordenamiento de los niveles de un factor surge cuando se necesita definir un nivel determinado como nivel de referencia.

Teniendo en cuenta que algunas funciones toman el primer nivel del factor como nivel de referencia, bastaría con ubicar el nivel deseado en la primera posición antes de aplicar tales funciones.

Para tal efecto se usa la función relevel (reference level).

Considérese nuevamente el factor a definido anteriormente y supóngase que se usará como argumento de una función, mediante la cual se pretende comparar los niveles de "j" contra los demás niveles.

Para tal efecto, se define “j” como el nivel de referencia, ubicándolo en la primera posición.

a <- factor(c("j", "m", "f", "m", "m", "f"))
levels(a)
[1] "f" "j" "m"

Hasta aquí todo lo que se ha hecho es definir el vector a como factor (¡en una sola instrucción!). Tal y como era de esperarse el orden de los niveles queda definido por el orden alfabético.

La siguiente instrucción redefine el orden, ubicando el nivel “j” en la primera posición:

a <- relevel(a, "j")
levels(a)
[1] "j" "f" "m"

Tal y como se observa, “j” queda definido como el nivel de referencia (primer nivel). Los demás niveles se reacomodan a partir de este, manteniendo el orden alfabético o cualquier otro orden personalizado que se hubiera definido.

¿Cómo se define la clase por defecto para un vector dentro de un data frame?

Cuando se importan datos, mediante cualquiera de las estrategias presentadas en el capítulo 6, el objeto resultante es un data frame, en el que la clase de cada uno de sus vectores queda definida automáticamente por su correspondiente contenido.

Las columnas que solo contienen información numérica se importan como vectores de la clase numeric, mientras que las que contienen caracteres o una combinación de números y caracteres se importan como objetos de la clase character.

Hay un aspecto particular de los vectores de la clase factor que podría generar desconcierto: su tipo.

b <- factor(c(9, 5, 3, 6, 10, 5, 5, 3))
typeof(b)
[1] "integer"

¡No parece tan desconcertante!

Pero veamos ahora el tipo del vector a:

a <- factor(c("j", "m", "f", "m", "m", "f"))
typeof(a)
[1] "integer"

Esto sí resulta bastante desconcertante. ¿Cómo puede ser que un vector cuyos elementos son “j”, “m” y “f” sea de tipo integer?

Aunque parezca extraño, así es: el tipo de todos los vectores de la clase factor —sin importar su tipo original— es integer.

Si no quiere quedarse con la intriga, puede averiguar el porqué de este comportamiento.

A continuación se discute el proceso interno involucrado en la definición de factores.

Aunque se trata de un tecnicismo, este permite resolver un par de aspectos desconcertantes relativos a los objetos de la clase factor.

En primera instancia, que los factores no son vectores, y en segunda instancia, que, sin importar cuál sea su contenido, siempre son de tipo entero.

La transformación de un vector a la clase factor se realiza en cinco pasos, tal y como se ilustra en la figura 10.1:

Figura 10.1: Proceso de generación de un factor
  1. El sistema determina los niveles, evaluando el conjunto de elementos únicos, mediante la función unique, o tomándolos del argumento levels, en caso de que este haya sido suministrado por el usuario.

  2. Mediante la función match, se genera un vector de índices enteros que guardan correspondencia con los niveles ordenados del vector.

  3. Se le asigna el atributo levels, al objeto que contiene los índices.

  4. Se clasifica dicho objeto como factor.

  5. Se remplaza el vector original con el objeto creado.

El tercer paso, es decir, la asignación del atributo levels al vector de índices enteros hace que este deje de ser reconocido como vector mediante la función is.vector. Esta función —más allá de lo que su nombre pueda sugerir— solo reconoce como vector a una estructura en la que todos sus elementos sean de un modo específico y que no tenga ningún atributo adicional al nombre de los elementos. Luego, aunque, en realidad, el objeto en cuestión sí sigue siendo un vector, no es reconocido como tal mediante la función is.vector, por el hecho de habérsele agregado el atributo levels.

Cuando, mediante el paso 4, a este “vector” de enteros se le asigna la clase factor, se establece una asociación entre su contenido y los correspondientes niveles, siendo estos últimos los que se muestran, pareciendo que el objeto original no se hubiera modificado. No obstante, lo que realmente queda almacenado internamente es el vector de índices enteros.

Esto explica por qué el tipo de cualquier objeto de la clase factor es integer.

Para recuperar el contenido del vector de índices enteros, basta con desclasificarlo o retirarle el atributo de clase, mediante la función unclass.

a <- factor(c("j", "m", "f", "m", "m", "f"))
unclass(a)
[1] 2 3 1 3 3 1
attr(,"levels")
[1] "f" "j" "m"

10.5 Tipo lógico

En programación, los booleanos son variables que solo pueden tomar dos posibles valores: falso y verdadero.

En R, los booleanos son objetos de tipo lógico (logical), en los que TRUE o T representa el valor verdadero, mientras que FALSE o F representa el valor falso. Estas constantes se escriben siempre en mayúscula sostenida y no van entrecomilladas.

Las constantes lógicas aparecen frecuentemente como argumentos de funciones, para discernir entre dos líneas de acción. También es común que se generen como resultado de alguna función.

Cuando se realizan operaciones aritméticas propias de variables numéricas, TRUE toma el valor de 1 y FALSE, de 0. Esto puede ser de utilidad para contabilizar el número de elementos que satisfacen una condición.

Considérese el siguiente vector.

x <- c(7, 23, 11, 8, 45, 90, 16, 21, 34, 57, 69)

Si se quisiera averiguar, por ejemplo, cuáles elementos son mayores que 20, se escribiría:

x > 20
 [1] FALSE  TRUE FALSE FALSE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE  TRUE

Para contabilizar el número de elementos que satisfacen esa condición, basta con sumar las respuestas verdaderas, escribiendo sum(x > 20).

Internamente, esto equivale a la siguiente suma: sum(c(0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1)).

sum(x > 20)
[1] 7
¡Una sola vía!

Debe tenerse presente que la relación entre los valores lógicos y las constante numéricas 0 y 1 solamente aplica en una vía; es decir que, cuando sea requerido, las constantes lógicas toman valores numéricos.

No obstante, no puede usarse 1 en lugar de TRUE ni 0 en lugar de FALSE.

Las funciones isTRUE e isFALSE permiten verificar si sus argumentos son verdaderos o falsos, correspondientemente.

10.6 Resumen de objetos contendores de información

Cuando se habla de contenedores de información en R, se hace referencia a los objetos detallados en el capítulo 8, sin considerar objetos de otra índole que cumplen diferentes roles en R, tales como las funciones, los entornos, las expresiones y las fórmulas.

En el ámbito de los contenedores de información, no existe en R el concepto de escalares, como elementos aislados por fuera de una estructura contenedora; todos los elementos —aun siendo unitarios— están contenidos en un vector. Los vectores son las estructuras contenedoras básicas. Todas las estructuras contenedoras complejas están conformadas en última instancia por vectores.

Todos los elementos de un vector tienen que ser de la misma naturaleza. Esto define el tipo de vector (typeof), siendo los más comunes integer, double, logical, character y complex.

Aunque eventualmente los vectores pueden hacerse corresponder con el concepto de los vectores del álgebra lineal (cf. sección 8.2.1), en principio son independientes de tal concepto, por lo que no se habla ni de vectores fila ni de vectores columna; simplemente, de vectores.

Los vectores son unidimensionales. No obstante, son susceptibles de conformar arreglos rectangulares o hiperrectangulares.

La matriz es el más básico de los arreglos dimensionales conformados por vectores. La matriz tiene dos dimensiones: filas y columnas. Para la conformación de tales estructuras basta con ligar un atributo de dimensionalidad al vector. Tales arreglos seguirán conteniendo elementos de la misma naturaleza, por lo cual siguen siendo del mismo tipo del vector base.

A estos objetos que solamente permiten alojar elementos de naturaleza común se les denomina atómicos.

La figura 10.2 se representan una serie de objetos atómicos. El panel de la izquierda representa una serie de vectores de diferentes tipos y de diferentes tamaños. No obstante, cada vector contiene elementos de un tipo común.

El panel de la derecha representa un arreglo rectangular en dos dimensiones (una matriz), cuyos elementos son todos del mismo tipo.

Cada uno de los vectores del panel izquierdo es un objeto atómico. La matriz que aparece representada en el panel derecho también es un objeto atómico.

Figura 10.2: Representación de objetos atómicos

Cualquier estructura contenedora que permita alojar elementos de diversa naturaleza se denomina recursiva.

La forma más general de las estructuras recursivas es la lista. Esta estructura permite alojar objetos de diferente tipo, sin importar el tamaño ni la dimensionalidad de los mismos, ni que estos tengan o no tengan nombres (Ver figura 10.3 (a)).

Un caso particular de lista es el data frame. Esta estructura recursiva está diseñada para alojar vectores de diferentes tipos. Los vectores son los elementos básicos de esta lista. Adicionalmente debe satisfacerse que todos los vectores tengan nombre y que tengan el mismo tamaño (Ver figura 10.3 (b)).

(a) Lista general
(b) Lista particular: Data frame
Figura 10.3: Listas generales y particulares

La figura 10.3 (a) ilustra una lista con 6 elementos: dos matrices, un data frame y tres vectores, uno de los cuales es de tamaño 1.

Nótese que las dos matrices contenidas en esta lista son de diferente tipo y de diferente tamaño, no obstante, cada matriz, al ser un objeto atómico, está conformada por elementos del mismo tipo.

Asimismo, los vectores pueden ser de diferente tipo y de diferente tamaño, pero, al tratarse también de objetos atómicos, cada vector está conformado por elementos del mismo tipo. Vale la pena llamar la atención sobre el tercer elemento de la lista: un vector de tamaño 1.

El cuarto elemento de esta lista es un data frame, el cual tiene las mismas restricciones del que se presenta en la figura 10.3 (b): está conformado por vectores del mismo tamaño, cada uno de los cuales tiene un nombre.


  1. Puede verificarse usando la instrucción class(a).↩︎

  2. Lo mismo aplica para el ejemplo con los vectores b y b2.↩︎