Tablas

Creación de tablas desde cero

La tabla será posiblemente el objeto de R más utilizado en el contexto de nuestro trabajo científico. Consiste en una estructura de datos que organiza la información en filas y columnas. Desde un punto de vista programático, es útil considerar a las tablas como una colección de vectores. De esta manera, veremos que manipular las filas y columnas de una tabla es similar a manipular vectores individuales. En una tabla, cada columna es un vector, pudiendo haber vectores de distintos tipos en una misma tabla (por ejemplo, vectores numéricos y de texto).

Para crear una tabla desde cero, en R utilizamos la clase data.frame, y una función con el mismo nombre para generarla:

df <- data.frame(Especie = c("Algarrobo", "Piquillin", "Molle"),
                 Individuos = c(15, 17, 9))

Aquí, generamos un objeto de tipo data.frame, definiendo el nombre de cada columna e igualando cada columna a un contenido en forma de vector. Existen algunas buenas prácticas a la hora de definir los nombres de las columnas, tanto si la generamos en R o con algún software externo (Excel, Calc, Google Sheets, etc.), que nos ahorrarán dolores de cabeza en el futuro:

  • Los nombres de las columnas no pueden comenzar con un número.
  • Utilizar caracteres alfanuméricos simples. Evitar la ñ u otras letras específicas de otros idiomas.
  • Evitar símbolos extraños reservados para el uso del lenguaje, tales como: !?¿$%&()@*+-><{}
  • Evitar el uso de tildes, comas y puntos y coma.
  • Evitar el uso de espacios, y reemplazarlos por punto, guión medio o guión bajo.

Al crear o cargar una tabla, veremos la tabla en el panel de entorno. Desplegando la flechita azul podemos obtener información sobre las columnas de la tabla. Además, haciendo click sobre el nombre del objeto, podemos visualizar la tabla entera en otra pestaña.

Normalmente, las tablas se generan fuera del entorno R para ser luego importadas a R. La creación de tablas desde cero adquiere mayor relevancia para tareas posteriores en el flujo de trabajo. Ahondaremos en esto más adelante.

Antes que nada, descargue el archivo zip con los datos necesarios para realizar el práctico, y extraiga los archivos dentro de una carpeta llamada “data” en el directorio “dia2” del proyecto del curso.

Importación y exploración de tablas

Existen muchas formas de importar tablas al ambiente de R. Quizás la forma más fácil es hacerlo desde la interfaz gráfica de RStudio: en el panel de entorno, clickeando en el ícono de importación.

Para hacerlo desde la consola, read.table() es la función genérica, indicando la dirección y nombre del archivo a importar:

iris <- read.table(file = "data/iris.csv")

En realidad, el dataset iris viene instalado por defecto con R, pero para ejemplificar lo hacemos visible en el panel de entorno mediante su importación. Esta tabla contiene el valor de distintas variables para 50 flores de 3 especies del género Iris.

Otras funciones similares son read.csv() y read.csv2(), cuyo funcionamiento es idéntico a read.table() pero sus argumentos poseen otras especificaciones por defecto.

Existen algunas funciones útiles para obtener información sobre su contenido o estructura. Simplemente llamando a la tabla por su nombre en la consola, R imprime todo el contenido de la tabla. Generalmente no es útil ver una tabla de esta forma, sobre todo si tiene muchas filas y columnas. Podemos visualizar las primeras filas con la función head():

head(iris)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa

La cual imprime las primeras 6 filas de la tabla. Podemos cambiar este número con el segundo argumento de la función (n):

head(iris, n = 10)
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1           5.1         3.5          1.4         0.2  setosa
2           4.9         3.0          1.4         0.2  setosa
3           4.7         3.2          1.3         0.2  setosa
4           4.6         3.1          1.5         0.2  setosa
5           5.0         3.6          1.4         0.2  setosa
6           5.4         3.9          1.7         0.4  setosa
7           4.6         3.4          1.4         0.3  setosa
8           5.0         3.4          1.5         0.2  setosa
9           4.4         2.9          1.4         0.2  setosa
10          4.9         3.1          1.5         0.1  setosa

La función tail() imprime las últimas filas de la tabla.

tail(iris)
    Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
145          6.7         3.3          5.7         2.5 virginica
146          6.7         3.0          5.2         2.3 virginica
147          6.3         2.5          5.0         1.9 virginica
148          6.5         3.0          5.2         2.0 virginica
149          6.2         3.4          5.4         2.3 virginica
150          5.9         3.0          5.1         1.8 virginica

La función str() devuelve información básica sobre las columnas, similar a lo que se detalla en el panel de entorno.

