Afin de réaliser un de nos tutoriels (D3JS - Sunburst) nous avons utilisé des données récupérées sur le site data.gouv.fr. Plus particulièrement nous nous sommes intéressés aux données de la transparence santé qui sont fournies par le site du même nom.
Les données rassemblées par ce site sont volumineuses et particulièrement intéressantes. Elles permettent en théorie de savoir s'il n'y a pas d'accointances entre certaines entreprises pharmaceutiques et des chirurgiens en hôpitaux par
exemple. La base permet de connaître chaque transaction financière à partir de 10 euros, un repas, un voyage ou encore un cadeau. Avec une analyse minutieuse il doit être possible d'identifier des disparités ou des anomalies. Le site permet d'effectuer
des recherches par entreprise comme par bénéficiaire. Bien sûr si nous parlons de ces données ici c'est parce que data.gouv.fr
fournit l'ensemble des données sous forme de fichiers CSV qu'il est plus facile d'exploiter
avec des outils informatiques.
Notre objectif est simplement de produire des données qui pourront ensuite être représentées sous forme hiérarchique et il n'est pas de s'assurer de l'exactitude de ces données. Une fois décompressées, les données fournies par le lien du premier paragraphe pèsent quasiment 5 Go, sous la forme de 4 fichiers CSV. On trouve également un fichier de description qui n'est plus à jour et un autre pour la licence qui rappelle que les fichiers CSV contiennent des informations nominatives. Un des fichiers CSV contient les informations de toutes les entreprises mentionnées et les trois autres contiennent rémunérations, avantages et conventions donnés aux bénéficiaires. Comme nous l'avons déjà fait nous utilisons le langage R qui permet de charger de gros fichiers et de rapidement étudier leur contenu.
> require(data.table)
> library(dplyr)
> dataRemuneration <- fread("declaration_remuneration_2020_01_28_04_00.csv", sep=";", encoding = "UTF-8")
Nous commençons par le plus petit fichier pour explorer ses 34 variables. La fonction head
peut vite devenir illisible avec autant de colonnes alors nous utilisons la fonction view
sous RStudio pour obtenir un affichage plus propre.
Ensuite nous réalisons les opérations suivantes et elles seront reproduites à l'identique sur les fichiers conventions et avantages.
# Filtre par date pour conserver les données d'une seule année
> dataRemuneration <- mutate(dataRemuneration, remu_date = as.Date(remu_date, format= "%d/%m/%Y"))
> dataRemuneration <- dataRemuneration %>% filter(remu_date >= as.Date("01/07/2018", "%d/%m/%Y") & remu_date <= as.Date("30/06/2019", "%d/%m/%Y"))
# Suppression des colonnes inutiles, nous ne conservons que le nom de l'entreprise, la catégorie associée au bénéficiaire et le montant.
> dataRemuneration <- dataRemuneration[, c("denomination_sociale", "categorie", "remu_montant_ttc")]
# Renommage des colonnes
> dataRemuneration <- rename(dataRemuneration, remu_montant = remu_montant_ttc, denomination = denomination_sociale)
# Regroupement par dénomination et catégorie, somme des montants et tri décroissant
> dataRemuneration <- dataRemuneration %>% group_by(denomination, categorie) %>% summarise(remu_montant = sum(remu_montant, na.rm = TRUE)) %>% arrange(desc(remu_montant))
# Libération de la mémoire, elle va devenir essentielle avec les deux autres fichiers
gc()
> head(dataRemuneration, 5)
denomination categorie remu_montant
1 SANOFI-AVENTIS RECHERCHE & DEVELOPPEMENT Académies, Fondation, sociétés savantes, organismes de conseils 91868459
2 SANOFI-AVENTIS GROUPE Académies, Fondation, sociétés savantes, organismes de conseils 29462866
3 SANOFI AVENTIS FRANCE Académies, Fondation, sociétés savantes, organismes de conseils 26230932
4 ARROW GENERIQUES SAS Académies, Fondation, sociétés savantes, organismes de conseils 18516076
5 NOVARTIS PHARMA SAS Académies, Fondation, sociétés savantes, organismes de conseils 16927982
Avec ce travail nous obtenons une structure à 3 niveaux avec un montant en face. On peut lire que SANOFI-AVENTIS R&D (niveau 1) a donné une rémunération (niveau 2) pour la catégorie "Académies, ..." (niveau 3) de 91 M€.
Le niveau 2 représente les trois types de fichiers (rémunération, avantages et conventions). La méthode head
nous permet aussi de voir que nos données sont segmentées au niveau de la dénomination alors
que nous voudrions voir les trois premières lignes regroupées en une seule. Nous tenterons de résoudre ce problème par la suite. Les deux autres fichiers sont traités de la même façon (le nom des colonnes associées au montant
et à la date ne sont pas les mêmes).
> dataAvantage <- fread("declaration_avantage_2020_01_28_04_00.csv", sep=";", encoding = "UTF-8")
> dataAvantage <- mutate(dataAvantage, avant_date_signature = as.Date(avant_date_signature, format= "%d/%m/%Y"))
> dataAvantage <- dataAvantage %>% filter(avant_date_signature >= as.Date("01/07/2018", "%d/%m/%Y") & avant_date_signature <= as.Date("30/06/2019", "%d/%m/%Y"))
> dataAvantage <- dataAvantage[, c("denomination_sociale", "categorie", "avant_montant_ttc")]
> dataAvantage <- rename(dataAvantage, avant_montant = avant_montant_ttc, denomination = denomination_sociale)
> dataAvantage <- dataAvantage %>% group_by(denomination, categorie) %>% summarise(avant_montant = sum(avant_montant, na.rm = TRUE)) %>% arrange(desc(avant_montant))
> head(dataAvantage, 5)
denomination categorie avant_montant
1 MicroPort CRM France SAS Professionnel de santé 31991002
2 AMGEN SAS Académies, Fondation, sociétés savantes, organismes de conseils 18401995
3 MSD France Etablissement de santé 14488273
4 BIOGEN FRANCE SAS Association professionnel de santé 10289661
5 NOVARTIS PHARMA SAS Professionnel de santé 9235105
> dataConvention <- fread("declaration_convention_2020_01_28_04_00.csv", sep=";", encoding = "UTF-8")
> dataConvention <- mutate(dataConvention, conv_date_signature = as.Date(conv_date_signature, format= "%d/%m/%Y"))
> dataConvention <- dataConvention %>% filter(conv_date_signature >= as.Date("01/07/2018", "%d/%m/%Y") & conv_date_signature <= as.Date("30/06/2019", "%d/%m/%Y"))
> dataConvention <- dataConvention[, c("denomination_sociale", "categorie", "conv_montant_ttc")]
> dataConvention <- rename(dataConvention, conv_montant = conv_montant_ttc, denomination = denomination_sociale)
> dataConvention <- dataConvention %>% group_by(denomination, categorie) %>% summarise(conv_montant = sum(conv_montant, na.rm = TRUE)) %>% arrange(desc(conv_montant))
> head(dataConvention, 5)
denomination categorie conv_montant
1 Roche Diagnostics Operations, Inc. Etablissement de santé 36782790
2 SA Bristol-Myers Squibb Belgium Etablissement de santé 34607706
3 Covidien AG Association professionnel de santé 18803031
4 BRISTOL-MYERS SQUIBB Etablissement de santé 14782340
5 ASTRAZENECA Etablissement de santé 13298630
Finalement nous regroupons ces trois jeux de données en un seul et le sauvegardons dans un fichier CSV.
> mergedData <- merge(dataAvantage, dataConvention, by.x = c("denomination", "categorie"), by.y = c("denomination", "categorie"), all = TRUE)
> mergedData <- merge(mergedData, dataRemuneration, by.x = c("denomination", "categorie"), by.y = c("denomination", "categorie"), all = TRUE)
> mergedData <- mergedData %>% replace(is.na(.), 0) %>% mutate(total = rowSums(.[3:5]))
> mergedData <- arrange(mergedData, desc(total))
> head(mergedData, 10)
denomination categorie avant_montant conv_montant remu_montant total
1 SANOFI-AVENTIS RECHERCHE & DEVELOPPEMENT Académies, ... 1000 0 91868459 91869459
2 Roche Diagnostics Operations, Inc. Etablissement de santé 0 36782790 0 36782790
3 SA Bristol-Myers Squibb Belgium Etablissement de santé 0 34607706 1425421 36033127
4 MicroPort CRM France SAS Professionnel de santé 31991002 434049 328706 32753757
5 SANOFI-AVENTIS GROUPE Académies, ... 4088 0 29462866 29466954
6 BRISTOL-MYERS SQUIBB Etablissement de santé 296515 14782340 12768095 27846950
7 SANOFI AVENTIS FRANCE Académies, ... 801444 0 26230932 27032376
8 AbbVie Académies, ... 1187500 12451106 9717956 23356562
9 NOVARTIS PHARMA SAS Académies, ... 601739 5625255 16927982 23154976
10 NOVARTIS PHARMA SAS Professionnel de santé 9235105 9532935 2587648 21355688
> fwrite(mergedData, "transparence_data_2018_2019.csv", sep = ";")
A partir du fichier CSV produit en R nous allons pouvoir construire notre structure arborescente. La partie création de la visualisation est décrite dans le tutoriel cité précédemment.
d3.text("d3js/sunburst-chart/transparence_data_2018_2019.csv").then(function(raw) {
let dsv = d3.dsvFormat(';');
let data = dsv.parse(raw);
let json = buildHierarchy(data);
//createVisualization(json);
});
La construction de la hiérarchie se déroule en trois étapes :
root
qui contient un nom et un tableau de fils vide. La boucle for
qui suit va ajouter les
niveaux 1 (entreprise) et niveaux 2 (type de montant). getParentNode
qui va utiliser nos chaines de caractères matchingNames
et si la ligne ne correspond pas à l'une de nos 9 entreprises
on continue.
function buildHierarchy(data) {
var bigOnes = [
{"displayName": "Sanofi", "matchingNames": ["sanofi"]},
{"displayName": "Astrazeneca", "matchingNames": ["astrazeneca"]},
{"displayName": "Novartis", "matchingNames": ["novartis"]},
{"displayName": "Bristol-Myers Squibb", "matchingNames": ["bristol"]},
{"displayName": "AbbVie", "matchingNames": ["abbvie"]},
{"displayName": "Roche", "matchingNames": ["roche"]},
{"displayName": "Merck Sharp and Dohme", "matchingNames": ["MSD France", "merck"]},
{"displayName": "Microport CRM", "matchingNames": ["Microport"]},
{"displayName": "GlaxoSmithKline", "matchingNames": ["glaxosmithkline"]}
//{"displayName": "Autres"}
];
var root = {"name": "root", "children": []};
// Ajout des entreprises dont les montants sont les plus importants
for (let i = 0; i < bigOnes.length; ++i) {
root.children.push({"name": bigOnes[i].displayName, "matchingNames": bigOnes[i].matchingNames, "position" : i, "children": [
{"name": "Avantage", "children": []}, {"name": "Convention", "children": []}, {"name": "Rémunération", "children": []}
]});
}
for (let i = 0; i < data.length; ++i) {
data[i].avant_montant = +data[i].avant_montant;
data[i].conv_montant = +data[i].conv_montant;
data[i].remu_montant = +data[i].remu_montant;
let parentNode = getParentNode(root, data[i]);
if (parentNode === undefined) {
continue;
}
// Avantage, parentNode.children[0] correspond à la première entrée ajoutée à chaque entreprise dans la boucle for du dessus.
// Pour Convention il faudra utiliser parentNode.children[1] et pour Rémunération parentNode.children[2]
if (data[i].avant_montant !== "" && data[i].avant_montant !== 0) {
let foundCategory = undefined;
for (var iCat = 0; iCat < parentNode.children[0].children.length; ++iCat) {
if (parentNode.children[0].children[iCat].name === data[i].categorie) {
foundCategory = parentNode.children[0].children[iCat];
}
}
if (foundCategory === undefined) {
parentNode.children[0].children.push({"name": data[i].categorie, "amount": 0});
foundCategory = parentNode.children[0].children[parentNode.children[0].children.length - 1];
}
foundCategory.amount = foundCategory.amount + data[i].avant_montant;
}
// Le même traitement est répété pour la colonne conv_montant de data[i] avec parentNode.children[1]
// Le même traitement est répété pour la colonne remu_montant de data[i] avec parentNode.children[2]
}
return root;
}
function getParentNode(root, current) {
let parentNode = undefined;
for (let j = 0; j < root.children.length; ++j) {
for (let k = 0; k < root.children[j].matchingNames.length; ++k) {
if (current.denomination.search(new RegExp(root.children[j].matchingNames[k], "i")) >= 0) {
return root.children[j];
}
}
}
return undefined;
//return root.children[root.children.length - 1]; // Autres
}
Nous terminons ici ce tutoriel qui montre comment récupérer des données volumineuses, les traiter avec un langage adapté pour finalement construire une représentation qui servira à une visualisation. Il y aurait beaucoup à faire avec ces trois fichiers qui contiennent presque 20 millions de lignes et nous reviendrons peut-être sur ce jeu de données ultérieurement.
COMMENTAIRES