1. Introducción

Este tutorial describe el procedimiento para crear un listado de aristas de una red semántica natural (rsn), a partir de una base de datos tradicional; a fin de graficarla y analizarla con el programa Gephi o cualquier otro programa de análisis y visualización de redes sociales. No obstante, si se prefiere, se puede graficar con R, como explicamos aquí mismo.

Es obvio que para entender este tutorial debes conocer la técnica de redes semánticas naturales y su análisis estructural. Si no sabes de que estamos hablando, te sugiero leer el libro: Marcas, Memoria y Significado: Análisis de Estructuras Semánticas. Así como saber utilizar R básico y, opcionalmente, un programa de análisis y visualización de redes sociales Gephi, NodeXl o cualquier otro.

En este tutorial, iremos avanzando como se describe a continuación:

2. Creación de base de datos de ejemplo.

En el siguiente código inventamos una base de datos, la cual hemos llamado coca.df. La base consta de 5 casos: folio 1 al 5; una varibale llamada demografico: m para masculino y f para femenino; y cinco variables: palabra1 a palabra5, las cuales simulan distintas palabras que se asocian con la palabra estímulo Coca Cola.

coca_df <- data.frame(folio = c(1:3, 3, 5),
                          demografico = c("m", "f", "m", "f", "m"),
                          palabra1 = c("Botella", "Sed",
                                       "ENVASE", "botella", "PERJUDICIAL"),
                          palabra2 = c("Refresco    ", "GAS",
                                       "Azúcar", "refresco", "AZUCAR"),
                          palabra3 = c("Hielos", "comida",
                                       "negra", "Fría", "Roja"),
                          palabra4 = c("fiesta", "rica",
                                       "   Hielo", "Liquido", "BEBIDA"),
                          palabra5 = c("Sed", "Familia",
                                       "Dañina", "Rica", NA),
                          stringsAsFactors = F )
coca_df
##   folio demografico    palabra1     palabra2 palabra3 palabra4 palabra5
## 1     1           m     Botella Refresco       Hielos   fiesta      Sed
## 2     2           f         Sed          GAS   comida     rica  Familia
## 3     3           m      ENVASE       Azúcar    negra    Hielo   Dañina
## 4     3           f     botella     refresco     Fría  Liquido     Rica
## 5     5           m PERJUDICIAL       AZUCAR     Roja   BEBIDA     <NA>

Por favor, nota que las palabras (palabra1 a palabra5) vienen en mayúsculas, minúsculas, con acentos y sin ellos; con caracteres especiales como la ñ de la palabra Dañina, y espacios en blanco entre palabras, como la palabra Refresco (caso 1, palabra2). Es común encontrar este debarajuste en muchas bases de datos, el cual debemos arreglar; más adelante te mostramos cómo.

3. Creación de variable de identificación: id.

La variable de identificación (v.gr. folio) es importante porque a través de ella se detectan todas las palabras que mencionó un sujeto; a fin de combinarlas en pares y crear el listado de aristas. Por esta razón, es necesario que ningun sujeto tenga el mismo número de identificación que otro. Si estás absolutamente seguro que la variable de identificación, por ejemplo, el folio de cada caso no se repite, entonces puedes brincarte este paso y utilizarlo en lugar del id nuevo que crearemos a continuación. Observa que en nuestro caso no se repite el folio; sin embargo, se crea la variable nueva id para ejemplificar el procedimiento.

Se requiere el paquete dplyr, el cual sirve para manipular y transformar una base de datos (data frame). dplyr forma parte de un conjunto de paquetes que funcionan de forma muy similar, compartiendo varias reglas y filosofías. Debido a ello el autor(es) los han agrupado bajo un mismo paquete llamado tidyverse; de tal forma que se puede cargar el paquete dplyr individualmente o en conjunto con todos los demás paquetes que integran tidyverse. Preferimos hacerlo a través del paquete tidyverse, porque más adelante utilizaremos otros paquetes que se encuentran dentro de éste.

El código utiliza la función mutate para crear variables nuevas en un data frame. Los argumentos son: el data frame, el nuestro es coca.df; el nombre de la variable nueva: id, la cual es igual al vector que va de 1 hasta el número de renglones de nuestra base de datos coca.df.

library(tidyverse)
coca_df <- mutate(coca_df, id = c(1:nrow(coca_df)))
coca_df
##   folio demografico    palabra1     palabra2 palabra3 palabra4 palabra5 id
## 1     1           m     Botella Refresco       Hielos   fiesta      Sed  1
## 2     2           f         Sed          GAS   comida     rica  Familia  2
## 3     3           m      ENVASE       Azúcar    negra    Hielo   Dañina  3
## 4     3           f     botella     refresco     Fría  Liquido     Rica  4
## 5     5           m PERJUDICIAL       AZUCAR     Roja   BEBIDA     <NA>  5