str(iris)
'data.frame':   150 obs. of  5 variables:
 $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
 $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
 $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
 $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
 $ Species     : chr  "setosa" "setosa" "setosa" "setosa" ...

La función dim() imprime las dimensiones de la tabla (número de filas y de columnas, respectivamente).

dim(iris)
[1] 150   5

Las funciones nrow() y ncol() devuelven dichos valores individualmente.

La función colnames() devuelve los nombres de las columnas en un vector de texto:

colnames(iris)
[1] "Sepal.Length" "Sepal.Width"  "Petal.Length" "Petal.Width"  "Species"     

La función rownames() funciona de manera idéntica, pero para las filas.

La función table() genera una tabla de contingencia, mostrando el número de ocurrencias de cada valor distinto en una columna. Por ejemplo, para la columna Species:

table(iris$Species)

    setosa versicolor  virginica 
        50         50         50 

Al importar un dataset, una buena práctica es realizar una exploración inicial de la estructura de la tabla, especialmente sus columnas y nombres. La importación incorrecta de datasets es una fuente de error muy frecuente.

Manipulación de tablas

Una tarea usual consiste en, una vez cargada una tabla, realizar modificaciones a la misma. Esto incluye, por ejemplo, obtener una nueva tabla de acuerdo a ciertas condiciones, eliminar ciertas filas o agregar nuevas columnas.

Indexación y filtrado

Los corchetes son muy versátiles para manipular y extraer datos de un data.frame. La sintaxis general es data[filas, columnas], siendo posible indicar índices, expresiones lógicas o nombres de columnas para obtener un subconjunto de datos (i.e. un subset).

iris_sub <- iris[1:5, ]

Aquí, generamos un nuevo dataset con sólo las 5 primeras filas de la tabla iris. Notar que al mismo tiempo que se realiza el filtrado, se asigna a un nuevo objeto. Si la asignación no se realiza, el objeto no queda guardado en el entorno, pero es útil si queremos probar rápidamente alguna línea de código, sin necesidad de generar un nuevo objeto.

Ahora, generamos un nuevo dataset que contiene sólo las columnas "Species" y "Sepal.Length".

iris_sub2 <- iris[, c("Species", "Sepal.Length")]
head(iris_sub2)
  Species Sepal.Length
1  setosa          5.1
2  setosa          4.9
3  setosa          4.7
4  setosa          4.6
5  setosa          5.0
6  setosa          5.4

Notar que la nueva tabla mantiene la identidad pero también el orden de las columnas indicadas en la sintaxis. Por lo tanto, esta sintaxis también puede utilizarse para cambiar el orden de las columnas, pudiendo indicar el nombre (como en el ejemplo) o los índices (para este ejemplo sería c(5, 1)).

La función order() permite ordenar una tabla de acuerdo a los valores de una columna o más columnas:

iris <- iris[order(iris$Sepal.Length), ]
head(iris, 10)
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
14          4.3         3.0          1.1         0.1  setosa
9           4.4         2.9          1.4         0.2  setosa
39          4.4         3.0          1.3         0.2  setosa
43          4.4         3.2          1.3         0.2  setosa
42          4.5         2.3          1.3         0.3  setosa
4           4.6         3.1          1.5         0.2  setosa
7           4.6         3.4          1.4         0.3  setosa
23          4.6         3.6          1.0         0.2  setosa
48          4.6         3.2          1.4         0.2  setosa
3           4.7         3.2          1.3         0.2  setosa

Para seleccionar una columna de una tabla utilizamos la sintaxis tabla$columna.

En este contexto, la expresión order(iris$Sepal.Length) genera nuevos índices para cada fila, indicando que nueva posición deberían ocupar para que la tabla quede ordenada. Por defecto, order() ordena de manera creciente. Indicando decreasing = TRUE se ordena la tabla de manera decreciente.

Es posible indicar una ordenación por más de una columna:

iris <- iris[order(iris$Species, iris$Sepal.Length, decreasing = TRUE), ]
head(iris, 10)
    Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
132          7.9         3.8          6.4         2.0 virginica
118          7.7         3.8          6.7         2.2 virginica
119          7.7         2.6          6.9         2.3 virginica
123          7.7         2.8          6.7         2.0 virginica
136          7.7         3.0          6.1         2.3 virginica
106          7.6         3.0          6.6         2.1 virginica
131          7.4         2.8          6.1         1.9 virginica
108          7.3         2.9          6.3         1.8 virginica
110          7.2         3.6          6.1         2.5 virginica
126          7.2         3.2          6.0         1.8 virginica

