Data.frames

Los data.frames son una clase de objetos especial en R. Normalmente, cuando se realiza un estudio estadístico sobre los sujetos u objetos de una muestra, la información se organiza precisamente en un dataframe: una hoja de datos, en los que cada fila corresponde a un sujeto y cada columna a una variable. La estructura de un data.frame es muy similar a la de una matriz. La diferencia es que una matriz sólo admite valores numéricos, mientras que en un dataframe podemos incluir también datos alfanuméricos.

Creación de un dataframe

El siguiente ejemplo muestra como crear un data.frame a partir de los datos recogidos sobre una muestra de 10 personas, para cada una de las cuales se ha registrado su edad, sexo y tiempo en minutos que estuvo hablando por teléfono el día antes de la encuesta:

edad <- c(22, 34, 29, 25, 30, 33, 31, 27, 25, 25)
tiempo <- c(14.21, 10.36, 11.89, 13.81, 12.03, 10.99, 12.48, 13.37, 12.29, 11.92)
sexo <- c("M","H","H","M","M","H","M","M","H","H")
misDatos <- data.frame(edad,tiempo,sexo)
misDatos
##    edad tiempo sexo
## 1    22  14.21    M
## 2    34  10.36    H
## 3    29  11.89    H
## 4    25  13.81    M
## 5    30  12.03    M
## 6    33  10.99    H
## 7    31  12.48    M
## 8    27  13.37    M
## 9    25  12.29    H
## 10   25  11.92    H
str(misDatos)    # Estructura de 'misDatos'
## 'data.frame':    10 obs. of  3 variables:
##  $ edad  : num  22 34 29 25 30 33 31 27 25 25
##  $ tiempo: num  14.2 10.4 11.9 13.8 12 ...
##  $ sexo  : chr  "M" "H" "H" "M" ...
names(misDatos)  # Nombre de las variables contenidas en 'misDatos'
## [1] "edad"   "tiempo" "sexo"

En este ejemplo se ha creado un data.frame llamado misDatos que contiene a las tres variables edad, tiempo y sexo. La función str() nos muestra la estructura de este objeto, confirmándonos que es un data.frame de tres variables con 10 observaciones cada una. Donde, las dos primeras variables son numéricas y la tercera, el sexo, es un factor con dos valores, “H” y “M”. La función names() por su parte, nos devuelve los nombres de las variables contenidas en misDatos.

Cuando desde R se leen datos situados en un fichero externo (un fichero de texto, una hoja excel, un archivo de datos de SPSS,…), estos datos se importan en un dataframe.

Acceso a variables dentro de un dataframe

El acceso a los datos que se encuentran en un data.frame es muy similar al acceso a los datos de una matriz que ya vimos en la sección anterior. Sin embargo, para los data.frames R dispone de algunas funciones que facilitan la tarea de seleccionar o filtrar datos. Así por ejemplo, si queremos ver sólo los datos de los sujetos (filas) 3 a 6, escribiríamos:

misDatos[3:6,]
##   edad tiempo sexo
## 3   29  11.89    H
## 4   25  13.81    M
## 5   30  12.03    M
## 6   33  10.99    H

Si queremos seleccionar los datos de edad (primera columna), podemos tratar a misDatos igual que si fuese una matriz:

misDatos[,1]
##  [1] 22 34 29 25 30 33 31 27 25 25

Aunque también podemos referirnos a la columna por su nombre:

misDatos$edad
##  [1] 22 34 29 25 30 33 31 27 25 25

Nótese que en este caso hemos de utilizar el nombre del data.frame (misDatos) seguido del símbolo $ y del nombre de la variable que nos interesa (edad). De manera equivalente, la selección de esa variable puede realizarse mediante:

misDatos[,"edad"]
##  [1] 22 34 29 25 30 33 31 27 25 25

o poniendo el nombre de la variable entre dobles corchetes y entre comillas:

misDatos[["edad"]]
##  [1] 22 34 29 25 30 33 31 27 25 25