4. Conversión del data frame en tidy data.

Tidy data significa datos ordenados; es decir, hay que ordenar la base de datos. Esto no quiere decir que una base de datos clásica esté desordenada, a lo que se refiere es que las variables deben ordenarse en un formato largo en lugar de uno ancho, como es el caso de las bases de datos tradicionales. En ese formato largo, todas las palabras mencionadas se forman en una sola columna. Una base de datos con este formato permite procesar la información de manera más amigable e ideal para R.

Se requiere el paquete tidyr. Este paquete se usa para ordenar datos; por supuesto, está incluido en la paquetería de tidyverse. Los argumentos son, en este mismo orden: la base de datos que se desea ordenar (coca_df). Las variables que se quieren formar en una columna (palabra1 a palabra5). Una variable clave (key) donde se etiquete la columna de origen de cada palabra, le pusimos de nombre pregunta. Y por último, una variable donde se depositan todos los valores (value) de cada palabra, utilizaos el nombre palabra. Los nombres como pregunta y palabra se usan a discreción.

library(tidyr)
coca_df <- gather(coca_df, palabra1:palabra5,
                          key = "pregunta", value = "palabra")
coca_df
##    folio demografico id pregunta      palabra
## 1      1           m  1 palabra1      Botella
## 2      2           f  2 palabra1          Sed
## 3      3           m  3 palabra1       ENVASE
## 4      3           f  4 palabra1      botella
## 5      5           m  5 palabra1  PERJUDICIAL
## 6      1           m  1 palabra2 Refresco    
## 7      2           f  2 palabra2          GAS
## 8      3           m  3 palabra2       Azúcar
## 9      3           f  4 palabra2     refresco
## 10     5           m  5 palabra2       AZUCAR
## 11     1           m  1 palabra3       Hielos
## 12     2           f  2 palabra3       comida
## 13     3           m  3 palabra3        negra
## 14     3           f  4 palabra3         Fría
## 15     5           m  5 palabra3         Roja
## 16     1           m  1 palabra4       fiesta
## 17     2           f  2 palabra4         rica
## 18     3           m  3 palabra4        Hielo
## 19     3           f  4 palabra4      Liquido
## 20     5           m  5 palabra4       BEBIDA
## 21     1           m  1 palabra5          Sed
## 22     2           f  2 palabra5      Familia
## 23     3           m  3 palabra5       Dañina
## 24     3           f  4 palabra5         Rica
## 25     5           m  5 palabra5         <NA>

5. Integración o normalización de palabras

Como mencionamos en la introducción, es necesario quitar los espacios en blanco que pudiera tener cualquier palabra antes o después, los acentos y caracteres especiales; así como convertir todas las palabras a mayúsculas o minúsculas, etc. Un par de palabras escritas de la misma forma, en las cuales una tiene espacios en blanco y la otra no, representan entidades distintas debido a estos espacios. Por ejemplo:" Refresco" (con espacio al inicio) es distinto de “Refresco”, misma palabra pero sin espacios. Para eliminar los espacios en blanco utilizaremos el paquete stringr, el cual sirve para manipular caracteres y expresiones regulares; viene incluido en tidyverse.

library(stringr)
coca_df$palabra <- str_trim(coca_df$palabra, side = "both")
coca_df
##    folio demografico id pregunta     palabra
## 1      1           m  1 palabra1     Botella
## 2      2           f  2 palabra1         Sed
## 3      3           m  3 palabra1      ENVASE
## 4      3           f  4 palabra1     botella
## 5      5           m  5 palabra1 PERJUDICIAL
## 6      1           m  1 palabra2    Refresco
## 7      2           f  2 palabra2         GAS
## 8      3           m  3 palabra2      Azúcar
## 9      3           f  4 palabra2    refresco
## 10     5           m  5 palabra2      AZUCAR
## 11     1           m  1 palabra3      Hielos
## 12     2           f  2 palabra3      comida
## 13     3           m  3 palabra3       negra
## 14     3           f  4 palabra3        Fría
## 15     5           m  5 palabra3        Roja
## 16     1           m  1 palabra4      fiesta
## 17     2           f  2 palabra4        rica
## 18     3           m  3 palabra4       Hielo
## 19     3           f  4 palabra4     Liquido
## 20     5           m  5 palabra4      BEBIDA
## 21     1           m  1 palabra5         Sed
## 22     2           f  2 palabra5     Familia
## 23     3           m  3 palabra5      Dañina
## 24     3           f  4 palabra5        Rica
## 25     5           m  5 palabra5        <NA>

