2.2 Manipulación de datos

Una vez cargada una (o varias) bases de datos hay una series de operaciones que serán de interés para el tratamiento de datos:

  • Operaciones con variables:
    • crear
    • recodificar (e.g. categorizar)
  • Operaciones con casos:
    • ordenar
    • filtrar
  • Operaciones con tablas de datos:
    • unir
    • combinar
    • consultar

A continuación se tratan algunas operaciones básicas.

2.2.1 Operaciones con variables

2.2.1.1 Creación (y eliminación) de variables

Consideremos de nuevo la base de datos cars incluida en el paquete datasets:

data(cars)
# str(cars)
head(cars)
##   speed dist
## 1     4    2
## 2     4   10
## 3     7    4
## 4     7   22
## 5     8   16
## 6     9   10

Utilizando el comando help(cars) se obtiene que cars es un data.frame con 50 observaciones y dos variables:

  • speed: Velocidad (millas por hora)

  • dist: tiempo hasta detenerse (pies)

Recordemos que, para acceder a la variable speed se puede hacer directamente con su nombre o bien utilizando notación “matricial”.

cars$speed
##  [1]  4  4  7  7  8  9 10 10 10 11 11 12 12 12 12 13 13 13 13 14 14 14 14 15 15
## [26] 15 16 16 17 17 17 18 18 18 18 19 19 19 20 20 20 20 20 22 23 24 24 24 24 25
cars[, 1]  # Equivalente
##  [1]  4  4  7  7  8  9 10 10 10 11 11 12 12 12 12 13 13 13 13 14 14 14 14 15 15
## [26] 15 16 16 17 17 17 18 18 18 18 19 19 19 20 20 20 20 20 22 23 24 24 24 24 25

Supongamos ahora que queremos transformar la variable original speed (millas por hora) en una nueva variable velocidad (kilómetros por hora) y añadir esta nueva variable al data.frame cars. La transformación que permite pasar millas a kilómetros es kilómetros=millas/0.62137 que en R se hace directamente con:

cars$speed/0.62137

Finalmente, incluimos la nueva variable que llamaremos velocidad en cars:

cars$velocidad <- cars$speed / 0.62137
head(cars)
##   speed dist velocidad
## 1     4    2  6.437388
## 2     4   10  6.437388
## 3     7    4 11.265430
## 4     7   22 11.265430
## 5     8   16 12.874777
## 6     9   10 14.484124

También transformaremos la variable dist (en pies) en una nueva variable distancia (en metros). Ahora la transformación deseada es metros=pies/3.2808:

cars$distancia <- cars$dis / 3.2808
head(cars)
##   speed dist velocidad distancia
## 1     4    2  6.437388 0.6096074
## 2     4   10  6.437388 3.0480371
## 3     7    4 11.265430 1.2192148
## 4     7   22 11.265430 6.7056815
## 5     8   16 12.874777 4.8768593
## 6     9   10 14.484124 3.0480371

Ahora, eliminaremos las variables originales speed y dist, y guardaremos el data.frame resultante con el nombre coches. En primer lugar, veamos varias formas de acceder a las variables de interés:

cars[, c(3, 4)]
cars[, c("velocidad", "distancia")]
cars[, -c(1, 2)]

Utilizando alguna de las opciones anteriores se obtiene el data.frame deseado:

coches <- cars[, c("velocidad", "distancia")]
# head(coches)
str(coches)
## 'data.frame':    50 obs. of  2 variables:
##  $ velocidad: num  6.44 6.44 11.27 11.27 12.87 ...
##  $ distancia: num  0.61 3.05 1.22 6.71 4.88 ...

Finalmente los datos anteriores podrían ser guardados en un fichero exportable a Excel con el siguiente comando:

write.csv2(coches, file = "coches.csv")

2.2.1.2 Recodificación de variables

Con el comando cut() podemos crear variables categóricas a partir de variables numéricas. El parámetro breaks permite especificar los intervalos para la discretización, puede ser un vector con los extremos de los intervalos o un entero con el número de intervalos. Por ejemplo, para categorizar la variable cars$speed en tres intervalos equidistantes podemos emplear1:

