3  Reglas de asociación

Vamos a utilizar reglas de asociación para detectar cuentas falsas en Instagram. Este método nos permitirá descubrir relaciones interesantes entre diferentes características observadas en el dataset. Utilizaremos el paquete arules para llevar a cabo estas operaciones.

3.1 Características importantes:

3.1.1 Medidas relevantes

Para evaluar la calidad y relevancia de las reglas de asociación, utilizaremos las siguientes medidas:

  1. Soporte (Support): Mide la proporción de cuentas en el dataset que contienen ambos conjuntos de características A y B. Un alto soporte indica que la regla se aplica a una gran proporción del dataset, lo que sugiere que la combinación de características es común y relevante.

  2. Confianza (Confidence): Mide cuán frecuentemente las características en B aparecen en las cuentas que contienen A. Una mayor confianza indica una mayor fiabilidad de que la presencia de las características en A implicará la presencia de las características en B.

  3. Elevación (Lift): Mide la relación entre la aparición conjunta de A y B y la aparición esperada de A y B si fueran independientes. Una elevación alta (mayor que 1) indica que la presencia de A incrementa significativamente la probabilidad de que B ocurra, lo que sugiere una fuerte asociación entre las características.

3.1.2 Algoritmo Apriori

Utilizaremos el algoritmo Apriori para obtener reglas a partir de nuestros datos. Este algoritmo se basa en la propiedad de que cualquier subconjunto de un conjunto frecuente también debe ser frecuente. Itera a través de los conjuntos de características, incrementando su tamaño en cada iteración y manteniendo solo los conjuntos que cumplen con un umbral mínimo de soporte.

3.1.3 Reglas

Las reglas de asociación consisten en implicaciones del tipo “Si A entonces B”, donde A y B son conjuntos de características o comportamientos de las cuentas. Por ejemplo, una regla podría ser “Si una cuenta tiene un número alto de cuentas seguidas y no tiene foto de perfil, entonces es probable que sea una cuenta falsa”.

3.2 Carga de datos:

Vamos a cargar las librerías necesarias y nuestro dataset.

library(arules)
Loading required package: Matrix

Attaching package: 'arules'
The following objects are masked from 'package:base':

    abbreviate, write
library(arulesViz)
Warning: package 'arulesViz' was built under R version 4.3.3
library(readr)
datos <- read_csv("Data/train.csv") 
Rows: 576 Columns: 12
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
dbl (12): profile pic, nums/length username, fullname words, nums/length ful...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

3.3 Discretizar datos

Puesto que el algoritmo de apriori necesita que el conjunto de datos sea binario o discreto.

Existen varias formas de discretizar datos, pero el objetivo principal es convertir las características continuas en valores discretos que representen de manera efectiva la información subyacente. Algunas técnicas comunes de discretización incluyen la binarización, la división en intervalos fijos o basados en cuantiles.

Tras haber realizado el previo análisis exploratorio podemos definir intervalos personalizados para cada variable, para ello usaremos las funciones ordered y cut. Además, las variables que son binarias como “fake”, vamos a ponerles “Si” o “No” para poder comprenderlas mejor.

datos_refinados <- datos

columnas_binarias = c("profile pic","name==username","external URL","fake","private")

for (columna in columnas_binarias) {
  datos_refinados[[columna]] <-  factor(datos_refinados[[columna]], labels = c("No", "Si"))
}
# Discretización de la columna #posts
datos_refinados$`#posts` <- ordered(cut(datos_refinados$`#posts`, 
                                 breaks = c(0,1, 5, 10, 50, Inf), 
                                 labels = c("muy bajo","medio", "alto", "muy alto", "extremadamente alto"),include.lowest = TRUE))

# Discretización de la columna #followers
datos_refinados$`#followers` <- ordered(cut(datos_refinados$`#followers`, 
                                     breaks = c(0, 10, 60, 200, Inf), 
                                     labels = c("bajo", "medio", "alto", "muy alto"),include.lowest = TRUE))

