El algoritmo Apriori es uno de los más empleados para la creación de reglas de asociación. A pesar de ello, no existe un paquete que se puede considerar el "estándar" en Python, como sucede con el caso de arules en R. En esta ocasión voy a analizar algunos paquetes que se pueden encontrar en PyPi en los que se implementa el algoritmo Apriori en Python para tener una comparativa de estos.
MLxtend
Hace un tiempo, al explicar las reglas de asociación, utilice la implementación de MLxtend para la creación de reglas de asociación. Para instalar este paquete solamente se es necesario ejecutar el siguiente comando en la terminal
pip install mlxtend
Una vez hecho esto, se puede usar el conjunto de datos Online Retail Data Set disponible en el repositorio de la Universidad de California Irvine. Este conjunto de datos se encuentra en un archivo Excel donde cada una de las filas son los productos de diferentes pedidos (identificable por la columna InvoiceNo
). Los datos se pueden cargar y limpiar con el siguiente código.
import pandas as pd df = pd.read_excel('http://archive.ics.uci.edu/ml/machine-learning-databases/00352/Online%20Retail.xlsx') # Preparación de los datos df['Description'] = df['Description'].str.strip() df.dropna(axis=0, subset=['InvoiceNo'], inplace=True) df['InvoiceNo'] = df['InvoiceNo'].astype('str') # Se elimina la referencia envio al estar en todos los pedidos df = df[df['Description'] != 'POSTAGE'] df.head()
InvoiceNo StockCode Description Quantity \ 0 536365 85123A WHITE HANGING HEART T-LIGHT HOLDER 6 1 536365 71053 WHITE METAL LANTERN 6 2 536365 84406B CREAM CUPID HEARTS COAT HANGER 8 3 536365 84029G KNITTED UNION FLAG HOT WATER BOTTLE 6 4 536365 84029E RED WOOLLY HOTTIE WHITE HEART. 6 InvoiceDate UnitPrice CustomerID Country 0 2010-12-01 08:26:00 2.55 17850.0 United Kingdom 1 2010-12-01 08:26:00 3.39 17850.0 United Kingdom 2 2010-12-01 08:26:00 2.75 17850.0 United Kingdom 3 2010-12-01 08:26:00 3.39 17850.0 United Kingdom 4 2010-12-01 08:26:00 3.39 17850.0 United Kingdom
En los datos se ha eliminado los registros que contenga la referencia POSTAGE
, ya que es el envío y se incluye en todos los pedidos.
Preparación de los datos para MLxtend
Para trabajar con la implementación de Apriori de MLxtend es necesario crear una tabla donde las filas representan los pedidos y las columnas las referencias. Indicando con el valor vedado cuando la referencia se incluye en el pedido y falso el cualquier otro caso. Una forma para crear esta tabla se puede ver en el siguiente ejemplo.
basket = (df[df['Country'] == 'Spain'] .groupby(['InvoiceNo', 'Description'])['Quantity'] .sum().apply(lambda x: x>0).unstack().reset_index().fillna(False) .set_index('InvoiceNo'))
Usando en este caso .groupby()
para agrupar los datos, contar el número de operaciones con .sum()
y asignar el valor verdadero cuando esta sea mayor de 0.
Obtención las reglas de asociación con MLxtend
Las reglas de asociación en MLxtend se obtienen en dos pasos. En primer lugar, es necesario obtener la lista de ítemsets candidatos con la función apriori()
. Una vez hecho esto, las reglas de asociación se pueden obtener con la función association_rules()
. En el siguiente código se muestran los pasos necesarios.
from mlxtend.frequent_patterns import apriori, association_rules frequent_itemsets = apriori(basket, min_support=0.06, use_colnames=True) rules = association_rules(frequent_itemsets, metric='confidence', min_threshold=0.8) frequent_itemsets.head()
support itemsets 0 0.145631 (6 RIBBONS RUSTIC CHARM) 1 0.067961 (ALARM CLOCK BAKELIKE GREEN) 2 0.116505 (ASSORTED COLOUR BIRD ORNAMENT) 3 0.097087 (CLASSIC METAL BIRDCAGE PLANT HOLDER) 4 0.087379 (DOLLY GIRL LUNCH BOX)
La lista de los itemsets frecuentes se puede ver en una DataFrame donde la primera columna es soporte y la segunda el nombre del itemset.
Por otro lado, las reglas también son un DataFrame con toda la información necesaria: antecedente, consecuente, soporte del antecedente, soporte del consecuente, soporte de la regla, confían, lift (mejora de la confianza), leverage y convicción
rules.head()
antecedents consequents \ 0 (POPPY'S PLAYHOUSE BEDROOM) (POPPY'S PLAYHOUSE KITCHEN) 1 (POPPY'S PLAYHOUSE KITCHEN) (POPPY'S PLAYHOUSE BEDROOM) antecedent support consequent support support confidence lift \ 0 0.077670 0.067961 0.067961 0.875 12.875 1 0.067961 0.077670 0.067961 1.000 12.875 leverage conviction 0 0.062683 7.456311 1 0.062683 inf
Una tabla en la que se puede encontrar toda la información necesaria para decidir la importancia de todas las reglas de asociación encontradas.
El paquete Efficient-Apriori contiene una implementación en Python del algoritmo Apriori. Un paquete que se puede instalar ejecutando el siguiente comando en la terminal.
pip install efficient-apriori
A diferencia MLxtend las transacciones de deben indicar como una lista de lista. En esta, cada una de las listas del primer nivel representa un ticket y la segunda lista debe contener el identificador de cara una de las referencias del ticket. Lo que se puede conseguir mediante empleando una lista por comprensión en la que se seleccionan los elementos.
df_es = df[df['Country'] == 'Spain'] transactions = [df_es['Description'][df_es['InvoiceNo'] == x].tolist() for x in df_es['InvoiceNo'].unique()] transactions[7:9]
[['RED 3 PIECE RETROSPOT CUTLERY SET', 'PINK 3 PIECE POLKADOT CUTLERY SET', 'BLUE 3 PIECE POLKADOT CUTLERY SET', 'GREEN 3 PIECE POLKADOT CUTLERY SET'], ['6 RIBBONS RUSTIC CHARM', 'RIBBON REEL STRIPES DESIGN', 'RIBBON REEL LACE DESIGN', 'CHOCOLATE BOX RIBBONS', 'BABY BOOM RIBBONS', 'LARGE WHITE/PINK ROSE ART FLOWER', 'ASSORTED COLOUR BIRD ORNAMENT', 'BLACK RECORD COVER FRAME', 'WOODEN FRAME ANTIQUE WHITE', 'FRENCH WC SIGN BLUE METAL', 'RED SPOTTY BISCUIT TIN', 'PACK OF 72 RETROSPOT CAKE CASES', '72 SWEETHEART FAIRY CAKE CASES', 'SPACEBOY LUNCH BOX', 'RED RETROSPOT CAKE STAND', 'REGENCY CAKESTAND 3 TIER', 'SET OF 72 RETROSPOT PAPER DOILIES', 'RED RETROSPOT MUG', 'GIN AND TONIC MUG', 'HOME SWEET HOME MUG', 'BLOSSOM IMAGES NOTEBOOK SET', 'CURIOUS IMAGES NOTEBOOK SET', 'FANCY FONTS BIRTHDAY WRAP', 'RED RETROSPOT WRAP', 'BLUE POLKADOT WRAP', 'WORLD WAR 2 GLIDERS ASSTD DESIGNS', 'TRADITIONAL WOODEN SKIPPING ROPE', 'SET OF 6 SOLDIER SKITTLES', 'BOX OF VINTAGE ALPHABET BLOCKS', 'CLASSIC METAL BIRDCAGE PLANT HOLDER']]
Lo que prepara los datos en el formato que necesita este paquete.
Obtención las reglas de asociación con Efficient-Apriori
Ahora, simplemente se tiene que llamar a la función apriori()
para obtener la lista de itemsets y reglas.
from efficient_apriori import apriori itemsets, rules = apriori(transactions, min_support=0.06, min_confidence=0.8) itemsets
{1: {('LUNCH BAG PINK POLKADOT',): 10, ('LUNCH BAG RED RETROSPOT',): 8, ('PARTY BUNTING',): 8, ('WHITE HANGING HEART T-LIGHT HOLDER',): 11, ('ALARM CLOCK BAKELIKE GREEN',): 8, ('PLASTERS IN TIN SKULLS',): 11, ('PLASTERS IN TIN CIRCUS PARADE',): 7, ('REGENCY CAKESTAND 3 TIER',): 25, ('CLASSIC METAL BIRDCAGE PLANT HOLDER',): 11, ("POPPY'S PLAYHOUSE BEDROOM",): 8, ("POPPY'S PLAYHOUSE KITCHEN",): 7, ('SPACEBOY LUNCH BOX',): 12, ('STRAWBERRY CERAMIC TRINKET BOX',): 7, ('PAPER BUNTING RETROSPOT',): 7, ('SET OF 3 HEART COOKIE CUTTERS',): 7, ('SET OF 72 RETROSPOT PAPER DOILIES',): 10, ('SET OF 36 PAISLEY FLOWER DOILIES',): 8, ('SET OF 36 TEATIME PAPER DOILIES',): 7, ('JAM MAKING SET WITH JARS',): 16, ('SET/10 PINK POLKADOT PARTY CANDLES',): 7, ('SET OF 6 GIRLS CELEBRATION CANDLES',): 7, ('PACK OF 60 PINK PAISLEY CAKE CASES',): 8, ('DOLLY GIRL LUNCH BOX',): 9, ('SET OF 3 CAKE TINS PANTRY DESIGN',): 7, ('SET/5 RED RETROSPOT LID GLASS BOWLS',): 7, ('6 RIBBONS RUSTIC CHARM',): 15, ('RED RETROSPOT CAKE STAND',): 10, ('PLASTERS IN TIN WOODLAND ANIMALS',): 9, ('RED RETROSPOT TAPE',): 7, ('ASSORTED COLOUR BIRD ORNAMENT',): 12, ('WOODEN FRAME ANTIQUE WHITE',): 7, ('PACK OF 72 RETROSPOT CAKE CASES',): 11, ('ROSES REGENCY TEACUP AND SAUCER',): 11, ('ROUND SNACK BOXES SET OF4 WOODLAND',): 12, ('DOORMAT SPOTTY HOME SWEET HOME',): 7, ('SET/20 RED RETROSPOT PAPER NAPKINS',): 9, ('POPCORN HOLDER',): 7, ('SPOTTY BUNTING',): 7, ('SET OF 3 REGENCY CAKE TINS',): 7}, 2: {('6 RIBBONS RUSTIC CHARM', 'ASSORTED COLOUR BIRD ORNAMENT'): 9, ("POPPY'S PLAYHOUSE BEDROOM", "POPPY'S PLAYHOUSE KITCHEN"): 7}}
En este caso los itemsets son un diccionario. En este diccionario la clave es el número de itemsets y el valor es un nuevo diccionario. Este segundo diccionario tiene como clave los itemsets y el valor es el número de veces que aparece. Así se puede comprobar que el resultado en ambos casos es similar ya que '6 RIBBONS RUSTIC CHARM' aparece 15 veces en 103 transacciones, esto es 0.14563 el mismo valor que se ha obtenido con MLxtend.
Por otro lado, las reglas es un listado de objetos reglas que contiene la información necesaria.
for rule in rules: print(rule)
{POPPY'S PLAYHOUSE KITCHEN} -> {POPPY'S PLAYHOUSE BEDROOM} (conf: 1.000, supp: 0.068, lift: 12.875, conv: 922330097.087) {POPPY'S PLAYHOUSE BEDROOM} -> {POPPY'S PLAYHOUSE KITCHEN} (conf: 0.875, supp: 0.068, lift: 12.875, conv: 7.456)
Pudiéndose comprobar que los resultados son iguales a los vistos en MLxtend.
Comparación de MLxtend y Efficient-Apriori
En base a las pruebas realizadas los resultados en ambos paquetes son similares, lo que indica que las implementaciones son similares. Obteniendo los mismos resultados con el conjunto de datos de prueba. Así a la hora de decantarnos por uno u otro debemos fijarnos en cuál de ellos se adapta mejor a nuestro flujo de trabajo.
Quizás e mayor problema de MLxtend sean los datos de entrada. Crear un DataFrame con tantas columnas como referencias, aunque el contenido sea un campo booleano, requiere mucha memoria. Por eso en las pruebas realizadas se centró el análisis solamente a un país. Así, para grandes conjuntos la mejor solución de los dos es Efficient-Apriori.
Por otro lado, en cuanto a los resultados la forma en la que se presentan los resultados es más fácil de leer en MLxtend ya que estos se presentan en DataFrames. Obtener el soporte de un itemset es algo más complicado en Efficient-Apriori. De este modo, para conjuntos de datos pequeños, donde se desea analizar en detalle los resultados, la mejor opción sea MLxtend.
Conclusiones
En esta entrada se ha visto una comparación entre dos paquetes de PyPi donde se implementa el algoritmo Apriori en Python. Por un lado, MLxtend muestra los resultados de una forma más clara mientras que es menos eficiente en cuestión de memoria. Por otro lado, el paquete Efficient-Apriori requiere menos recursos, aunque obtener la información detallada de las reglas puede ser un poco más complicado. Aunque la eficiencia de este último lo convierte en la mejor elección para producción.