fspeed <- cut(cars$speed, 3, labels = c("Baja", "Media", "Alta"))
table(fspeed)
## fspeed
##  Baja Media  Alta 
##    11    24    15

Para categorizar esta variable en tres niveles con aproximadamente el mismo número de observaciones podríamos combinar esta función con quantile():

breaks <- quantile(cars$speed, probs = seq(0, 1, len = 4))
fspeed <- cut(cars$speed, breaks, labels = c("Baja", "Media", "Alta"))
table(fspeed)
## fspeed
##  Baja Media  Alta 
##    17    16    15

Para otro tipo de recodificaciones podríamos emplear la función ifelse() vectorial:

fspeed <- ifelse(cars$speed < 15, "Baja", "Alta")
fspeed <- factor(fspeed, levels = c("Baja", "Alta"))
table(fspeed)
## fspeed
## Baja Alta 
##   23   27

Alternativamente en el caso de dos niveles podríamos emplear directamente la función factor():

fspeed <- factor(cars$speed >= 15, labels = c("Baja", "Alta")) # levels = c("FALSE", "TRUE")
table(fspeed)
## fspeed
## Baja Alta 
##   23   27

En el caso de múltiples niveles se podría emplear ifelse() anidados:

fspeed <- ifelse(cars$speed < 10, "Baja",
                 ifelse(cars$speed < 20, "Media", "Alta"))
fspeed <- factor(fspeed, levels = c("Baja", "Media", "Alta"))
table(fspeed)
## fspeed
##  Baja Media  Alta 
##     6    32    12

Otra alternativa sería emplear la función recode() del paquete car.

NOTA: Para acceder directamente a las variables de un data.frame podríamos emplear la función attach() para añadirlo a la ruta de búsqueda y detach() al finalizar. Sin embargo esta forma de proceder puede causar numerosos inconvenientes, especialmente al modificar la base de datos, por lo que la recomendación sería emplear with(). Por ejemplo, podríamos calcular el factor anterior empleando:

fspeed <- with(cars, ifelse(speed < 10, "Baja",
                 ifelse(speed < 20, "Media", "Alta")))
fspeed <- factor(fspeed, levels = c("Baja", "Media", "Alta"))
table(fspeed)
## fspeed
##  Baja Media  Alta 
##     6    32    12

2.2.2 Operaciones con casos

2.2.2.1 Ordenación

Continuemos con el data.frame cars. Se puede comprobar que los datos disponibles están ordenados por los valores de speed. A continuación haremos la ordenación utilizando los valores de dist. Para ello utilizaremos el conocido como vector de índices de ordenación. Este vector establece el orden en que tienen que ser elegidos los elementos para obtener la ordenación deseada. Veamos un ejemplo sencillo:

x <- c(2.5, 4.3, 1.2, 3.1, 5.0) # valores originales
ii <- order(x)
ii    # vector de ordenación
## [1] 3 1 4 2 5
x[ii] # valores ordenados
## [1] 1.2 2.5 3.1 4.3 5.0

En el caso de vectores, el procedimiento anterior se podría hacer directamente con:

sort(x)

Sin embargo, para ordenar data.frames será necesario la utilización del vector de índices de ordenación. A continuación, los datos de cars ordenados por dist:

ii <- order(cars$dist) # Vector de índices de ordenación
cars2 <- cars[ii, ]    # Datos ordenados por dist
head(cars2)
##    speed dist velocidad distancia
## 1      4    2  6.437388 0.6096074
## 3      7    4 11.265430 1.2192148
## 2      4   10  6.437388 3.0480371
## 6      9   10 14.484124 3.0480371
## 12    12   14 19.312165 4.2672519
## 5      8   16 12.874777 4.8768593

2.2.2.2 Filtrado

El filtrado de datos consiste en elegir una submuestra que cumpla determinadas condiciones. Para ello se puede utilizar la función subset() (que además permite seleccionar variables).

A continuación se muestran un par de ejemplos:

subset(cars, dist > 85) # datos con dis>85
##    speed dist velocidad distancia
## 47    24   92  38.62433  28.04194
## 48    24   93  38.62433  28.34674
## 49    24  120  38.62433  36.57644
subset(cars, speed > 10 & speed < 15 & dist > 45) # speed en (10,15) y dist>45
##    speed dist velocidad distancia
## 19    13   46  20.92151  14.02097
## 22    14   60  22.53086  18.28822
## 23    14   80  22.53086  24.38430

También se pueden hacer el filtrado empleando directamente los correspondientes vectores de índices:

ii <- cars$dist > 85
cars[ii, ]   # dis>85
##    speed dist velocidad distancia
## 47    24   92  38.62433  28.04194
## 48    24   93  38.62433  28.34674
## 49    24  120  38.62433  36.57644
ii <- cars$speed > 10 & cars$speed < 15 & cars$dist > 45
cars[ii, ]  # speed en (10,15) y dist>45
##    speed dist velocidad distancia
## 19    13   46  20.92151  14.02097
## 22    14   60  22.53086  18.28822
## 23    14   80  22.53086  24.38430

En este caso puede ser de utilidad la función which():

it <- which(ii)
str(it)
##  int [1:3] 19 22 23
cars[it, 1:2]
##    speed dist
## 19    13   46
## 22    14   60
## 23    14   80
# rownames(cars[it, 1:2])

id <- which(!ii)
str(cars[id, 1:2])
## 'data.frame':    47 obs. of  2 variables:
##  $ speed: num  4 4 7 7 8 9 10 10 10 11 ...
##  $ dist : num  2 10 4 22 16 10 18 26 34 17 ...
# Equivalentemente:
str(cars[-it, 1:2])
## 'data.frame':    47 obs. of  2 variables:
##  $ speed: num  4 4 7 7 8 9 10 10 10 11 ...
##  $ dist : num  2 10 4 22 16 10 18 26 34 17 ...
# Se podría p.e. emplear cars[id, ] para predecir cars[it, ]$speed
# ?which.min

2.2.3 Funciones apply

2.2.3.1 La función apply

Una forma de evitar la utilización de bucles es utilizando la sentencia apply que permite evaluar una misma función en todas las filas, columnas, etc. de un array de forma simultánea.

La sintaxis de esta función es:

apply(X, MARGIN, FUN, ...)
  • X: matriz (o array)
  • MARGIN: Un vector indicando las dimensiones donde se aplicará la función. 1 indica filas, 2 indica columnas, y c(1,2) indica filas y columnas.
  • FUN: función que será aplicada.
  • ...: argumentos opcionales que serán usados por FUN.

Veamos la utilización de la función apply con un ejemplo:

x <- matrix(1:9, nrow = 3)
x
##      [,1] [,2] [,3]
## [1,]    1    4    7
## [2,]    2    5    8
## [3,]    3    6    9
apply(x, 1, sum)    # Suma por filas
## [1] 12 15 18
apply(x, 2, sum)    # Suma por columnas
## [1]  6 15 24
apply(x, 2, min)    # Mínimo de las columnas
## [1] 1 4 7
apply(x, 2, range)  # Rango (mínimo y máximo) de las columnas
##      [,1] [,2] [,3]
## [1,]    1    4    7
## [2,]    3    6    9

2.2.3.2 Variantes de la función apply

lapply():

# lista con las medianas de las variables
list <- lapply(cars, median)
str(list)
## List of 4
##  $ speed    : num 15
##  $ dist     : num 36
##  $ velocidad: num 24.1
##  $ distancia: num 11

sapply():

# matriz con las medias, medianas y desv. de las variables
res <- sapply(cars, 
          function(x) c(mean = mean(x), median = median(x), sd = sd(x)))
# str(res)
res
##            speed     dist velocidad distancia
## mean   15.400000 42.98000 24.783945 13.100463
## median 15.000000 36.00000 24.140206 10.972933
## sd      5.287644 25.76938  8.509655  7.854602
knitr::kable(t(res), digits = 1)
mean median sd
speed 15.4 15.0 5.3
dist 43.0 36.0 25.8
velocidad 24.8 24.1 8.5
distancia 13.1 11.0 7.9
cfuns <- function(x, funs = c(mean, median, sd))
            sapply(funs, function(f) f(x))