Así pues, los siguientes comandos son equivalentes y dan el mismo resultado:

mean(misDatos[,1])
## [1] 28.1
mean(misDatos$edad)
## [1] 28.1
mean(misDatos[,"edad"])
## [1] 28.1
mean(misDatos[["edad"]])
## [1] 28.1

Los comandos attach y detach

El acceso a las variables dentro de un dataframe puede hacerse engorroso cuando hemos de escribir constantemente el nombre del dataframe (en particular si éste es muy largo). Imaginemos, por ejemplo, que para el conjunto misDatos deseamos construir tablas de frecuencias de cada una de las variables que contiene, una tabla de frecuencias cruzadas para el nivel de estudios por sexo, y que además queremos calcular la edad media de los individuos de cada sexo. La sintaxis a utilizar sería la siguiente:

table(misDatos$estudios)
## < table of extent 0 >
table(misDatos$sexo)
## 
## H M 
## 5 5
table(misDatos$edad)
## 
## 22 25 27 29 30 31 33 34 
##  1  3  1  1  1  1  1  1
table(misDatos$sexo,misDatos$edad)
##    
##     22 25 27 29 30 31 33 34
##   H  0  2  0  1  0  0  1  1
##   M  1  1  1  0  1  1  0  0
mean(misDatos$edad[misDatos$sexo=="M"])
## [1] 27
mean(misDatos$edad[misDatos$sexo=="H"])
## [1] 29.2

Obviamente, escribir tantas veces misDatos resulta tedioso, al margen de que se multiplica el riesgo de cometer errores en la redacción de los comandos. Para evitar este problema podemos utilizar el comando attach(), cuyo objetivo consiste básicamente en “enganchar” el contenido del dataframe al entorno donde R busca los nombres de variable; de esta forma se puede acceder directamente a las variables del dataframe por su nombre, sin necesidad de que éste tenga que ser precedido con el nombre del dataframe y el símbolo $; una vez que hayamos acabado nuestra tarea “desenganchamos” el dataframe con detach(). La tarea anterior, utilizando estos comandos, se puede llevar a cabo mediante:

attach(misDatos)
## The following objects are masked _by_ .GlobalEnv:
## 
##     edad, sexo, tiempo
#table(estudios)
table(sexo)
## sexo
## H M 
## 5 5
table(edad)
## edad
## 22 25 27 29 30 31 33 34 
##  1  3  1  1  1  1  1  1
table(sexo,edad)
##     edad
## sexo 22 25 27 29 30 31 33 34
##    H  0  2  0  1  0  0  1  1
##    M  1  1  1  0  1  1  0  0
mean(edad[sexo=="M"])
## [1] 27
mean(edad[sexo=="H"])
## [1] 29.2
detach(misDatos)

lo cual es notablemente más simple. No obstante hay que ser extremadamente precavido al utilizar estos comandos ya que cuando se utilizan varios dataframes simultáneamente es muy fácil hacer attachs y detachs en lugares incorrectos, lo que puede conducir a mezclar datos de distinta procedencia y cometer errores inadvertidos. Obsérvese el siguiente ejemplo:

# Creamos el dataframe 'divisas' con la tasa de cambio de algunas monedas
divisas = data.frame(moneda=c("Libra", "Euro", "Rublo"), cambio=c(1.2, 1, 0.02))
divisas
##   moneda cambio
## 1  Libra   1.20
## 2   Euro   1.00
## 3  Rublo   0.02
# Creamos un dataframe con el nombre de algunos paises y su moneda nacional
paises = data.frame(pais=c("EEUU", "Venezuela", "Japón"),moneda=c("Dólar", "Bolívar", "Yen"))
paises
##        pais  moneda
## 1      EEUU   Dólar
## 2 Venezuela Bolívar
## 3     Japón     Yen
attach(divisas) # Enganchamos 'divisas' al entorno de búsqueda de nombres de variables.
moneda          # Se muestra la variable 'moneda' del dataframe 'divisas'
## [1] "Libra" "Euro"  "Rublo"
attach(paises)  # ¡CUIDADO! no se ha hecho el 'detach' del datafame 'divisas'
## The following object is masked from divisas:
## 
##     moneda
moneda          # 'moneda' se lee del último dataframe "enganchado", que es 'paises'
## [1] "Dólar"   "Bolívar" "Yen"
cambio          # ¡Pero esta variable se sigue leyendo del dataframe 'divisas'!
## [1] 1.20 1.00 0.02
# Si, por ejemplo, pegamos los valores de ambas variables pensando que corresponden al mismo dataframe estaríamos metiendo la pata.
paste(moneda,cambio, sep=": ")                                
## [1] "Dólar: 1.2" "Bolívar: 1" "Yen: 0.02"
detach(paises)  # Desenganchamos el dataframe 'paises'
moneda          # Recuperamos la variable 'moneda' que estaba en 'divisas'
## [1] "Libra" "Euro"  "Rublo"
detach(divisas) # Desenganchamos 'divisas

Pregunta

¿Que pasa si al hacer el detach(divisas) llamamos de nuevo la variable moneda?

Ejemplo

Se Crea una variable llamada longitud, con 6 valores, y a continuación un dataframe llamado medidas que contiene tres variables con tres valores cada una; en el dataframe una de las variables también se llama longitud:

longitud=c(12,10,11,13,14,17)
medidas=data.frame(longitud=c(6,4,7), peso=c(240,326,315), diametro=c(8,9,9))

Calculamos el valor medio de estas cuatro variables:

mean(longitud)
## [1] 12.83333
mean(medidas$longitud)
## [1] 5.666667
mean(medidas$peso)
## [1] 293.6667
mean(medidas$diametro)
## [1] 8.666667

Efectuamos el attach del dataframe medidas para acceder a sus variables y calculamos de nuevo la media de cada una:

attach(medidas)
## The following object is masked _by_ .GlobalEnv:
## 
##     longitud
mean(peso)
## [1] 293.6667
mean(diametro)
## [1] 8.666667
mean(longitud)
## [1] 12.83333

Obsérvese de que R nos advierte de que el objeto longitud ha quedado enmascarado. Vemos que, si bien R ha calculado los valores medios del diámetro y peso correspondientes a las variables del dataframe medidas, la longitud sigue siendo la de la variable longitud previa a hacer el attach.

Lo anterior ocurre pues ya existe una variable de enterno con el mismo nombre de alguna variable del data.frame y al hacer uso del attach esta se engancha al data.frame. Esto deja innacesible la variable la variable del data.frame.

Comando with

El comando with permite ejecutar una o varias instrucciones sobre las variables de un dataframe accediendo a ellas solamente por su nombre, sin necesidad de utilizar attach. Resulta particularmente útil para realizar cálculos con las variables dentro de un dataframe.

Si, por ejemplo, queremos calcular la densidad de los objetos cuyas medidas figuran en el dataframe anterior, podemos utilizar la siguiente sintaxis

with(medidas,{
  volumen=longitud*pi*(diametro/2)^2  # Calcula el volumen de los objetos
  densidad=peso/volumen               # Calcula su densidad
  densidad                            # Muestra los valores de densidad
})
## [1] 0.7957747 1.2810990 0.7073553

La estructura que se acabo de definir no modifica la el contenido del data.frame medidas.

medidas
##   longitud peso diametro
## 1        6  240        8
## 2        4  326        9
## 3        7  315        9

Si lo que se quiere es incluir la densidad dentro del data.frame medidas deberíamos proceder del siguiente modo:

medidas$densidad=with(medidas,{
  volumen=longitud*pi*(diametro/2)^2  # Calcula el volumen de los objetos
  densidad=peso/volumen               # Calcula su densidad
  densidad                            # Muestra los valores de densidad
})
medidas # Mostramos el dataframe 'medidas'. Ahora sí que contiene la densidad
##   longitud peso diametro  densidad
## 1        6  240        8 0.7957747
## 2        4  326        9 1.2810990
## 3        7  315        9 0.7073553

Subconjuntos de un dataframe

La función subset() nos permite seleccionar una parte del data.frame. Por ejemplo, si deseamos crear dos dataframes nuevos, uno solo con los hombres y otro con las mujeres utilizaríamos:

hombres=subset(misDatos,sexo=="H")
hombres
##    edad tiempo sexo
## 2    34  10.36    H
## 3    29  11.89    H
## 6    33  10.99    H
## 9    25  12.29    H
## 10   25  11.92    H
mujeres=subset(misDatos,sexo=="M")
mujeres
##   edad tiempo sexo
## 1   22  14.21    M
## 4   25  13.81    M
## 5   30  12.03    M
## 7   31  12.48    M
## 8   27  13.37    M

Elaboración de selecciones más complejas

Sujetos que sean hombres y tengan más de 30 años (la condición “ y ” se especifica mediante el símbolo “ & ”):

mayores=subset(misDatos,sexo=="H" & edad>30)
mayores
##   edad tiempo sexo
## 2   34  10.36    H
## 6   33  10.99    H

Sujetos que tengan menos de 25 o más 30 años (la condición “ o ” se expresa mediante la línea vertical “ | ”):

extremos=subset(misDatos,edad<25|edad>30)
extremos
##   edad tiempo sexo
## 1   22  14.21    M
## 2   34  10.36    H
## 6   33  10.99    H
## 7   31  12.48    M

Podemos seleccionar además un subconjunto de variables del data.frame. Por ejemplo, si nos interesan solo la edad y el tiempo de uso del móvil de los hombres de la muestra:

hombres=subset(misDatos,sexo=="H", select=c(edad, tiempo))
hombres
##    edad tiempo
## 2    34  10.36
## 3    29  11.89
## 6    33  10.99
## 9    25  12.29
## 10   25  11.92

Combinación de dataframes: rbind y merge

Si tenemos dos dataframes con la misma estructura (idénticas variables), pero distintos datos, podemos combinarlos pegando uno a continuación del otro mediante rbind (acrónimo de rowbind, pegar por filas):

animales1 = data.frame(animal=c("vaca","perro","rana","lagarto","mosca","jilguero"), 
                       clase=c("mamífero","mamífero","anfibio","reptil","insecto","ave"))
animales1
##     animal    clase
## 1     vaca mamífero
## 2    perro mamífero
## 3     rana  anfibio
## 4  lagarto   reptil
## 5    mosca  insecto
## 6 jilguero      ave
animales2 = data.frame(animal=c("atún", "cocodrilo", "gato","rana"), clase=c("pez", "reptil", "mamífero","anfibio"))
animales2
##      animal    clase
## 1      atún      pez
## 2 cocodrilo   reptil
## 3      gato mamífero
## 4      rana  anfibio
animales3 = rbind(animales1,animales2)
animales3
##       animal    clase
## 1       vaca mamífero
## 2      perro mamífero
## 3       rana  anfibio
## 4    lagarto   reptil
## 5      mosca  insecto
## 6   jilguero      ave
## 7       atún      pez
## 8  cocodrilo   reptil
## 9       gato mamífero
## 10      rana  anfibio

El comando rbind no controla la posible aparición de casos repetidos en los dos dataframes (podemos comprobar que la rana está repetida en el dataframe ‘animales3’). La función merge() evita este problema; utilizando la opción all=TRUE ó all=FALSE (valor por defecto) se consigue que se muestren todos los datos de ambos dataframes, o solo aquellos que son comunes a ambos

animales4=merge(animales1,animales2)
animales4
##   animal   clase
## 1   rana anfibio
animales5=merge(animales1,animales2,all=TRUE)
animales5
##      animal    clase
## 1      atún      pez
## 2 cocodrilo   reptil
## 3      gato mamífero
## 4  jilguero      ave
## 5   lagarto   reptil
## 6     mosca  insecto
## 7     perro mamífero
## 8      rana  anfibio
## 9      vaca mamífero

Si los dataframes tienen estructura distinta, pero contienen variables en común que permiten identificar unívocamente a los mismos objetos en ambos conjuntos, también podemos combinarlos mediante merge():

superficieAnimales=data.frame(animal=c("perro","tortuga","jilguero", 
                                       "cocodrilo","vaca","lagarto","sardina"),
                     superficie=c("pelo","placas óseas","plumas",
                                  "escamas","pelo","escamas","escamas"))
superficieAnimales
##      animal   superficie
## 1     perro         pelo
## 2   tortuga placas óseas
## 3  jilguero       plumas
## 4 cocodrilo      escamas
## 5      vaca         pelo
## 6   lagarto      escamas
## 7   sardina      escamas
merge(animales3,superficieAnimales) # Muestra sólo los animales comunes a ambos dataframes
##      animal    clase superficie
## 1 cocodrilo   reptil    escamas
## 2  jilguero      ave     plumas
## 3   lagarto   reptil    escamas
## 4     perro mamífero       pelo
## 5      vaca mamífero       pelo
merge(animales3,superficieAnimales, all.x=TRUE) # Muestra todos los animales del primer dataframe.
##       animal    clase superficie
## 1       atún      pez       <NA>
## 2  cocodrilo   reptil    escamas
## 3       gato mamífero       <NA>
## 4   jilguero      ave     plumas
## 5    lagarto   reptil    escamas
## 6      mosca  insecto       <NA>
## 7      perro mamífero       pelo
## 8       rana  anfibio       <NA>
## 9       rana  anfibio       <NA>
## 10      vaca mamífero       pelo
merge(animales3,superficieAnimales, all.y=TRUE) # Muestra todos los animales del segundo dataframe.
##      animal    clase   superficie
## 1 cocodrilo   reptil      escamas
## 2  jilguero      ave       plumas
## 3   lagarto   reptil      escamas
## 4     perro mamífero         pelo
## 5   sardina     <NA>      escamas
## 6   tortuga     <NA> placas óseas
## 7      vaca mamífero         pelo
merge(animales3,superficieAnimales, all=TRUE) # Muestra todos los animales de ambos dataframes.
##       animal    clase   superficie
## 1       atún      pez         <NA>
## 2  cocodrilo   reptil      escamas
## 3       gato mamífero         <NA>
## 4   jilguero      ave       plumas
## 5    lagarto   reptil      escamas
## 6      mosca  insecto         <NA>
## 7      perro mamífero         pelo
## 8       rana  anfibio         <NA>
## 9       rana  anfibio         <NA>
## 10   sardina     <NA>      escamas
## 11   tortuga     <NA> placas óseas
## 12      vaca mamífero         pelo

Como vemos, cuando en la combinación de dataframes faltan datos, se rellenan los huecos con valores perdidos (NA)

Ordenación de dataframes

Para ordenar un dataframe hemos de aplicar la función order() al elemento o elementos por el que queramos ordenar, y utilizar el resultado de esta función como índice del dataframe.

Por ejemplo, si queremos ordenar el data.frame animales1 por orden alfabético de animales, haríamos:

ordenacion=order(animales1$animal)
ordenacion  # Posiciones dentro del dataframe 'animales1' de los animales ordenados alfabéticamente
## [1] 6 4 5 2 3 1
animales1=animales1[ordenacion,]  # Se reordenan las filas del dataframe animales1
animales1
##     animal    clase
## 6 jilguero      ave
## 4  lagarto   reptil
## 5    mosca  insecto
## 2    perro mamífero
## 3     rana  anfibio
## 1     vaca mamífero

de modo equivalente, en una línea de código:

animales1=animales1[order(animales1$animal),]