J'ai écrit un article parce qu'il n'y avait pas beaucoup de tutoriels dans le monde qui ont été mis en œuvre à l'aide d'exemples de données concernant les recommandations.
Il existe des méthodes telles que l'apprentissage automatique pour créer des recommandations, mais cet article explique comment créer des recommandations à l'aide de méthodes basées sur des statistiques.
Je vais expliquer l'utilisation de python et d'un jeu de données ouvert.
Ceci est un article sur l'implémentation par python. Pour le concept de recommandations, consultez les articles suivants. Tutoriel de recommandation utilisant l'analyse d'association (concept)
Il sera mis en œuvre selon le flux de l'article de l'édition conceptuelle.
Il y a des frais, mais nous avons préparé un environnement d'exécution pour Google Colaboratory à ici.
Importez les bibliothèques requises.
#Importer la bibliothèque
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
Chargez l'ensemble de données utilisé dans ce didacticiel. Ceci charge un ensemble de données depuis le référentiel github.
#Charger le jeu de données
import urllib.request
from io import StringIO
url = "https://raw.githubusercontent.com/tachiken0210/dataset/master/dataset_cart.csv"
#Fonction de lecture de csv
def read_csv(url):
res = urllib.request.urlopen(url)
res = res.read().decode("utf-8")
df = pd.read_csv(StringIO( res) )
return df
#Courir
df_cart_ori = read_csv(url)
Vérifiez le contenu de l'ensemble de données utilisé cette fois.
df_cart_ori.head()
cart_id | goods_id | action | create_at | update_at | last_update | time | |
---|---|---|---|---|---|---|---|
0 | 108750017 | 583266 | UPD | 2013-11-26 03:11:06 | 2013-11-26 03:11:06 | 2013-11-26 03:11:06 | 1385478215 |
1 | 108750017 | 662680 | UPD | 2013-11-26 03:11:06 | 2013-11-26 03:11:06 | 2013-11-26 03:11:06 | 1385478215 |
2 | 108750017 | 664077 | UPD | 2013-11-26 03:11:06 | 2013-11-26 03:11:06 | 2013-11-26 03:11:06 | 1385478215 |
3 | 108199875 | 661648 | ADD | 2013-11-26 03:11:10 | 2013-11-26 03:11:10 | 2013-11-26 03:11:10 | 1385478215 |
4 | 105031004 | 661231 | ADD | 2013-11-26 03:11:41 | 2013-11-26 03:11:41 | 2013-11-26 03:11:41 | 1385478215 |
Cet ensemble de données est les données du journal lorsqu'un client sur un certain site EC met un produit dans le panier.
** ・ card_id: identifiant de panier associé au client (il est acceptable de le considérer comme un identifiant de client, il sera donc appelé le client ci-dessous)
**** ・ goods_id: identifiant de produit
**** ・ action: Action du client (AJOUTER: Ajouter au panier, DEL: Supprimer du panier, etc.)
**** ・ create_at: Heure de création du journal
**** ・ update_at: Le journal est mis à jour Heure (non utilisée cette fois)
**** ・ last_update: Heure à laquelle le journal a été mis à jour (non utilisée cette fois)
**** ・ heure: Horodatage
**
En outre, les ensembles de données ouverts généraux, y compris l'ensemble de données utilisé cette fois, incluent rarement des noms spécifiques tels que les noms de produits dans l'ensemble de données et sont essentiellement exprimés par l'ID de produit. Par conséquent, il est difficile de savoir de quel type de produit il s'agit, mais veuillez noter que cela est dû au contenu des données.
Jetons ensuite un coup d'œil au contenu de ces données. Cet ensemble de données contient des données pour tous les clients sous forme de journal chronologique, alors concentrons-nous sur un seul client. Tout d'abord, extrayons le client avec le plus grand nombre de journaux dans les données.
df_cart_ori["cart_id"].value_counts().head()
#production
110728794 475
106932411 426
115973611 264
109269739 205
112332751 197
Name: cart_id, dtype: int64
Il existe 475 journaux pour 110728794 utilisateurs, ce qui semble être le plus enregistré. Extrayons le journal de ce client.
df_cart_ori[df_cart_ori["cart_id"]==110728794].head(10)
cart_id | goods_id | action | create_at | update_at | last_update | time | |
---|---|---|---|---|---|---|---|
32580 | 110728794 | 664457 | ADD | 2013-11-26 22:54:13 | 2013-11-26 22:54:13 | 2013-11-26 22:54:13 | 1385478215 |
32619 | 110728794 | 664885 | ADD | 2013-11-26 22:55:09 | 2013-11-26 22:55:09 | 2013-11-26 22:55:09 | 1385478215 |
33047 | 110728794 | 664937 | ADD | 2013-11-26 22:58:52 | 2013-11-26 22:58:52 | 2013-11-26 22:58:52 | 1385478215 |
33095 | 110728794 | 664701 | ADD | 2013-11-26 23:00:25 | 2013-11-26 23:00:25 | 2013-11-26 23:00:25 | 1385478215 |
34367 | 110728794 | 665050 | ADD | 2013-11-26 23:02:40 | 2013-11-26 23:02:40 | 2013-11-26 23:02:40 | 1385478215 |
34456 | 110728794 | 664989 | ADD | 2013-11-26 23:05:03 | 2013-11-26 23:05:03 | 2013-11-26 23:05:03 | 1385478215 |
34653 | 110728794 | 664995 | ADD | 2013-11-26 23:07:00 | 2013-11-26 23:07:00 | 2013-11-26 23:07:00 | 1385478215 |
34741 | 110728794 | 664961 | ADD | 2013-11-26 23:09:41 | 2013-11-26 23:09:41 | 2013-11-26 23:09:41 | 1385478215 |
296473 | 110728794 | 665412 | DEL | 2013-12-03 17:17:30 | 2013-12-03 17:17:30 | 2013-12-03 07:41:13 | 1386083014 |
296476 | 110728794 | 665480 | DEL | 2013-12-03 17:17:37 | 2013-12-03 17:17:37 | 2013-12-03 07:42:29 | 1386083014 |
Si vous regardez ce client, vous pouvez voir qu'il ajoute continuellement des paniers à ses produits tout au long de la journée.
En analysant cela pour d'autres clients de la même manière, il semble qu'il est facile de voir que le produit y est susceptible d'être ajouté au panier à côté du produit x qui est généralement présent.
De cette manière, le but de ce temps est d'analyser le modèle selon lequel ** "Le produit y est susceptible d'être ajouté au panier à côté d'un certain produit x" à partir de cet ensemble de données. **
Nous traiterons en fait les données du suivant, mais gardez ce qui précède dans le coin de votre tête.
À partir de là, nous prétraiterons les données. Tout d'abord, si vous regardez la colonne "action", il y a diverses choses telles que ADD et DEL. Pour l'instant, concentrons-nous sur le journal appelé ADD (ajouté au panier). Cela signifie se concentrer uniquement sur la demande de produit du client.
df = df_cart_ori.copy()
df = df[df["action"]=="ADD"]
df.head()
cart_id | goods_id | action | create_at | update_at | last_update | time | |
---|---|---|---|---|---|---|---|
3 | 108199875 | 661648 | ADD | 2013-11-26 03:11:10 | 2013-11-26 03:11:10 | 2013-11-26 03:11:10 | 1385478215 |
4 | 105031004 | 661231 | ADD | 2013-11-26 03:11:41 | 2013-11-26 03:11:41 | 2013-11-26 03:11:41 | 1385478215 |
6 | 110388732 | 661534 | ADD | 2013-11-26 03:11:55 | 2013-11-26 03:11:55 | 2013-11-26 03:11:55 | 1385478215 |
7 | 110388740 | 662336 | ADD | 2013-11-26 03:11:58 | 2013-11-26 03:11:58 | 2013-11-26 03:11:58 | 1385478215 |
8 | 110293997 | 661648 | ADD | 2013-11-26 03:12:13 | 2013-11-26 03:12:13 | 2013-11-26 03:12:13 | 1385478215 |
Ensuite, triez l'ensemble de données pour classer par ordre chronologique les articles du panier pour chaque client.
df = df[["cart_id","goods_id","create_at"]]
df = df.sort_values(["cart_id","create_at"],ascending=[True,True])
df.head()
cart_id | goods_id | create_at | |
---|---|---|---|
548166 | 78496306 | 661142 | 2013-11-15 23:07:02 |
517601 | 79100564 | 662760 | 2013-11-24 18:17:24 |
517404 | 79100564 | 661093 | 2013-11-24 18:25:29 |
23762 | 79100564 | 664856 | 2013-11-26 13:41:47 |
22308 | 79100564 | 562296 | 2013-11-26 13:44:20 |
Le processus d'ici formatera les données pour s'adapter à la bibliothèque d'analyse d'association.
Pour chaque client, nous ajouterons les articles du panier à la liste dans l'ordre.
df = df[["cart_id","goods_id"]]
df["goods_id"] = df["goods_id"].astype(int).astype(str)
df = df.groupby(["cart_id"])["goods_id"].apply(lambda x:list(x)).reset_index()
df.head()
| cart_id | goods_id |
|---:|----------:|:---------------------------------------------------------------------------------------------------------------------------------------------|
| 0 | 78496306 | ['661142'] |
| 1 | 79100564 | ['662760', '661093', '664856', '562296', '663364', '664963', '664475', '583266'] |
| 2 | 79455669 | ['663801', '664136', '664937', '663932', '538673', '663902', '667859'] |
| 3 | 81390353 | ['663132', '661725', '664236', '663331', '659679', '663847', '662340', '662292', '664099', '664165', '663581', '665426', '663899', '663405'] |
| 4 | 81932021 | ['662282', '664218']
Ensuite, vérifions le contenu du bloc de données ci-dessus.
Remarquez le client sur la deuxième ligne (79100564).
Tout d'abord, après l'ajout du premier produit 663801, 664136 est ajouté au panier. Autrement dit, X = "663801" et Y = "664136". <
De plus, après 664136, 664937 est ajouté au panier. Pour ceux-ci, X = "664136" et Y = "664937".
De cette manière, en disposant cette paire XY pour chaque client, il est possible d'effectuer une analyse d'association.
Formatez maintenant les données pour que ce XY soit apparié.
def get_combination(l):
length = len(l)
list_output = []
list_pair = []
for i in range(length-1):
if l[i]==l[i+1]:
pass
else:
list_pair =[l[i],l[i+1]]
list_output.append(list_pair)
return list_output
df["comb_goods_id"] = df["goods_id"].apply(lambda x:get_combination(x))
#Faire une liste des entrées d'association
dataset= []
for i,contents in enumerate(df["comb_goods_id"].values):
for c in contents:
dataset.append(c)
print("Le nombre de paires XY est",len(dataset))
print("Contenu de la paire XY",dataset[:5])
#production
Le nombre de paires XY est 141956
Contenu de la paire XY[['662760', '661093'], ['661093', '664856'], ['664856', '562296'], ['562296', '663364'], ['663364', '664963']]
Ce qui précède est utilisé comme entrée pour l'analyse d'association. J'ai créé une méthode qui effectue une analyse d'association en tant que méthode d'association, je vais donc l'utiliser.
#Bibliothèque d'association
def association(dataset):
df = pd.DataFrame(dataset,columns=["x","y"])
num_dataset = df.shape[0]
df["sum_count_xy"]=1
print("calculating support....")
df_a_support = (df.groupby("x").sum()/num_dataset).rename(columns={"sum_count_xy":"support_x"})
df_b_support = (df.groupby("y").sum()/num_dataset).rename(columns={"sum_count_xy":"support_y"})
df = df.groupby(["x","y"]).sum()
df["support_xy"]=df["sum_count_xy"]/num_dataset
df = df.reset_index()
df = pd.merge(df,df_a_support,on="x")
df = pd.merge(df,df_b_support,on="y")
print("calculating confidence....")
df_temp = df.groupby("x").sum()[["sum_count_xy"]].rename(columns={"sum_count_xy":"sum_count_x"})
df = pd.merge(df,df_temp,on="x")
df_temp = df.groupby("y").sum()[["sum_count_xy"]].rename(columns={"sum_count_xy":"sum_count_y"})
df = pd.merge(df,df_temp,on="y")
df["confidence"]=df["sum_count_xy"]/df["sum_count_x"]
print("calculating lift....")
df["lift"]=df["confidence"]/df["support_y"]
df["sum_count"] = num_dataset
df_output = df
return df_output
#Appliquer le jeu de données à l'analyse d'association
df_output = association(dataset)
df_output.head()
x | y | sum_count_xy | support_xy | support_x | support_y | sum_count_x | sum_count_y | confidence | lift | sum_count | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 485836 | 662615 | 1 | 7.04444e-06 | 7.04444e-06 | 0.000147933 | 1 | 21 | 1 | 6759.81 | 141956 |
1 | 549376 | 662615 | 1 | 7.04444e-06 | 2.11333e-05 | 0.000147933 | 3 | 21 | 0.333333 | 2253.27 | 141956 |
2 | 654700 | 662615 | 1 | 7.04444e-06 | 0.000464933 | 0.000147933 | 66 | 21 | 0.0151515 | 102.421 | 141956 |
3 | 661475 | 662615 | 1 | 7.04444e-06 | 0.000965088 | 0.000147933 | 137 | 21 | 0.00729927 | 49.3417 | 141956 |
4 | 661558 | 662615 | 1 | 7.04444e-06 | 0.000408577 | 0.000147933 | 58 | 21 | 0.0172414 | 116.548 | 141956 |
J'ai eu le résultat.
Le contenu de chaque colonne est le suivant
** ・ x: Condition partie X
・ y: Conclusion partie Y
・ sum_count_xy: Nombre de données applicables à XY
・ support_xy: support XY
・ support_x: support X
> ・ Support_y: Support Y
・ sum_count_x: Nombre de données applicables à X
・ sum_count_y: Nombre de données applicables à Y
・ confiance: Confiance
・ lift: Lift
**
Eh bien, l'exécution de l'association est terminée, mais il y a une mise en garde.
** Fondamentalement, une valeur de portance élevée semble être très pertinente, mais si le nombre de données est petit, on soupçonne que cela peut être arrivé par accident. **
Prenez la ligne du haut, par exemple. C'est la partie de x = "485836", y = "662615".
Cela a une valeur de portance très élevée de 6760, mais cela ne s'est produit qu'une seule fois (= support_xy). En supposant que le nombre de fois soit suffisamment de données, il est fort possible que cela se soit produit par accident.
Alors, comment dériver le seuil support_xy qui détermine s'il est accidentel ou non?
.. .. .. En réalité, il est difficile de déterminer ce seuil. (Il n'y a pas de bonne réponse)
Ici, vérifions une fois l'histogramme de support_xy.
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.hist(df_output["sum_count_xy"], bins=100)
ax.set_title('')
ax.set_xlabel('xy')
ax.set_ylabel('freq')
#C'est difficile à voir, alors limitez la portée de l'axe y
ax.set_ylim(0,500)
fig.show()
Vous pouvez voir que la plupart des données ont un support_xy sur l'axe des x concentré près de 0.
Dans ce tutoriel, toutes les données proches de 0 sont considérées comme une coïncidence et seront supprimées.
Lors de la définition du seuil, la fraction est utilisée et le seuil est défini de manière à ce que les données des 98% inférieurs puissent être considérées comme une coïncidence.
df = df_output.copy()
df = df[df["support_xy"]>=df["support_xy"].quantile(0.98)]
df.head()
x | y | sum_count_xy | support_xy | support_x | support_y | sum_count_x | sum_count_y | confidence | lift | sum_count | |
---|---|---|---|---|---|---|---|---|---|---|---|
58 | 662193 | 667129 | 8 | 5.63555e-05 | 0.0036138 | 0.00281073 | 513 | 399 | 0.0155945 | 5.54822 | 141956 |
59 | 665672 | 667129 | 12 | 8.45332e-05 | 0.00395193 | 0.00281073 | 561 | 399 | 0.0213904 | 7.61026 | 141956 |
60 | 666435 | 667129 | 30 | 0.000211333 | 0.0082279 | 0.00281073 | 1168 | 399 | 0.0256849 | 9.13817 | 141956 |
62 | 666590 | 667129 | 7 | 4.93111e-05 | 0.00421257 | 0.00281073 | 598 | 399 | 0.0117057 | 4.16464 | 141956 |
63 | 666856 | 667129 | 8 | 5.63555e-05 | 0.00390966 | 0.00281073 | 555 | 399 | 0.0144144 | 5.12835 | 141956 |
Cela m'a sauvé les données de la paire xy que je pensais être arrivées par accident.
Puis le dernier processus, ne laissant que y, qui est pertinent pour x. Comme expliqué précédemment, cela signifie que seules les paires xy avec des valeurs de portance élevées doivent être conservées.
Ici, nous ne laisserons que les paires avec une élévation de 2.0 ou plus.
** À propos, il s'agit d'une image de la signification de l'ascenseur (valeur de l'ascenseur), mais c'est une valeur qui montre à quel point il est facile d'ajouter y produits au panier lorsque x produits sont ajoutés au panier par rapport à d'autres produits. **
Par exemple, dans le contenu du bloc de données ci-dessus, lorsque le produit "662193" (x) est ajouté au panier, le produit "667129" (y) est ** 5,5 fois plus facile à ajouter au panier * * Ça veut dire.
df = df[df["lift"]>=2.0]
df_recommendation = df.copy()
df_recommendation.head()
x | y | sum_count_xy | support_xy | support_x | support_y | sum_count_x | sum_count_y | confidence | lift | sum_count | |
---|---|---|---|---|---|---|---|---|---|---|---|
58 | 662193 | 667129 | 8 | 5.63555e-05 | 0.0036138 | 0.00281073 | 513 | 399 | 0.0155945 | 5.54822 | 141956 |
59 | 665672 | 667129 | 12 | 8.45332e-05 | 0.00395193 | 0.00281073 | 561 | 399 | 0.0213904 | 7.61026 | 141956 |
60 | 666435 | 667129 | 30 | 0.000211333 | 0.0082279 | 0.00281073 | 1168 | 399 | 0.0256849 | 9.13817 | 141956 |
62 | 666590 | 667129 | 7 | 4.93111e-05 | 0.00421257 | 0.00281073 | 598 | 399 | 0.0117057 | 4.16464 | 141956 |
63 | 666856 | 667129 | 8 | 5.63555e-05 | 0.00390966 | 0.00281073 | 555 | 399 | 0.0144144 | 5.12835 | 141956 |
Ceci termine la génération des données de recommandation de base.
L'utilisation spécifique de ceci est aussi simple que la promotion du produit "667129" auprès de la personne qui a ajouté le produit "662193" au panier, par exemple.
En fait, les données de cette recommandation sont stockées dans la base de données afin que y soit sorti lorsque x est entré.
Maintenant que j'ai créé les données de recommandation, je vais vous montrer les problèmes avec cette recommandation (sans la cacher).
Autrement dit ** il n'est pas possible de cibler tous les produits d'entrée (x). **
Vérifions ça.
Tout d'abord, vérifions le nombre de types de x dans les données de transaction d'origine.
#x est la marchandise des données originales_Puisqu'il s'agit d'un identifiant, il agrège le numéro unique de celui-ci.
df_cart_ori["goods_id"].nunique()
#production
8398
À l'origine, il existe 8398 types de x.
En d'autres termes, nous devrions être en mesure de recommander des produits hautement pertinents (y) pour ces 8398 types d'entrées (x).
Voyons maintenant à quelle quantité d'entrée (x) correspond la sortie précédente.
#Regroupez le nombre unique de x dans le résultat de la recommandation.
df_recommendation["x"].nunique()
#production
463
L'entrée (x) prise en charge par les données de recommandation ** ci-dessus ne prend en charge que 463 types. ** (463/8392, donc environ 5%)
C'est l'une des faiblesses des recommandations basées sur le comportement des utilisateurs.
Peut-être, en réalité, faudra-t-il résoudre ce problème.
Les éléments suivants peuvent être considérés comme des contre-mesures.
Pour les recommandations qui utilisent des associations, il est relativement facile de créer des données de recommandation si les données de transaction sont accumulées. Si vous essayez de créer un moteur de recommandation dans votre travail réel, essayez-le!