Es posible obtener subconjuntos de datos que cumplan con ciertas condiciones. Las condiciones, en general, serán sobre los datos contenidos en las filas.

iris_sub3 <- iris[iris$Sepal.Length > 5, ]
head(iris_sub3)
    Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
132          7.9         3.8          6.4         2.0 virginica
118          7.7         3.8          6.7         2.2 virginica
119          7.7         2.6          6.9         2.3 virginica
123          7.7         2.8          6.7         2.0 virginica
136          7.7         3.0          6.1         2.3 virginica
106          7.6         3.0          6.6         2.1 virginica

Aquí estamos seleccionando aquellas filas para los cuales Sepal.Length es mayor a 5. ¿Por qué esta sintaxis tan rara? En realidad, lo que estamos haciendo es indicar una expresión lógica para las filas, especificando el nombre de la tabla y la columna (es un vector!) mediante el operador $:

iris$Sepal.Length > 5
  [1]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
 [13]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
 [25]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
 [37]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
 [49]  TRUE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
 [61]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
 [73]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
 [85]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
 [97]  TRUE FALSE FALSE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
[109]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
[121]  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[133] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[145] FALSE FALSE FALSE FALSE FALSE FALSE

Es posible, entonces, indicar cualquier expresión lógica referida a una o más columnas de la tabla. El mismo filtrado puede realizarse mediante la función subset(), siendo este método un poco menos “engorroso”:

iris_sub3 <- subset(iris, Sepal.Length > 5)
head(iris_sub3)
    Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
132          7.9         3.8          6.4         2.0 virginica
118          7.7         3.8          6.7         2.2 virginica
119          7.7         2.6          6.9         2.3 virginica
123          7.7         2.8          6.7         2.0 virginica
136          7.7         3.0          6.1         2.3 virginica
106          7.6         3.0          6.6         2.1 virginica

El primer argumento indica el conjunto de datos a utilizar, y el segundo argumento la expresión a aplicar para filtrar dicho conjunto de datos. Notar que no es necesario aclarar la sintaxis iris$Sepal.Length. Esto hace más fácil la escritura ante la necesidad de indicar múltiples condiciones lógicas.

El argumento select permite además seleccionar una o más columnas a dejar luego del filtro:

iris_sub4 <- subset(iris, Sepal.Length > 5, select = c("Species", "Sepal.Width"))
head(iris_sub4)
      Species Sepal.Width
132 virginica         3.8
118 virginica         3.8
119 virginica         2.6
123 virginica         2.8
136 virginica         3.0
106 virginica         3.0

¿Cómo modificamos el contenido de una tabla? Para ello, debemos primero seleccionar qué campos queremos modificar, y luego asignarle el nuevo contenido. Por ejemplo, si quisiéramos cambiar el nombre de una de las especies por un sinónimo:

iris$Species[iris$Species == "setosa"] <- "arctica"

Analicemos con cuidado esta expresión. La primera parte, iris$Species, indica la columna Species. Es decir, selecciono dicho el vector-columna. Entre corchetes indico una expresión lógica para seleccionar todos aquellos elementos del vector en donde dicha expresión sea verdadera. En este caso, donde el vector iris$Species sea igual (==) a "setosa". Finalmente, le asignamos el valor "arctica". Dado que queremos un único valor para todos los campos, no es necesario asignar un vector con elementos repetidos para "arctica".

Para pensar: ¿Por qué no hay coma en la sintaxis de filtrado anterior?

Podemos corroborar que se ha modificado la tabla con la función unique(), que devuelve los valores únicos para un objeto dado:

unique(iris$Species)
[1] "virginica"  "versicolor" "arctica"   

Ejercicio 1

Genere nuevas tablas a partir de la tabla iris (utilizar nombres nuevos para cada tabla), para:

  • La especie “setosa”, y las columnas “Species” y “Petal.Width”.
  • Las especies “setosa” y “versicolor”. Deberá utilizar el operador %in% en vez del == (recordá de qué estamos hablando en Vectores).
  • Flores en donde “Petal.Length” es mayor o igual a 4.
  • Flores en donde “Sepal.Width” es mayor a 3 o “Sepal.Length” < 4.

Creación de nuevas columnas

Para agregar una nueva columna utilizamos la sintaxis data$nueva_columna. La columna debe asociarse a un contenido, las filas, mediante el operador de asignación. Lo que hacemos es agregar un nuevo vector-columna a la colección de vectores-columnas que es el data.frame:

iris$Seed.set <- NA
head(iris)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species Seed.set
1          5.1         3.5          1.4         0.2  setosa       NA
2          4.9         3.0          1.4         0.2  setosa       NA
3          4.7         3.2          1.3         0.2  setosa       NA
4          4.6         3.1          1.5         0.2  setosa       NA
5          5.0         3.6          1.4         0.2  setosa       NA
6          5.4         3.9          1.7         0.4  setosa       NA

Vemos que se ha generado una nueva columna con campos igual a NA (no dato). Crear una columna con datos vacíos de esta manera puede llegar a ser útil para luego ir rellenando cada fila de acuerdo al contenido de otras columnas. Spoiler: el control de flujo podría ser útil para esta tarea.

La siguiente línea genera una nueva columna a partir del cociente de otras dos:

iris$Sepal.Petal.ratio <- iris$Sepal.Length/iris$Petal.Length
head(iris)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species Seed.set
1          5.1         3.5          1.4         0.2  setosa       NA
2          4.9         3.0          1.4         0.2  setosa       NA
3          4.7         3.2          1.3         0.2  setosa       NA
4          4.6         3.1          1.5         0.2  setosa       NA
5          5.0         3.6          1.4         0.2  setosa       NA
6          5.4         3.9          1.7         0.4  setosa       NA
  Sepal.Petal.ratio
1          3.642857
2          3.500000
3          3.615385
4          3.066667
5          3.571429
6          3.176471

La función ifelse() es útil para generar nuevas columnas basadas en la información de columnas ya existentes. Funciona de manera similar a un “SI()” de Excel/Calc. El primer argumento establece la condición, el segundo el valor devuelto si la condición es verdadera, y el tercero si es falsa:

iris$Sepal.width.cat <- ifelse(iris$Sepal.Width < 3, "Corto", "Largo")
head(iris)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species Seed.set
1          5.1         3.5          1.4         0.2  setosa       NA
2          4.9         3.0          1.4         0.2  setosa       NA
3          4.7         3.2          1.3         0.2  setosa       NA
4          4.6         3.1          1.5         0.2  setosa       NA
5          5.0         3.6          1.4         0.2  setosa       NA
6          5.4         3.9          1.7         0.4  setosa       NA
  Sepal.Petal.ratio Sepal.width.cat
1          3.642857           Largo
2          3.500000           Largo
3          3.615385           Largo
4          3.066667           Largo
5          3.571429           Largo
6          3.176471           Largo

Es posible anidar la función ifelse() para obtener más categorías:

iris$Sepal.width.cat2 <- ifelse(iris$Sepal.Width < 2.8, "Corto",
                               ifelse(iris$Sepal.Width >= 2.8 & iris$Sepal.Width < 3.3, "Mediano", "Largo"))
head(iris)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species Seed.set
1          5.1         3.5          1.4         0.2  setosa       NA
2          4.9         3.0          1.4         0.2  setosa       NA
3          4.7         3.2          1.3         0.2  setosa       NA
4          4.6         3.1          1.5         0.2  setosa       NA
5          5.0         3.6          1.4         0.2  setosa       NA
6          5.4         3.9          1.7         0.4  setosa       NA
  Sepal.Petal.ratio Sepal.width.cat Sepal.width.cat2
1          3.642857           Largo            Largo
2          3.500000           Largo          Mediano
3          3.615385           Largo          Mediano
4          3.066667           Largo          Mediano
5          3.571429           Largo            Largo
6          3.176471           Largo            Largo

Traducción en palabras: Si el largo del sépalo es menor a 2.8, clasificarlo como “Corto”. En caso contrario, si es mayor o igual a 2.8 Y menor a 3.3, clasificarlo como “Mediano”; si no es así, clasificarlo como “Largo”.

Con la función factor(), es posible asignarle a un vector-columna la clase de factor:

iris$Sepal.width.cat2 <- factor(iris$Sepal.width.cat2)
iris$Sepal.width.cat2
  [1] Largo   Mediano Mediano Mediano Largo   Largo   Largo   Largo   Mediano
 [10] Mediano Largo   Largo   Mediano Mediano Largo   Largo   Largo   Largo  
 [19] Largo   Largo   Largo   Largo   Largo   Largo   Largo   Mediano Largo  
 [28] Largo   Largo   Mediano Mediano Largo   Largo   Largo   Mediano Mediano
 [37] Largo   Largo   Mediano Largo   Largo   Corto   Mediano Largo   Largo  
 [46] Mediano Largo   Mediano Largo   Largo   Mediano Mediano Mediano Corto  
 [55] Mediano Mediano Largo   Corto   Mediano Corto   Corto   Mediano Corto  
 [64] Mediano Mediano Mediano Mediano Corto   Corto   Corto   Mediano Mediano
 [73] Corto   Mediano Mediano Mediano Mediano Mediano Mediano Corto   Corto  
 [82] Corto   Corto   Corto   Mediano Largo   Mediano Corto   Mediano Corto  
 [91] Corto   Mediano Corto   Corto   Corto   Mediano Mediano Mediano Corto  
