From 7f2528244882e65af581010d55af9f5f35986cc1 Mon Sep 17 00:00:00 2001 From: Thomas Grandjean Date: Sat, 27 Jul 2024 17:13:00 +0200 Subject: [PATCH 01/20] Remove unecessary notebooks --- notebooks_docs/usecase_5.py | 92 ------------------------------------- notebooks_docs/usecase_6.py | 50 -------------------- 2 files changed, 142 deletions(-) delete mode 100644 notebooks_docs/usecase_5.py delete mode 100644 notebooks_docs/usecase_6.py diff --git a/notebooks_docs/usecase_5.py b/notebooks_docs/usecase_5.py deleted file mode 100644 index 0455a02..0000000 --- a/notebooks_docs/usecase_5.py +++ /dev/null @@ -1,92 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Jun 18 20:22:35 2024 - -@author: utilisateur -""" - -import io -import logging -import numpy as np -import pandas as pd -from requests_cache import CachedSession -from zipfile import ZipFile - -from french_cities import find_city - -logging.basicConfig(level=logging.INFO) - - -def try_read_csv(data): - encodings = ["utf8", "cp1252"] - while encodings: - x = encodings.pop() - try: - df = pd.read_csv( - data, - dtype=str, - sep=";", - encoding=x, - quoting=1, - header=0, - encoding_errors="replace", - ) - except UnicodeDecodeError: - data.seek(0) - continue - break - try: - return df - except NameError: - raise ValueError("Aucun encoding n'est satisfaisant") - - -def main(): - - url = "https://media.interieur.gouv.fr/rna/rna_import_20240601.zip" - s = CachedSession() - r = s.get(url) - with ZipFile(io.BytesIO(r.content)) as z: - files = z.filelist - concat = [] - for x in files: - obj = io.BytesIO(z.read(x.filename)) - try: - concat.append(try_read_csv(obj)) - except ValueError: - raise ValueError(f"Erreur sur {x}") - df = pd.concat(concat, ignore_index=True) - - df.info() - - # Retraitement des codes postaux manifestement erronés - ix = df[~df.adrs_codepostal.fillna("").str.match("[0-9AB]{5}")].index - df.loc[ix, "adrs_codepostal"] = np.nan - df["adrs_codepostal"] = df["adrs_codepostal"].replace("00000", np.nan) - - df = find_city( - df, - year="last", - x=False, - y=False, - epsg=False, - city="libcom", - address=False, - postcode="adrs_codepostal", - use_nominatim_backend=True, - field_output="codeInsee", - ) - - errors = ( - df[(df["codeInsee"].isnull()) & (df["libcom"].notnull())][ - ["libcom", "adrs_codepostal"] - ] - .drop_duplicates() - .sort_values("adrs_codepostal") - ) - errors.to_csv("test.csv") - return df - - -if __name__ == "__main__": - df = main() diff --git a/notebooks_docs/usecase_6.py b/notebooks_docs/usecase_6.py deleted file mode 100644 index 79de0c6..0000000 --- a/notebooks_docs/usecase_6.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Jun 28 22:05:19 2024 - -@author: utilisateur -""" - -import io -import pandas as pd -from requests_cache import CachedSession -from zipfile import ZipFile - -from french_cities import find_city - -# ============================================================================= -# Récupération du jeu de données 2022 -# ============================================================================= -url = "https://www.douane.gouv.fr/sites/default/files/openData/files/annuaire-des-debits-de-tabac-2018.zip" -s = CachedSession() -r = s.get(url) -with ZipFile(io.BytesIO(r.content)) as z: - content = z.read("annuaire-des-debits-de-tabac-2018.csv") - df = pd.read_csv( - io.BytesIO(content), dtype=str, sep=";", encoding="cp1252" - ) - - -# %% -df = find_city( - df, - x=False, - y=False, - dep=False, - city="COMMUNE", - address="ADRESSE", - postcode="CODE POSTAL", - field_output="insee_com", - session=s, - use_nominatim_backend=True, -) - -df.insee_com.value_counts() - -print(df[df.insee_com.isnull()]) -print( - "Une seule commune n'a pas été reconnue (OCCHIATANA en Haute-Corse). " - "En effet, une erreur de saisie existe sur le code postal qui renvoie" - "sur le mauvais département (20182 est un code CEDEX réel de Corse du " - "Sud), ce qui induit l'algorithme en erreur." -) From 4307685158c0ce12a5c802fbc119a35fb7228a9f Mon Sep 17 00:00:00 2001 From: Thomas Grandjean Date: Sat, 27 Jul 2024 17:13:57 +0200 Subject: [PATCH 02/20] Upgrade notebooks --- notebooks_docs/usecase_1.ipynb | 453 ++++++++++--- notebooks_docs/usecase_2.ipynb | 1095 +++++++++++++++++++++++++++++++- notebooks_docs/usecase_3.ipynb | 5 +- notebooks_docs/usecase_4.ipynb | 2 +- 4 files changed, 1456 insertions(+), 99 deletions(-) diff --git a/notebooks_docs/usecase_1.ipynb b/notebooks_docs/usecase_1.ipynb index ee3d05f..de65217 100644 --- a/notebooks_docs/usecase_1.ipynb +++ b/notebooks_docs/usecase_1.ipynb @@ -57,7 +57,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "querying georisques: 92%|████████████████████████████████████████████████████████▎ | 12/13 [00:00<00:00, 15.29it/s]\n" + "querying georisques: 92%|████████████████████████████████████████████████████████▎ | 12/13 [00:00<00:00, 18.74it/s]\n" ] } ], @@ -106,13 +106,6 @@ "id": "9f5dfdca-c01f-4e19-83a8-99db6aefde20", "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(12180, 31)\n" - ] - }, { "data": { "text/html": [ @@ -278,69 +271,274 @@ " 48908926800036\n", " NaN\n", " \n", + " \n", + " ...\n", + " ...\n", + " ...\n", + " ...\n", + " ...\n", + " ...\n", + " ...\n", + " ...\n", + " ...\n", + " ...\n", + " ...\n", + " ...\n", + " ...\n", + " ...\n", + " ...\n", + " ...\n", + " ...\n", + " ...\n", + " ...\n", + " ...\n", + " ...\n", + " ...\n", + " \n", + " \n", + " 12175\n", + " PARC EOLIEN DE NOYERS ST MARTIN (repowering ex...\n", + " Lieu-dit Le Cornouiller\n", + " 60480\n", + " 60634\n", + " Noyers-Saint-Martin\n", + " 2.287214\n", + " 49.548878\n", + " False\n", + " False\n", + " False\n", + " ...\n", + " DREAL HdF\n", + " Autorisation\n", + " [{'numeroRubrique': '2980', 'nature': 'Eolienn...\n", + " []\n", + " []\n", + " 2024-07-11/11-50-02\n", + " NaN\n", + " 35\n", + " 45058829800056\n", + " NaN\n", + " \n", + " \n", + " 12176\n", + " LEFEBVRE Mathieu\n", + " rue de Ferfay\n", + " 62190\n", + " 62028\n", + " Ames\n", + " 2.419674\n", + " 50.540442\n", + " False\n", + " False\n", + " False\n", + " ...\n", + " DREAL HdF\n", + " Non ICPE\n", + " []\n", + " [{'dateInspection': '2024-07-02'}]\n", + " []\n", + " 2024-07-17/14-03-09\n", + " NaN\n", + " NaN\n", + " NaN\n", + " NaN\n", + " \n", + " \n", + " 12177\n", + " CAR PRO\n", + " 12 GRANDE RUE DE VAUX\n", + " 60390\n", + " 60063\n", + " BERNEUIL-EN-BRAY\n", + " 2.041738\n", + " 49.355651\n", + " False\n", + " False\n", + " False\n", + " ...\n", + " DREAL HdF\n", + " Non ICPE\n", + " []\n", + " [{'dateInspection': '2024-07-04'}]\n", + " []\n", + " 2024-07-12/17-41-57\n", + " NaN\n", + " 45\n", + " 98476821800013\n", + " NaN\n", + " \n", + " \n", + " 12178\n", + " LEROY MERLIN FRANCE\n", + " RTE DE ST QUENTIN\n", + " 80330\n", + " 80489\n", + " LONGUEAU\n", + " 2.367384\n", + " 49.867366\n", + " False\n", + " False\n", + " False\n", + " ...\n", + " DREAL HdF\n", + " Non ICPE\n", + " []\n", + " [{'dateInspection': '2024-07-04'}]\n", + " []\n", + " 2024-07-19/13-01-57\n", + " 2 passage du rayon vert\n", + " 47\n", + " 38456094200755\n", + " NaN\n", + " \n", + " \n", + " 12179\n", + " RICHARD Arnaud\n", + " 136 rue Jean Jaurès\n", + " 59224\n", + " 59589\n", + " Thiant\n", + " 3.451233\n", + " 50.311974\n", + " False\n", + " False\n", + " False\n", + " ...\n", + " DREAL HdF\n", + " Non ICPE\n", + " []\n", + " [{'dateInspection': '2024-07-08'}]\n", + " []\n", + " 2024-07-12/10-40-06\n", + " NaN\n", + " NaN\n", + " 81805127800021\n", + " NaN\n", + " \n", " \n", "\n", - "

5 rows × 31 columns

\n", + "

12180 rows × 31 columns

\n", "" ], "text/plain": [ - " raisonSociale adresse1 codePostal \\\n", - "0 SCEA FERME LABALETTE Lieu-dit Le Dièvre 62860 \n", - "1 ISDI de Saint-Laurent-Blangy Zone des Trois Fontaines 62223 \n", - "2 RECYCL'ELECTRONIC SARL 2 Bis Rue \n", " \n", " \n", + " count\n", + " \n", + " \n", " systemeCoordonneesAIOT\n", + " \n", " \n", " \n", " \n", " \n", - " 17\n", - " 2154\n", + " est manquant\n", + " 239\n", " \n", " \n", - " 501\n", - " NaN\n", + " 2154\n", + " 15\n", " \n", " \n", "\n", "" ], "text/plain": [ - " systemeCoordonneesAIOT\n", - "17 2154\n", - "501 NaN" + " count\n", + "systemeCoordonneesAIOT \n", + "est manquant 239\n", + "2154 15" ] }, "execution_count": 7, @@ -538,7 +740,7 @@ } ], "source": [ - "missing[[\"systemeCoordonneesAIOT\"]].drop_duplicates()" + "missing[[\"systemeCoordonneesAIOT\"]].fillna(\"est manquant\").value_counts().to_frame()" ] }, { @@ -584,6 +786,8 @@ "name": "stderr", "output_type": "stream", "text": [ + "WARNING:root:get_descending_area with area='collectivitesDOutreMer' does not support date='*': querying for date='*' instead\n", + "WARNING:root:get_descending_area with area='collectivitesDOutreMer' does not support date='*': querying for date='*' instead\n", " \r" ] }, @@ -1069,7 +1273,7 @@ "id": "665eed09-a004-4299-89a0-37519a7b28bd", "metadata": {}, "source": [ - "### Evaluation des données" + "### Evaluation des résultats" ] }, { @@ -1275,30 +1479,18 @@ { "cell_type": "code", "execution_count": 13, - "id": "8181da5a-6d10-4358-94cb-0a60d4fcefd1", + "id": "85f0b61f-6133-4675-9790-837174b38b8b", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "Usage of Nominatim for geocoding is **NOT** encouraged. Please, have a look at the Geocoding Policy at https://operations.osmfoundation.org/policies/nominatim/ . \n", - "Nominatim API will perform requests at a rate of one request per second : this task may take up to 1 min (estimation without cache processing)...\n", - "Usage of Nominatim for geocoding is **NOT** encouraged. Please, have a look at the Geocoding Policy at https://operations.osmfoundation.org/policies/nominatim/ . \n", - "Nominatim API will perform requests at a rate of one request per second : this task may take up to 1 min (estimation without cache processing)...\n", + "WARNING:root:get_descending_area with area='collectivitesDOutreMer' does not support date='*': querying for date='*' instead\n", + "WARNING:french_cities.city_finder:Usage of Nominatim for geocoding is **NOT** encouraged. Please, have a look at the Geocoding Policy at https://operations.osmfoundation.org/policies/nominatim/ . \n", + "WARNING:french_cities.city_finder:Nominatim API will perform requests at a rate of one request per second : this task may take up to 1 min (estimation without cache processing)...\n", " \r" ] - }, - { - "data": { - "text/plain": [ - "8441 62746\n", - "Name: newCodeInsee, dtype: object" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -1328,10 +1520,93 @@ " postcode=\"codePostal\",\n", " use_nominatim_backend=True,\n", " field_output=\"newCodeInsee\",\n", - ")\n", - "\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "820e41d4-7613-4a55-b107-6e479187eb6e", + "metadata": {}, + "source": [ + "Utilisons pynsee pour récupérer la liste des communes pour contrôler le résultat (et pas simplement son code commune) :" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "d7c2dec5-ce02-44e1-bb4f-0ad367bac228", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
newCodeInseeCODEURIAREA_TYPEDATE_CREATIONTITLE_SHORTDETERMINER_TYPETITLEDATE_DELETION
06274662746http://id.insee.fr/geo/commune/adc106e4-5055-4...Commune1943-01-01Saint-Étienne-au-Mont0Saint-Étienne-au-MontNaN
\n", + "
" + ], + "text/plain": [ + " newCodeInsee CODE URI \\\n", + "0 62746 62746 http://id.insee.fr/geo/commune/adc106e4-5055-4... \n", + "\n", + " AREA_TYPE DATE_CREATION TITLE_SHORT DETERMINER_TYPE \\\n", + "0 Commune 1943-01-01 Saint-Étienne-au-Mont 0 \n", + "\n", + " TITLE DATE_DELETION \n", + "0 Saint-Étienne-au-Mont NaN " + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ "# Présentation des résultats\n", - "missing[\"newCodeInsee\"]" + "from pynsee.localdata import get_area_list\n", + "cities = get_area_list(\"communes\", date=\"*\")\n", + "missing[\"newCodeInsee\"].to_frame().merge(cities, left_on=\"newCodeInsee\", right_on=\"CODE\")" ] }, { @@ -1339,7 +1614,7 @@ "id": "e11abf6f-af5b-4f30-892e-5928a99a09dd", "metadata": {}, "source": [ - "L'exécution de Nominatim ne conduit pas systématiquement au même résultat (et même parfois ne produit pas de résultat). Cela n'est pas totalement absurde, le hameau manquant étant à cheval sur plusieurs communes. Les résultats fournis restent généralement pertinents." + "💡 Nota : l'exécution de Nominatim ne conduit pas systématiquement au même résultat (et même parfois ne produit pas de résultat). Cela n'est pas totalement absurde, le hameau manquant étant à cheval sur plusieurs communes. Les résultats fournis restent généralement pertinents." ] } ], diff --git a/notebooks_docs/usecase_2.ipynb b/notebooks_docs/usecase_2.ipynb index 2eaaf80..0044fd8 100644 --- a/notebooks_docs/usecase_2.ipynb +++ b/notebooks_docs/usecase_2.ipynb @@ -294,10 +294,12 @@ "name": "stderr", "output_type": "stream", "text": [ - "Usage of Nominatim for geocoding is **NOT** encouraged. Please, have a look at the Geocoding Policy at https://operations.osmfoundation.org/policies/nominatim/ . \n", - "Nominatim API will perform requests at a rate of one request per second : this task may take up to 1 min (estimation without cache processing)...\n", - "Usage of Nominatim for geocoding is **NOT** encouraged. Please, have a look at the Geocoding Policy at https://operations.osmfoundation.org/policies/nominatim/ . \n", - "Nominatim API will perform requests at a rate of one request per second : this task may take up to 1 min (estimation without cache processing)...\n", + "WARNING:root:get_descending_area with area='collectivitesDOutreMer' does not support date='*': querying for date='*' instead\n", + "WARNING:root:get_descending_area with area='collectivitesDOutreMer' does not support date='*': querying for date='*' instead\n", + "WARNING:french_cities.city_finder:Usage of Nominatim for geocoding is **NOT** encouraged. Please, have a look at the Geocoding Policy at https://operations.osmfoundation.org/policies/nominatim/ . \n", + "WARNING:french_cities.city_finder:Nominatim API will perform requests at a rate of one request per second : this task may take up to 1 min (estimation without cache processing)...\n", + "WARNING:french_cities.city_finder:Usage of Nominatim for geocoding is **NOT** encouraged. Please, have a look at the Geocoding Policy at https://operations.osmfoundation.org/policies/nominatim/ . \n", + "WARNING:french_cities.city_finder:Nominatim API will perform requests at a rate of one request per second : this task may take up to 1 min (estimation without cache processing)...\n", " \r" ] }, @@ -551,6 +553,1085 @@ "df.codeInsee.isnull().value_counts().to_frame(\"est vide\")" ] }, + { + "cell_type": "markdown", + "id": "e64b00ca-8125-4a40-877f-8eef27e922b8", + "metadata": {}, + "source": [ + "Examinons sommairement les lignes pour lesquelles les résultats sont absents (exclusion faite des cas où les communes ne sont pas même nommées dans le jeu de données) :" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "09dc380e-8b22-46aa-a43f-02cf6bee855e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Année de notificationEntité PubliqueEntite d'achatNom AttributaireSIRET AttributaireDate de notificationCode Postal AttributaireVilleNature du MarchéObjet du marchéTranche budgetaireMontantcodeInsee
52015Ministère des Finances et des Comptes publics ...MFCP-MEIN / DGFIP / DGFIP-R / DGFIP-078 - DGFI...SYNOPTIC INTERNATIONALFR840000183572015-01-1398000MonacoFournituresFournitures de supports numériques : lot n° 3 ...0 à 3 999,99 HT2 000,00NaN
72015Ministère des Finances et des Comptes publics ...MFCP-MEIN / DGFIP / DGFIP-R / DGFIP-078 - DGFI...SYNOPTIC INTERNATIONALFR840000183572015-01-1398000MonacoFournituresFournitures de supports numériques : lot n° 5 ...0 à 3 999,99 HT500,00NaN
10462015Ministère de la DéfenseMINDEF / Air / SIMMAD - Structure Intégrée de ...LEVASUD320476476000252015-01-27NaNSAINT JEANNETFournituresAcquisition d'élingues NH90 de type DYNLEV20E...20 000 HT à 49 999,99 HT46 704,00NaN
11232015Ministère de la DéfenseMINDEF / EMA / DC SSA / DAPSA - Direction des ...VECTEC413247719000372015-05-11NaNHAUTERIVEFournituresFourniture d'agrafeuses et applicateurs de cli...20 000 HT à 49 999,99 HT41 602,00NaN
14412015Ministère de la DéfenseMINDEF / EMA / DC SSA / DAPSA - Direction des ...Xstrahl31052562015-12-23NaNCamberley, SurreyFournituresAcquisition d'une cabine auto-protégée d'irrad...193 000 HT à 999 999,99 HT290 989,00NaN
18012015Union des Groupements d'Achats PublicsUGAP / DA / DA-SER - Département ServicesGROUPE SERVICES FRANCE775675291004282015-03-10NaNBIOTServicesPrestations de propreté de locaux et de surfac...3 000 000 HT à 4 844 999,99 HT3 411 636,00NaN
18022015Union des Groupements d'Achats PublicsUGAP / DA / DA-SER - Département ServicesGROUPE SERVICES FRANCE775675291004282015-03-10NaNBIOTServicesPrestations de propreté de locaux et de surfac...3 000 000 HT à 4 844 999,99 HT3 411 636,00NaN
21462015Ministère des Affaires étrangeres et du Dévelo...NoneFLYTRANS332640945000662015-05-07NaNBIOTServicesAffrétement d'un avion pour le Népal suite au ...193 000 HT à 999 999,99 HT238 178,00NaN
28492015Ministère de l'Environnement, de l'Énergie et ...MEEM-MLDH / SD / DIR / DIR A - Direction Inter...GEDIVEPRO339901522000472015-10-06NaNSAINT VICTORFournituresFourniture de vêtements de travail et d'équipe...193 000 HT à 999 999,99 HT240 000,00NaN
28502015Ministère de l'Environnement, de l'Énergie et ...MEEM-MLDH / SD / DIR / DIR A - Direction Inter...GEDIVEPRO339901522000472015-10-06NaNSAINT VICTORFournituresFourniture de vêtements de travail et d'équipe...193 000 HT à 999 999,99 HT240 000,00NaN
29202015Ministère de l'Environnement, de l'Énergie et ...MEEM-MLDH / EP / ENTPE - Ecole Nationale des T...LaVision20200326212015-10-1537081GoettingenFournituresFourniture, livraison, installation, mise en s...90 000 HT à 124 999,99 HT98 900,00NaN
31962015Etablissements et Organismes d'Enseignement Su...EOESR / USMB - UNIVERSITE SAVOIE MONT BLANCDIETMAR DREIER Wissenschaftliche Versandbuchha...DE1778132512015-07-2247228DuisburgFournituresLe présent marché porte sur la fourniture de l...50 000 HT à 89 999,99 HTNaNNaN
35912015Ministère de l'IntérieurMINT / DGOM / CSMA - RSMA GUADELOUPE - Command...B3E- BUREAU D'ETUDES TECHNIQUE798187613000122015-07-0997196JARRY CEDEXTravauxRSMA-Ga - Camp de la Jaille - Baie Mahault - M...20 000 HT à 49 999,99 HT25 500,00NaN
35922015Ministère de l'IntérieurMINT / DGOM / CSMA - RSMA GUADELOUPE - Command...B3E- BUREAU D'ETUDES TECHNIQUE798187613000122015-07-0997196JARRY CEDEXTravauxRSMA-Ga - Camp de la Jaille - Baie Mahault - M...20 000 HT à 49 999,99 HT25 500,00NaN
36882015Ministère de l'IntérieurMINT / SGAMI / SGAMI Bordeaux - DAGF - Bureau ...SERGE BONTEMPS431928993000362015-09-0919100BRIVESTravauxTravaux de remplacement des menuiseries métal...20 000 HT à 49 999,99 HT21 726,68NaN
38332015Ministère de l'IntérieurMINT / SGAR / SGAR Nord Pas de Calais - Secrét...Constellation côte dOpale-La F793849142000212015-11-1256760GRANDE SYNTHEServicesLe présent marché a pour objet la réservation ...20 000 HT à 49 999,99 HT34 000,00NaN
44252015Ministère de la DéfenseMINDEF / SGA / DCSID / ESID Metz - Etablisseme...ENTREPRISE EVRARD420425209000122015-06-01NaNMONTAIGUTravauxMarché à bons de commande sur bordereau de pri...193 000 HT à 999 999,99 HT200 000,00NaN
45442015Ministère de la DéfenseMINDEF / EMA / DC SCA / PFAF O (Brest) - Plate...Inter-Op Canada, Inc.11656693192015-06-1135043Dollard-Des-OrmeauxFournituresEtude, conception et livraison d'un collecteur...50 000 HT à 89 999,99 HT57 835,00NaN
45552015Ministère de la DéfenseMINDEF / EMA / DC SCA / PFAF O (Brest) - Plate...Inter-Op Canada, Inc.11656693192015-06-1135043Dollard-Des-OrmeauxFournituresEtude et réalisation de deux prototypes de col...50 000 HT à 89 999,99 HT57 835,00NaN
50182015Ministère de la DéfenseMINDEF / EMA / DC SCA / PFAF SE (Toulon) - Pla...DERMO HYGIENE FRANCE 06790255905000162015-10-06NaNBIOTServicesNettoyage périodique des locaux de divers orga...193 000 HT à 999 999,99 HT359 888,00NaN
50192015Ministère de la DéfenseMINDEF / EMA / DC SCA / PFAF SE (Toulon) - Pla...DERMO HYGIENE FRANCE 06790255905000162015-10-06NaNBIOTServicesNettoyage périodique des locaux de divers orga...125 000 HT à 192 999,99 HT161 457,00NaN
52492015Ministère de la DéfenseMINDEF / EMA / DC SCA / DICOM FAZSOI et GSBdD ...SOFAREM325242782000692015-11-10NaNLE PORTFournituresFourniture et livraison d'articles d'électromé...125 000 HT à 192 999,99 HT142 546,65NaN
52502015Ministère de la DéfenseMINDEF / EMA / DC SCA / DICOM FAZSOI et GSBdD ...SOFAREM325242782000692015-11-10NaNLE PORTFournituresFourniture et livraison d'articles d'électromé...50 000 HT à 89 999,99 HT83 595,13NaN
52512015Ministère de la DéfenseMINDEF / EMA / DC SCA / DICOM FAZSOI et GSBdD ...SOFAREM325242782000692015-11-10NaNLE PORTFournituresFourniture et livraison d'articles d'électromé...90 000 HT à 124 999,99 HT101 909,28NaN
58052015Chambres de Commerce et d'Industrie (CCI)CCI / Grenoble - Chambre de Commerce et d'Indu...SOCIETE RECYCLAGE PAPIERS META325329746000292015-05-0438120LE FONGTAILServicesCONTRAT D'ENLEVEMENT ET DE RECYCLAGE DES DECHE...20 000 HT à 49 999,99 HT21 200,00NaN
63222015Etablissements et Organismes d'Enseignement Su...EOESR / URCA - Université de Reims Champagne A...Voxeljet AG3128723162015-11-1786316friedbergFournituresImprimante 3D Sable90 000 HT à 124 999,99 HT93 000,00NaN
65492015Chambres de Commerce et d'Industrie (CCI)CCI / Amiens - Picardie - Chambre de Commerce ...GAS NATURAL EUROPE SASRCS Nanterre B 477 92015-04-25NaNParis la DéfenseFournituresLa présente consultation a pour objet la fourn...125 000 HT à 192 999,99 HT131 496,00NaN
65942015Ministère de la DéfenseMINDEF / ESTM / EPA / SHOM - Service hydrograp...IIC Technologies Ltd43372015-02-20NaNHyderabadServicesProduction de données cartographiques50 000 HT à 89 999,99 HT83 132,00NaN
66162015Ministère de la DéfenseMINDEF / ESTM / EPA / SHOM - Service hydrograp...CARIS BV160857212015-10-13NaNHeeswijkServicesExpertise CARIS HPD20 000 HT à 49 999,99 HT24 046,00NaN
66402015Services du Premier ministreSPM / DILA - Direction de l'information légale...GOSS INTERNATIONAL EUROPE B.V.795273093000182015-03-02991355931AZ BOXMEERFournituresFourniture de pièces détachées et de prestati...193 000 HT à 999 999,99 HT660 000,00NaN
66492015Ministère de la DéfenseMINDEF / ESTM / EPA / SHOM - Service hydrograp...CherSoft Ltd38616332015-01-1400000Chapeltown, S35 2PYServicesDéveloppement d'un module de traitement de la ...20 000 HT à 49 999,99 HT33 547,00NaN
71262015Ministère de la DéfenseMINDEF / Marine / DCSSF / DSSF Toulon - Direct...JCE BIOTECHNOLOGY418489704000192015-07-06NaNHAUTERIVEFournituresfourniture de 2 boites à gants , formation au ...90 000 HT à 124 999,99 HT103 007,00NaN
74102015Chambres de Commerce et d'Industrie (CCI)CCI / Alès - Chambre de Commerce et d'Industri...VOLKSWAGEN BANK GESELLSCHAFT MIT BESCHRAENKTER...451618904000102015-07-099910938112 BRAUNSCHWEIGFournitureslocation de deux véhicules de tourisme pour la...4 000 HT à 19 999,99 HT6 014,00NaN
74442015Ministère de la DéfenseMINDEF / ESTM / EPA / SHOM - Service hydrograp...IIC Technologies Ltd43372015-07-30NaNHyderabadServicesProduction de données cartographiques sur CARI...20 000 HT à 49 999,99 HT22 933,00NaN
75162015Ministères sociaux (santé, travail, jeunesse e...MSJSVA / OP / ARS75 - ARS Ile de FranceBUREAU VERITAS77569062131322015-07-1345520LAXOUServicesRéalisation de contrôles réglementaires des in...125 000 HT à 192 999,99 HT134 000,00NaN
76682015Ministère de la DéfenseMINDEF / DGA / DS / ITE - Intelligence techniq...ProQuest, LLC1187175292015-12-07NaNSeattleFournituresConditions dutilisation de la base de données ...125 000 HT à 192 999,99 HT136 536,00NaN
77292015Ministère de la DéfenseMINDEF / ESTM / EPA / SHOM - Service hydrograp...CARIS BV160857212015-12-16NaNHeeswijkServicesMCO parc de licences193 000 HT à 999 999,99 HT301 600,00NaN
84972015Chambres de Commerce et d'Industrie (CCI)CCI / Grenoble - Chambre de Commerce et d'Indu...COMMUNICATION MANAGEMENT6018112015-02-17NaNSAINT ALBAN UKServicesRelations Presses internationales pour Grenobl...50 000 HT à 89 999,99 HT55 000,00NaN
85872015Chambres de Commerce et d'Industrie (CCI)CCI / Grenoble - Chambre de Commerce et d'Indu...SMART CLIC FRANCE530412006000132015-11-30NaNSAINT JEANNETServicesPour le concours Admissibles 2016, nous souha...20 000 HT à 49 999,99 HT32 000,00NaN
89162015Chambres de Commerce et d'Industrie (CCI)CCI / Ajaccio - Chambre de Commerce et d'Indus...NOVA NAUTIC511219370000182015-12-14NaNPORTTravauxPort de Commerce d'Ajaccio , création de passe...193 000 HT à 999 999,99 HT694 038,40NaN
91742015Ministère de la DéfenseMINDEF / ESTM / EPA / Ecole Polytechnique - Ec...NKT Photonics GmbHDE8130282802015-09-2151063CologneFournituresAcquisition d'une bobine de fibre amplificatri...90 000 HT à 124 999,99 HT112 954,17NaN
93882015Ministère de l'Environnement, de l'Énergie et ...MEEM-MLDH / DGAC / SC / BEA - Bureau d'Enquête...GEDIVEPRO339901522000472015-11-18NaNSAINT VICTORFournituresAchat de vêtements de travail (MAPA 12/15 - Lot)20 000 HT à 49 999,99 HT40 415,00NaN
95502015Ministère de l'IntérieurMINT / PREF / PREF 09 - Préfecture de l'AriègeSCOP COUSERANS CONSTRUCTION403165384000132015-03-10NaNSAINT GIRONSTravauxREMANIEMENT DE TOITURE - RESIDENCE SG4 000 HT à 19 999,99 HT10 083,68NaN
\n", + "
" + ], + "text/plain": [ + " Année de notification \\\n", + "5 2015 \n", + "7 2015 \n", + "1046 2015 \n", + "1123 2015 \n", + "1441 2015 \n", + "1801 2015 \n", + "1802 2015 \n", + "2146 2015 \n", + "2849 2015 \n", + "2850 2015 \n", + "2920 2015 \n", + "3196 2015 \n", + "3591 2015 \n", + "3592 2015 \n", + "3688 2015 \n", + "3833 2015 \n", + "4425 2015 \n", + "4544 2015 \n", + "4555 2015 \n", + "5018 2015 \n", + "5019 2015 \n", + "5249 2015 \n", + "5250 2015 \n", + "5251 2015 \n", + "5805 2015 \n", + "6322 2015 \n", + "6549 2015 \n", + "6594 2015 \n", + "6616 2015 \n", + "6640 2015 \n", + "6649 2015 \n", + "7126 2015 \n", + "7410 2015 \n", + "7444 2015 \n", + "7516 2015 \n", + "7668 2015 \n", + "7729 2015 \n", + "8497 2015 \n", + "8587 2015 \n", + "8916 2015 \n", + "9174 2015 \n", + "9388 2015 \n", + "9550 2015 \n", + "\n", + " Entité Publique \\\n", + "5 Ministère des Finances et des Comptes publics ... \n", + "7 Ministère des Finances et des Comptes publics ... \n", + "1046 Ministère de la Défense \n", + "1123 Ministère de la Défense \n", + "1441 Ministère de la Défense \n", + "1801 Union des Groupements d'Achats Publics \n", + "1802 Union des Groupements d'Achats Publics \n", + "2146 Ministère des Affaires étrangeres et du Dévelo... \n", + "2849 Ministère de l'Environnement, de l'Énergie et ... \n", + "2850 Ministère de l'Environnement, de l'Énergie et ... \n", + "2920 Ministère de l'Environnement, de l'Énergie et ... \n", + "3196 Etablissements et Organismes d'Enseignement Su... \n", + "3591 Ministère de l'Intérieur \n", + "3592 Ministère de l'Intérieur \n", + "3688 Ministère de l'Intérieur \n", + "3833 Ministère de l'Intérieur \n", + "4425 Ministère de la Défense \n", + "4544 Ministère de la Défense \n", + "4555 Ministère de la Défense \n", + "5018 Ministère de la Défense \n", + "5019 Ministère de la Défense \n", + "5249 Ministère de la Défense \n", + "5250 Ministère de la Défense \n", + "5251 Ministère de la Défense \n", + "5805 Chambres de Commerce et d'Industrie (CCI) \n", + "6322 Etablissements et Organismes d'Enseignement Su... \n", + "6549 Chambres de Commerce et d'Industrie (CCI) \n", + "6594 Ministère de la Défense \n", + "6616 Ministère de la Défense \n", + "6640 Services du Premier ministre \n", + "6649 Ministère de la Défense \n", + "7126 Ministère de la Défense \n", + "7410 Chambres de Commerce et d'Industrie (CCI) \n", + "7444 Ministère de la Défense \n", + "7516 Ministères sociaux (santé, travail, jeunesse e... \n", + "7668 Ministère de la Défense \n", + "7729 Ministère de la Défense \n", + "8497 Chambres de Commerce et d'Industrie (CCI) \n", + "8587 Chambres de Commerce et d'Industrie (CCI) \n", + "8916 Chambres de Commerce et d'Industrie (CCI) \n", + "9174 Ministère de la Défense \n", + "9388 Ministère de l'Environnement, de l'Énergie et ... \n", + "9550 Ministère de l'Intérieur \n", + "\n", + " Entite d'achat \\\n", + "5 MFCP-MEIN / DGFIP / DGFIP-R / DGFIP-078 - DGFI... \n", + "7 MFCP-MEIN / DGFIP / DGFIP-R / DGFIP-078 - DGFI... \n", + "1046 MINDEF / Air / SIMMAD - Structure Intégrée de ... \n", + "1123 MINDEF / EMA / DC SSA / DAPSA - Direction des ... \n", + "1441 MINDEF / EMA / DC SSA / DAPSA - Direction des ... \n", + "1801 UGAP / DA / DA-SER - Département Services \n", + "1802 UGAP / DA / DA-SER - Département Services \n", + "2146 None \n", + "2849 MEEM-MLDH / SD / DIR / DIR A - Direction Inter... \n", + "2850 MEEM-MLDH / SD / DIR / DIR A - Direction Inter... \n", + "2920 MEEM-MLDH / EP / ENTPE - Ecole Nationale des T... \n", + "3196 EOESR / USMB - UNIVERSITE SAVOIE MONT BLANC \n", + "3591 MINT / DGOM / CSMA - RSMA GUADELOUPE - Command... \n", + "3592 MINT / DGOM / CSMA - RSMA GUADELOUPE - Command... \n", + "3688 MINT / SGAMI / SGAMI Bordeaux - DAGF - Bureau ... \n", + "3833 MINT / SGAR / SGAR Nord Pas de Calais - Secrét... \n", + "4425 MINDEF / SGA / DCSID / ESID Metz - Etablisseme... \n", + "4544 MINDEF / EMA / DC SCA / PFAF O (Brest) - Plate... \n", + "4555 MINDEF / EMA / DC SCA / PFAF O (Brest) - Plate... \n", + "5018 MINDEF / EMA / DC SCA / PFAF SE (Toulon) - Pla... \n", + "5019 MINDEF / EMA / DC SCA / PFAF SE (Toulon) - Pla... \n", + "5249 MINDEF / EMA / DC SCA / DICOM FAZSOI et GSBdD ... \n", + "5250 MINDEF / EMA / DC SCA / DICOM FAZSOI et GSBdD ... \n", + "5251 MINDEF / EMA / DC SCA / DICOM FAZSOI et GSBdD ... \n", + "5805 CCI / Grenoble - Chambre de Commerce et d'Indu... \n", + "6322 EOESR / URCA - Université de Reims Champagne A... \n", + "6549 CCI / Amiens - Picardie - Chambre de Commerce ... \n", + "6594 MINDEF / ESTM / EPA / SHOM - Service hydrograp... \n", + "6616 MINDEF / ESTM / EPA / SHOM - Service hydrograp... \n", + "6640 SPM / DILA - Direction de l'information légale... \n", + "6649 MINDEF / ESTM / EPA / SHOM - Service hydrograp... \n", + "7126 MINDEF / Marine / DCSSF / DSSF Toulon - Direct... \n", + "7410 CCI / Alès - Chambre de Commerce et d'Industri... \n", + "7444 MINDEF / ESTM / EPA / SHOM - Service hydrograp... \n", + "7516 MSJSVA / OP / ARS75 - ARS Ile de France \n", + "7668 MINDEF / DGA / DS / ITE - Intelligence techniq... \n", + "7729 MINDEF / ESTM / EPA / SHOM - Service hydrograp... \n", + "8497 CCI / Grenoble - Chambre de Commerce et d'Indu... \n", + "8587 CCI / Grenoble - Chambre de Commerce et d'Indu... \n", + "8916 CCI / Ajaccio - Chambre de Commerce et d'Indus... \n", + "9174 MINDEF / ESTM / EPA / Ecole Polytechnique - Ec... \n", + "9388 MEEM-MLDH / DGAC / SC / BEA - Bureau d'Enquête... \n", + "9550 MINT / PREF / PREF 09 - Préfecture de l'Ariège \n", + "\n", + " Nom Attributaire SIRET Attributaire \\\n", + "5 SYNOPTIC INTERNATIONAL FR84000018357 \n", + "7 SYNOPTIC INTERNATIONAL FR84000018357 \n", + "1046 LEVASUD 32047647600025 \n", + "1123 VECTEC 41324771900037 \n", + "1441 Xstrahl 3105256 \n", + "1801 GROUPE SERVICES FRANCE 77567529100428 \n", + "1802 GROUPE SERVICES FRANCE 77567529100428 \n", + "2146 FLYTRANS 33264094500066 \n", + "2849 GEDIVEPRO 33990152200047 \n", + "2850 GEDIVEPRO 33990152200047 \n", + "2920 LaVision 2020032621 \n", + "3196 DIETMAR DREIER Wissenschaftliche Versandbuchha... DE177813251 \n", + "3591 B3E- BUREAU D'ETUDES TECHNIQUE 79818761300012 \n", + "3592 B3E- BUREAU D'ETUDES TECHNIQUE 79818761300012 \n", + "3688 SERGE BONTEMPS 43192899300036 \n", + "3833 Constellation côte dOpale-La F 79384914200021 \n", + "4425 ENTREPRISE EVRARD 42042520900012 \n", + "4544 Inter-Op Canada, Inc. 1165669319 \n", + "4555 Inter-Op Canada, Inc. 1165669319 \n", + "5018 DERMO HYGIENE FRANCE 06 79025590500016 \n", + "5019 DERMO HYGIENE FRANCE 06 79025590500016 \n", + "5249 SOFAREM 32524278200069 \n", + "5250 SOFAREM 32524278200069 \n", + "5251 SOFAREM 32524278200069 \n", + "5805 SOCIETE RECYCLAGE PAPIERS META 32532974600029 \n", + "6322 Voxeljet AG 312872316 \n", + "6549 GAS NATURAL EUROPE SAS RCS Nanterre B 477 9 \n", + "6594 IIC Technologies Ltd 4337 \n", + "6616 CARIS BV 16085721 \n", + "6640 GOSS INTERNATIONAL EUROPE B.V. 79527309300018 \n", + "6649 CherSoft Ltd 3861633 \n", + "7126 JCE BIOTECHNOLOGY 41848970400019 \n", + "7410 VOLKSWAGEN BANK GESELLSCHAFT MIT BESCHRAENKTER... 45161890400010 \n", + "7444 IIC Technologies Ltd 4337 \n", + "7516 BUREAU VERITAS 7756906213132 \n", + "7668 ProQuest, LLC 118717529 \n", + "7729 CARIS BV 16085721 \n", + "8497 COMMUNICATION MANAGEMENT 601811 \n", + "8587 SMART CLIC FRANCE 53041200600013 \n", + "8916 NOVA NAUTIC 51121937000018 \n", + "9174 NKT Photonics GmbH DE813028280 \n", + "9388 GEDIVEPRO 33990152200047 \n", + "9550 SCOP COUSERANS CONSTRUCTION 40316538400013 \n", + "\n", + " Date de notification Code Postal Attributaire Ville \\\n", + "5 2015-01-13 98000 Monaco \n", + "7 2015-01-13 98000 Monaco \n", + "1046 2015-01-27 NaN SAINT JEANNET \n", + "1123 2015-05-11 NaN HAUTERIVE \n", + "1441 2015-12-23 NaN Camberley, Surrey \n", + "1801 2015-03-10 NaN BIOT \n", + "1802 2015-03-10 NaN BIOT \n", + "2146 2015-05-07 NaN BIOT \n", + "2849 2015-10-06 NaN SAINT VICTOR \n", + "2850 2015-10-06 NaN SAINT VICTOR \n", + "2920 2015-10-15 37081 Goettingen \n", + "3196 2015-07-22 47228 Duisburg \n", + "3591 2015-07-09 97196 JARRY CEDEX \n", + "3592 2015-07-09 97196 JARRY CEDEX \n", + "3688 2015-09-09 19100 BRIVES \n", + "3833 2015-11-12 56760 GRANDE SYNTHE \n", + "4425 2015-06-01 NaN MONTAIGU \n", + "4544 2015-06-11 35043 Dollard-Des-Ormeaux \n", + "4555 2015-06-11 35043 Dollard-Des-Ormeaux \n", + "5018 2015-10-06 NaN BIOT \n", + "5019 2015-10-06 NaN BIOT \n", + "5249 2015-11-10 NaN LE PORT \n", + "5250 2015-11-10 NaN LE PORT \n", + "5251 2015-11-10 NaN LE PORT \n", + "5805 2015-05-04 38120 LE FONGTAIL \n", + "6322 2015-11-17 86316 friedberg \n", + "6549 2015-04-25 NaN Paris la Défense \n", + "6594 2015-02-20 NaN Hyderabad \n", + "6616 2015-10-13 NaN Heeswijk \n", + "6640 2015-03-02 99135 5931AZ BOXMEER \n", + "6649 2015-01-14 00000 Chapeltown, S35 2PY \n", + "7126 2015-07-06 NaN HAUTERIVE \n", + "7410 2015-07-09 99109 38112 BRAUNSCHWEIG \n", + "7444 2015-07-30 NaN Hyderabad \n", + "7516 2015-07-13 45520 LAXOU \n", + "7668 2015-12-07 NaN Seattle \n", + "7729 2015-12-16 NaN Heeswijk \n", + "8497 2015-02-17 NaN SAINT ALBAN UK \n", + "8587 2015-11-30 NaN SAINT JEANNET \n", + "8916 2015-12-14 NaN PORT \n", + "9174 2015-09-21 51063 Cologne \n", + "9388 2015-11-18 NaN SAINT VICTOR \n", + "9550 2015-03-10 NaN SAINT GIRONS \n", + "\n", + " Nature du Marché Objet du marché \\\n", + "5 Fournitures Fournitures de supports numériques : lot n° 3 ... \n", + "7 Fournitures Fournitures de supports numériques : lot n° 5 ... \n", + "1046 Fournitures Acquisition d'élingues NH90 de type DYNLEV20E... \n", + "1123 Fournitures Fourniture d'agrafeuses et applicateurs de cli... \n", + "1441 Fournitures Acquisition d'une cabine auto-protégée d'irrad... \n", + "1801 Services Prestations de propreté de locaux et de surfac... \n", + "1802 Services Prestations de propreté de locaux et de surfac... \n", + "2146 Services Affrétement d'un avion pour le Népal suite au ... \n", + "2849 Fournitures Fourniture de vêtements de travail et d'équipe... \n", + "2850 Fournitures Fourniture de vêtements de travail et d'équipe... \n", + "2920 Fournitures Fourniture, livraison, installation, mise en s... \n", + "3196 Fournitures Le présent marché porte sur la fourniture de l... \n", + "3591 Travaux RSMA-Ga - Camp de la Jaille - Baie Mahault - M... \n", + "3592 Travaux RSMA-Ga - Camp de la Jaille - Baie Mahault - M... \n", + "3688 Travaux Travaux de remplacement des menuiseries métal... \n", + "3833 Services Le présent marché a pour objet la réservation ... \n", + "4425 Travaux Marché à bons de commande sur bordereau de pri... \n", + "4544 Fournitures Etude, conception et livraison d'un collecteur... \n", + "4555 Fournitures Etude et réalisation de deux prototypes de col... \n", + "5018 Services Nettoyage périodique des locaux de divers orga... \n", + "5019 Services Nettoyage périodique des locaux de divers orga... \n", + "5249 Fournitures Fourniture et livraison d'articles d'électromé... \n", + "5250 Fournitures Fourniture et livraison d'articles d'électromé... \n", + "5251 Fournitures Fourniture et livraison d'articles d'électromé... \n", + "5805 Services CONTRAT D'ENLEVEMENT ET DE RECYCLAGE DES DECHE... \n", + "6322 Fournitures Imprimante 3D Sable \n", + "6549 Fournitures La présente consultation a pour objet la fourn... \n", + "6594 Services Production de données cartographiques \n", + "6616 Services Expertise CARIS HPD \n", + "6640 Fournitures Fourniture de pièces détachées et de prestati... \n", + "6649 Services Développement d'un module de traitement de la ... \n", + "7126 Fournitures fourniture de 2 boites à gants , formation au ... \n", + "7410 Fournitures location de deux véhicules de tourisme pour la... \n", + "7444 Services Production de données cartographiques sur CARI... \n", + "7516 Services Réalisation de contrôles réglementaires des in... \n", + "7668 Fournitures Conditions dutilisation de la base de données ... \n", + "7729 Services MCO parc de licences \n", + "8497 Services Relations Presses internationales pour Grenobl... \n", + "8587 Services Pour le concours Admissibles 2016, nous souha... \n", + "8916 Travaux Port de Commerce d'Ajaccio , création de passe... \n", + "9174 Fournitures Acquisition d'une bobine de fibre amplificatri... \n", + "9388 Fournitures Achat de vêtements de travail (MAPA 12/15 - Lot) \n", + "9550 Travaux REMANIEMENT DE TOITURE - RESIDENCE SG \n", + "\n", + " Tranche budgetaire Montant codeInsee \n", + "5 0 à 3 999,99 HT 2 000,00 NaN \n", + "7 0 à 3 999,99 HT 500,00 NaN \n", + "1046 20 000 HT à 49 999,99 HT 46 704,00 NaN \n", + "1123 20 000 HT à 49 999,99 HT 41 602,00 NaN \n", + "1441 193 000 HT à 999 999,99 HT 290 989,00 NaN \n", + "1801 3 000 000 HT à 4 844 999,99 HT 3 411 636,00 NaN \n", + "1802 3 000 000 HT à 4 844 999,99 HT 3 411 636,00 NaN \n", + "2146 193 000 HT à 999 999,99 HT 238 178,00 NaN \n", + "2849 193 000 HT à 999 999,99 HT 240 000,00 NaN \n", + "2850 193 000 HT à 999 999,99 HT 240 000,00 NaN \n", + "2920 90 000 HT à 124 999,99 HT 98 900,00 NaN \n", + "3196 50 000 HT à 89 999,99 HT NaN NaN \n", + "3591 20 000 HT à 49 999,99 HT 25 500,00 NaN \n", + "3592 20 000 HT à 49 999,99 HT 25 500,00 NaN \n", + "3688 20 000 HT à 49 999,99 HT 21 726,68 NaN \n", + "3833 20 000 HT à 49 999,99 HT 34 000,00 NaN \n", + "4425 193 000 HT à 999 999,99 HT 200 000,00 NaN \n", + "4544 50 000 HT à 89 999,99 HT 57 835,00 NaN \n", + "4555 50 000 HT à 89 999,99 HT 57 835,00 NaN \n", + "5018 193 000 HT à 999 999,99 HT 359 888,00 NaN \n", + "5019 125 000 HT à 192 999,99 HT 161 457,00 NaN \n", + "5249 125 000 HT à 192 999,99 HT 142 546,65 NaN \n", + "5250 50 000 HT à 89 999,99 HT 83 595,13 NaN \n", + "5251 90 000 HT à 124 999,99 HT 101 909,28 NaN \n", + "5805 20 000 HT à 49 999,99 HT 21 200,00 NaN \n", + "6322 90 000 HT à 124 999,99 HT 93 000,00 NaN \n", + "6549 125 000 HT à 192 999,99 HT 131 496,00 NaN \n", + "6594 50 000 HT à 89 999,99 HT 83 132,00 NaN \n", + "6616 20 000 HT à 49 999,99 HT 24 046,00 NaN \n", + "6640 193 000 HT à 999 999,99 HT 660 000,00 NaN \n", + "6649 20 000 HT à 49 999,99 HT 33 547,00 NaN \n", + "7126 90 000 HT à 124 999,99 HT 103 007,00 NaN \n", + "7410 4 000 HT à 19 999,99 HT 6 014,00 NaN \n", + "7444 20 000 HT à 49 999,99 HT 22 933,00 NaN \n", + "7516 125 000 HT à 192 999,99 HT 134 000,00 NaN \n", + "7668 125 000 HT à 192 999,99 HT 136 536,00 NaN \n", + "7729 193 000 HT à 999 999,99 HT 301 600,00 NaN \n", + "8497 50 000 HT à 89 999,99 HT 55 000,00 NaN \n", + "8587 20 000 HT à 49 999,99 HT 32 000,00 NaN \n", + "8916 193 000 HT à 999 999,99 HT 694 038,40 NaN \n", + "9174 90 000 HT à 124 999,99 HT 112 954,17 NaN \n", + "9388 20 000 HT à 49 999,99 HT 40 415,00 NaN \n", + "9550 4 000 HT à 19 999,99 HT 10 083,68 NaN " + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df[(df.codeInsee.isnull()) & (df.Ville.notnull()) & (~df.Ville.fillna(\"\").str.match(\"-+\"))]" + ] + }, + { + "cell_type": "markdown", + "id": "5a8e02a8-70da-4ee7-85b4-aeb4aa5782b2", + "metadata": {}, + "source": [ + "Il n'y a en réalité que trois résultats absents. Parmi ceux-ci, nous avons deux relevant de la principauté de Monaco et une commune insuffisamment décrite (pas de code postal).\n", + "\n", + "💡Nota : pour aller un tout petit peu plus loin, notons que `french-cities` est tout à fait capable de retrouver une commune sans son code postal, mais à la seule condition qu'il n'y ait aucun homonyme sur le territoire national. En l'occurrence, il y a deux \"Saint Jeannet\" en France (dans les [Alpes-Maritimes](https://fr.wikipedia.org/wiki/Saint-Jeannet_(Alpes-Maritimes)) et dans les [Alpes de Haute-Provence](https://fr.wikipedia.org/wiki/Saint-Jeannet_(Alpes-de-Haute-Provence)))..." + ] + }, { "cell_type": "markdown", "id": "cc9cbbcc-6403-4113-ab19-3f1b9aec62e2", @@ -561,7 +1642,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "4421bc2c-8595-406e-b2ad-34f28634f756", "metadata": {}, "outputs": [], @@ -571,7 +1652,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "id": "51a63bfd", "metadata": {}, "outputs": [ @@ -582,7 +1663,7 @@ " Text(0, 0.5, 'Montant (en euros)')]" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" }, diff --git a/notebooks_docs/usecase_3.ipynb b/notebooks_docs/usecase_3.ipynb index 7bbf375..8738590 100644 --- a/notebooks_docs/usecase_3.ipynb +++ b/notebooks_docs/usecase_3.ipynb @@ -35,7 +35,7 @@ "import pandas as pd\n", "from requests_cache import CachedSession\n", "\n", - "from french_cities import find_departements_from_names, find_city" + "from french_cities import find_departements, find_city" ] }, { @@ -560,7 +560,7 @@ } ], "source": [ - "df = find_departements_from_names(df, label=\"Département\", alias=\"DEP_CODE\")\n", + "df = find_departements(df, source=\"Département\", alias=\"DEP_CODE\", type_field=\"label\")\n", "df" ] }, @@ -574,6 +574,7 @@ "name": "stderr", "output_type": "stream", "text": [ + "WARNING:root:get_descending_area with area='collectivitesDOutreMer' does not support date='*': querying for date='*' instead\n", " \r" ] }, diff --git a/notebooks_docs/usecase_4.ipynb b/notebooks_docs/usecase_4.ipynb index fcbad6c..80eb45f 100644 --- a/notebooks_docs/usecase_4.ipynb +++ b/notebooks_docs/usecase_4.ipynb @@ -962,7 +962,7 @@ { "data": { "text/plain": [ - "False" + "np.False_" ] }, "execution_count": 8, From 6eb06cbdc7cd6c59c3c52ecbd6e46de7cce6adb9 Mon Sep 17 00:00:00 2001 From: Thomas Grandjean Date: Sat, 27 Jul 2024 17:15:32 +0200 Subject: [PATCH 03/20] Fix typo on ultramarine deps --- french_cities/ultramarine_pseudo_cog.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/french_cities/ultramarine_pseudo_cog.py b/french_cities/ultramarine_pseudo_cog.py index f48b9c0..051e55b 100644 --- a/french_cities/ultramarine_pseudo_cog.py +++ b/french_cities/ultramarine_pseudo_cog.py @@ -157,7 +157,7 @@ def get_departements_and_ultramarines(date=None, update=None): ultramarine = get_area_list("collectivitesDOutreMer", date, update) ultramarine = ultramarine.sort_values(["CODE", "DATE_CREATION"]) - cities = get_area_list("departements", date, update) - cities = cities.drop("chefLieu", axis=1) - full = pd.concat([ultramarine, cities], ignore_index=True) + deps = get_area_list("departements", date, update) + deps = deps.drop("chefLieu", axis=1) + full = pd.concat([ultramarine, deps], ignore_index=True) return full From 3efdedd306b2f7c21f5a0e57e2de88a9d15e13db Mon Sep 17 00:00:00 2001 From: Thomas Grandjean Date: Sat, 27 Jul 2024 17:16:21 +0200 Subject: [PATCH 04/20] Refacto find_departements_from_names & find_departements --- french_cities/__init__.py | 6 +- french_cities/city_finder.py | 8 +- french_cities/departement_finder.py | 160 ++++++++++++++++------------ 3 files changed, 97 insertions(+), 77 deletions(-) diff --git a/french_cities/__init__.py b/french_cities/__init__.py index 4354389..a73fe27 100644 --- a/french_cities/__init__.py +++ b/french_cities/__init__.py @@ -6,10 +6,7 @@ from .config import DIR_CACHE from .city_finder import find_city -from .departement_finder import ( - find_departements, - find_departements_from_names, -) +from .departement_finder import find_departements from .vintage import set_vintage load_dotenv(override=True) @@ -20,6 +17,5 @@ __all__ = [ "find_city", "find_departements", - "find_departements_from_names", "set_vintage", ] diff --git a/french_cities/city_finder.py b/french_cities/city_finder.py index af2a2cd..c303b2c 100644 --- a/french_cities/city_finder.py +++ b/french_cities/city_finder.py @@ -550,7 +550,7 @@ def list_map(df, columns): missing, source="insee_com_nominatim", alias="dep_nominatim", - type_code="insee", + type_field="insee", ) temp = df.loc[ix] temp["query"] = temp[use] + " " + temp["city_cleaned"] @@ -753,7 +753,11 @@ def _find_from_fuzzymatch_cities_names( df = df.loc[:, ["TITLE_SHORT", "CODE"]] df = find_departements( - df, source="CODE", alias="dep", type_code="insee", do_set_vintage=False + df, + source="CODE", + alias="dep", + type_field="insee", + do_set_vintage=False, ) df = df.drop_duplicates(["TITLE_SHORT", "dep"]) df = df.reset_index(drop=False) diff --git a/french_cities/departement_finder.py b/french_cities/departement_finder.py index 2e81aac..f09c6e0 100644 --- a/french_cities/departement_finder.py +++ b/french_cities/departement_finder.py @@ -254,12 +254,14 @@ def get(x): if not postal_codes_missing.empty: # Still no results -> assume we can use the first characters of - # postcode anyway + # postcode anyway (and set do_set_vintage to False, as this may be + # entirely wrong codes, but the first digits may at least be ok) last_resort_results = _process_departements_from_insee_code( postal_codes_missing, source=source, alias="dep", session=session, + do_set_vintage=False, ) last_resort_results = last_resort_results.dropna() else: @@ -366,77 +368,13 @@ def _process_departements_from_insee_code( return df -def find_departements( +def _find_departements_from_names( df: pd.DataFrame, source: str, alias: str, - type_code: str, session: Session = None, authorize_duplicates: bool = False, do_set_vintage: bool = True, -) -> pd.DataFrame: - """ - Compute departement's codes from postal or official codes (ie. INSEE COG)' - Adds the result as a new column to dataframe under the label 'alias'. - - Parameters - ---------- - df : pd.DataFrame - DataFrame containing official cities codes - source : str - Field containing the post or official codes - alias : str - Column to store the departements' codes unto - type_code : str - Type of codes passed under `alias` label. Should be either 'insee' for - official codes or 'postcode' for postal codes. - session : Session, optional - Web session. The default is None (and will use a CachedSession with - 30 days expiration) - authorize_duplicates : bool, optional - If True, authorize duplication of results when multiple results are - acceptable for a given postcode (for instance, 13780 can result to - either 13 or 83 ). If False, duplicates will be removed, hence no - result will be available. False by default. - do_set_vintage : bool, optional - If True, set a vintage projection for df. If False, don't bother (will - be faster). Should be False when df was already computed from a given - function out of pynsee or french-cities. Should be True when used on - almost any other dataset. - The default is True. - - Raises - ------ - ValueError - If type_code not among "postcode", "insee". - - Returns - ------- - df : pd.DataFrame - Updated DataFrame with departement's codes - - """ - if type_code not in {"postcode", "insee"}: - msg = ( - "type_code must be among ('postcode', 'insee') - " - f"found {type_code} instead" - ) - raise ValueError(msg) - - init_pynsee() - - df = df.copy() - if type_code == "postcode": - func = _process_departements_from_postal - elif type_code == "insee": - func = _process_departements_from_insee_code - return func( - df, source, alias, session, authorize_duplicates, do_set_vintage - ) - - -def find_departements_from_names( - df: pd.DataFrame, label: str, alias: str = "DEP_CODE" ) -> pd.DataFrame: """ Retrieve departement's codes from their names. @@ -445,11 +383,20 @@ def find_departements_from_names( ---------- df : pd.DataFrame DataFrame containing official departement's names - label : str + source : str Field containing the label of the departements alias : str, optional Column to store the departements' codes unto. Default is "DEP_CODE" + session : Session, optional + **ignored argument, set only for coherence with + _process_departements_from_postal** + autorize_duplicates : bool, optional + **ignored argument, set only for coherence with + _process_departements_from_postal** + do_set_vintage : bool, optional + **ignored argument, set only for coherence with + _process_departements_from_insee_code** Returns ------- @@ -459,8 +406,8 @@ def find_departements_from_names( """ init_pynsee() - candidates = get_departements_and_ultramarines() - candidates = candidates[["CODE", "TITLE"]] + candidates = get_departements_and_ultramarines("*") + candidates = candidates[["CODE", "TITLE"]].drop_duplicates() candidates["TITLE"] = ( candidates["TITLE"] .apply(unidecode) @@ -472,7 +419,7 @@ def find_departements_from_names( df = df.copy() df["FORMATTED"] = ( - df[label] + df[source] .apply(unidecode) .str.upper() .str.replace(r"\W+", " ", regex=True) @@ -494,3 +441,76 @@ def try_extract_one(x): df = df.drop("FORMATTED", axis=1) return df + + +def find_departements( + df: pd.DataFrame, + source: str, + alias: str, + type_field: str, + session: Session = None, + authorize_duplicates: bool = False, + do_set_vintage: bool = True, +) -> pd.DataFrame: + """ + Compute departement's codes from postal, official codes (ie. INSEE COG) + or labels in full text. + Adds the result as a new column to dataframe under the label 'alias'. + + Parameters + ---------- + df : pd.DataFrame + DataFrame containing official cities codes + source : str + Field containing the post codes, official codes or labels + alias : str + Column to store the departements' codes unto + type_field : str + Type of codes passed under `alias` label. Should be either 'insee' for + official codes, 'postcode' for postal codes or 'label' for labels. + session : Session, optional + Web session. The default is None (and will use a CachedSession with + 30 days expiration) + authorize_duplicates : bool, optional + If True, authorize duplication of results when multiple results are + acceptable for a given postcode (for instance, 13780 can result to + either 13 or 83 ). If False, duplicates will be removed, hence no + result will be available. False by default. + do_set_vintage : bool, optional + If True, set a vintage projection for df. If False, don't bother (will + be faster). Should be False when df was already computed from a given + function out of pynsee or french-cities. Should be True when used on + almost any other dataset. + The default is True. + + Raises + ------ + ValueError + If type_field not among "postcode", "insee", "labels". + + Returns + ------- + df : pd.DataFrame + Updated DataFrame with departement's codes + + """ + + if type_field not in {"postcode", "insee", "label"}: + msg = ( + "type_field must be among ('postcode', 'insee', 'label') - " + f"found {type_field=} instead" + ) + raise ValueError(msg) + + init_pynsee() + + df = df.copy() + if type_field == "postcode": + func = _process_departements_from_postal + elif type_field == "insee": + func = _process_departements_from_insee_code + else: + func = _find_departements_from_names + return func( + df, source, alias, session, authorize_duplicates, do_set_vintage + ) From 106a18f8f06b4c7900a58bffffe24775e0ff3181 Mon Sep 17 00:00:00 2001 From: Thomas Grandjean Date: Sat, 27 Jul 2024 17:16:34 +0200 Subject: [PATCH 05/20] Refacto test --- tests/test_departement_finder.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tests/test_departement_finder.py b/tests/test_departement_finder.py index 71f9a23..90d4b88 100644 --- a/tests/test_departement_finder.py +++ b/tests/test_departement_finder.py @@ -4,10 +4,7 @@ import pandas as pd -from french_cities.departement_finder import ( - find_departements, - find_departements_from_names, -) +from french_cities.departement_finder import find_departements input_df = pd.DataFrame( { @@ -100,8 +97,5 @@ def test_from_insee(self): assert (test["dep_test"] == test["deps"]).all() def test_from_name(self): - test = find_departements_from_names( - input_df2, - "deps", - ) + test = find_departements(input_df2, "deps", "DEP_CODE", "label") assert (test["DEP_CODE"] == test["codes"]).all() From 82e3faaeadf5873a780063e51639fa674d1d2e50 Mon Sep 17 00:00:00 2001 From: Thomas Grandjean Date: Sat, 27 Jul 2024 17:19:08 +0200 Subject: [PATCH 06/20] Update README.md --- README.md | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 8c82599..5fe539d 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,32 @@ # french-cities -Toolbox on french cities: set vintage, find departments, find cities... +This repo contains the documentation of the python french-cities package, a +package aimed at improving the referencing of municipalities in French 🇫🇷 +datasets. # Documentation -A full documentation with usecases is available at [https://tgrandje.github.io/french-cities/](https://tgrandje.github.io/french-cities/). +A full documentation with usecases is available at +[https://tgrandje.github.io/french-cities/](https://tgrandje.github.io/french-cities/). Obviously, it is only available in french as yet. Any help is welcome to build a multi-lingual documentation website. Until then, a basic english documentation will stay available in the present README. -# Installation +# Why french-cities? + +Do you have any data: +* which municipal locations are provided through approximate addresses, or via geographical 🗺️ coordinates? +* which municipalities are referenced by their postal codes and their labels 😮? +* which departments are written in full text 🔡? +* which spelling are dubious (for instance, torturing the _Loire Loir-et-Cher_) or obsolete +(for instance, referencing _Templeuve_, a city renamed as _Templeuve-en-Pévèle_ since 2015)? +* or compiled over the years and where cities' codes are a patchwork of multiple 🤯 vintages? + +**Then 'french-cities' is for you 🫵!** -`pip install french-cities[full]` +# Installation -Note that the "full" installation will also install geopy, which might use -Nominatim API for city recognition as a last resort. +`pip install french-cities` # Configuration @@ -41,17 +53,6 @@ variables for `pynsee` and `geopy`. ## Basic usage -### Why french-cities? -There are already available packages and APIs meant to be used for basic french -cities management. For instance, `pynsee` uses the INSEE's API to retrieve -multiple data (including departement, region, ...). `geopy` can also retrieve -cities from their names using the BAN (Base Adresse Nationale) API or the -Nominatim geocoding service. - -The difference is that `french-cities` is primarly meant to perform against whole -pandas series/dataframes. It should handle better performance than multiple API -calls and will optimize the call to each endpoints. - ### Retrieve departements' codes `french-cities` can retrieve departement's codes from postal codes or official (COG/INSEE) codes. @@ -256,4 +257,4 @@ Thomas GRANDJEAN (DREAL Hauts-de-France, service Information, Développement Dur GPL-3.0-or-later ## Project Status -In production. \ No newline at end of file +Stable. \ No newline at end of file From 37762f5f711cd6bfbdef97aed2fa6472ac86c208 Mon Sep 17 00:00:00 2001 From: Thomas Grandjean Date: Sat, 27 Jul 2024 17:19:59 +0200 Subject: [PATCH 07/20] Set geopy as full dependency Due to nominatim effective caching, ok to keep it as semi-standard practice --- pyproject.toml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e286c19..52e6c56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "french-cities" -version = "0.4.2" +version = "1.0.0" description = "Toolbox on french cities: set vintage, find departments, find cities..." authors = ["thomas.grandjean "] license = "GPL-3.0-or-later" @@ -36,9 +36,6 @@ geopy = "^2.3.0" diskcache = "^5.6.3" platformdirs = "^4.2.2" -[tool.poetry.extras] -full = ["geopy"] - [tool.poetry.group.dev.dependencies] spyder = "^5.5.5" pytest = "^7.4.0" From c9281c11e8fea3236115e4d3d99ea016de85d7a4 Mon Sep 17 00:00:00 2001 From: Thomas Grandjean Date: Sat, 27 Jul 2024 17:20:06 +0200 Subject: [PATCH 08/20] Update index.md --- docs/index.md | 134 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 88 insertions(+), 46 deletions(-) diff --git a/docs/index.md b/docs/index.md index 00b7ee4..ed06825 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,52 +9,94 @@ nav_order: 1 # Documentation du package python `french-cities` -Ce site contient la documentation du package python `french-cities`. +Ce site contient la documentation du package python `french-cities`, +un package visant à améliorer le référencement des communes dans les jeux +de données français 🇫🇷. ## Pourquoi french-cities ? -Des packages et des API sont déjà disponibles en langage python pour des -recherches usuelles. -Par exemple, l'excellent package `pynsee` utilise les API de l'INSEE pour -retrouver de multiples données : -* départements, -* régions, -* etc. - -`geopy` peut également retrouver des communes à partir de leurs noms en -s'appuyant sur la BAN (Base Adresse Nationale) ou sur le service de géocodage -Nominatim. - -`french-cities` est quant à lui optimisé pour travailler avec des données -fournies sous la forme de Series ou DataFrames `pandas`. -Les packages pré-cités (`pynsee`, `geopy`) sont toujours utilisés et -constituent au demeurant des dépendances importantes de `french-cities` : -le présent package peut être considéré comme une surcouche plus adaptée aux -volumes importants de données. - -## Mise en cache et optimisation des algorithmes - -Les algorithmes proposés tiennent globalement compte de la disponibilité -des différentes sources de données. Par exemple, l'API Nominatim n'autorise -pas plus d'une requête par seconde, ce qui explique que `geopy` constitue une -dépendance optionnelle permettant d'utliser Nominatim en dernier recours. - -En outre, `french-cities` s'appuie sur deux systèmes de mise en cache : -* `pynsee` gère sa mise en cache nativement ; -* et pour d'autres requêtes web (base adresse nationale, API opendatasoft sur -les codes Cedex), les requêtes sont mises en cache avec une durée d'expiration -de 30 jours grâce au module `requests-cache` (ce qui explique la création de -fichiers SQLite au droit de l'exécution des scripts) ; -* seul le requêtage de l'API Nominatim (s'appuyant sur geopy) n'est pas mis en -cache à date. - -A moins d'utiliser spécifiquement Nominatim à haute capacité (ce que les -algorithmes visent justement à prévenir), on peut donc considérer que -`french-cities` est relativement sûr à utiliser pour des tâches automatisées -répétitives. Bien évidemment, chaque utilisateur se doit d'utiliser ce package -de manière responsable et sous son entière responsabilité : il lui revient de -contrôler finement les résultats avant de se lancer dans des automatisations à -grande échelle. - -Il est également conseillé de consulter [les documentations externes](./external_doc) -des sources utilisées. \ No newline at end of file +Avez-vous des données : +* dont la localisation communale est fournie par le biais d'adresses +approximatives, ou via des coordonnées géographiques 🗺️ ? +* dont les communes sont référencées par leurs codes postaux et +leurs libellés 😮 ? +* dont les départements sont écrits en toutes lettres 🔡 ? +* dont les libellés sont douteux (torturant par exemple +le _Loire Loir-et-Cher_) ou obsolètes (mentionnant +par exemple _Templeuve_, une commune renommée en + _Templeuve-en-Pévèle_ depuis 2015) 🤦‍♂️ ? +* ou compilées au fil des années et dont les codes communes +constituent un patchwork de millésimes différents 🤯 ? + +**Alors `french-cities` est fait pour vous 🫵 !** + +## Contexte + +Ce projet est né des besoins de la +[DREAL Hauts-de-France](https://www.hauts-de-france.developpement-durable.gouv.fr/) +pour la consolidation de jeux de données (opendata ou internes) 📊. +Et curieusement, nous n'avons pas trouvé de solution sur étagère +répondant à ce besoin. + +Nous avons alors construit, amélioré et maintenu notre propre algorithme. +Aujourd'hui, une nouvelle étape a été franchie avec la publication et +l'ouverture 🔓 de cet algorithme dans un package python. + +Ce package s'appuie bien sûr sur des librairies déjà existantes. +Car si aucune réponse globale au problème n'a été trouvée, +de nombreuses solutions *partielles* ont été identifiées. La plus-value +de `french-cities` tient justement dans l'articulation de ces +solutions et à leur optimisation pour répondre aux cas d'usage. + +Par exemple, les API de l'INSEE permettent de réaliser des projections +d'un code commune vers un millésime donné. L'excellente implémentation +en python du package `pynsee` le permet effectivement. +Oui, mais voilà, pour +exécuter une projection, il faut connaître la date de départ, ce qui +est souvent une gageure 🕵️. En outre, la consommation des API de l'INSEE +est plafonnée à 30 requêtes par minute, ce qui pour un petit jeu +de données de 1000 lignes nous emmène déjà à plus de 30 minutes +d'exécution ⌛. + +D'un autre côté, l'API Base Adresse Nationale (BAN) a déjà été +intégrée dans le package `geopy`, au côté d'autres geocodeurs 🌍 comme +le moteur de recherche OpenStreetMap Nominatim. +Mais là aussi, l'exercice ne tient pas la charge d'un jeu de données +un tant soit peu volumineux : `geopy` exécute des requêtes individuelles +à la BAN (limitées à 50 appels par seconde) alors qu'elle dispose +d'un point d'entrée CSV. Pour un jeu de données conséquent de 100 000 lignes, +cela nous amène tout de même à 30 minutes de temps de traitement. +Quant à Nominatim, les conditions générales d'utilisation limitent +sa consommation au rythme de 30 requêtes par minute ⌛. + +En outre, si `pynsee` dispose d'un cache pour les jeux de données +nationaux, ce n'est pas totalement le cas pour les projections de millésime. +Ce n'est pas non plus le cas de `geopy` (alors même qu'il s'agit d'une +recommandation forte de Nominatim, même pour un usage raisonné). + +`french-cities` a quant à lui été optimisé pour travailler avec des +jeux de données volumineux (sans avoir la prétention d'être le plus +efficace pour de petits datasets)[^1]. A cet effet, il +travaille essentiellement à partir de Series ou DataFrames `pandas` 🐼. + +[^1]:A titre d'exemple, les utilisations publiées dans la présente + documentation s'appuient sur le traitement de jeux de données + entre 200 et 12000 lignes ; des tests sur des jeux de données à plus de + 1 million de lignes ont également été effectués. + +Les premières utilisations vous sembleront particulièrement lentes, +mais le système de mise en cache devrait permettre des résultats +plus rapides lors d'utilisation ultérieures. +En particulier, l'optimisation du cache permet à `french-cities` +d'être parfaitement adapté à une utilisation +dans des tâches automatisées 🕙. + +Nous vous invitons aussi à consulter [les documentations externes](./external_doc) +des sources utilisées. + +## Bugues + +Aucun package n'est parfait, et sûrement pas du premier coup. +Si vous détectez des bugues, vous êtes invités à nous les partager +sur [le guichet](https://github.com/tgrandje/french-cities/issues) +du projet. \ No newline at end of file From 2ac2eb0871ae58d1ab5d6270eaff60cd468ccf36 Mon Sep 17 00:00:00 2001 From: Thomas Grandjean Date: Sat, 27 Jul 2024 17:20:14 +0200 Subject: [PATCH 09/20] Update install.md --- docs/install.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/docs/install.md b/docs/install.md index 0b989fc..5cf9c72 100644 --- a/docs/install.md +++ b/docs/install.md @@ -9,18 +9,8 @@ nav_order: 2 # Installation du package -## Installation classique - `french-cities` est hébergé [sur pypi](https://pypi.org/project/french-cities/). Il peut donc être installé très classiquement à l'aide de la commande suivante : `pip install french-cities` - -## Installation avec dépendances optionnelles - -`pip install french-cities[full]` - -L'installation "full" permet d'installer `geopy` qui constitue une dépendance -optionnelle utilisable en dernier ressort pour la reconnaissance de communes. - From 974d09dc3cb365244ad41ec507525bb46958c3d9 Mon Sep 17 00:00:00 2001 From: Thomas Grandjean Date: Sat, 27 Jul 2024 17:21:06 +0200 Subject: [PATCH 10/20] Fix README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5fe539d..5e292fe 100644 --- a/README.md +++ b/README.md @@ -83,8 +83,8 @@ df = pd.DataFrame( "deps": ["59", "977", "2A"], } ) -df = find_departements(df, source="code_postal", alias="dep_A", type_code="postcode") -df = find_departements(df, source="code_commune", alias="dep_B", type_code="insee") +df = find_departements(df, source="code_postal", alias="dep_A", type_field="postcode") +df = find_departements(df, source="code_commune", alias="dep_B", type_field="insee") print(df) ``` From 1d54ed71b2bbed71b85ea51696e12ed7afad6403 Mon Sep 17 00:00:00 2001 From: Thomas Grandjean Date: Sat, 27 Jul 2024 17:23:29 +0200 Subject: [PATCH 11/20] Update README.md --- README.md | 32 +++++++------------------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 5e292fe..3e4acd0 100644 --- a/README.md +++ b/README.md @@ -54,8 +54,8 @@ variables for `pynsee` and `geopy`. ## Basic usage ### Retrieve departements' codes -`french-cities` can retrieve departement's codes from postal codes or official -(COG/INSEE) codes. +`french-cities` can retrieve departement's codes from postal codes, official +(COG/INSEE) codes or labels. Working from postal codes will make use of the BAN (Base Adresse Nationale) and should return correct results. The case of "Cedex" codes is only partially @@ -65,9 +65,9 @@ This consumes the freemium API and no authentication is included: the user of the present package should check the current API's legal terms directly on OpenDataSoft's website. -Working from official codes may give wrong results when working on an old -dataset and with cities which have changed of departments (which is rarely seen). -This is deliberate: it will use the first characters of the cities' codes +Working from official codes may sometime give empty results (when working on an old +dataset and with cities which have changed of departments, which is rarely seen). +This is deliberate: it will mostly use the first characters of the cities' codes (which is a fast process and 99% accurate) instead of using an API (which is lengthy though foolproof). @@ -85,30 +85,12 @@ df = pd.DataFrame( ) df = find_departements(df, source="code_postal", alias="dep_A", type_field="postcode") df = find_departements(df, source="code_commune", alias="dep_B", type_field="insee") +df = find_departements(df, source="communes", alias="dep_C", type_field="label") print(df) ``` -One can also work directly from departement's names, using -`find_departements_from_names` instead : - -``` -from french_cities import find_departements_from_names -import pandas as pd - -df = pd.DataFrame( - { - "deps": ["Corse sud", "Alpe de Haute-Provence", "Aisne", "Ain"], - } -) -df = find_departements_from_names(df, label="deps") - -print(df) -``` - -For a complete documentation on `find_departements` or -`find_departements_from_names`, please type `help(find_departements)` or -`help(find_departements_from_names)`. +For a complete documentation on `find_departements`, please type `help(find_departements)`. ### Retrieve cities' codes `french-cities` can retrieve cities' codes from multiple fields. It will work From ed949b6abb8ae568b66b1409f458755e92e2994e Mon Sep 17 00:00:00 2001 From: Thomas Grandjean Date: Sat, 27 Jul 2024 19:41:23 +0200 Subject: [PATCH 12/20] Add proxies to default session objects --- french_cities/city_finder.py | 4 ++++ french_cities/departement_finder.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/french_cities/city_finder.py b/french_cities/city_finder.py index c303b2c..7a6d026 100644 --- a/french_cities/city_finder.py +++ b/french_cities/city_finder.py @@ -297,6 +297,10 @@ def find_city( allowable_methods=("GET", "POST"), expire_after=timedelta(days=30), ) + proxies = {} + proxies["http"] = os.environ.get("http_proxy", None) + proxies["https"] = os.environ.get("https_proxy", None) + session.proxies.update(proxies) if len(necessary3 - columns) == 0 and not epsg: logger.warning( diff --git a/french_cities/departement_finder.py b/french_cities/departement_finder.py index f09c6e0..7bcf021 100644 --- a/french_cities/departement_finder.py +++ b/french_cities/departement_finder.py @@ -81,6 +81,10 @@ def _process_departements_from_postal( allowable_methods=("GET", "POST"), expire_after=timedelta(days=30), ) + proxies = {} + proxies["http"] = os.environ.get("http_proxy", None) + proxies["https"] = os.environ.get("https_proxy", None) + session.proxies.update(proxies) df["#CachedResult#"] = df[source].apply(cache_departments.get) From 6e141f6764d189a2fd7e839a2160975d6bbaae4b Mon Sep 17 00:00:00 2001 From: Thomas Grandjean Date: Sat, 27 Jul 2024 21:28:58 +0200 Subject: [PATCH 13/20] Set doc's usecases from github repo --- docs/use_cases/usecase_1.md | 177 +- docs/use_cases/usecase_1_notebook.html | 8755 ------------------------ docs/use_cases/usecase_2.md | 83 +- docs/use_cases/usecase_2_notebook.html | 8081 ---------------------- docs/use_cases/usecase_3.md | 72 +- docs/use_cases/usecase_3_notebook.html | 8304 ---------------------- docs/use_cases/usecase_4.md | 69 +- docs/use_cases/usecase_4_notebook.html | 8462 ----------------------- 8 files changed, 14 insertions(+), 33989 deletions(-) delete mode 100644 docs/use_cases/usecase_1_notebook.html delete mode 100644 docs/use_cases/usecase_2_notebook.html delete mode 100644 docs/use_cases/usecase_3_notebook.html delete mode 100644 docs/use_cases/usecase_4_notebook.html diff --git a/docs/use_cases/usecase_1.md b/docs/use_cases/usecase_1.md index 8c79b5c..2006d7e 100644 --- a/docs/use_cases/usecase_1.md +++ b/docs/use_cases/usecase_1.md @@ -9,9 +9,10 @@ nav_order: 1 --- # Cas d'usage + ## Combler des données communales manquantes à partir de libellés, codes postaux, adresses et coordonnées géographiques -Accéder au notebook ici. +Accéder au notebook ici. Dans cet exemple, il s'agit de compléter les données communales d'un jeu plutôt bien renseigné, mais incomplet. On s'intéresse à l'exemple des données @@ -26,177 +27,3 @@ Pour en savoir plus sur les installations classées, vous pouvez consulter * pandas * tqdm * french-cities - -### Constitution du jeu de données - -```python -import pandas as pd -from requests_cache import CachedSession -from tqdm import tqdm - -# ============================================================================= -# Récupération des ICPE de la région Hauts-de-France -# ============================================================================= -code_region = "32" -page_size = 1000 - -s = CachedSession() -r = s.get( - "https://georisques.gouv.fr/api/v1/installations_classees", - params={"page": "1", "page_size": page_size, "region": code_region}, -) -number_pages = r.json()["total_pages"] -for x in tqdm(range(number_pages), desc="querying georisques"): - try: - data - except NameError: - data = [] - else: - r = s.get( - "https://georisques.gouv.fr/api/v1/installations_classees", - params={ - "page": x + 1, - "page_size": page_size, - "region": code_region, - }, - ) - finally: - data += r.json()["data"] - if not r.json()["next"]: - break -data = pd.DataFrame(data) -``` - -### Analyse du jeu de données obtenu - -```python -print(data.shape) -print(data.head()) - -# ============================================================================= -# ICPE dépourvues de codes communes INSEE : -# ============================================================================= -print("-" * 50) -print("Codes INSEE manquants :") -print(data.codeInsee.isnull().value_counts()) -print("-" * 50) -``` - -A date du 30/05/2024, le jeu de données est constitué de 12117 lignes et -31 colonnes, dont 257 codes communes INSEE manquants. Pourtant, d'autres champs -sont disponibles et exploitables pour retrouver les communes manquantes : -* des champs adresse ; -* un libellé de commune ; -* des coordonnées géographiques et référentiel de projection ; -* un code postal. - - -### Complétion des données avec french-cities - -```python -from french_cities import find_city - -# ============================================================================= -# Configuration de l'API INSEE -# ============================================================================= -os.environ["insee_key"] = "********************" -os.environ["insee_secret"] = "********************" - -# ============================================================================= -# Utilisation de french-cities pour trouver les codes communes manquants -# ============================================================================= - -missing = data[data.codeInsee.isnull()].copy() - -# Au besoin, vérifier que le système de projection des coordonnées est en -# EPSG 2154 -# print(missing.systemeCoordonneesAIOT.unique()) - -# Concaténer les champs adresses : -cols = [f"adresse{x}" for x in range(1, 4)] -cols = [f"adresse{x}" for x in range(1, 4)] -addresses = ( - missing[cols[0]] - .str.cat(missing[cols[1:]], sep=" ", na_rep="") - .str.replace(" +", " ", regex=True) - .str.strip(" ") -) -missing["adresse"] = addresses - -# Recherche des communes manquantes à l'aide de french-cities -filled = find_city( - missing, - year="last", - x="coordonneeXAIOT", - y="coordonneeYAIOT", - epsg=2154, - city="commune", - address="adresse", - postcode="codePostal", - use_nominatim_backend=False, - field_output="newCodeInsee", -) - -# Réinjection les codes manquants dans le dataframe complet -data = data.join(filled[["newCodeInsee"]]) -data["codeInsee"] = data["codeInsee"].combine_first(data["newCodeInsee"]) -data = data.drop("newCodeInsee", axis=1) - -print("-" * 50) -print("Codes INSEE manquants après utilisation du package:") -print(data.codeInsee.isnull().value_counts()) -print("-" * 50) - - -print("Données toujours manquantes:") -print(data[data.codeInsee.isnull()]) -``` - -A date du 30/05/2024, une seule commune n'a pas été trouvée. -Effectivement, dans ce cas de figure, le lieu-dit (PONT DE BRIQUES) et la -commune (SAINT ETIENNE AU MONT) ont été inversés : ceci explique que le score -de la base adresse nationale n'ait pas été jugé suffisamment bon pour que le -résultat de Saint-Etienne-au-Mont puisse être retenu... - -Si cette fois, on décide d'utiliser l'API Nominatim en dernier recours, le code -devient : - -```python -# On isole la(es) ligne(s) manquante(s) -missing = data[data.codeInsee.isnull()].copy() - -# On concatène de nouveau les champs adresses : -cols = [f"adresse{x}" for x in range(1, 4)] -addresses = ( - missing[cols[0]] - .str.cat(missing[cols[1:]], sep=" ", na_rep="") - .str.replace(" +", " ", regex=True) - .str.strip(" ") -) -missing.loc[:, "adresse"] = addresses - -# Et on spécifie l'usage de Nominatim -missing = find_city( - missing, - year="last", - x="coordonneeXAIOT", - y="coordonneeYAIOT", - epsg=2154, - city="commune", - address="adresse", - postcode="codePostal", - use_nominatim_backend=True, - field_output="newCodeInsee", -) - -# Présentation des résultats -print(missing["newCodeInsee"]) -``` - -L'exécution de Nominatim ne conduit pas systématiquement au même résultat -(et même parfois ne produit pas de résultat). -Cela n'est pas totalement absurde, le hameau manquant étant à cheval sur -plusieurs communes. Les résultats fournis restent généralement pertinents. - - - diff --git a/docs/use_cases/usecase_1_notebook.html b/docs/use_cases/usecase_1_notebook.html deleted file mode 100644 index 2914bdc..0000000 --- a/docs/use_cases/usecase_1_notebook.html +++ /dev/null @@ -1,8755 +0,0 @@ - - - - - -usecase_1 - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - -
- - diff --git a/docs/use_cases/usecase_2.md b/docs/use_cases/usecase_2.md index b0e739a..c8c3d1b 100644 --- a/docs/use_cases/usecase_2.md +++ b/docs/use_cases/usecase_2.md @@ -11,7 +11,7 @@ nav_order: 2 # Cas d'usage ## Retrouver des codes communes à partir de codes postaux et libellés de communes -Accéder au notebook ici. +Accéder au notebook ici. La qualité de l'opendata français continue à s'améliorer d'année en année et ce cas d'usage devient aujourd'hui rare. @@ -25,84 +25,9 @@ plateforme des achats de l’Etat en 2015. Le jeu de données peut être retrouv ### Dépendances utilisées dans ce projet * openpyxl -* matplotlib (facultatif) +* numpy * pandas * requests-cache * french-cities - -### Constitution du jeu de données - -```python -import io -import numpy as np -import os -import pandas as pd -from requests_cache import CachedSession - -# ============================================================================= -# Récupération des marchés publics conclus -# ============================================================================= -url = ( - "https://static.data.gouv.fr/" - "resources/" - "marches-publics-conclus-recenses-sur-la-plateforme-des-achats-de-letat/" - "20160701-120733/Export_ETALAB_2015_complete.xlsx" -) -s = CachedSession() -r = s.get(url) -obj = io.BytesIO(r.content) -obj.seek(0) -df = pd.read_excel(obj) -for c in df.columns: - try: - df[c] = df[c].str.replace("^ *$", "", regex=True).replace("", None) - except AttributeError: - pass -df = df.dropna(how="all", axis=1) - -# Retraitement des codes postaux manifestement erronés -ix = df[ - ~df["Code Postal Attributaire"].fillna("").str.fullmatch("[0-9]{5}") -].index -df.loc[ix, "Code Postal Attributaire"] = np.nan -``` - -### Reconnaissance des codes communes avec french-cities - -```python -from french_cities import find_city - -# ============================================================================= -# Configuration de l'API INSEE -# ============================================================================= -os.environ["insee_key"] = "********************" -os.environ["insee_secret"] = "********************" - - -df = find_city( - df, - year="last", - x=False, - y=False, - epsg=False, - city="Ville", - dep=False, - address=False, - postcode="Code Postal Attributaire", - use_nominatim_backend=True, - field_output="codeInsee", -) - -print(df.codeInsee.isnull().value_counts()) - ->> codeInsee -False 11267 -True 567 -Name: count, dtype: int64 -``` - -### Tracer un graphe des principaux montants agrégés à la commune de l'attributaire -```python -df["Montant"] = pd.to_numeric(df["Montant"].str.replace(",", ".").str.replace(" ", "")), -df.groupby("codeInsee")["Montant"].sum().sort_values(ascending=False).head(10).plot(kind="bar") -``` \ No newline at end of file +* matplotlib (facultatif) +* seaborn (facultatif) \ No newline at end of file diff --git a/docs/use_cases/usecase_2_notebook.html b/docs/use_cases/usecase_2_notebook.html deleted file mode 100644 index b59a8a4..0000000 --- a/docs/use_cases/usecase_2_notebook.html +++ /dev/null @@ -1,8081 +0,0 @@ - - - - - -usecase_2 - - - - - - - - - - - - -
- - - - - - -
- - diff --git a/docs/use_cases/usecase_3.md b/docs/use_cases/usecase_3.md index 71ccb36..6c1818a 100644 --- a/docs/use_cases/usecase_3.md +++ b/docs/use_cases/usecase_3.md @@ -11,7 +11,7 @@ nav_order: 3 # Cas d'usage ## Retrouver des codes communes à partir de libellés communaux et départementaux -Accéder au notebook ici. +Accéder au notebook ici. Dans cet exemple, il s'agit de retrouver les communes ciblées par un arrêté de reconnaissance de l'état de @@ -34,71 +34,5 @@ choisi de rester le plus concis possible._ * pandas * lxml * french-cities - -### Constitution du jeu de données - -```python - -import os -import pandas as pd -from requests_cache import CachedSession - - -from french_cities import find_departements_from_names, find_city - -# ============================================================================= -# Configuration de l'API INSEE -# ============================================================================= -os.environ["insee_key"] = "********************" -os.environ["insee_secret"] = "********************" - -# ============================================================================= -# Récupération de l'arrêté "CATNAT" et chargement de l'annexe 1 -# ============================================================================= -url = "https://www.legifrance.gouv.fr/jorf/id/JORFTEXT000048393151" -s = CachedSession() -FIREFOX = ( - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) " - "Gecko/20100101 Firefox/126.0" -) -s.headers.update({"User-Agent": FIREFOX}) -r = s.get(url) -df = pd.read_html(r.content, encoding="utf8")[0] - -print(df) -``` - -Nous avons bien obtenu un dataframe avec (notamment) deux colonnes comprenant -des libellés communaux et départementaux en plein texte. - -### Complétion des données avec french-cities - -```python - -# ============================================================================= -# Reconnaissance des départements, puis des communes -# ============================================================================= -df = find_departements_from_names(df, label="Département", alias="DEP_CODE") -df = find_city( - df, - x=False, - y=False, - dep="DEP_CODE", - city="Commune", - address=False, - postcode=False, - session=s, -) -``` - -### Analyse du jeu de données reconstitué -```python - -# ============================================================================= -# Analyse des résultats -# ============================================================================= - -print(df[['DEP_CODE', 'insee_com']].info()) - -``` -Nous avons (normalement) bien reconnu les 205 communes ciblées par l'arrêté. \ No newline at end of file +* matplotlib (facultatif) +* geopandas (facultatif) diff --git a/docs/use_cases/usecase_3_notebook.html b/docs/use_cases/usecase_3_notebook.html deleted file mode 100644 index f793b84..0000000 --- a/docs/use_cases/usecase_3_notebook.html +++ /dev/null @@ -1,8304 +0,0 @@ - - - - - -usecase_3 - - - - - - - - - - - - -
- - - - - - - - - -
- - diff --git a/docs/use_cases/usecase_4.md b/docs/use_cases/usecase_4.md index c0ab0f1..7a131d2 100644 --- a/docs/use_cases/usecase_4.md +++ b/docs/use_cases/usecase_4.md @@ -12,7 +12,7 @@ nav_order: 4 ## Millésimer un jeu de données -Accéder au notebook ici. +Accéder au notebook ici. La qualité de l’opendata français continue à s’améliorer d’année en année et ce cas d’usage devient aujourd’hui rare (dans les jeux de données mis à @@ -31,66 +31,7 @@ Pour en savoir plus sur les sites pollués, le lecteur est invité à consulter [cette page](https://georisques.gouv.fr/consulter-les-dossiers-thematiques/pollutions-sols-sis-anciens-sites-industriels) sur le site georisques.gouv.fr. -### Constituer le jeu de données - -```python -import io -import os -import pandas as pd -from requests_cache import CachedSession - -s = CachedSession() - -url = "https://data.ademe.fr/data-fair/api/v1/datasets/srd-ademe/lines?size=10000&page=1&format=csv" - -r = s.get(url) -df = pd.read_csv(io.BytesIO(r.content), sep=",", dtype=str) -``` - -### Corriger le jeu de données - -Certains codes INSEE ont été mal formatés dans le fichier source : -malgré la précaution de spécifier un type "str", il manque des 0 en tête de -code commune pour les neuf premiers départements français. - -```python -ix = df[df.Code_INSEE.str.len() == 4].index -print(df.loc[ix, "Code_INSEE"]) -df["Code_INSEE"] = df["Code_INSEE"].str.zfill(5) -print(df.loc[ix, "Code_INSEE"]) -``` - -### Millésimer le jeu de données - -```python - -from french_cities import set_vintage - -# ============================================================================= -# Configuration de l'API INSEE -# ============================================================================= -os.environ["insee_key"] = "********************" -os.environ["insee_secret"] = "********************" - -# ============================================================================= -# Stocker les codes initiaux pour effectuer une comparaison ultérieure -# ============================================================================= -init = df["Code_INSEE"].copy() -init.name = "initial" - -# ============================================================================= -# Utiliser french-cities -# ============================================================================= -df = set_vintage(df, 2024, "Code_INSEE") - -# ============================================================================= -# Comparer les résultats aux données initiales -# ============================================================================= -new = df["Code_INSEE"].copy() -new.name = "final" - -print((init == new).all()) -ix = init[init != new].index - -print(init.to_frame().join(new).loc[ix]) -``` +### Dépendances utilisées dans ce projet +* requests-cache +* pandas +* french-cities diff --git a/docs/use_cases/usecase_4_notebook.html b/docs/use_cases/usecase_4_notebook.html deleted file mode 100644 index 64faf9c..0000000 --- a/docs/use_cases/usecase_4_notebook.html +++ /dev/null @@ -1,8462 +0,0 @@ - - - - - -usecase_4 - - - - - - - - - - - - -
- - - - - - - - - - - - - - -
- - From 83e8231d3a484050da9539e72c8039c085f70e22 Mon Sep 17 00:00:00 2001 From: Thomas Grandjean Date: Sat, 27 Jul 2024 21:29:08 +0200 Subject: [PATCH 14/20] Update config.md --- docs/config.md | 46 ++++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/docs/config.md b/docs/config.md index b3d6faa..262f92b 100644 --- a/docs/config.md +++ b/docs/config.md @@ -11,27 +11,41 @@ nav_order: 3 ## Ajout des clefs API INSEE `french-cities` utilise `pynsee`, qui nécessite des cles API INSEE pour être -fonctionnel. Jusqu'à quatre clefs peuvent être spécifiées à l'aide de variables -d'environnement : +fonctionnel. Deux variables d'environnement doivent impérativement être spécifiées : * insee_key * insee_secret -* http_proxy (le cas échéant, pour accès web derrière un proxy professionnel) -* https_proxy (le cas échéant, pour accès web derrière un proxy professionnel) Merci de se référer à [la documentation de `pynsee`](https://pynsee.readthedocs.io/en/latest/api_subscription.html) pour plus d'information sur les clefs API et la configuration. -A noter que la configuration des proxy par variable d'environnement sera -fonctionnelle pour à la fois pynsee et geopy. - -## Gestion des sessions web -`pynsee` et `geopy` utilisent leurs propres gestionnaires de session web. - -Ainsi, les objets `Session` passés en argument à french-cities ne seront -**PAS** partagés avec `pynsee` ou `geopy`. - -Cela explique la possibilité de passer une session en argument alors même que -des proxy professionnels peuvent être spécifiés par variables d'environnement -pour `pynsee` et `geopy`). \ No newline at end of file +Pour mémoire, il est tout à fait possible de fixer des variables d'environnement +depuis un environnement python, à l'aide des instructions suivantes : + +```python +import os +os.environ["insee_key"] = "ma-clef-applicative" +os.environ["insee_secret"] = "ma-clef-secrete" +``` +## Configuration des proxies + +Les requêtes web fournies `french-cities` sont de trois types : +* celles générées par `pynsee`, interrogeant les API INSEE ; +* celles générées par `geopy`, interrogeant l'API Nominatim ; +* celles générées en propre par `french-cities` pour interroger l'API de la +Base Adresse Nationale et l'API Base officielle des codes postaux. + +Dans le cas où l'on souhaiterait utiliser des proxies professionnels +pour connexion internet, il suffit de fixer deux variables d'environnement +supplémentaires : + +* http_proxy +* https_proxy + +Néanmoins, si l'utilisateur souhaite configurer son propre objet session +et le fournir en argument optionnel à `french-cities`, il lui revient : +* de fixer par lui-même le(s) proxy(ies) attachés à sa session ; +* de continuer à fixer les variables d'environnement `https_proxy` et +`http_proxy` (utilisées en propre par `pynsee` et `geopy` qui utilisent +leurs propres objets session). From 072eee9eb6c2007f84a3caf45bf9d34e9fb3dfb3 Mon Sep 17 00:00:00 2001 From: Thomas Grandjean Date: Sat, 27 Jul 2024 21:29:13 +0200 Subject: [PATCH 15/20] Update external_doc.md --- docs/external_doc.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/external_doc.md b/docs/external_doc.md index 6995664..d4a3bc3 100644 --- a/docs/external_doc.md +++ b/docs/external_doc.md @@ -10,6 +10,7 @@ nav_order: 7 `french-cities` utilise plusieurs API externes. N'hésitez pas à consulter : -* la [documentation](https://adresse.data.gouv.fr/api-doc/adresse) (en Français) de l'API Adresse ; -* la [documentation](https://public.opendatasoft.com/explore/dataset/correspondance-code-cedex-code-insee/api/?flg=fr&q=code%3D68013&lang=fr) (en Français) de l'API OpenDataSoft ; +* la [documentation](https://adresse.data.gouv.fr/api-doc/adresse) de l'API Adresse ; +* la [documentation](https://public.opendatasoft.com/explore/dataset/correspondance-code-cedex-code-insee/api/?flg=fr&q=code%3D68013&lang=fr) de l'API OpenDataSoft ; +* la [documentation](https://datanova.laposte.fr/datasets/laposte-hexasmal) de la base officielle des codes postaux ; * la [politique d'usage](https://operations.osmfoundation.org/policies/nominatim/) de Nominatim. From b79fbdc355a4e409349c9a51ff5f70195a562227 Mon Sep 17 00:00:00 2001 From: Thomas Grandjean Date: Sat, 27 Jul 2024 21:29:21 +0200 Subject: [PATCH 16/20] Update search_departements.md --- docs/search_departements.md | 221 +++++++++++++++++++++++------------- 1 file changed, 142 insertions(+), 79 deletions(-) diff --git a/docs/search_departements.md b/docs/search_departements.md index 8e6d495..6dfbb9e 100644 --- a/docs/search_departements.md +++ b/docs/search_departements.md @@ -6,64 +6,85 @@ handle: /search_departements nav_order: 4 --- -# Reconnaissance des départements +# Reconnaissance des départements: `find_departements` -## A partir de codes communes ou codes postaux : `find_departements` +`french-cities` peut retrouver un code département à partir de codes postaux, +de codes communes officiels (COG/INSEE) ou de libellés en toutes lettres. -`french-cities` peut retrouver un code département à partir de codes postaux ou -de codes communes officiels (COG/INSEE). +## A partir des codes postaux -Travailler à partir de codes postaux entraînera l'utilisation de la BAN (Base -Adresse Nationale) et devrait fournir des résultats corrects. Le cas des codes -Cedex n'étant que partiellement géré par la BAN, un appel est fait dans un -second temps à l'[API d'OpenDataSoft](https://public.opendatasoft.com/explore/dataset/correspondance-code-cedex-code-insee/api/?flg=fr&q=code%3D68013&lang=fr) -construite sur la base des [travaux de Christian Quest](https://public.opendatasoft.com/explore/dataset/correspondance-code-cedex-code-insee/information/?flg=fr&q=code%3D68013&lang=fr). -Cette utilisation s'appuie sur un accès freemium non authentifié; l'utilisateur -du package est invité à contrôler les conditions générales d'utilisation de l'API auprès du -fournisseur. +Travailler à partir de codes postaux entraînera l'utilisation (par ordre de priorité) : +* de l'API [base officielle des codes postaux](https://datanova.laposte.fr/datasets/laposte-hexasmal) ; +* de l'API [BAN](https://adresse.data.gouv.fr/api-doc/adresse#csv-search) (Base Adresse Nationale) ; +* de l'API d'[OpenDataSoft](https://public.opendatasoft.com/explore/dataset/correspondance-code-cedex-code-insee/api/?flg=fr&q=code%3D68013&lang=fr) sur la base des [travaux de Christian Quest](https://public.opendatasoft.com/explore/dataset/correspondance-code-cedex-code-insee/information/?flg=fr&q=code%3D68013&lang=fr).. + +La première étape permet de faire matcher des codes postaux avec l'intégralité +de la base nationale officielle. + +La seconde étape permet d'exploiter les algorithmes de la BAN en cas d'échec +sur la base nationale officielle (qui n'est mise à jour que 2 fois par an uniquement). + +Le cas des codes Cedex n'est que partiellement géré par la BAN et la +troisième étape permet d'exploiter les travaux de Christian Quest sur ce sujet. +Il est à noter que le jeu de données n'a pas fait l'objet d'une mise à jour depuis mars 2017. + +En dernier recours, une tentative est effectuée à partir des deux (ou trois pour les DOM) +premiers caractères du code postal non reconnu. Si celui-ci correspond à un département +effectif, il sera retenu. + +**⚠️ Attention :** il n'y a pas unicité entre un code postal donné et les départements déduits. +Par exemple, le code postal 13780 est rattaché à des communes des départements 13 **et** 83. +Dans ce cas, il est possible de spécifier le comportement de `find_departements` à l'aide de +l'argument `authorize_duplicates` : +* si `authorize_duplicates=True`, les résultats seront dupliqués ; +* si `authorize_duplicates=False` (valeur par défaut) les potentiels doublons seront éliminés, +et aucun résultat ne sera fourni. + +Exemple d'utilisation : +```python +from french_cities import find_departements +import pandas as pd + +df = pd.DataFrame( + { + "code_postal": ["59800", "97133", "20000"], + "code_commune": ["59350", "97701", "2A004"], + "communes": ["Lille", "Saint-Barthélemy", "Ajaccio"], + "deps": ["59", "977", "2A"], + "deps_labels": ["Nord", "Saint-Barthélemy", "Corse du Sud"], + } +) +find_departements( + df, + source="code_postal", + alias="dep_A", + type_field="postcode", +) + +>>> + code_postal code_commune communes deps deps_labels dep_A +0 59800 59350 Lille 59 Nord 59 +1 97133 97701 Saint-Barthélemy 977 Saint-Barthélemy 977 +2 20000 2A004 Ajaccio 2A Corse du Sud 2A +``` + +## A partir des codes communes Travailler à partir de codes communes officiels peut entraîner des résultats -erronés pour des données anciennes, dans le cas de communes ayant changé de -département (ce qui est relativement rare). +erronés (absence de résultats) pour des données anciennes, dans le cas de +communes ayant changé de département (ce qui est relativement rare). + Ce choix est délibéré : seuls les premiers caractères des codes commune sont utilisés pour la reconnaissance du département (algorithme rapide et qui donne des résultats corrects pour 99% des cas), par opposition à un requêtage systématique aux API (processus sans erreur mais long). -### Docstring de la fonction `find_departements` -``` -find_departements(df: pandas.core.frame.DataFrame, source: str, alias: str, type_code: str, session: requests.sessions.Session = None) -> pandas.core.frame.DataFrame - Compute departement's codes from postal or official codes (ie. INSEE COG)' - Adds the result as a new column to dataframe under the label 'alias'. - - Parameters - ---------- - df : pd.DataFrame - DataFrame containing official cities codes - source : str - Field containing the post or official codes - alias : str - Column to store the departements' codes unto - type_code : str - Type of codes passed under `alias` label. Should be either 'insee' for - official codes or 'postcode' for postal codes. - session : Session, optional - Web session. The default is None (and will use a CachedSession with - 30 days expiration) - - Raises - ------ - ValueError - If type_code not among "postcode", "insee". - - Returns - ------- - df : pd.DataFrame - Updated DataFrame with departement's codes -``` - -### Exemples d'utilisation basiques +Il est néanmoins possible de limiter ce comportement en effectuant au préalable +une projection des codes dans un millésime donné en activant l'argument +`do_set_vintage=True` ; cette opération ralentira considérablement le calcul +des codes département. +Exemple d'utilisation : ```python from french_cities import find_departements import pandas as pd @@ -74,52 +95,94 @@ df = pd.DataFrame( "code_commune": ["59350", "97701", "2A004"], "communes": ["Lille", "Saint-Barthélémy", "Ajaccio"], "deps": ["59", "977", "2A"], + "deps_labels": ["Nord", "Saint-Barthélemy", "Corse du Sud"], } ) -df = find_departements(df, source="code_postal", alias="dep_A", type_code="postcode") -df = find_departements(df, source="code_commune", alias="dep_B", type_code="insee") - -print(df) +df = find_departements( + df, + source="code_commune", + alias="dep_B", + type_field="insee", + do_set_vintage=True, +) ``` -## A partir de noms de départements : `find_departements_from_names` +## A partir des libellés communaux + +Dans ce cas, `french-cities` exécute un simple fuzzy-matching assorti d'un filtre +sur score minimal. -On peut également travailler directement à partir des noms de départements, -en utilisant à la place la fonction `find_departements_from_names`. +Exemple d'utilisation : +```python +from french_cities import find_departements +import pandas as pd + +df = pd.DataFrame( + { + "code_postal": ["59800", "97133", "20000"], + "code_commune": ["59350", "97701", "2A004"], + "communes": ["Lille", "Saint-Barthélémy", "Ajaccio"], + "deps": ["59", "977", "2A"], + "deps_labels": ["Nord", "Saint-Barthélemy", "Corse du Sud"], + } +) +df = find_departements( + df, + source="deps_labels", + alias="dep_C", + type_field="label", +) +``` -### Docstring de la fonction `find_departements_from_names` +## Docstring de la fonction `find_departements` ``` -find_departements_from_names(df: pandas.core.frame.DataFrame, label: str, alias: str = 'DEP_CODE') -> pandas.core.frame.DataFrame - Retrieve departement's codes from their names. +find_departements( + df: pandas.DataFrame, + source: str, + alias: str, + type_field: str, + session: requests.Session = None, + authorize_duplicates: bool = False, + do_set_vintage: bool = True +) -> pandas.DataFrame: + + Compute departement's codes from postal, official codes (ie. INSEE COG) + or labels in full text. + Adds the result as a new column to dataframe under the label 'alias'. Parameters ---------- df : pd.DataFrame - DataFrame containing official departement's names - label : str - Field containing the label of the departements - alias : str, optional - Column to store the departements' codes unto. - Default is "DEP_CODE" + DataFrame containing official cities codes + source : str + Field containing the post codes, official codes or labels + alias : str + Column to store the departements' codes unto + type_field : str + Type of codes passed under `alias` label. Should be either 'insee' for + official codes, 'postcode' for postal codes or 'label' for labels. + session : Session, optional + Web session. The default is None (and will use a CachedSession with + 30 days expiration) + authorize_duplicates : bool, optional + If True, authorize duplication of results when multiple results are + acceptable for a given postcode (for instance, 13780 can result to + either 13 or 83 ). If False, duplicates will be removed, hence no + result will be available. False by default. + do_set_vintage : bool, optional + If True, set a vintage projection for df. If False, don't bother (will + be faster). Should be False when df was already computed from a given + function out of pynsee or french-cities. Should be True when used on + almost any other dataset. + The default is True. + + Raises + ------ + ValueError + If type_field not among "postcode", "insee", "labels". Returns ------- df : pd.DataFrame Updated DataFrame with departement's codes ``` - -### Exemple d'utilisation basique - -```python -from french_cities import find_departements_from_names -import pandas as pd - -df = pd.DataFrame( - { - "deps": ["Corse sud", "Alpe de Haute-Provence", "Aisne", "Ain"], - } -) -df = find_departements_from_names(df, label="deps") - -print(df) -``` \ No newline at end of file From 7c896ab13f0314199210b24fcd7fd7cc5c2a7a86 Mon Sep 17 00:00:00 2001 From: Thomas Grandjean Date: Sat, 27 Jul 2024 21:29:31 +0200 Subject: [PATCH 17/20] Update project_cities_to_year.md --- docs/project_cities_to_year.md | 66 ++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/docs/project_cities_to_year.md b/docs/project_cities_to_year.md index a04d7f2..717f796 100644 --- a/docs/project_cities_to_year.md +++ b/docs/project_cities_to_year.md @@ -15,16 +15,16 @@ la date initiale demeurant inconnue (voire inexistante, les cas de fichiers Des erreurs peuvent survenir, notamment pour les communes restaurées (dans la mesure où la date initiale de la donnée est inconnue ou inexistante). -Dans le cas où la date des données est connue, il peut être pertinent d'utiliser -l'API de projection mise à disposition par l'INSEE et accessible au travers de -`pynsee`. Il convient de noter que cette utilisation peut être lente, dans la +Dans le cas où la date des données est connue, il peut être préférable d'utiliser +l'API de projection mise à disposition par l'INSEE et [accessible au travers de +`pynsee`](https://pynsee.readthedocs.io/en/latest/get_data.html#pynsee.localdata.get_new_city). + Il convient de noter que cette utilisation peut être lente, dans la mesure ou chaque commune devra être testée via l'API (qui n'autorise que 30 requêtes par minute). En substance, l'algorithme de `french-cities` contrôle si le code commune existe dans le millésime souhaité : -* s'il existe il sera conservé (à l'approximation précédente près qui peut donc -impacter les communes restaurées) ; +* s'il existe il sera conservé (modulo un risque d'erreur pour les communes restaurées) ; * s'il n'existe pas, le code est recherché dans des millésimes antérieurs (et l'API de projection de l'INSEE sera mobilisée de manière ciblée). @@ -34,28 +34,7 @@ leurs communes de rattachement; * convertir les codes des communes associées et déléguées en celui de leurs communes de rattachement. -### Docstring de la fonction `set_vintage` -``` -set_vintage(df: pandas.core.frame.DataFrame, year: int, field: str) -> pandas.core.frame.DataFrame - Project (approximatively) the cities codes of a dataframe into a desired - vintage. - - Parameters - ---------- - df : pd.DataFrame - DataFrame containing city codes - year : int - Year to project the dataframe's city codes into - field : str - Field (column) of dataframe containing the city code - - Returns - ------- - pd.DataFrame - Projected DataFrame -``` - -### Exemple d'utilisation basique +Exemple d'utilisation: ```python from french_cities import set_vintage import pandas as pd @@ -77,3 +56,36 @@ df = set_vintage(df, 2023, field="A") print(df) ``` +## Docstring de la fonction `set_vintage` +``` +set_vintage( + df: pandas.DataFrame, + year: int, + field: str, +) -> pandas.DataFrame: + + Project (approximatively) the cities codes of a dataframe into a desired + vintage. + + Note that this may **NOT** work for cities which used to whole, then + merged to another and finally reset as a whole city; + the algorithm has 50% chances of setting wrong results for any dataset of + an initial vintage set during this transition period (this should be rare + enough). + + In case of failure, the projected city code will be set to None. + + Parameters + ---------- + df : pd.DataFrame + DataFrame containing city codes + year : int + Year to project the dataframe's city codes into + field : str + Field (column) of dataframe containing the city code + + Returns + ------- + pd.DataFrame + Projected DataFrame +``` \ No newline at end of file From 6143565340ef119b8404b91635fa0c40f25b030a Mon Sep 17 00:00:00 2001 From: Thomas Grandjean Date: Sat, 27 Jul 2024 21:51:08 +0200 Subject: [PATCH 18/20] Update project_cities_to_year.md --- docs/project_cities_to_year.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/project_cities_to_year.md b/docs/project_cities_to_year.md index 717f796..ab0b062 100644 --- a/docs/project_cities_to_year.md +++ b/docs/project_cities_to_year.md @@ -6,7 +6,7 @@ handle: /project_cities_to_year nav_order: 6 --- -# Projection de codes communes dans un millésime donné +# Projection de codes communes dans un millésime donné : `set_vintage` `french-cities` peut tenter de "projeter" un dataframe dans un millésime donné, la date initiale demeurant inconnue (voire inexistante, les cas de fichiers From 447009ee922f8d1a1682c81a64e44c4a416a32f1 Mon Sep 17 00:00:00 2001 From: Thomas Grandjean Date: Sat, 27 Jul 2024 21:51:15 +0200 Subject: [PATCH 19/20] Update search_cities.md --- docs/search_cities.md | 162 ++++++++++++++++++++++++++---------------- 1 file changed, 99 insertions(+), 63 deletions(-) diff --git a/docs/search_cities.md b/docs/search_cities.md index 9da6426..1ddf6a2 100644 --- a/docs/search_cities.md +++ b/docs/search_cities.md @@ -6,7 +6,7 @@ handle: /search_cities nav_order: 5 --- -# Reconnaissance des communes +# Reconnaissance des communes : `find_city` `french-cities` peut retrouver un code commune à partir de champs multiples. Il est capable de détecter certaines erreurs simples dans les champs (jusqu'à @@ -19,21 +19,113 @@ de priorité) : * 'address', 'postcode' et 'city' * 'department' et 'city' -Il est à noter que l'algorithme peu faire être source d'erreur dès lors que +💡 Nota : pour désactiver l'utilisation d'un champ (ou signaler son absence), il +convient de fournir la valeur `False` aux arguments de la fonction `find_city`. + +L'algorithme peut être source d'erreur dès lors que la jointure spatiale (coordonnées x & y) sera sollicitée sur un millésime ancien. Les communes impactées sont les communes restaurées ("scission"), le flux de données spatialisées du COG servi par `pynsee` n'étant pas millésimé à ce jour. -La reconnaissance syntaxique (champs postcode, city, address, departement) est -basée sur un fuzzy matching en langage python, l'API BAN (base adresse nationale), -ou l'API Nominatim d'OSM (si activé). -L'algorithme ne conservera pas les résultats insuffisamment fiables, mais des +Les reconnaissances exécutées à partir des autres champs sont toutes syntaxiques : +elles utilisent des techniques de fuzzy-matching internes ou externe (BAN par exemple). +L'algorithme tâchera d'éliminer les résultats insuffisamment fiables, mais des erreurs peuvent bien sûr subsister. +Les étapes de l'algorithme sont les suivantes, par ordre de priorité : +* reconnaissance par jointure spatiale (coordonnées SIG et code EPSG requis) +* ajout d'une reconnaissance du département si non fourni initialement (à partir des codes postaux) +* reconnaissance par fuzzy-matching : + * département par département si disponible (pour éviter les homonymes) + * sur la France entière sinon +* si code postal disponible : + * géocodage BAN CSV via code postal + nom de commune + * géocodage BAN individuel restreint aux communes +* si adresse et code postal disponibles, géocodage BAN CSV +* si département disponible : + * géocodage BAN CSV via code département + nom de commune + * géocodage BAN individuel restreint aux communes +* si recherche Nominatim activée : + * si code postal disponible : géocodage Nominatim individuel (code postal + nom de commune) suivi d'une jointure spatiale + * si département disponible : géocodage Nominatim individuel (département + nom de commune) suivi d'une jointure spatiale + +💡 Nota : au vu de l'algorithme, toute erreur de département (éventuellement sur le code postal fourni) +risque fortement d'entraîner une absence de résultats. Ce comportement est considéré comme normal et ne +pourra être résorbé que si la commune visée n'a aucun homonyme sur la France. + +Exemple d'utilisation : +```python + +from french_cities import find_city +import pandas as pd + +df = pd.DataFrame( + [ + { + "x": 2.294694, + "y": 48.858093, + "location": "Tour Eiffel", + "dep": "75", + "city": "Paris", + "address": "5 Avenue Anatole France", + "postcode": "75007", + "target": "75056", + }, + { + "x": 8.738962, + "y": 41.919216, + "location": "mairie", + "dep": "2A", + "city": "Ajaccio", + "address": "Antoine Sérafini", + "postcode": "20000", + "target": "2A004", + }, + { + "x": -52.334990, + "y": 4.938194, + "location": "mairie", + "dep": "973", + "city": "Cayenne", + "address": "1 rue de Rémire", + "postcode": "97300", + "target": "97302", + }, + { + "x": np.nan, + "y": np.nan, + "location": "Erreur code postal Lille/Lyon", + "dep": "59", + "city": "Lille", + "address": "1 rue Faidherbe", + "postcode": "69000", + "target": "59350", + }, + ] +) +df = find_city(df, epsg=4326) + +print(df) +``` + ### Docstring de la fonction `find_city` ``` -find_city(df: pandas.core.frame.DataFrame, year: str = 'last', x: Union[str, bool] = 'x', y: Union[str, bool] = 'y', dep: Union[str, bool] = 'dep', city: Union[str, bool] = 'city', address: Union[str, bool] = 'address', postcode: Union[str, bool] = 'postcode', field_output: str = 'insee_com', epsg: int = None, session: requests.sessions.Session = None, use_nominatim_backend: bool = False) -> pandas.core.frame.DataFrame +find_city( + df: pandas.DataFrame, + year: str = 'last', + x: Union[str, bool] = 'x', + y: Union[str, bool] = 'y', + dep: Union[str, bool] = 'dep', + city: Union[str, bool] = 'city', + address: Union[str, bool] = 'address', + postcode: Union[str, bool] = 'postcode', + field_output: str = 'insee_com', + epsg: int = None, + session: requests.Session = None, + use_nominatim_backend: bool = False +) -> pandas.DataFrame: + Find cities in a dataframe using multiple methods (either based on valid geolocation or lexical fields). @@ -114,59 +206,3 @@ find_city(df: pandas.core.frame.DataFrame, year: str = 'last', x: Union[str, boo Initial dataframe with a new column containing cities' codes (labelled according to `field_output` value.) ``` - -### Exemple d'utilisation basique - -```python - -from french_cities import find_city -import pandas as pd - -df = pd.DataFrame( - [ - { - "x": 2.294694, - "y": 48.858093, - "location": "Tour Eiffel", - "dep": "75", - "city": "Paris", - "address": "5 Avenue Anatole France", - "postcode": "75007", - "target": "75056", - }, - { - "x": 8.738962, - "y": 41.919216, - "location": "mairie", - "dep": "2A", - "city": "Ajaccio", - "address": "Antoine Sérafini", - "postcode": "20000", - "target": "2A004", - }, - { - "x": -52.334990, - "y": 4.938194, - "location": "mairie", - "dep": "973", - "city": "Cayenne", - "address": "1 rue de Rémire", - "postcode": "97300", - "target": "97302", - }, - { - "x": np.nan, - "y": np.nan, - "location": "Erreur code postal Lille/Lyon", - "dep": "59", - "city": "Lille", - "address": "1 rue Faidherbe", - "postcode": "69000", - "target": "59350", - }, - ] -) -df = find_city(df, epsg=4326) - -print(df) -``` \ No newline at end of file From 25803a125112578b1de5e7221e9f24246635279b Mon Sep 17 00:00:00 2001 From: Thomas Grandjean Date: Sat, 27 Jul 2024 21:53:59 +0200 Subject: [PATCH 20/20] Fix workflow --- .github/workflows/publish.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8413dd8..bd2af37 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -43,7 +43,7 @@ jobs: #---------------------------------------------- - name: Install dependencies if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' - run: poetry install --no-interaction --without dev --extras "full" + run: poetry install --no-interaction --without dev #---------------------------------------------- # add only pytest for tests (not the full dev dependencies to avoid installing spyder) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 05091ce..e327c07 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -45,7 +45,7 @@ jobs: #---------------------------------------------- - name: Install dependencies if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' - run: poetry install --no-interaction --without dev --extras "full" + run: poetry install --no-interaction --without dev #---------------------------------------------- # add only pytest for tests (not the full dev dependencies to avoid installing spyder)