En seguida, cambiamos el tipo de letra a minúscula para homologar todos los términos, sin que unos vengan en mayúsculas, otros en minúsculas o en formato de título.

coca_df$palabra <- str_to_lower(coca_df$palabra, locale = "es")
coca_df
##    folio demografico id pregunta     palabra
## 1      1           m  1 palabra1     botella
## 2      2           f  2 palabra1         sed
## 3      3           m  3 palabra1      envase
## 4      3           f  4 palabra1     botella
## 5      5           m  5 palabra1 perjudicial
## 6      1           m  1 palabra2    refresco
## 7      2           f  2 palabra2         gas
## 8      3           m  3 palabra2      azúcar
## 9      3           f  4 palabra2    refresco
## 10     5           m  5 palabra2      azucar
## 11     1           m  1 palabra3      hielos
## 12     2           f  2 palabra3      comida
## 13     3           m  3 palabra3       negra
## 14     3           f  4 palabra3        fría
## 15     5           m  5 palabra3        roja
## 16     1           m  1 palabra4      fiesta
## 17     2           f  2 palabra4        rica
## 18     3           m  3 palabra4       hielo
## 19     3           f  4 palabra4     liquido
## 20     5           m  5 palabra4      bebida
## 21     1           m  1 palabra5         sed
## 22     2           f  2 palabra5     familia
## 23     3           m  3 palabra5      dañina
## 24     3           f  4 palabra5        rica
## 25     5           m  5 palabra5        <NA>

Para juntar las palabras con un mismo significado, pero que fueron escritas con acento y sin él, los acentos tienen que ser removidos.

coca_df$palabra <- chartr('áéíóúñ','aeioun',
                          coca_df$palabra)
coca_df
##    folio demografico id pregunta     palabra
## 1      1           m  1 palabra1     botella
## 2      2           f  2 palabra1         sed
## 3      3           m  3 palabra1      envase
## 4      3           f  4 palabra1     botella
## 5      5           m  5 palabra1 perjudicial
## 6      1           m  1 palabra2    refresco
## 7      2           f  2 palabra2         gas
## 8      3           m  3 palabra2      azucar
## 9      3           f  4 palabra2    refresco
## 10     5           m  5 palabra2      azucar
## 11     1           m  1 palabra3      hielos
## 12     2           f  2 palabra3      comida
## 13     3           m  3 palabra3       negra
## 14     3           f  4 palabra3        fria
## 15     5           m  5 palabra3        roja
## 16     1           m  1 palabra4      fiesta
## 17     2           f  2 palabra4        rica
## 18     3           m  3 palabra4       hielo
## 19     3           f  4 palabra4     liquido
## 20     5           m  5 palabra4      bebida
## 21     1           m  1 palabra5         sed
## 22     2           f  2 palabra5     familia
## 23     3           m  3 palabra5      danina
## 24     3           f  4 palabra5        rica
## 25     5           m  5 palabra5        <NA>

6. Creación del diccionario.

En una red semántica natural siempre nos encontramos con palabras mal escritas o capturadas que requieren edición. Lo mismo sucede con la multiplicidad de términos, que en esencia son sinonimos (sinonimia) y significan lo mismo. Como en nuestro ejemplo: envase es lo mismo que botella. Todas esas instancias se deben editar juntando términos semejantes y corrigiendo los mal escritos. Eso se hace a través de un diccionario que se crea ex profeso. Este diccionario tendrá una aplicación muy útil. Se compara la base de datos original con él para anexarle las palabras corregidas, pero conservando el término original.

Crear un diccionario tiene varias ventajas:

El primer paso es extraer los términos o plabras únicas, no repetidas. El propósito es trabajar sobre un listado pequeño en lugar de uno larguísimo de términos repetidos. Para completar esta tarea vamos a emplear la función distinct del paquete dplyr. Los argumentos son una base de datos: coca.df, y la variable dentro de la cual se eliminan los términos repetidos.

coca_dicc <- distinct(coca_df, palabra)
coca_dicc
##        palabra
## 1      botella
## 2          sed
## 3       envase
## 4  perjudicial
## 5     refresco
## 6          gas
## 7       azucar
## 8       hielos
## 9       comida
## 10       negra
## 11        fria
## 12        roja
## 13      fiesta
## 14        rica
## 15       hielo
## 16     liquido
## 17      bebida
## 18     familia
## 19      danina
## 20        <NA>

