Tirez parti de quelques équations simples pour générer des colonnes associées dans des tableaux de test.
J'ai récemment joué avec Databricks Labs Data Generator pour créer des ensembles de données entièrement synthétiques à partir de zéro. Dans ce cadre, j'ai étudié la création de données de ventes autour de différents magasins, employés et clients. En tant que tel, je voulais créer des relations entre les colonnes que je remplissais artificiellement, comme par exemple mapper les employés et les clients à un certain magasin.
En utilisant les UDF PySpark et un peu de logique, nous pouvons générer des colonnes associées qui suivent une relation plusieurs-à-un. Avec un peu de magie, nous sommes même capables d'étendre la logique pour donner une certaine variation à ce mappage – comme un client qui achète généralement dans son magasin local mais parfois dans un magasin différent.
Remarque : Vous pouvez ignorer cette section si cela n'est pas nécessaire !
Tout d'abord, nous devons créer un DataFrame avec notre première colonne générée aléatoirement. Dans notre cas, nous allons commencer par le magasin, car logiquement nous aurons « de nombreux employés par magasin » et « de nombreux clients faisant leurs achats de manière répétée dans un magasin ».
Avec un modèle de données Star Schema à l'esprit, nous allons commencer par notre table de faits sur les ventes – une table transactionnelle qui contiendra les valeurs clés pour l'identifiant de vente, l'identifiant de magasin, l'identifiant d'employé et l'identifiant de client, le montant de la vente ainsi qu'une date/heure. données pour l'achat. Nous pouvons ensuite renseigner les détails du magasin, de l'employé et du client dans des tableaux de dimensions plus loin.
Nous allons commencer petit : une table avec 1 000 ventes fera l’affaire. Il faut maintenant décider comment répartir ces ventes entre les magasins, les collaborateurs et les clients. Proposons ce qui suit :
- # Magasins = 20
- # Employés = 100
- # Clients = 700
On peut aussi dire que les ventes seront enregistrées au cours du mois dernier :
- Date de première vente = 2023-11-01
- Date de la dernière vente = 2023-11-30
L'ID de vente doit être une colonne unique afin que nous puissions générer une colonne Id pour cela. Il faut maintenant répartir les 1000 ventes dans les 20 magasins. Par souci de simplicité, nous supposerons que c'est aléatoire.
En utilisant Databricks Lab Generator, nous pouvons le faire avec le code suivant :
Ajoutez maintenant du code pour enregistrer quand les ventes ont été réalisées et leur montant. Pour simplifier les choses, nous arrondirons l’horodatage de la vente à l’heure la plus proche.
Pour calculer le montant de la vente, nous pouvons utiliser le paramètre « expr » dans notre expression withColumn pour nous permettre de générer un nombre aléatoire, avec certaines règles/limites.
Dans ce cas, l'expression est assez simple : produisez un nombre aléatoire (entre 0 et 1), ajoutez 0,1 (en vous assurant que les valeurs de vente ne sont pas 0) et multipliez par 350.
Nous avons maintenant notre forme de base pour le DataFrame, alors rassemblez le tout :
Nous pouvons créer un profil de données rapide pour examiner la répartition des valeurs dans les colonnes :
Nous pouvons voir que la distribution StoreId est relativement uniforme dans les 20 magasins, sans valeurs manquantes ni moyennes autour du centre comme on pourrait s'y attendre. Il en va de même pour les valeurs d'horodatage et de montant.
Nous pouvons maintenant ajouter notre colonne Employee ID au DataFrame. Nous en avons maintenant fini avec Databricks Lab Data Generator, nous allons donc simplement utiliser les opérations PySpark pour ajouter des colonnes au DataFrame.
En prenant du recul par rapport au code, nous souhaitons modéliser cela comme les instructions suivantes :
- Il y a 20 magasins.
- Chaque magasin compte plus d'un employé.
- Chaque employé travaille dans un seul magasin.
Nous devons d’abord répartir les employés entre les magasins. La fonction python suivante peut être utilisée pour ce faire :
Maintenant que nous avons notre répartition des employés pour chaque magasin, commençons à attribuer des identifiants !
La liste EmployeesPerStore garantit que les identifiants des employés par magasin ne se chevauchent pas. Nous pouvons l'utiliser pour attribuer aléatoirement un identifiant d'employé à une vente dans le tableau avec l'équation suivante :
Cette fonction ne fonctionne actuellement que pour une seule valeur — nous devons la mettre dans quelque chose avec lequel un PySpark DataFrame peut fonctionner (fonctionnellement et rapidement !)
Nous pouvons transmettre les UDF PySpark au avecColonne méthode, reformatons donc cette logique en fonction et définissons-la sur une UDF :
Appelez maintenant ceci comme une nouvelle colonne dans le DataFrame :
Nous pouvons rapidement tester que cela semble correct en utilisant l'outil de visualisation dans Databricks pour voir le nombre distinct d'identifiants d'employé par identifiant de magasin. C'est ma préférence, mais vous pouvez également utiliser le groupe par logique ou d'autres modules de traçage, si vous le souhaitez.
Note importante: Cette logique permet aux salariés d'être manqué à partir des résultats. Cela signifie qu'il est possible pour un employé de réaliser 0 vente, et donc de ne pas être inclus dans le DataFrame. Nous verrons comment garantir que tous les clients enregistrent des ventes pour eux dans la section suivante.
La colonne des clients est un peu différente… même si notre cas d'utilisation suggère qu'il est courant qu'un client fasse ses achats plusieurs fois dans un même magasin, il est tout à fait possible qu'il se soit rendu dans un autre magasin un jour. Comment modéliser cela ?
Nous avons les points de départ avec la colonne du travail effectué pour nos employés, nous pouvons donc répéter le obtenir_employés fonction et logique UDF pour les clients comme ci-dessous :
Nous avons encore une fois potentiellement manqué quelques clients ici. Voici quelques approches pour remédier à ce problème :
- Recalculer dans alors que boucle jusqu'à ce que vous convergez vers un DataFrame qui contient tous les clients (inefficace, coûteux, pourrait fonctionner indéfiniment)
- Mettre à jour de manière aléatoire les identifiants des clients dans alors que boucle jusqu'à ce que tous les clients dans DataFrame (nécessite une logique pour écraser uniquement les mêmes magasins, peut également fonctionner indéfiniment)
- Renvoie une liste de tous les identifiants de clients avec plus d'un enregistrement dans la table des ventes et écrase-la de manière aléatoire jusqu'à ce que tous les identifiants manquants soient ajoutés (nécessite également une logique pour écraser les clients du même magasin, peut également nécessiter alors que logique de boucle)
- Inversez le processus et commencez par les employés. Cela garantit que chaque employé est affecté au hasard à des lignes. Nous pouvons ensuite utiliser le mappage et appliquer l'identifiant du magasin.
Espérons que la raison pour laquelle la dernière option nécessite le moins d’effort de calcul soit claire : nous avons tout le code requis, il suffit donc de reformater légèrement les choses.
Nos nouveaux scripts se présentent comme suit :
Ce dont nous avons besoin maintenant, c'est d'un peu d'aléatoire, qu'il nous faut définir. Pour notre exemple, disons que chaque client a 90 % de chances de faire ses achats dans le magasin habituel (le magasin « local »). Si nous n'avons pas besoin que tous les clients soient renvoyés dans l'ensemble de résultats, nous pouvons simplement ajuster notre clients_udf comme suit, et utilisez df2:
La logique implique d'utiliser le choix aléatoires fonction pour fournir une liste pondérée et renvoyer une valeur unique.
Pour calculer la liste pondérée, nous disposons du poids de notre magasin « local » pour le client, en l'occurrence 90 %, il faut donc attribuer les 10 % restants aux autres magasins, en l'occurrence 19 magasins. La probabilité que chaque autre magasin soit sélectionné sera donc de 10/19 = 0,526 %. Nous pouvons remplir un tableau avec ces pourcentages, qui ressembleraient à ceci : (0,526,0,526,0,526,…,90,0,526,…0,526)
En passant cela dans random.choices, nous sélectionnons ensuite au hasard un identifiant de magasin dans la liste avec les poids correspondants et l'utilisons comme entrée pour le N ° de client variable, comme auparavant.
Note: La sortie de random.choices renvoie une liste (car vous pouvez demander k résultats), accédez donc au 0ème élément de la liste pour obtenir le store_id sous forme de valeur entière.
Si nous devons combiner cette logique avec un DataFrame incluant tous les clients, nous pouvons légèrement inverser le processus. La logique des pondérations est toujours valide, nous pouvons donc simplement la brancher sur un magasin sélectionné au hasard et renvoyer ceci comme résultat :
Voilà, nous l'avons ! Un DataFrame créé synthétiquement avec des mappages stricts et lâches entre les colonnes. Vous pouvez maintenant passer aux étapes suivantes pour remplir les tables associées qui peuvent contenir des informations plus descriptives, telles que les tables de dimension des noms de magasins, des adresses, des noms d'employés, des rôles, etc. Cela peut également être fait à l'aide du générateur de données Databricks Labs ou de tout autre outil/ processus avec lequel vous êtes à l’aise.
Il existe d'excellents exemples sur le générateur de données Databricks Labs Dépôt GitHub ainsi que la documentation, alors n'hésitez pas à y jeter un œil si vous êtes curieux d'en savoir plus.
Tout mon code est accessible à partir de ce qui suit Dépôt GitHub.
Si vous avez des idées, des commentaires ou des alternatives à cette démo, veuillez nous contacter dans les commentaires. Merci!