[100] Mediano Largo   Corto   Mediano Mediano Mediano Mediano Corto   Mediano
[109] Corto   Largo   Mediano Corto   Mediano Corto   Mediano Mediano Mediano
[118] Largo   Corto   Corto   Mediano Mediano Mediano Corto   Largo   Mediano
[127] Mediano Mediano Mediano Mediano Mediano Largo   Mediano Mediano Corto  
[136] Mediano Largo   Mediano Mediano Mediano Mediano Mediano Corto   Mediano
[145] Largo   Mediano Corto   Mediano Largo   Mediano
Levels: Corto Largo Mediano

Recordemos, como vimos en la clase de Vectores, que al indicar un factor es posible también indicar el orden de sus niveles. Esto también aplica en el contexto de manipulación de tablas.

No obstante, a la hora de generar vectores (aquí asociados a una tabla), la función cut() es más adecuada. Esta función divide el rango de valores de un vector en intervalos específicos, y define nombres para cada intervalo. Por ejemplo:

iris$Sepal.width.cat4 <- cut(x = iris$Sepal.Width, breaks = 3, labels = c("Corto", "Mediano", "Largo"))
head(iris)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species Seed.set
1          5.1         3.5          1.4         0.2  setosa       NA
2          4.9         3.0          1.4         0.2  setosa       NA
3          4.7         3.2          1.3         0.2  setosa       NA
4          4.6         3.1          1.5         0.2  setosa       NA
5          5.0         3.6          1.4         0.2  setosa       NA
6          5.4         3.9          1.7         0.4  setosa       NA
  Sepal.Petal.ratio Sepal.width.cat Sepal.width.cat2 Sepal.width.cat4
1          3.642857           Largo            Largo          Mediano
2          3.500000           Largo          Mediano          Mediano
3          3.615385           Largo          Mediano          Mediano
4          3.066667           Largo          Mediano          Mediano
5          3.571429           Largo            Largo          Mediano
6          3.176471           Largo            Largo            Largo

Aquí, creamos una nueva columna, construida a partir de los valores de la columna Sepal.Width. Mediante breaks = 3, indicamos que el rango de valores de Sepal.Width se divida en 3 partes iguales, y definimos los nombres para cada intervalo en el argumento labels. El argumento breaks también admite un vector numérico con los valores que dividirán los intervalos generados.

La función cut() devuelve un factor con niveles, adecuado en contextos de análisis estadísticos y graficación:

class(iris$Sepal.width.cat4)
[1] "factor"

Ejercicio 2

Genere una nueva columna para la tabla iris llamada “Flower.color”. Para las especies Iris setosa y Iris versicolor, asignarle “Purple”. Para la especie Iris virginica asignarle “Blue”.

Unión de tablas

Las funciones rbind() y cbind() permiten añadir filas (“r” de “row”) y columnas (“c” de “column”) a un data.frame, respectivamente.

A modo de ejemplo, importaremos un data.frame conteniendo las medidas de variables florales (inventadas para el ejemplo) para la especie Iris florentina. Asimismo, cargamos nuevamente la tabla iris con la estructura y datos originales:

# Cargamos nuevamente la tabla con la estructura y datos originales
iris <- read.table("data/iris.csv")
# Cargamos la nueva tabla
iris_fl <- read.table("data/iris_florentina.csv")
head(iris_fl)
  Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
1          6.9         2.5          3.8         1.6 florentina
2          5.4         3.1          3.5         2.0 florentina
3          7.8         3.0          5.8         0.5 florentina
4          6.2         2.0          3.5         0.3 florentina
5          7.2         3.2          5.2         2.2 florentina
6          5.7         1.9          5.4         0.6 florentina

Podemos ver que tiene la misma cantidad de columnas y los mismos nombres que la tabla original con la que venimos trabajando. Lo lógico sería unir estas dos tablas mediante rbind():

iris_new <- rbind(iris, iris_fl)

La exploración de las últimas filas de la nueva tabla comprueban que hemos hecho la unión correctamente:

tail(iris_new)
    Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
451          5.1         3.2          4.0         0.6 florentina
461          6.7         4.0          5.0         0.7 florentina
471          5.3         3.9          4.2         0.6 florentina
481          7.1         2.5          4.8         1.9 florentina
491          4.7         2.1          3.5         1.0 florentina
501          4.7         2.0          5.0         0.9 florentina

Al utilizar rbind(), debemos asegurarnos de que la cantidad de columnas y sus nombres sean idénticos. De lo contrario, R devuelve un error.

De manera similar, la función cbind() une tablas a lo largo de sus columnas, y su sintaxis tiene la forma de cbind(tabla1, tabla2). Lógicamente, el número de filas de ambas tablas debe ser el mismo, de lo contrario obtendremos una advertencia o un error.

Usualmente, la información está repartida en más de una tabla. ¿Cómo generamos una nueva tabla que reúna toda la información junta? Para ello utilizamos la función merge() (fusionar/combinar). A modo de ejemplo, cargamos una nueva tabla con los nombres comunes de las especies de Iris con las que venimos trabajando:

iris_names <- read.table("data/iris_common_names.csv")
iris_names
     Species           Common.name
1     setosa Iris de punta erizada
2 versicolor Iris azul de arlequín
3  virginica     Iris azul del sur
4 florentina           Iris blanca

Vemos que la tabla tiene dos columnas, una para la especie y otra para el nombre común. La idea sería agregar a la tabla generada anteriormente iris_new una nueva columna con los nombres comunes, repetidos para cada fila correspondiente. Hacerlo manualmente no es viable, siendo la función merge() la adecuada para esta tarea:

iris_new2 <- merge(iris_new, iris_names, by = "Species")
head(iris_new2)
     Species Sepal.Length Sepal.Width Petal.Length Petal.Width Common.name
1 florentina          6.9         2.5          3.8         1.6 Iris blanca
2 florentina          5.4         3.1          3.5         2.0 Iris blanca
3 florentina          7.8         3.0          5.8         0.5 Iris blanca
4 florentina          6.2         2.0          3.5         0.3 Iris blanca
5 florentina          7.2         3.2          5.2         2.2 Iris blanca
6 florentina          5.7         1.9          5.4         0.6 Iris blanca

Los primeros dos argumentos indican las tablas a combinar, y el argumento by la columna común a ambas tablas (pueden indicarse más columnas con la sintaxis c("columna1", "columna2")). La función busca coincidencias de campos en la columna (o columnas) en ambas tablas, y rellena correspondientemente.

La función merge() es una función muy útil para combinar tablas, pero debe utilizarse con cuidado, pensando qué tabla final es la que nos proponemos generar. Por ejemplo, obtendremos resultados distintos si los campos de la columna especificada en by (en el ejemplo, las especies) difieren de alguna manera, o si hay campos faltantes. Esto es particularmente importante para datasets muy grandes en donde no podemos corroborar que lo que se ha fusionado es lo que realmente nos proponíamos hacer. Es conveniente explorar las distintas opciones en ?merge.

Cálculo de estadísticas sobre tablas

La función aggregate() es útil para generar tablas con estadísticas de resumen de acuerdo a los niveles de un determinado factor. Por ejemplo, para calcular el valor medio de la longitud del sépalo para cada especie utilizamos la siguiente sintaxis:

iris_mean <- aggregate(Sepal.Width ~ Species, data = iris, FUN = mean)
iris_mean
     Species Sepal.Width
1     setosa       3.428
2 versicolor       2.770
3  virginica       2.974

El primer argumento indica una fórmula con la sintaxis variable ~ factor, el segundo el data.frame de referencia y el tercero la función a aplicar. Esta función debe existir en el entorno, tanto por defecto (las cuales no aparecen visibles, como mean() o sd()) o programadas por el usuario.

No es útil que la columna de esta nueva tabla se siga llamando Sepal.Width. Podemos cambiarla asignando un nuevo nombre al vector generado con la función colnames():

colnames(iris_mean) <- c("Species", "Sepal.Width.mean")
iris_mean
     Species Sepal.Width.mean
1     setosa            3.428
2 versicolor            2.770
3  virginica            2.974

Para pensar: Aquí debí aclarar “Species” para la primera columna, que no cambia de nombre. ¿Cuál podría ser la línea de código para cambiar únicamente la segunda columna?

Para calcular la media de varias variables en una misma línea de código, utilizamos la función cbind():

iris_mean <- aggregate(cbind(Sepal.Length, Sepal.Width) ~ Species, data = iris, FUN = mean)
iris_mean
     Species Sepal.Length Sepal.Width
1     setosa        5.006       3.428
2 versicolor        5.936       2.770
3  virginica        6.588       2.974

