La data quality est un sujet qui ne fait pas beaucoup de bruit et à tort ! Si vous demandez à un data scientist ce qui lui prend le plus de temps, vous aimeriez qu'il vous dise que la majorité de son temps est consacrée à comparer des modèles de machine learning pour trouver le plus approprié ou qu'il est en train d'effectuer une tout autre analyse permettant, à l'aide de la data, de comprendre le problème donné. La vérité est tout autre, et ce, très souvent dû à une mauvaise qualité de données.

Dans la réalité qui est la nôtre, ces mêmes personnes vous diront qu'une large partie de leur temps est consacrée au data cleaning et à la data préparation. Selon Amelia Arbisser dans un talk au spark summit, ces deux phases occuperaient plus de 80% du temps d'un ingénieur travaillant avec des données.

L'ennemi numéro 1 du data scientist

L'un des nombreux problèmes que l'on rencontre aujourd'hui, c'est qu'il y a trop de données inconsistantes et sales. Cela oblige les ingénieurs à consacrer une très (trop) grande partie de leur temps à la recherche de solutions contournant le problème ou à nettoyer la donnée. Dans certains cas, la donnée est complètement inutilisable et il suffit de se rendre sur un site comme Kaggle pour y trouver des dizaines de datasets incomplets qui sont pourtant très intéressants au premier abord.

Rien ne vaut un exemple pour appuyer mes propos, voici un dataset provenant de Kaggle https://www.kaggle.com/datafiniti/womens-shoes-prices. Ce dataset peut être une véritable mine d'or d'information. Imaginons que cette data provienne d'un magasin de chaussures, cette dernière pourrait servir à savoir la couleur à la mode actuelle et ainsi engranger de meilleures ventes. Cette information est très intéressante pour le commerçant alors on décide de s’adonner à la tâche et là, le drame, 41% des articles n'ont pas la couleur renseignée. Cela fait plus de 13000 articles sur les 33800, que contient le dataset, qui sont inutilisables. Cela n'est pas une fin en soi mais on perd beaucoup d'informations alors on s'applique, on remarque un champ "feature" avec un JSON à l’intérieur et on essaye de l'exploiter pour y obtenir la couleur. Et mince! Ici encore, il manque 20% de la donnée, la couleur n'est pas toujours présente on se retrouve avec un code immense, et ce, pour combler un manque de data quality qui peut être évité.

La data quality à travers le Constraint Checking

L’exemple précédent est très basique, le but étant qu'il parle au plus grand nombre. Généralement, la data est absorbée par d'autres systèmes accompagnés d'hypothèses implicites. Par exemple, une telle application va supposer qu'une des colonnes d'un quelconque dataset est d'un type X et qu'elle ne contient pas de null. Si ces conditions ne sont pas respectées, l'application risque fortement de planter ou de proposer des résultats erronés. Ce fût le cas pour la NASA qui avait des capteurs de radiation, ces mêmes capteurs devaient renvoyer 0 lorsqu'il n'y avait pas de radiations mais dans la réalité, il renvoyait des nombres négatifs. Si ces nombres sont directement incorporés dans un réseau de neurones, cela pourrait provoquer une erreur dans les prédictions amenant à des conséquences plus ou moins catastrophiques. Heureusement pour nous, Miles Soloman, un étudiant de 17 ans a mis en lumière ce problème de fonctionnement et la NASA a ainsi pu corriger le tir.

Pour éviter ce genre de problèmes, il faut attaquer le problème à la racine avant que la donnée nourrisse les algorithmes de machine learning et autres systèmes. Heureusement pour nous, il existe des librairies appliquant la data quality permettant de rajouter une frontière de contrôle avant de nourrir ces futurs système. Deequ est l'une d'entre elles et propose beaucoup de fonctionnalités en rapport avec la data quality. Car oui, il n'y a pas qu'une seule façon de faire de la data quality mais des dizaines et des dizaines de pratiques toutes essayant de répondre à des problématiques particulières.

Parmi elles se trouve le constraint checking, cette pratique consiste à appliquer des matrices aux différentes colonnes pour vérifier qu'elles respectent certaines contraintes. Prenons un dataframe contenant des informations utilisateur et utilisons le constraint checking proposé par Deequ pour contrôler la qualité de notre donnée.

case class Profile(id: Long, name: String, age: Int, sexe: String, photo: String)

val rdd = sc.parallelize(Seq(
  Profile(1, "Georgie Hester", 25, "male", "<https://randomuser.me/api/portraits/men/11.jpg>"),
  Profile(2, "Daisie Farrington", 17,  "other", "<https://randomuser.me/api/portraits/women/11.jpg>"),
  Profile(3, "Raveena Espinoza", 37, "female", "<https://randomuser.me/api/portraits/women/12.jpg>"),
  Profile(4, "Donovan Salt", 21, "male", null),
  Profile(5, "Ella-Mai Landry", 49, "female", "<https://randomuser.me/api/portraits/women/13.jpg>")))

val data = sqlContext.createDataFrame(rdd)

Ce genre de data suppose certaines propriétés. Il faut, par exemple, absolument que l'id soit unique et non null mais on peut rajouter énormément de règles pour essayer d'avoir le jeu de données le plus exploitable possible, comme ce qui suit:

val verification = VerificationSuite()
	.onData(data)
	.addCheck(
	  Check(CheckLevel.Error, "unit testing")
	    .hasSize(_ == 5) // 5 éléments dans notre Dataframe
	
	    .isComplete("id") // La colonne "id" n'a pas de Null
	    .isUnique("id") // La colonne "id" n'a pas de doublon
	
	    .isComplete("name") // La colonne "name" n'a pas de Null
	
	    .isPositive("age") // La colonne "age" est définit dans l'ensemble Z+
			.hasMin("age", _ == 18) // La colonne "age" 

	    .isContainedIn("sexe", Array("male", "female", "other")) // La colonne "sexe" contient que des valeurs présentes dans l'array
	
	    .containsURL("photo", _ >= 0.5)) // La colonne "photo" contient au minimum 50% d'URL
	.run()

Une fois la vérification faite, on obtient un objet VerificationResult contenant les résultats de l'analyse. On peut ainsi prendre en considération le cas où au moins une contrainte n'est pas respectée. Dans notre cas, on va juste les afficher en console mais l'objectif serait ici de traiter ces cas, soit en n'acceptant pas la source et en annulant alors l'action, soit en traitant le dataframe en question à l'aide des informations données par le résultat.

if (verification.status == CheckStatus.Success) {
    // On envoie la data aux services d'après ou en route pour du machine learning 🧠
  } else {
    // On rejete la data, on l'a clean, on l'a fixe, on fait globalement ce qu'on veut

		// On peut récupérer les contraintes qui ont échouer comme suit
    val contraintes = verification.checkResults
      .flatMap { case (_, checkResult) => checkResult.constraintResults }

    val contraintes_ratées = 
      .filter { _.status != ConstraintStatus.Success }
      
    // On affiche les contraintes qui ont empéché une bonne exécution du programme
		contraintes_ratées 
			.foreach { result => println(s"${result.constraint}: ${result.message.get}") }
  }

Dans notre cas, la data étant plutôt propre, on obtient juste l'erreur suivante:

We found errors in the data:

MinimumConstraint(Minimum(age,None)): Value: 17.0 does not meet the constraint requirement!

Et en effet, Daisie n'a pas encore 18 ans !

Comme dit précédemment, Le constraint checking est l'une des nombreuses façons de faire de la data quality sur nos jeux de données. Une autre manière, par exemple, serait d'utiliser les annotations fournies par une de nos librairies: Centrifuge. Cette dernière est très similaire au constraint checking à une différence près, ici, chaque ligne est associée à son lot de contraintes permettant une analyse cas par cas. Tout dépend du problème qui vous fait face et de ce que vous recherchez. J'ai choisi ici de me concentrer sur Deequ, mais j'aurais bien pu faire écho à d'autres librairies proposant des solutions afin de répondre aux problèmes de qualité de la donnée. Drunken data quality, propose, à titre d’exemple, un service similaire à Deequ.

Un problème de taille

Qu'importe la solution envisagée, la data quality a un problème majeur, il alourdit le processus. Créer un profil résumant votre data ne peut pas être gratuit en matière de calcul et donc en terme de temps. Nombreux sont les outils qui, aujourd'hui, ne peuvent pas être utilisés, car rallongeant le processus d'un temps incommensurablement long à moins d'augmenter notre puissance de calcul et demandant une capacité de stockage infiniment trop grande en vue de nos jeux de données immenses. Cependant, il existe des moyens de réduire drastiquement son impact. Cela n'est pas pour autant gratuit et généralement, un gain de temps et d'espace signifie obligatoirement une perte d'information.

Nous sommes, par exemple en ce moment même, en train de construire une librairie nommée Parka. Cette dernière applique une technique de data quality nommée delta QA. delta QA compare deux dataframes entre eux et nous informe de leurs différences. L'objectif premier de cette librairie est de faciliter les tests de non-régression en vérifiant que les changements effectués n'affectent pas de manière négative la data. Vous pouvez retrouver une présentation complète de ce qu'est delta QA ici.

Pour cette librairie, un de nos objectifs était de rendre le processus adaptable à de grandes quantités de données. Cela implique de devoir faire des compromis. Le compromis de Parka est l'approximation des mesures lorsqu'il s'agit d'engranger une large quantité de données en implémentant des structures approximatives qui sont aujourd'hui, au sein de Parka, au nombre de deux, l'utilisation de QTree comme structure approximative d'un histogramme et l'utilisation du count min sketch pour l'énumération des éléments au sein d'une même colonne.

Nous avons aussi donner à notre profil une structure monoïdale. Sans rentrer dans les détails (je vous invite cependant à regarder du côté des structures algébriques et un article apparaîtra sans doute dans le futur), cela permet de diviser pour mieux régner, c'est-à-dire fragmenter notre donnée, lancer ces fragments en parallèle pour ensuite les rassembler en les combinant et tout ceci grâce à sa loi associative interne.

Cela n'était qu'un avant-goût de cette librairie. Parka aura, tout comme les autres librairies de spark-tools, le droit à son propre article 😋.

Ce qu'il faut retenir

Qu'importe le coût de la data quality, cette dernière ne devrait pas être mise en second plan comme elle est aujourd'hui. Une mauvaise qualité de données coûte énormément d'argent aux entreprises et peut-être dans certains cas une perte d'information incorrigible par la suite.

Aujourd'hui, toutes les entreprises ont des problèmes de data quality, et nombreuses d'entre elles sont corrigibles par le biais de techniques déjà existantes. Nous en avons vu certaines aujourd'hui telles que le constraint checking, les annotations ou encore delta QA. Mais il en existe plein d'autres et je vous invite fortement à vous y intéresser de plus près.

Une des façons la plus ludique de s'y intéresser, c'est de regarder des talks à ce sujet, ainsi voici une liste de talks non exhaustive parlant de data quality: