Un objetivo del curso es que puedan lograr usar R para todos sus análisis de datos, y en el mejor de los casos, se convierta en su herramienta de cabecera para estos fines.
Muchos de ustedes seguramente usan o han usado SPSS para trabajar sus datos, así que lo más probable es que quieran importar los datos que tienen guardados en formato .sav de SPSS. Para lograr eso, pueden usar el paquete foreign. Este paquete provee una función para eso, read.spss().
stroop1=read.spss('input/stroop.sav')Bueno, siempre que cargo un archivo de SPSS aparecen advertencias. Por eso siempre hay que revisar que todo esté bien, echando un vistazo a la estructura con str() y mirando los datos con View().
Uno pensaría que la función read.spss() importa los datos en un data frame. Veamos:
class(stroop1)## [1] "list"
Hmm… será mejor que lo convirtamos en un data frame…
stroopdf=data.frame(stroop1)
class(stroopdf)## [1] "data.frame"
Mucho mejor.
Veamos en qué consisten los datos que he cargado:
stroopdf## sujeto nItem congruente incongruente neutro
## 1 s1 1 0.7270088 0.7442364 0.7343573
## 2 s1 2 0.6633626 0.7589562 0.8428272
## 3 s1 3 0.7028775 0.7017512 0.6405716
## 4 s1 4 0.7228268 0.7237155 0.6164296
## 5 s1 5 0.6027954 0.5935075 0.5950094
## 6 s1 6 0.6726145 0.6654623 0.6507331
## 7 s1 7 0.7269427 0.6926963 0.7709390
## 8 s1 8 0.7936476 0.6378932 0.7752800
## 9 s1 9 0.8183397 0.6421697 0.7338001
## 10 s1 10 0.6912212 0.6852872 0.6646137
## 11 s2 1 0.6192978 0.6863666 0.8084911
## 12 s2 2 0.6978338 0.6668122 0.6948135
## 13 s2 3 0.5721056 0.5315954 0.6867370
## 14 s2 4 0.5599478 0.7573074 0.5869964
## 15 s2 5 0.6510638 0.6501127 0.6365099
## 16 s2 6 0.6820505 0.6633447 0.6582907
## 17 s2 7 0.8221746 0.6541794 0.6367485
## 18 s2 8 0.6888775 0.6514095 0.7848203
## 19 s2 9 0.7345593 0.6606288 0.5849794
## 20 s2 10 0.7161782 0.7500015 0.7000869
Bueno, aparecen las clásicas variables de un stroop: una columna para identificar el sujeto, otra para el ítem (o sea, la palabra), otra para el tiempo de reacción, y otra que designa la condición a la que corresponde.
¿Cómo que no hay columna de tiempo de reacción? ¿No hay columna RT? Ah, esperen, hay 3 columnas que dicen, congruente, incongruente, y neutro. Ah, esos deben ser los TR. Ok, en tres columnas, got it. Cada fila entonces corresponde a una combinación de sujeto X ítem, y en vez de tener una columna para los TR, tenemos tres. A esta forma de guardar los datos se le llama formato ancho, o wide format en inglish.
El formato ancho es el formato típico con el que se trabaja en SPSS, y adivinen, exacto, NO es el formato típico de trabajo en R. Por varias razones.
Para resumirlas, digamos que siempre conviene que cada columna corresponda a una variable, y cada fila a una observación. Cuando veamos cómo graficar con ggplot2 y cómo ajustar y analizar modelos lineales verán que esta disposición tiene muchas ventajas.
El formato ancho no respeta esta máxima. Veamos: la variable “tiempo de reacción” está “cruzada” por los 3 niveles del factor “condición”, para cada sujeto y cada ítem. Cada fila, por tanto, no representa una observación, sino 3: tres tiempos de reacción medidos para cada una de las 3 condiciones, para un sujeto y un ítem.
En contraste, se le denomina formato largo al formato que respeta la máxima: cada columna corresponda a una variable, y cada fila a una observación. En la sesión anterior, habíamos creado un data frame de stroop con este formato: una varible de sujeto, otra de ítem, otra de condición, y otra de RT. Cada columna, una variable. Cada fila, en tanto, es una observación, una medida: u único TR para una condición, para un sujeto, para un ítem.
Entonces, ¿cómo puedo convertir los datos de un formato al otro? Como siempre en R, de muchas formas. Una muy utilizada, es mediante el paquete reshape2. Este paquete provee, entre otras, dos funciones clave: una para derretir el data frame (llevarlo de ancho a largo), y otra para moldear (llevarlo largo a ancho). Veamos la primera, la más sencilla y útil de las dos.
La función para derretir se llama melt(), y admite varios argumentos. Pasemos nuestro stroopdf a formato largo:
melt(stroopdf)## Using sujeto as id variables
## sujeto variable value
## 1 s1 nItem 1.0000000
## 2 s1 nItem 2.0000000
## 3 s1 nItem 3.0000000
## 4 s1 nItem 4.0000000
## 5 s1 nItem 5.0000000
## 6 s1 nItem 6.0000000
## 7 s1 nItem 7.0000000
## 8 s1 nItem 8.0000000
## 9 s1 nItem 9.0000000
## 10 s1 nItem 10.0000000
## 11 s2 nItem 1.0000000
## 12 s2 nItem 2.0000000
## 13 s2 nItem 3.0000000
## 14 s2 nItem 4.0000000
## 15 s2 nItem 5.0000000
## 16 s2 nItem 6.0000000
## 17 s2 nItem 7.0000000
## 18 s2 nItem 8.0000000
## 19 s2 nItem 9.0000000
## 20 s2 nItem 10.0000000
## 21 s1 congruente 0.7270088
## 22 s1 congruente 0.6633626
## 23 s1 congruente 0.7028775
## 24 s1 congruente 0.7228268
## 25 s1 congruente 0.6027954
## 26 s1 congruente 0.6726145
## 27 s1 congruente 0.7269427
## 28 s1 congruente 0.7936476
## 29 s1 congruente 0.8183397
## 30 s1 congruente 0.6912212
## 31 s2 congruente 0.6192978
## 32 s2 congruente 0.6978338
## 33 s2 congruente 0.5721056
## 34 s2 congruente 0.5599478
## 35 s2 congruente 0.6510638
## 36 s2 congruente 0.6820505
## 37 s2 congruente 0.8221746
## 38 s2 congruente 0.6888775
## 39 s2 congruente 0.7345593
## 40 s2 congruente 0.7161782
## 41 s1 incongruente 0.7442364
## 42 s1 incongruente 0.7589562
## 43 s1 incongruente 0.7017512
## 44 s1 incongruente 0.7237155
## 45 s1 incongruente 0.5935075
## 46 s1 incongruente 0.6654623
## 47 s1 incongruente 0.6926963
## 48 s1 incongruente 0.6378932
## 49 s1 incongruente 0.6421697
## 50 s1 incongruente 0.6852872
## 51 s2 incongruente 0.6863666
## 52 s2 incongruente 0.6668122
## 53 s2 incongruente 0.5315954
## 54 s2 incongruente 0.7573074
## 55 s2 incongruente 0.6501127
## 56 s2 incongruente 0.6633447
## 57 s2 incongruente 0.6541794
## 58 s2 incongruente 0.6514095
## 59 s2 incongruente 0.6606288
## 60 s2 incongruente 0.7500015
## 61 s1 neutro 0.7343573
## 62 s1 neutro 0.8428272
## 63 s1 neutro 0.6405716
## 64 s1 neutro 0.6164296
## 65 s1 neutro 0.5950094
## 66 s1 neutro 0.6507331
## 67 s1 neutro 0.7709390
## 68 s1 neutro 0.7752800
## 69 s1 neutro 0.7338001
## 70 s1 neutro 0.6646137
## 71 s2 neutro 0.8084911
## 72 s2 neutro 0.6948135
## 73 s2 neutro 0.6867370
## 74 s2 neutro 0.5869964
## 75 s2 neutro 0.6365099
## 76 s2 neutro 0.6582907
## 77 s2 neutro 0.6367485
## 78 s2 neutro 0.7848203
## 79 s2 neutro 0.5849794
## 80 s2 neutro 0.7000869
Hmm, no es tan simple. Veamos el mensaje de advertencia: Using sujeto as id variables. Cierto, hay que avisarle a melt() cuáles son las variables que “identifican” a una observación, es decir, las que no están cruzadas. En nuestro data frame, el tiempo de reacción está cruzado por los niveles de condición, pero cada combinación de sujeto y número de ítem ya están “en formato largo”.
A ver ahora…
melt(stroopdf,id=c("sujeto","nItem"))## sujeto nItem variable value
## 1 s1 1 congruente 0.7270088
## 2 s1 2 congruente 0.6633626
## 3 s1 3 congruente 0.7028775
## 4 s1 4 congruente 0.7228268
## 5 s1 5 congruente 0.6027954
## 6 s1 6 congruente 0.6726145
## 7 s1 7 congruente 0.7269427
## 8 s1 8 congruente 0.7936476
## 9 s1 9 congruente 0.8183397
## 10 s1 10 congruente 0.6912212
## 11 s2 1 congruente 0.6192978
## 12 s2 2 congruente 0.6978338
## 13 s2 3 congruente 0.5721056
## 14 s2 4 congruente 0.5599478
## 15 s2 5 congruente 0.6510638
## 16 s2 6 congruente 0.6820505
## 17 s2 7 congruente 0.8221746
## 18 s2 8 congruente 0.6888775
## 19 s2 9 congruente 0.7345593
## 20 s2 10 congruente 0.7161782
## 21 s1 1 incongruente 0.7442364
## 22 s1 2 incongruente 0.7589562
## 23 s1 3 incongruente 0.7017512
## 24 s1 4 incongruente 0.7237155
## 25 s1 5 incongruente 0.5935075
## 26 s1 6 incongruente 0.6654623
## 27 s1 7 incongruente 0.6926963
## 28 s1 8 incongruente 0.6378932
## 29 s1 9 incongruente 0.6421697
## 30 s1 10 incongruente 0.6852872
## 31 s2 1 incongruente 0.6863666
## 32 s2 2 incongruente 0.6668122
## 33 s2 3 incongruente 0.5315954
## 34 s2 4 incongruente 0.7573074
## 35 s2 5 incongruente 0.6501127
## 36 s2 6 incongruente 0.6633447
## 37 s2 7 incongruente 0.6541794
## 38 s2 8 incongruente 0.6514095
## 39 s2 9 incongruente 0.6606288
## 40 s2 10 incongruente 0.7500015
## 41 s1 1 neutro 0.7343573
## 42 s1 2 neutro 0.8428272
## 43 s1 3 neutro 0.6405716
## 44 s1 4 neutro 0.6164296
## 45 s1 5 neutro 0.5950094
## 46 s1 6 neutro 0.6507331
## 47 s1 7 neutro 0.7709390
## 48 s1 8 neutro 0.7752800
## 49 s1 9 neutro 0.7338001
## 50 s1 10 neutro 0.6646137
## 51 s2 1 neutro 0.8084911
## 52 s2 2 neutro 0.6948135
## 53 s2 3 neutro 0.6867370
## 54 s2 4 neutro 0.5869964
## 55 s2 5 neutro 0.6365099
## 56 s2 6 neutro 0.6582907
## 57 s2 7 neutro 0.6367485
## 58 s2 8 neutro 0.7848203
## 59 s2 9 neutro 0.5849794
## 60 s2 10 neutro 0.7000869
Excelente! O casi. La salid es un data frame derretido, sí, pero faltan los nombres de las variables “condición” y “rt”. En vez de esos nombres aparecen unos genéricos “variable”, y “value”. Los podemos cambiar con la función colnames() cierto, pero podemos ya incluirlos en la llamada a melt().
Veamos ahora si queda listo:
stroop=melt(stroopdf,id=c("sujeto","nItem"),variable.name = "condicion",value.name = "rt")
stroop## sujeto nItem condicion rt
## 1 s1 1 congruente 0.7270088
## 2 s1 2 congruente 0.6633626
## 3 s1 3 congruente 0.7028775
## 4 s1 4 congruente 0.7228268
## 5 s1 5 congruente 0.6027954
## 6 s1 6 congruente 0.6726145
## 7 s1 7 congruente 0.7269427
## 8 s1 8 congruente 0.7936476
## 9 s1 9 congruente 0.8183397
## 10 s1 10 congruente 0.6912212
## 11 s2 1 congruente 0.6192978
## 12 s2 2 congruente 0.6978338
## 13 s2 3 congruente 0.5721056
## 14 s2 4 congruente 0.5599478
## 15 s2 5 congruente 0.6510638
## 16 s2 6 congruente 0.6820505
## 17 s2 7 congruente 0.8221746
## 18 s2 8 congruente 0.6888775
## 19 s2 9 congruente 0.7345593
## 20 s2 10 congruente 0.7161782
## 21 s1 1 incongruente 0.7442364
## 22 s1 2 incongruente 0.7589562
## 23 s1 3 incongruente 0.7017512
## 24 s1 4 incongruente 0.7237155
## 25 s1 5 incongruente 0.5935075
## 26 s1 6 incongruente 0.6654623
## 27 s1 7 incongruente 0.6926963
## 28 s1 8 incongruente 0.6378932
## 29 s1 9 incongruente 0.6421697
## 30 s1 10 incongruente 0.6852872
## 31 s2 1 incongruente 0.6863666
## 32 s2 2 incongruente 0.6668122
## 33 s2 3 incongruente 0.5315954
## 34 s2 4 incongruente 0.7573074
## 35 s2 5 incongruente 0.6501127
## 36 s2 6 incongruente 0.6633447
## 37 s2 7 incongruente 0.6541794
## 38 s2 8 incongruente 0.6514095
## 39 s2 9 incongruente 0.6606288
## 40 s2 10 incongruente 0.7500015
## 41 s1 1 neutro 0.7343573
## 42 s1 2 neutro 0.8428272
## 43 s1 3 neutro 0.6405716
## 44 s1 4 neutro 0.6164296
## 45 s1 5 neutro 0.5950094
## 46 s1 6 neutro 0.6507331
## 47 s1 7 neutro 0.7709390
## 48 s1 8 neutro 0.7752800
## 49 s1 9 neutro 0.7338001
## 50 s1 10 neutro 0.6646137
## 51 s2 1 neutro 0.8084911
## 52 s2 2 neutro 0.6948135
## 53 s2 3 neutro 0.6867370
## 54 s2 4 neutro 0.5869964
## 55 s2 5 neutro 0.6365099
## 56 s2 6 neutro 0.6582907
## 57 s2 7 neutro 0.6367485
## 58 s2 8 neutro 0.7848203
## 59 s2 9 neutro 0.5849794
## 60 s2 10 neutro 0.7000869
Perfecto! El data frame se ha derretido!
Son pocas las veces que he tenido que moldear un data frame derretido. Pero entonces, ha sido un dolor de cabeza. Eso es porque no había entendido bien la sintaxis de la función dcast(), la encargada de moldear un data frame con el formato que uno quiera. Para entender bien cómo funciona el asunto, necesitamos introducir la función tilde, o como me gusta llamarla, la función ñoqui (~).
El simbolito ~ sólo cumple una función en R, y es delimitar los dos lados de una fórmula. ¿Y qué es una fórmula? Es más fácil mostrarlo que definirlo:
#esto es una fórmula
y~x+z## y ~ x + z
#se puede asignar a una variable
formula1 = y~x+z
class(formula1)## [1] "formula"
#una formula tiene dos partes
parte_izquierda ~ parte_derecha## parte_izquierda ~ parte_derecha
#a la izquierda, variables dependientes: a la derecha, las independientes
dep1 + dep2 ~ ind1 + ind2## dep1 + dep2 ~ ind1 + ind2
Veremos cómo usar fórmulas cuando veamos modelos lineales. Por ahora, basta decir que una fórmula “dice” que lo que está “a la izquierda” depende de lo que está “a la derecha” del ñoqui ~.
Cuando escribimos una fórmula, no tienen por qué referir a nignún objeto. Claro que cuando las querramos usar para algo, cada término de la fórmula tendrá que hacer referencia a algo: típicamente, a columnas de un data frame.
Entonces, ¿cómo vuelvo del formato largo al formato ancho? Así:
dcast(stroop,sujeto+nItem~condicion,value.var="rt")## sujeto nItem congruente incongruente neutro
## 1 s1 1 0.7270088 0.7442364 0.7343573
## 2 s1 2 0.6633626 0.7589562 0.8428272
## 3 s1 3 0.7028775 0.7017512 0.6405716
## 4 s1 4 0.7228268 0.7237155 0.6164296
## 5 s1 5 0.6027954 0.5935075 0.5950094
## 6 s1 6 0.6726145 0.6654623 0.6507331
## 7 s1 7 0.7269427 0.6926963 0.7709390
## 8 s1 8 0.7936476 0.6378932 0.7752800
## 9 s1 9 0.8183397 0.6421697 0.7338001
## 10 s1 10 0.6912212 0.6852872 0.6646137
## 11 s2 1 0.6192978 0.6863666 0.8084911
## 12 s2 2 0.6978338 0.6668122 0.6948135
## 13 s2 3 0.5721056 0.5315954 0.6867370
## 14 s2 4 0.5599478 0.7573074 0.5869964
## 15 s2 5 0.6510638 0.6501127 0.6365099
## 16 s2 6 0.6820505 0.6633447 0.6582907
## 17 s2 7 0.8221746 0.6541794 0.6367485
## 18 s2 8 0.6888775 0.6514095 0.7848203
## 19 s2 9 0.7345593 0.6606288 0.5849794
## 20 s2 10 0.7161782 0.7500015 0.7000869
¿Por qué? Bueno: el primer argumento de la función dcast() es el data frame a moldear. El tercer argumento, value.var, es la variable (típicamente numérica) que queremos cruzar por cada nivel de factor.
El segundo es argumento es una fórmula. ¿Qué dice la fórmula? Algo así como “sujeto y nItem dependen de la condición”. O sea: lo que esta “a la izquierda” de la fórmula queda en formato largo, mientras que lo que queda “a la derecha” queda ancho.
Veamos otros formatos posibles para moldear:
dcast(stroop,condicion+nItem~sujeto,value.var="rt")## condicion nItem s1 s2
## 1 congruente 1 0.7270088 0.6192978
## 2 congruente 2 0.6633626 0.6978338
## 3 congruente 3 0.7028775 0.5721056
## 4 congruente 4 0.7228268 0.5599478
## 5 congruente 5 0.6027954 0.6510638
## 6 congruente 6 0.6726145 0.6820505
## 7 congruente 7 0.7269427 0.8221746
## 8 congruente 8 0.7936476 0.6888775
## 9 congruente 9 0.8183397 0.7345593
## 10 congruente 10 0.6912212 0.7161782
## 11 incongruente 1 0.7442364 0.6863666
## 12 incongruente 2 0.7589562 0.6668122
## 13 incongruente 3 0.7017512 0.5315954
## 14 incongruente 4 0.7237155 0.7573074
## 15 incongruente 5 0.5935075 0.6501127
## 16 incongruente 6 0.6654623 0.6633447
## 17 incongruente 7 0.6926963 0.6541794
## 18 incongruente 8 0.6378932 0.6514095
## 19 incongruente 9 0.6421697 0.6606288
## 20 incongruente 10 0.6852872 0.7500015
## 21 neutro 1 0.7343573 0.8084911
## 22 neutro 2 0.8428272 0.6948135
## 23 neutro 3 0.6405716 0.6867370
## 24 neutro 4 0.6164296 0.5869964
## 25 neutro 5 0.5950094 0.6365099
## 26 neutro 6 0.6507331 0.6582907
## 27 neutro 7 0.7709390 0.6367485
## 28 neutro 8 0.7752800 0.7848203
## 29 neutro 9 0.7338001 0.5849794
## 30 neutro 10 0.6646137 0.7000869
Y otra, la más ancha de las tres:
dcast(stroop,sujeto+condicion~nItem,value.var="rt")## sujeto condicion 1 2 3 4 5
## 1 s1 congruente 0.7270088 0.6633626 0.7028775 0.7228268 0.6027954
## 2 s1 incongruente 0.7442364 0.7589562 0.7017512 0.7237155 0.5935075
## 3 s1 neutro 0.7343573 0.8428272 0.6405716 0.6164296 0.5950094
## 4 s2 congruente 0.6192978 0.6978338 0.5721056 0.5599478 0.6510638
## 5 s2 incongruente 0.6863666 0.6668122 0.5315954 0.7573074 0.6501127
## 6 s2 neutro 0.8084911 0.6948135 0.6867370 0.5869964 0.6365099
## 6 7 8 9 10
## 1 0.6726145 0.7269427 0.7936476 0.8183397 0.6912212
## 2 0.6654623 0.6926963 0.6378932 0.6421697 0.6852872
## 3 0.6507331 0.7709390 0.7752800 0.7338001 0.6646137
## 4 0.6820505 0.8221746 0.6888775 0.7345593 0.7161782
## 5 0.6633447 0.6541794 0.6514095 0.6606288 0.7500015
## 6 0.6582907 0.6367485 0.7848203 0.5849794 0.7000869
En suma, en la fórmula que especifica cómo se ha de moldear el data frame derretido, la parte “a la izquierda” del ~ establece cuántas filas van a quedar (una para cada combinación de los niveles de esas variables), mientras que la parte “a la derecha” del ~ indica cuántas columnas “extra” van a quedar (una para cada combinación de los niveles de esas variables).
Si quisiera el data frame bien ancho (?), podría hacer esto:
dcast(stroop,sujeto~nItem+condicion,value.var="rt")## sujeto 1_congruente 1_incongruente 1_neutro 2_congruente 2_incongruente
## 1 s1 0.7270088 0.7442364 0.7343573 0.6633626 0.7589562
## 2 s2 0.6192978 0.6863666 0.8084911 0.6978338 0.6668122
## 2_neutro 3_congruente 3_incongruente 3_neutro 4_congruente
## 1 0.8428272 0.7028775 0.7017512 0.6405716 0.7228268
## 2 0.6948135 0.5721056 0.5315954 0.6867370 0.5599478
## 4_incongruente 4_neutro 5_congruente 5_incongruente 5_neutro
## 1 0.7237155 0.6164296 0.6027954 0.5935075 0.5950094
## 2 0.7573074 0.5869964 0.6510638 0.6501127 0.6365099
## 6_congruente 6_incongruente 6_neutro 7_congruente 7_incongruente
## 1 0.6726145 0.6654623 0.6507331 0.7269427 0.6926963
## 2 0.6820505 0.6633447 0.6582907 0.8221746 0.6541794
## 7_neutro 8_congruente 8_incongruente 8_neutro 9_congruente
## 1 0.7709390 0.7936476 0.6378932 0.7752800 0.8183397
## 2 0.6367485 0.6888775 0.6514095 0.7848203 0.7345593
## 9_incongruente 9_neutro 10_congruente 10_incongruente 10_neutro
## 1 0.6421697 0.7338001 0.6912212 0.6852872 0.6646137
## 2 0.6606288 0.5849794 0.7161782 0.7500015 0.7000869
aunque ahora no se me ocurre una buena razón para hacer eso.
Acá está el ejercicio que acompaña a la sesión 5