El uso del punto . es útil para hacer el cálculo sobre todas las columnas del dataset, a excepción de la definida a la derecha de ~:

iris_mean <- aggregate(. ~ Species, data = iris, FUN = mean)
iris_mean
     Species Sepal.Length Sepal.Width Petal.Length Petal.Width
1     setosa        5.006       3.428        1.462       0.246
2 versicolor        5.936       2.770        4.260       1.326
3  virginica        6.588       2.974        5.552       2.026

Ejercicio 3

Generar una tabla que contenga el cálculo de la media de “Petal.Length” para cada especie. Luego, generar otra tabla con la desviación estándar de “Petal.Length” para cada especie. En cada tabla, cambiar los nombres de las nuevas columnas a “Petal.Length.mean” y “Petal.Length.sd”. Finalmente, unir ambas tablas con cbind().

Exportación de tablas

La exportación de un data.frame se realiza con write.table:

write.table(iris_mean, file = "iris.mean.csv", row.names = F)

Análogamente a las funciones de importación, también disponemos de las funciones write.csv() y write.csv2.

Otras estructuras de datos

Matrices

Las matrices en R pertenecen a la clase matrix. Es una colección de datos agrupados en filas y columnas y, si bien admiten objetos de distinto tipo, en general se utilizan para almacenar números. La utilidad principal de una matriz, a diferencia de un data.frame, es que permite operaciones matemáticas propias. Por ello, es un objeto fundamental para el análisis estadístico en múltiples disciplinas.

Para crear una matriz en R, utilizamos la función matrix():

mat <- matrix(data = 1:25, ncol = 5, nrow = 5)
mat
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    6   11   16   21
[2,]    2    7   12   17   22
[3,]    3    8   13   18   23
[4,]    4    9   14   19   24
[5,]    5   10   15   20   25

El primer argumento indica los elementos que contendrá la matriz. En este caso, se utiliza la sucesión de números enteros desde el 1 al 25. Los argumentos ncol y nrow indican el número de columnas y filas correspondientes para generar la matriz. Un detalle importante es la forma en la que los elementos de la matriz se van rellenando. Por defecto, los datos se van agregando por columnas. En cambio, si se indica byrow = TRUE, los datos se agregan por fila:

mat <- matrix(data = 1:25, ncol = 5, nrow = 5, byrow = TRUE)
mat
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    2    3    4    5
[2,]    6    7    8    9   10
[3,]   11   12   13   14   15
[4,]   16   17   18   19   20
[5,]   21   22   23   24   25

La función dim() nos indica el número de filas y columnas de la matriz:

dim(mat)
[1] 5 5

Al igual que para la clase data.frame, las funciones nrow() y ncol() indican el número de filas y columnas de una matriz.

Los corchetes son los operadores indicados para acceder a los elementos de una matriz, indicando los índices para las filas y las columnas separado por una coma:

mat[2, 3]
[1] 8

Las matrices se asocian a numerosas funciones especiales para matrices, pero no es la idea de este curso indagar mucho en este tema. Podemos mencionar las funciones rowSums() y colSums(), que devuelven las sumas de las filas y las columnas respectivamente. Análogamente, las funciones rowMeans() y colMeans() devuelven los valores medios.

Listas

Una gran parte de diversos paquetes de R contienen funciones cuyas salidas son estructuras más complejas que las aprendidas hasta ahora. Un tipo de estas estructuras es la lista; estos son objetos de clase list. Es una colección de objetos de cualquier tipo. Por ejemplo:

iris_info <- list(data = iris,
                  mean_stats = iris_mean,
                  year_of_creation = 2024)

La información guardada en una lista se accede mediante el operador $, si es que los elementos de la lista a acceder tienen asociado un nombre:

iris_info$mean_stats
     Species Sepal.Length Sepal.Width Petal.Length Petal.Width
1     setosa        5.006       3.428        1.462       0.246
2 versicolor        5.936       2.770        4.260       1.326
3  virginica        6.588       2.974        5.552       2.026

También es posible acceder a la información de un elemento de una lista indicando el índice del elemento entre corchetes dobles:

iris_info[[3]]
[1] 2024

La estructura y parte del contenido de una lista también puede desplegarse en el panel de entorno.

Es posible modificar el contenido de un elemento de la lista, incluso indicando un elemento de otra clase:

iris_info$year_of_creation <- "Dos mil veinticuatro"
iris_info$year_of_creation
[1] "Dos mil veinticuatro"

Por ejemplo, la salida de la función lm(), que confecciona una regresión lineal, es una lista:

mod <- lm(Sepal.Length ~ Petal.Length, data = iris)
is.list(mod)
[1] TRUE