x <- 1:10
cfuns(x)
## [1] 5.50000 5.50000 3.02765
sapply(cars, cfuns)
##          speed     dist velocidad distancia
## [1,] 15.400000 42.98000 24.783945 13.100463
## [2,] 15.000000 36.00000 24.140206 10.972933
## [3,]  5.287644 25.76938  8.509655  7.854602
nfuns <- c("mean", "median", "sd")
sapply(nfuns, function(f) eval(parse(text = paste0(f, "(x)"))))
##    mean  median      sd 
## 5.50000 5.50000 3.02765

2.2.3.3 La función tapply

La function tapply() es similar a la función apply() y permite aplicar una función a los datos desagregados, utilizando como criterio los distintos niveles de una variable factor. La sintaxis de esta función es como sigue:

    tapply(X, INDEX, FUN, ...,)
  • X: matriz (o array).
  • INDEX: factor indicando los grupos (niveles).
  • FUN: función que será aplicada.
  • ...: argumentos opcionales .

Consideremos, por ejemplo, el data.frame ChickWeight con datos de un experimento relacionado con la repercusión de varias dietas en el peso de pollos.

data(ChickWeight)
# str(ChickWeight)
head(ChickWeight)
##   weight Time Chick Diet
## 1     42    0     1    1
## 2     51    2     1    1
## 3     59    4     1    1
## 4     64    6     1    1
## 5     76    8     1    1
## 6     93   10     1    1
peso <- ChickWeight$weight
dieta <- ChickWeight$Diet
levels(dieta) <- c("Dieta 1", "Dieta 2", "Dieta 3", "Dieta 4")
tapply(peso, dieta, mean)  # Peso medio por dieta
##  Dieta 1  Dieta 2  Dieta 3  Dieta 4 
## 102.6455 122.6167 142.9500 135.2627
tapply(peso, dieta, summary)
## $`Dieta 1`
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   35.00   57.75   88.00  102.65  136.50  305.00 
## 
## $`Dieta 2`
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    39.0    65.5   104.5   122.6   163.0   331.0 
## 
## $`Dieta 3`
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    39.0    67.5   125.5   142.9   198.8   373.0 
## 
## $`Dieta 4`
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   39.00   71.25  129.50  135.26  184.75  322.00

Otro ejemplo:

provincia <- as.factor(c(1, 3, 4, 2, 4, 3, 2, 1, 4, 3, 2))
levels(provincia) = c("A Coruña", "Lugo", "Orense", "Pontevedra")
hijos <- c(1, 2, 0, 3, 4, 1, 0, 0, 2, 3, 1)
data.frame(provincia, hijos)
##     provincia hijos
## 1    A Coruña     1
## 2      Orense     2
## 3  Pontevedra     0
## 4        Lugo     3
## 5  Pontevedra     4
## 6      Orense     1
## 7        Lugo     0
## 8    A Coruña     0
## 9  Pontevedra     2
## 10     Orense     3
## 11       Lugo     1
tapply(hijos, provincia, mean) # Número medio de hijos por provincia
##   A Coruña       Lugo     Orense Pontevedra 
##   0.500000   1.333333   2.000000   2.000000

Alternativamente se podría emplear la función aggregate() que tiene las ventajas de admitir fórmulas y disponer de un método para series de tiempo.

2.2.4 Operaciones con tablas de datos

Unir tablas:

  • rbind(): combina vectores, matrices, arrays o data.frames por filas.

  • cbind(): Idem por columnas.

Combinar tablas:

  • match(x, table) devuelve un vector (de la misma longitud que x) con las (primeras) posiciones de coincidencia de x en table (o NA, por defecto, si no hay coincidencia).

    Para realizar consultas combinando tablas puede ser más cómodo el operador %in% (?'%in%').

  • pmatch(x, table, ...): similar al anterior pero con coincidencias parciales de cadenas de texto.


  1. Aunque si el objetivo es obtener las frecuencias de cada intervalo puede ser más eficiente emplear hist() con plot = FALSE.↩︎