Como puedes observar, las palabras botella, sed, refresco, azucar y rica han sido removidas porque aparencen repetidas. De los 25 términos originales, ahora quedan 20. Sin embargo, el NA (Not Available) o missing data permanece, para removerlo utilizamos la función na.omit(), la cual es nativa de R, es decir, no necesitas ninguna librería adicional.

coca_dicc <- na.omit(coca_dicc)
coca_dicc
##        palabra
## 1      botella
## 2          sed
## 3       envase
## 4  perjudicial
## 5     refresco
## 6          gas
## 7       azucar
## 8       hielos
## 9       comida
## 10       negra
## 11        fria
## 12        roja
## 13      fiesta
## 14        rica
## 15       hielo
## 16     liquido
## 17      bebida
## 18     familia
## 19      danina

Antes de exportar los términos obtenidos a Excel (donde haremos nuestro diccionario) hay que ordenar la base de datos para facilitar el trabajo de edición.

Usamos la función arrange de dplyr para ordenar por palabras. Los argumentos son la base de datos (coca.dicc) y la variable por la que hay que ordenar: palabra.

coca_dicc <- arrange(coca_dicc, palabra)
coca_dicc
##        palabra
## 1       azucar
## 2       bebida
## 3      botella
## 4       comida
## 5       danina
## 6       envase
## 7      familia
## 8       fiesta
## 9         fria
## 10         gas
## 11       hielo
## 12      hielos
## 13     liquido
## 14       negra
## 15 perjudicial
## 16    refresco
## 17        rica
## 18        roja
## 19         sed

Asimismo, creamos una variable a la que llamaremos edicion. Allí copiaremos los términos originales para editarlos, con ello logramos que las palabras originales queden intactas.

El siguiente código crera una variable con la función mutate(), que ya conoces.

coca_dicc <- mutate(coca_dicc, edicion = palabra)
coca_dicc
##        palabra     edicion
## 1       azucar      azucar
## 2       bebida      bebida
## 3      botella     botella
## 4       comida      comida
## 5       danina      danina
## 6       envase      envase
## 7      familia     familia
## 8       fiesta      fiesta
## 9         fria        fria
## 10         gas         gas
## 11       hielo       hielo
## 12      hielos      hielos
## 13     liquido     liquido
## 14       negra       negra
## 15 perjudicial perjudicial
## 16    refresco    refresco
## 17        rica        rica
## 18        roja        roja
## 19         sed         sed

En la práctica querras hacer el trabajo de edición en Excel debido a que hay muchas palabras que editar, al menos la primera vez. Entonces lo que sigue es exportar tu base a formato .csv (valores separados por coma), el cual Excel lee fácilmente. La función write_csv() del paquete readr es adecuada para esta situación. Declara únicamente el archivo que deseas exportar, en este caso es coca.dicc, seguido del nombre de archivo exportado. Cómo te suena coca_dicc.csv. Nota que si tienes algún otro archivo con ese nombre R te lo sobreescribira automaticamente, así que ojo.

write_csv(coca_dicc, "coca_dicc.csv")

Aplicación del diccionario.

La tabla de abajo muestra el trabajo de edición hecho. Como se observa, la palabra envase (término 6) se editó transformándole en botella; hielos (caso 12) pasó al singular hielo; y perjudicial (palabra 15) se unifica con danina.

palabra edicion
1. azucar azucar
2. bebida bebida
3. botella botella
4. comida comida
5. danina danina
6. envase botella
7. familia familia
8. fiesta fiesta
9. fria fria
10. gas gas
11. hielo hielo
12. hielos hielo
13. liquido liquido
14. negra negra
15. perjudicial danina
16. refresco refresco
17. rica rica
18. roja roja
19. sed sed

Ahora, ya estamos listos para juntar la base de datos coca.df con el diccionario coca_dicc.csv. Para ello vamos a usar la función left_join() de nuestro paquete aliado dplyr. ¿Qué hace esta función? Compara dos tablas (data frames) con base a una variable clave (en nuestro caso es palabra) y las fuisiona en una sola. La función se llama left_join (unión izquierda) porque anexa las columnas de la tabla derecha a la de la izquierda, cuando ambas coinciden en la misma variable (i.e. palabra). Cuando no hay coincidencias los casos de la tabla derecha desaparecen, pero los de la izquierda permanecen y son etiquetados con NA (Not Available). Es decir, no se encuentran en el diccionario. Esto es muy conveniente pues en una subsecuente medición nuestra función nos etiquetará todos los términos que no existen en el diccionario.

coca_dicc <- read_csv("coca_diccionario.csv")

coca_df <- left_join(coca_df, coca_dicc,
                     key = "palabra")
coca_df
##    folio demografico id pregunta     palabra  edicion
## 1      1           m  1 palabra1     botella  botella
## 2      2           f  2 palabra1         sed      sed
## 3      3           m  3 palabra1      envase  botella
## 4      3           f  4 palabra1     botella  botella
## 5      5           m  5 palabra1 perjudicial   danina
## 6      1           m  1 palabra2    refresco refresco
## 7      2           f  2 palabra2         gas      gas
## 8      3           m  3 palabra2      azucar   azucar
## 9      3           f  4 palabra2    refresco refresco
## 10     5           m  5 palabra2      azucar   azucar
## 11     1           m  1 palabra3      hielos    hielo
## 12     2           f  2 palabra3      comida   comida
## 13     3           m  3 palabra3       negra    negra
## 14     3           f  4 palabra3        fria     fria
## 15     5           m  5 palabra3        roja     roja
## 16     1           m  1 palabra4      fiesta   fiesta
## 17     2           f  2 palabra4        rica     rica
## 18     3           m  3 palabra4       hielo    hielo
## 19     3           f  4 palabra4     liquido  liquido
## 20     5           m  5 palabra4      bebida   bebida
## 21     1           m  1 palabra5         sed      sed
## 22     2           f  2 palabra5     familia  familia
## 23     3           m  3 palabra5      danina   danina
## 24     3           f  4 palabra5        rica     rica
## 25     5           m  5 palabra5        <NA>     <NA>

Seguramente notas que el sujeto 5 no proporcionó una quinta palabra (palabra 25); por tanto tampoco se encontró el termino en el diccionario y es etiquetado con NA. La próxima vez que hagas el estudio con el mismo concepto, los términos que no aprezacan en tu diccionario serán etiquetados con NA, de esa forma se facilita su identificación e incorporación al diccionario.

7. Remoción de palabras duplicadas y missing values NA.

En ocasiones un sujeto porporciona la misma palabra más de una vez, o pudiera ser que al aplicar el diccionario, dos palabras que aprentemente son distintas se agrupan, por circunstancias de su sinomimia, en un mismo termino lo que ocasiona que un sujeto tenga palabras duplicadas. Por lo anterior, tenemos que eliminar la redundancia de términos por sujeto.

Usemos el mismo procedimiento con la función distinct; sin embargo en esta ocasión debemos utilizar un par de argumentos adicionales. El primero es el id, pues no queremos palabras repetidas por sujeto. El segundo es .keep_all = TRUE, que impide que sean borradas las variables adicionales a la usada o usadas para buscar los términos distintos.

coca_df <- distinct(coca_df, id, palabra, .keep_all = TRUE)
coca_df

Asimismo removemos los casos con NA.

palabras_tdy.df <- drop_na(palabras_tdy.df, palabra)
palabras_tdy.df

8. Listado de Aristas

Se cuenta cada par de palabras y su incidencia

La combinación de palabras en pares se hace con la ayuda del paquete widyr Los argumentos de la función son:

  • tbl. Significa tibble, en esencia es una base de datos con un formato más amigable y ventajas, que un tradicional data frame.
  • item. Es la variable que se desea combinar en pares. En nuestro caso es “palabra”.
  • feature. Es la variable que controla el conjunto de palabras que se combinan en pares. Por ejemplo, el folio o el id (número de identificación que creamos) 31 y 32 no combinan sus palabras en pares; sólo se combinan las palabras del folio 31 entre ellas y las del 32 entre ellas mismas.
  • upper. Se usa para indicar que no deseamos que nos devuelva, en el listado de combinaciones, el triángulo superior de la matriz combinada, por eso usamos FALSE.
  • diag. Se usa para indicar que no deseamos que nos devuelva, en el listado de combinaciones, la diagonal de la matriz combinada, por eso usamos FALSE.
  • sort. Le pedimos que nos ordene los pares de palabras de mayor a menor frecuencia.
library(widyr)
pairwise_count(tbl = palabras_tdy.df, item = palabra,
               feature = id_nuevo, upper = FALSE,
               diag = FALSE, sort = TRUE)

Código completo

aristas.df <- gather(palabras.df, p1:p3, key = "pregunta", value = "palabras") %>%
  distinct(folio, palabras) %>%
  pairwise_count(palabras, folio, upper = FALSE, sort = TRUE) %>%
  filter(n >= 2) %>%
  mutate(type = "undirected") %>%
  rename(source = item1, target = item2, weigth = n) %>%
  write.csv("red_general.csv", row.names = F)