Aquí, en el primer argumento, indicamos para modelar la variable Sepal.Length en función de la variable Petal.Length. El símbolo ~ (virgulilla) es el indicado para expresar la fórmula. En el argumento data indicamos el data.frame de referencia. Por otro lado, la función is.list() devuelve TRUE si el objeto indicado es de clase list.

La función summary() devuelve información relevante sobre el modelo:

mod_summ <- summary(mod)
mod_summ

Call:
lm(formula = Sepal.Length ~ Petal.Length, data = iris)

Residuals:
     Min       1Q   Median       3Q      Max 
-1.24675 -0.29657 -0.01515  0.27676  1.00269 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept)   4.30660    0.07839   54.94   <2e-16 ***
Petal.Length  0.40892    0.01889   21.65   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.4071 on 148 degrees of freedom
Multiple R-squared:   0.76, Adjusted R-squared:  0.7583 
F-statistic: 468.6 on 1 and 148 DF,  p-value: < 2.2e-16

Que también guarda la información en una lista:

is.list(mod_summ)
[1] TRUE

Es posible acceder a los coeficientes del modelo, en forma de tabla, explorando la salida de summary():

mod_summ$coefficients
              Estimate Std. Error  t value      Pr(>|t|)
(Intercept)  4.3066034 0.07838896 54.93890 2.426713e-100
Petal.Length 0.4089223 0.01889134 21.64602  1.038667e-47

Clases S4

Otra salida menos común de algunos paquetes son las clases S4. En esencia, son muy similares a las listas en el sentido de que pueden almacenar cualquier tipo de objeto. Una de las diferencias radica en que el tipo de elemento a almacenar está predefinido, y no puede cambiarse.

Un ejemplo es la salida de la función raster() del paquete raster, la cual genera un objeto de tipo raster, muy utilizado en el contexto de Sistemas de Información Geográfica. Por ahora, basta con saber que la información de estos objetos se acceden con el operador @, mediante la sintaxis objeto@elemento.

El paquete dplyr

El paquete dplyr ofrece numerosas herramientas R para manipular y transformar datos. Es parte del universo tidyverse, diseñado para facilitar el trabajo con datos tabulares de manera eficiente y legible. A diferencia del manejo básico de tablas en R, dplyr introduce una sintaxis diferente a través del operador %>%, que relaciona una expresión con otra como si fuera una oración de texto:

cargar tabla %>% seleccionar columnas %>% filtrar filas %>% crear nueva columna

Por ejemplo, dplyr ofrece funciones intuitivas para realizar operaciones comunes, como filtrar filas (filter()), seleccionar columnas (select()), reordenar datos (arrange()), crear nuevas variables (mutate()) y resumir información (summarize()).

El presente curso está orientado a entender los fundamentos básicos del lenguaje R y no ahondaremos en el uso de este paquete. No obstante, creemos que es útil saber de su existencia y explorar sobre su uso en caso de necesidad.

Ejercicio final

  1. Cargue la tabla del archivo “analisis_tierra.csv”, la cual muestra los resultados de un análisis de muestras de tierra, detallando las concentraciones de N, C y Ca. Explore su estructura y contenido utilizando las funciones head(), tail(), str() y dim().
  2. Explore los nombres de las columnas. Corrija el error en la segunda columna, que debería llamarse “Parcela”.
  3. Hay valores negativos para la concentración de Ca, lo cual no tiene sentido. Cambie estos valores por ceros.
  4. Hay valores NA en la columna que indica la concentración de N. Genere una nueva tabla, utilizando el mismo nombre que antes, que no contenga estas observaciones (filas). Puede utilizar la expresión is.na(), pero explore también la ayuda de na.omit().
  5. Cargue la tabla del archivo “analisis_tierra2.csv”, la cual muestra los resultados realizados por otra técnica (Claudia). Incorpore estos nuevos datos a la tabla anterior. Es necesario corregir algunos errores antes de hacer esto . Entre ellos, los números asignados a cada muestra deben cambiarse, para que estos no coincidan con los números de muestra de la tabla anterior.
  6. Ordene la tabla según la Parcela (orden alfabético) y por valores crecientes en la cantidad de N, en ese orden!
  7. Exprese a la columna “Tecnico” como factor.
  8. Genere una tabla de contingencia considerando la variable “Tecnico” y “Parcela”.
  9. Genere una tabla resumen con los valores medios de N, C y Ca por Parcela, y otra con los valores de desviación estándar de N, C y Ca por Técnico. Exporte dichas tablas resumen.

Volver arriba