# Discretización de la columna #follows
datos_refinados$`#follows` <- ordered(cut(datos_refinados$`#follows`, 
                                   breaks = c(0, 10, 60, 200, Inf), 
                                   labels = c("bajo", "medio", "alto", "muy alto"),include.lowest = TRUE))

# Discretización de la columna nums/length username
datos_refinados$`nums/length username` <- ordered(cut(datos_refinados$`nums/length username`,
                                                      breaks = c(0, 0.2, 0.4, 0.6, 0.8, 1),
                                                      labels = c("muy bajo", "bajo", "medio", "alto", "muy alto"),
                                                      include.lowest = TRUE))

# Discretización de la columna nums/length fullname
datos_refinados$`nums/length fullname` <- ordered(cut(datos_refinados$`nums/length fullname`, 
                                                      breaks = c(0, 0.2, 0.4, 0.6, 0.8, 1), 
                                                      labels = c("muy bajo", "bajo", "medio", "alto", "muy alto"),
                                                      include.lowest = TRUE))
# Discretización de la columna description length
datos_refinados$`description length` <- ordered(cut(datos_refinados$`description length`,
                                                     breaks = c(0, 15, 25, 80, 150),
                                                     labels = c("muy corto" , "medio", "largo", "muy largo"),
                                                    include.lowest = TRUE))
# Discretización de la columna fullname words
datos_refinados$`fullname words` <- ordered(cut(datos_refinados$`fullname words`,
                                                 breaks = c(0, 1, 3, 5, Inf),
                                                 labels = c("muy corto", "medio", "largo", "muy largo"),include.lowest = TRUE))

3.3.1 discretizeDF

Esta función del paquete de arules implementa varios métodos básicos no supervisados para convertir una variable continua en una variable categórica (factor) usando diferentes estrategias de agrupamiento.

Vamos a quitar primero las columnas binarias a las que queremos asignar un valor personalizado.

datos_refinados_clone <- datos

columnas_binarias = c("profile pic","name==username","external URL","fake","private")

for (columna in columnas_binarias) {
  datos_refinados_clone[[columna]] <-  factor(datos_refinados_clone[[columna]], labels = c("No", "Si"))
}

Vamos a ver algunas estrategias:

3.3.1.1 K-means:

kmeansDisc <- discretizeDF(datos_refinados_clone, default = list(method = "cluster", breaks = 5, 
  labels = c("muy bajo", "bajo","medio","alto","muy alto")))
head(kmeansDisc)
# A tibble: 6 × 12
  `profile pic` `nums/length username` `fullname words` `nums/length fullname`
  <fct>         <fct>                  <fct>            <fct>                 
1 Si            medio                  muy bajo         muy bajo              
2 Si            muy bajo               medio            muy bajo              
3 Si            bajo                   medio            muy bajo              
4 Si            muy bajo               bajo             muy bajo              
5 Si            muy bajo               medio            muy bajo              
6 Si            muy bajo               alto             muy bajo              
# ℹ 8 more variables: `name==username` <fct>, `description length` <fct>,
#   `external URL` <fct>, private <fct>, `#posts` <fct>, `#followers` <fct>,
#   `#follows` <fct>, fake <fct>

3.3.1.2 interval

fixedDisc <- discretizeDF(datos_refinados_clone, default = list(method = "interval", breaks = 5, 
  labels = c("muy bajo", "bajo","medio","alto","muy alto")))
head(fixedDisc)
# A tibble: 6 × 12
  `profile pic` `nums/length username` `fullname words` `nums/length fullname`
  <fct>         <fct>                  <fct>            <fct>                 
1 Si            bajo                   muy bajo         muy bajo              
2 Si            muy bajo               muy bajo         muy bajo              
3 Si            muy bajo               muy bajo         muy bajo              
4 Si            muy bajo               muy bajo         muy bajo              
5 Si            muy bajo               muy bajo         muy bajo              
6 Si            muy bajo               bajo             muy bajo              
# ℹ 8 more variables: `name==username` <fct>, `description length` <fct>,
#   `external URL` <fct>, private <fct>, `#posts` <fct>, `#followers` <fct>,
#   `#follows` <fct>, fake <fct>

