start contest of bad data
This commit is contained in:
parent
d3bf80c481
commit
b13bd34658
|
@ -1,68 +1,160 @@
|
||||||
import geojson
|
# url = "https://www.data.gouv.fr/fr/datasets/r/7eee8f09-5d1b-4f48-a304-5e99e8da1e26"
|
||||||
import pandas as pd
|
import pandas_geojson as pdg
|
||||||
import dash
|
from pandas import DataFrame
|
||||||
import dash_table
|
from pandas_geojson import GeoJSON
|
||||||
import requests
|
import re
|
||||||
from dash.dash_table.Format import Group
|
|
||||||
import dash_html_components as html
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Vérifier si le fichier local existe
|
|
||||||
local_file = "irve.geojson"
|
|
||||||
if not os.path.exists(local_file):
|
|
||||||
# Télécharger les données geojson si le fichier local n'existe pas
|
|
||||||
url = "https://www.data.gouv.fr/fr/datasets/r/7eee8f09-5d1b-4f48-a304-5e99e8da1e26"
|
|
||||||
response = requests.get(url)
|
|
||||||
with open(local_file, 'w') as f:
|
|
||||||
f.write(response.text)
|
|
||||||
|
|
||||||
# Ouvrir le fichier local pour la suite du script
|
|
||||||
with open(local_file, 'r') as f:
|
|
||||||
data = geojson.load(f)
|
|
||||||
|
|
||||||
# Convertir les données geojson en DataFrame pandas
|
|
||||||
df = pd.DataFrame(data['features'])
|
|
||||||
|
|
||||||
|
|
||||||
# Calcul des mauvaises qualités
|
|
||||||
def calcul_mauvaise_qualite(df):
|
|
||||||
df['mauvaise_qualite'] = 0
|
|
||||||
|
|
||||||
# Une ligne n'ayant rien dans une des colonnes "id_pdc_itinerance", "telephone_operateur" fait gagner 1 point
|
|
||||||
df.loc[(df['properties']['id_pdc_itinerance'] == '') | (
|
|
||||||
df['properties']['telephone_operateur'] == ''), 'mauvaise_qualite'] += 1
|
|
||||||
|
|
||||||
# Un texte dans "telephone_operateur" fait gagner 2 points
|
|
||||||
df.loc[df['properties']['telephone_operateur'].str.contains(r'\D'), 'mauvaise_qualite'] += 2
|
|
||||||
|
|
||||||
# Si une ligne a les mêmes coordonnées géographiques qu'une autre, on gagne 3 points pour l'opérateur
|
|
||||||
df['coordinates'] = df['geometry']['coordinates'].apply(lambda x: tuple(x))
|
|
||||||
df['duplicated'] = df.duplicated(subset='coordinates', keep=False)
|
|
||||||
df.loc[df['duplicated'], 'mauvaise_qualite'] += 3
|
|
||||||
|
|
||||||
|
def load_small_dataset():
|
||||||
|
geojson: GeoJSON = pdg.read_geojson('small.geojson')
|
||||||
|
df: DataFrame = geojson.to_dataframe()
|
||||||
|
# df = clean_geojson_properties(df)
|
||||||
return df
|
return df
|
||||||
|
|
||||||
|
|
||||||
df = calcul_mauvaise_qualite(df)
|
def clean_geojson_properties(gdf):
|
||||||
|
"""Convertit en True et False les valeurs de propriétés qui valent des string "True" ou "False" avec une case mixte.
|
||||||
|
Applique la conversion uniquement aux colonnes textuelles."""
|
||||||
|
|
||||||
# Compter les mauvaises qualités par opérateur
|
PROPERTY_NAMES = (
|
||||||
operator_counts = df.groupby(df['properties']['nom_amenageur'])['mauvaise_qualite'].sum().reset_index()
|
"prise_type_ef",
|
||||||
operator_counts.columns = ['nom_amenageur', 'nbre_mauvaise_qualite']
|
"prise_type_2",
|
||||||
|
"prise_type_combo_ccs",
|
||||||
# Ajouter le nombre de points de recharge par opérateur
|
"prise_type_chademo",
|
||||||
operator_counts['nbre_pdc'] = df.groupby('properties')['nom_amenageur'].size().values
|
"prise_type_autre",
|
||||||
|
"gratuit",
|
||||||
# Créer le tableau de bord
|
"paiement_acte",
|
||||||
app = dash.Dash(__name__)
|
"paiement_cb",
|
||||||
|
"paiement_autre",
|
||||||
app.layout = html.Div([
|
"station_deux_roues",
|
||||||
html.H1('Trophées de bras cassés de l\'open data'),
|
"consolidated_is_lon_lat_correct",
|
||||||
dash_table.DataTable(
|
"consolidated_is_code_insee_verified",
|
||||||
id='table',
|
|
||||||
columns=[{'name': i, 'id': i} for i in operator_counts.columns],
|
|
||||||
data=operator_counts.to_dict('records')
|
|
||||||
)
|
)
|
||||||
])
|
|
||||||
|
for col_name in PROPERTY_NAMES:
|
||||||
|
pattern = r"^(?:true|false)$", re.IGNORECASE
|
||||||
|
gdf[col_name] = gdf[col_name].str.match(pattern).replace({True: True, False: False}).astype(bool)
|
||||||
|
|
||||||
|
return gdf
|
||||||
|
|
||||||
|
###
|
||||||
|
# les propriétés de chaque point de charge sont:
|
||||||
|
# ['nom_amenageur', 'iren_amenageur', 'contact_amenageur', 'properties.nom_operateur', 'contact_operateur', 'telephone_operateur', 'nom_enseigne', 'id_station_itinerance', 'id_station_local', 'nom_station', 'implantation_station', 'adresse_station', 'code_insee_commune', 'coordonneesXY', 'nbre_pdc', 'id_pdc_itinerance', 'id_pdc_local', 'puissance_nominale', 'prise_type_ef', 'prise_type_2', 'prise_type_combo_ccs', 'prise_type_chademo', 'prise_type_autre', 'gratuit', 'paiement_acte', 'paiement_cb', 'paiement_autre', 'tarification', 'condition_acces', 'eservation', 'horaires', 'accessibilite_pmr', 'estriction_gabarit', 'tation_deux_roues', 'raccordement', 'num_pdl', 'date_mise_en_service', 'observations', 'date_maj', 'cable_t2_attache', 'last_modified', 'datagouv_dataset_id', 'datagouv_resource_id', 'datagouv_organization_or_owner', 'created_at', 'consolidated_longitude', 'consolidated_latitude', 'consolidated_code_postal', 'consolidated_commune', 'consolidated_is_lon_lat_correct', 'consolidated_is_code_insee_verified']
|
||||||
|
#
|
||||||
|
###
|
||||||
|
def evaluate_score_of_operators(df):
|
||||||
|
operateurs: int = df['properties.nom_amenageur'].value_counts()
|
||||||
|
print('score de chaque opérateur:')
|
||||||
|
for operateur, count in operateurs.items():
|
||||||
|
print(f'- {operateur}: {count} points')
|
||||||
|
|
||||||
|
|
||||||
|
properties_quality_check = [
|
||||||
|
'missing__phone',
|
||||||
|
'missing__id_pdc_itinerance',
|
||||||
|
'bad_format__telephone_operateur',
|
||||||
|
'duplicate__coordinates',
|
||||||
|
'missing__puissance_nominale',
|
||||||
|
'missing__cable_attached',
|
||||||
|
'bad__lon_lat'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# on crée des colonnes supplémentaires pour stocker les erreurs de chaque type,
|
||||||
|
# puis on utilise la fonction groupby pour stocker les erreurs par opérateur.
|
||||||
|
# Le résultat est un DataFrame errors_by_operator qui contient
|
||||||
|
# le nombre d'erreurs de chaque type pour chaque opérateur.
|
||||||
|
def calculate_bad_quality(df):
|
||||||
|
df['bad_quality'] = 0
|
||||||
|
|
||||||
|
# Une ligne n'ayant rien dans une des colonnes "id_pdc_itinerance", "telephone_operateur" fait gagner 1 point.
|
||||||
|
df['missing__phone'] = df['properties.telephone_operateur'].isnull().astype(int)
|
||||||
|
df['missing__id_pdc_itinerance'] = df['properties.id_pdc_itinerance'].isnull().astype(int)
|
||||||
|
df['missing__puissance_nominale'] = df['properties.puissance_nominale'].isnull().astype(int)
|
||||||
|
df['missing__cable_attached'] = df['properties.cable_t2_attache'].isnull().astype(int)
|
||||||
|
# print(df['properties.consolidated_is_lon_lat_correct'])
|
||||||
|
df['bad__lon_lat'] = df['properties.consolidated_is_lon_lat_correct'] == False
|
||||||
|
|
||||||
|
# Un texte dans "telephone_operateur" fait gagner 2 points.
|
||||||
|
df['bad_format__telephone_operateur'] = df['properties.telephone_operateur'].apply(
|
||||||
|
lambda x: isinstance(x, str)).astype(int)
|
||||||
|
|
||||||
|
# Si une ligne a les mêmes coordonnées géographiques qu'une autre, on gagne 3 points pour l'opérateur.
|
||||||
|
df['coordinates'] = df['geometry.coordinates'].apply(lambda x: tuple(x))
|
||||||
|
duplicate_coordinates = df[df.duplicated(subset='coordinates', keep=False)]
|
||||||
|
df['duplicate__coordinates'] = df['coordinates'].isin(duplicate_coordinates['coordinates']).astype(int)
|
||||||
|
|
||||||
|
# Calcul du score de mauvaise qualité
|
||||||
|
df['bad_quality'] = df['missing__phone'] + df['missing__id_pdc_itinerance'] + 2 * df[
|
||||||
|
'bad_format__telephone_operateur'] + 3 * df['duplicate__coordinates']
|
||||||
|
|
||||||
|
# Stockage des erreurs par opérateur
|
||||||
|
errors_by_operator = df.groupby('properties.nom_amenageur')[
|
||||||
|
properties_quality_check
|
||||||
|
].sum()
|
||||||
|
|
||||||
|
return df, errors_by_operator
|
||||||
|
|
||||||
|
|
||||||
|
def render_champions_of_bad_data(df):
|
||||||
|
df, errors_by_operator = calculate_bad_quality(df)
|
||||||
|
|
||||||
|
print('* Nombre d\'opérateurs: ', len(df.groupby('properties.nom_amenageur')))
|
||||||
|
operateurs = df.groupby('properties.nom_amenageur')['bad_quality'].sum().sort_values(ascending=False)
|
||||||
|
champions = operateurs.nlargest(3)
|
||||||
|
print('\n* Les champions des bras cassés de l\'open data:')
|
||||||
|
for i, (operateur, score) in enumerate(champions.items()):
|
||||||
|
if i == 0:
|
||||||
|
print(f'** Médaille d\'or: {operateur} avec {score} points de mauvaise qualité')
|
||||||
|
elif i == 1:
|
||||||
|
print(f'** Médaille d\'argent: {operateur} avec {score} points de mauvaise qualité')
|
||||||
|
elif i == 2:
|
||||||
|
print(f'** Médaille de bronze: {operateur} avec {score} points de mauvaise qualité')
|
||||||
|
|
||||||
|
|
||||||
|
def render_all_operator(df):
|
||||||
|
df, errors_by_operator = calculate_bad_quality(df)
|
||||||
|
operateurs = df.groupby('properties.nom_amenageur')['bad_quality'].sum().sort_values(ascending=False)
|
||||||
|
champions = operateurs.nlargest(500)
|
||||||
|
|
||||||
|
print(errors_by_operator)
|
||||||
|
print('\n* Les opérateurs:')
|
||||||
|
for i, (operateur, score) in enumerate(champions.items()):
|
||||||
|
print('\n** ', operateur, ', ', score, ' points')
|
||||||
|
|
||||||
|
search_bad_property_for_operator(df, errors_by_operator, 'bad_format__telephone_operateur',
|
||||||
|
'mauvais numéros de téléphone', operateur)
|
||||||
|
search_bad_property_for_operator(df, errors_by_operator, 'duplicate__coordinates', 'coordonnées GPS dupliquées',
|
||||||
|
operateur)
|
||||||
|
search_bad_property_for_operator(df, errors_by_operator, 'missing__id_pdc_itinerance',
|
||||||
|
'Identifiant en itinérance manquant', operateur)
|
||||||
|
search_bad_property_for_operator(df, errors_by_operator, 'missing__phone', 'numéro de téléphone manquant',
|
||||||
|
operateur)
|
||||||
|
search_bad_property_for_operator(df, errors_by_operator, 'missing__puissance_nominale',
|
||||||
|
'puissance nominale manquante', operateur)
|
||||||
|
search_bad_property_for_operator(df, errors_by_operator, 'bad__lon_lat',
|
||||||
|
'coordonnées éronnées', operateur)
|
||||||
|
|
||||||
|
|
||||||
|
def search_bad_property_for_operator(gdf, errors_by_operator, property_name, error_description, operator_name):
|
||||||
|
"""Recherche les occurrences de la mauvaise propriété pour un opérateur donné
|
||||||
|
:type operator_name: str
|
||||||
|
"""
|
||||||
|
|
||||||
|
counter_bad_lines = errors_by_operator.loc[operator_name, property_name]
|
||||||
|
if counter_bad_lines:
|
||||||
|
|
||||||
|
total_lines = len(gdf.loc[gdf['properties.nom_amenageur'] == operator_name])
|
||||||
|
|
||||||
|
if total_lines != 0:
|
||||||
|
proportion = (counter_bad_lines / total_lines) * 100
|
||||||
|
else:
|
||||||
|
proportion = 0
|
||||||
|
|
||||||
|
print(f'- {error_description}:\t{counter_bad_lines}/{total_lines}\t ({proportion:.2f}%)')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run_server(debug=True)
|
df = load_small_dataset()
|
||||||
|
# evaluate_score_of_operators(df)
|
||||||
|
render_champions_of_bad_data(df)
|
||||||
|
render_all_operator(df)
|
||||||
|
|
Loading…
Reference in New Issue