3.4 Generar dataset de transacciones

Ahora, una vez discretizado el dataframe, el siguiente paso es generar un dataset de transacciones. Este tipo de dataset es esencial para aplicar algoritmos de reglas de asociación como Apriori.

En un dataset de transacciones, cada fila representa una transacción, que es una colección de elementos o ítems.

datos_refinadosT <- as(datos_refinados, "transactions")

3.5 Generar reglas

Ahora que ya tenemos todo listo, podemos utilizar los algoritmos de generación de reglas. En nuestro caso, vamos a utilizar Apriori. Para generar reglas primero necesitamos establecer un valor para el soporte y confianza mínima, estos valores nos permitirán controlar la cantidad y calidad de las reglas que se generarán.

 rules <- apriori(datos_refinadosT,  parameter = list(supp = 0.3, conf = 0.01, target = "rules")) 
Apriori

Parameter specification:
 confidence minval smax arem  aval originalSupport maxtime support minlen
       0.01    0.1    1 none FALSE            TRUE       5     0.3      1
 maxlen target  ext
     10  rules TRUE

Algorithmic control:
 filter tree heap memopt load sort verbose
    0.1 TRUE TRUE  FALSE TRUE    2    TRUE

Absolute minimum support count: 172 

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[40 item(s), 576 transaction(s)] done [0.00s].
sorting and recoding items ... [16 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 3 4 5 6 7 done [0.00s].
writing ... [1157 rule(s)] done [0.00s].
creating S4 object  ... done [0.00s].
 rules
set of 1157 rules 

Hemos obtenido una buena cantidad de reglas para continuar nuestro análisis.

3.6 Refinar reglas

Ahora que hemos obtenido las reglas, necesitamos cribarlas y eliminar todas aquellas que no nos interesan, que sean redundantes o no significativas.

3.6.1 Eliminar reglas redundantes

rules <- rules[which(is.redundant(rules))]

3.6.2 Eliminar reglas no significativas

rules <- rules[which(is.significant(rules))]

Vamos a ver cuentas reglas han quedado después de filtrarlas:

length(rules)
[1] 569

3.7 Análisis de reglas obtenidas

Nuestro objetivo es detectar y diferenciar cuentas falsas de las verdaderas, por lo tanto, vamos a centrar nuestro análisis en esos dos atributos: “fake=Si” y “fake=No”. Como tenemos diferentes métricas, vamos a analizarlas por separado:

3.7.1 Soporte

Vamos primero a analizar las reglas ordenándolas por el soporte. Recordamos que un soporte alto indica que la regla se aplica a una gran proporción del dataset, lo que sugiere que la combinación de características es común y relevante.

rules <- sort(rules,by="support")
inspect(head(rules))
    lhs                                 rhs                               support confidence  coverage     lift count
[1] {nums/length fullname=muy bajo,                                                                                  
     external URL=No}                => {name==username=No}             0.7881944  0.9848156 0.8003472 1.020241   454
[2] {nums/length username=muy bajo,                                                                                  
     name==username=No}              => {nums/length fullname=muy bajo} 0.6250000  0.9863014 0.6336806 1.078007   360
[3] {nums/length fullname=muy bajo,                                                                                  
     name==username=No}              => {nums/length username=muy bajo} 0.6250000  0.6936416 0.9010417 1.071146   360
[4] {name==username=No,                                                                                              
     description length=muy corto}   => {external URL=No}               0.6041667  0.9747899 0.6197917 1.103102   348
[5] {name==username=No,                                                                                              
     external URL=No}                => {description length=muy corto}  0.6041667  0.7102041 0.8506944 1.096723   348
[6] {nums/length fullname=muy bajo,                                                                                  
     description length=muy corto}   => {external URL=No}               0.5590278  0.9728097 0.5746528 1.100861   322

En este caso, el soporte es 0.7881944, lo que significa que el 78.82% de las transacciones en el dataset contienen tanto el antecedente {nums/length fullname=muy bajo, external URL=No} como el consecuente {name==username=No}.

r2 <- subset(rules, subset = rhs %in% c("fake=Si"))
inspect(head(r2))
    lhs                                 rhs         support confidence  coverage     lift count
[1] {name==username=No,                                                                        
     external URL=No}                => {fake=Si} 0.4670139  0.5489796 0.8506944 1.097959   269
[2] {name==username=No,                                                                        
     description length=muy corto}   => {fake=Si} 0.4253472  0.6862745 0.6197917 1.372549   245
[3] {name==username=No,                                                                        
     description length=muy corto,                                                             
     external URL=No}                => {fake=Si} 0.4253472  0.7040230 0.6041667 1.408046   245
[4] {nums/length fullname=muy bajo,                                                            
     external URL=No}                => {fake=Si} 0.4218750  0.5271150 0.8003472 1.054230   243
[5] {nums/length fullname=muy bajo,                                                            
     description length=muy corto}   => {fake=Si} 0.3819444  0.6646526 0.5746528 1.329305   220
[6] {nums/length fullname=muy bajo,                                                            
     description length=muy corto,                                                             
     external URL=No}                => {fake=Si} 0.3819444  0.6832298 0.5590278 1.366460   220

3.7.2 Confianza

Ahora vamos a analizar las reglas ordenándolas por la confianza. Recordamos que a mayor confianza, mayor es la fiabilidad de que la presencia de las características en el antecedente de la regla A implicará la presencia de las características en el consecuente de la regla B.

rules <- sort(rules,by="confidence")
inspect(head(rules)) 
    lhs                                 rhs                   support confidence  coverage     lift count
[1] {name==username=No,                                                                                  
     fake=Si}                        => {external URL=No}   0.4670139          1 0.4670139 1.131631   269
[2] {description length=muy corto,                                                                       
     fake=Si}                        => {external URL=No}   0.4531250          1 0.4531250 1.131631   261
[3] {name==username=No,                                                                                  
     description length=muy corto,                                                                       
     fake=Si}                        => {external URL=No}   0.4253472          1 0.4253472 1.131631   245
[4] {nums/length fullname=muy bajo,                                                                      
     fake=Si}                        => {external URL=No}   0.4218750          1 0.4218750 1.131631   243
[5] {profile pic=Si,                                                                                     
     nums/length fullname=muy bajo,                                                                      
     #followers=muy alto}            => {name==username=No} 0.4166667          1 0.4166667 1.035971   240
[6] {nums/length fullname=muy bajo,                                                                      
     name==username=No,                                                                                  
     fake=Si}                        => {external URL=No}   0.4097222          1 0.4097222 1.131631   236

En este caso, la confianza es 1, lo que significa que el 100% de las transacciones que tienen el antecedente también tienen el consecuente.

r2 <- subset(rules, subset = rhs %in% c("fake=Si"))
inspect(head(r2))
    lhs                                 rhs         support confidence  coverage     lift count
[1] {external URL=No,                                                                          
     #posts=muy bajo}                => {fake=Si} 0.3072917  0.9567568 0.3211806 1.913514   177
[2] {fullname words=muy corto,                                                                 
     name==username=No,                                                                        
     description length=muy corto,                                                             
     external URL=No}                => {fake=Si} 0.3454861  0.8122449 0.4253472 1.624490   199
[3] {fullname words=muy corto,                                                                 
     name==username=No,                                                                        
     description length=muy corto}   => {fake=Si} 0.3454861  0.8024194 0.4305556 1.604839   199
[4] {fullname words=muy corto,                                                                 
     nums/length fullname=muy bajo,                                                            
     description length=muy corto,                                                             
     external URL=No}                => {fake=Si} 0.3072917  0.7972973 0.3854167 1.594595   177
[5] {fullname words=muy corto,                                                                 
     nums/length fullname=muy bajo,                                                            
     description length=muy corto}   => {fake=Si} 0.3072917  0.7866667 0.3906250 1.573333   177
[6] {fullname words=muy corto,                                                                 
     name==username=No,                                                                        
     external URL=No}                => {fake=Si} 0.3750000  0.7105263 0.5277778 1.421053   216

3.7.3 Lift

Por último, vamos a analizar las reglas ordenándolas primero por el lift de las reglas. Recordamos que un lift alto indica que la presencia de A incrementa significativamente la probabilidad de que B ocurra, lo que sugiere una fuerte asociación entre las características.

rules <- sort(rules,by="lift")
inspect(head(rules)) 
    lhs                                 rhs                     support confidence  coverage     lift count
[1] {name==username=No,                                                                                    
     #follows=muy alto,                                                                                    
     fake=No}                        => {#followers=muy alto} 0.3506944  0.9223744 0.3802083 2.059255   202
[2] {profile pic=Si,                                                                                       
     #follows=muy alto,                                                                                    
     fake=No}                        => {#followers=muy alto} 0.3472222  0.9216590 0.3767361 2.057657   200
[3] {profile pic=Si,                                                                                       
     name==username=No,                                                                                    
     #follows=muy alto,                                                                                    
     fake=No}                        => {#followers=muy alto} 0.3472222  0.9216590 0.3767361 2.057657   200
[4] {nums/length fullname=muy bajo,                                                                        
     #follows=muy alto,                                                                                    
     fake=No}                        => {#followers=muy alto} 0.3437500  0.9209302 0.3732639 2.056030   198
[5] {nums/length fullname=muy bajo,                                                                        
     name==username=No,                                                                                    
     #follows=muy alto,                                                                                    
     fake=No}                        => {#followers=muy alto} 0.3437500  0.9209302 0.3732639 2.056030   198
[6] {nums/length username=muy bajo,                                                                        
     #follows=muy alto,                                                                                    
     fake=No}                        => {#followers=muy alto} 0.3229167  0.9207921 0.3506944 2.055722   186

En este caso, el lift es 2.059, lo que sugiere que la aparición de “external URL=Si” es aproximadamente 2 veces más probable cuando se dan las condiciones en el antecedente.

r2 <- subset(rules, subset = rhs %in% c("fake=Si")) 
inspect(head(r2))
    lhs                                 rhs         support confidence  coverage     lift count
[1] {external URL=No,                                                                          
     #posts=muy bajo}                => {fake=Si} 0.3072917  0.9567568 0.3211806 1.913514   177
[2] {fullname words=muy corto,                                                                 
     name==username=No,                                                                        
     description length=muy corto,                                                             
     external URL=No}                => {fake=Si} 0.3454861  0.8122449 0.4253472 1.624490   199
[3] {fullname words=muy corto,                                                                 
     name==username=No,                                                                        
     description length=muy corto}   => {fake=Si} 0.3454861  0.8024194 0.4305556 1.604839   199
[4] {fullname words=muy corto,                                                                 
     nums/length fullname=muy bajo,                                                            
     description length=muy corto,                                                             
     external URL=No}                => {fake=Si} 0.3072917  0.7972973 0.3854167 1.594595   177
[5] {fullname words=muy corto,                                                                 
     nums/length fullname=muy bajo,                                                            
     description length=muy corto}   => {fake=Si} 0.3072917  0.7866667 0.3906250 1.573333   177
[6] {fullname words=muy corto,                                                                 
     name==username=No,                                                                        
     external URL=No}                => {fake=Si} 0.3750000  0.7105263 0.5277778 1.421053   216

3.8 Visualización de reglas

plot(rules)
To reduce overplotting, jitter is added! Use jitter = 0 to prevent jitter.