Approfondissement ElasticSearch pour des recherches de proximité géographique

Un chapitre plus approfondi sur ElasticSearch

Tutoriel
Avancé
Auteur·rice

Lino Galiana

Date de publication

2024-12-07

Pour essayer les exemples présents dans ce tutoriel :

html`<div>${getConditionalHTML(path, true)}</div>`
function getConditionalHTML(path, print) {
    if (print === false) return ``
    if (isBetweenSeptAndDec()) {
        return md`<i>La correction sera visible prochainement sur cette page. En attendant, la liste des corrections déjà acccessibles est [ici](/content/annexes/corrections.html)</i>`; // Return an empty string if not between the dates
    } else {
        return html`
        <details>
            <summary>
                Pour ouvrir la version corrigée sous forme de <i>notebook</i>
            </summary>
            ${printBadges({ fpath: path, correction: true })}
        </details>
        `;
    }
}
function renderCorrection({ fpath, correction }) {
    if (correction) {
        return html`${printBadges({ fpath: fpath, correction: true })}`;
    } else {
        return html`<i>La correction sera visible prochainement sur cette page.</i>`;
    }
}

Ce chapitre est issu du travail produit dans le cadre d’un hackathon de l’Insee avec Raphaële Adjerad et présente quelques éléments qui peuvent être utiles pour l’enrichissement de données d’entreprises à partir d’un répertoire officiel.

:warning: Il nécessite une version particulière du package elasticsearch pour tenir compte de l’héritage de la version 7 du moteur Elastic. Pour cela, faire

pip install elasticsearch==8.2.0

1 Introduction

Dans le cadre particulier de l’identification des entreprises, Elasticsearch fait partie de la solution retenue par l’API “Sirene données ouvertes” (DINUM) (cf https://annuaire-entreprises.data.gouv.fr/) l’API de recherche d’entreprises Française de la Fabrique numérique des Ministères Sociaux (cf https://api.recherche-entreprises.fabrique.social.gouv.fr/)le projet de l’Insee “Amélioration de l’identification de l’employeur dans le recensement”, pour faire une première sélection des établissements pertinents pour un bulletin de recensement donné. Dans le cadre de l’identification des individus, Elasticsearch fait partie de la solution envisagée pour l’identification des individus au RNIPP (Répertoire national des personnes physiques) pour le projet CSNS (Code statistique non signifiant), et est la solution technique sous-jacente au projet matchID du ministère de l’intérieur.

Au delà du secteur public, on peut citer qu’Amazon AWS fait partie des utilisateurs historiques d’Elasticsearch.

1.1 Objectif

Ce chapitre vise à approfondir les éléments présentés sur Elastic précédemment. L’idée est de se placer dans un contexte opérationnel où on reçoit des informations sur des entreprises telles que l’adresse et la localisation et qu’on désire associer à des données administratives considérées plus fliables.

1.2 Réplication de ce chapitre

Comme le précédent, ce chapitre est plus exigeant en termes d’infrastructures que les précédents. Il nécessite un serveur Elastic. Les utilisateurs du SSP Cloud pourront répliquer les exemples de ce cours car cette technologie est disponible (que ce soit pour indexer une base ou pour requêter une base existante).

La première partie de ce tutoriel, qui consiste à créer une base Sirene géolocalisée à partir des données open-data ne nécessite pas d’architecture particulière et peut ainsi être exécutée en utilisant les packages suivants :

import numpy as np
import pandas as pd

1.3 Sources

Ce chapitre va utiliser plusieurs sources de diffusion de l’Insee:

Les données à siretiser sont celles du registre Français des émissions polluantes établi par le Ministère de la Transition Energétique. Le jeu de données est disponible sur data.gouv

2 Préparation des données à identifier

Le jeu de données présente déjà l’identifiant de l’établissement, dit numéro siret. Nous allons faire comme si nous étions en amont de cet appariement et que nous désirons trouver ce numéro. La présence dans la base de ce numéro nous permettra d’évaluer la qualité de notre méthode de recherche avec Elastic.

import requests
import zipfile
import pandas as pd

url = "https://www.data.gouv.fr/fr/datasets/r/9af639b9-e2b6-4d7d-8c5f-0c4953c48663"
req = requests.get(url)

with open("irep.zip", "wb") as f:
    f.write(req.content)

with zipfile.ZipFile("irep.zip", "r") as zip_ref:
    zip_ref.extractall("irep")

etablissements = pd.read_csv("irep/2019/etablissements.csv", sep=";")

3 Constitution du référentiel administratif géolocalisé

Dans un premier temps, on va combiner ensemble les différentes sources open-data pour créer un référentiel fiable d’entreprises géolocalisées.

3.1 Importer la base déjà créée

Les données à utiliser pour constuire une base Sirene géolocalisée sont trop volumineuses pour les serveurs mis à disposition gratuitement par Github pour la compilation de ce site web. Nous proposons ainsi une version déjà construite, stockée dans l’espace de mise à disposition du SSP Cloud. Ce fichier est au format parquet et est ouvert à tous, même pour les personnes ne disposant pas d’un compte. Le code ayant construit cette base est présenté ci-dessous.

Pour importer cette base, on utilise les fonctionalités de pyarrow qui permettent d’importer un fichier sur un système de stockage cloud comme s’il était présent sur le disque :

from pyarrow import fs
import pyarrow as pa
import pyarrow.parquet as pq

bucket = "lgaliana"
path = "diffusion/sirene_geolocalized.parquet"

s3 = fs.S3FileSystem(endpoint_override="http://" + "minio.lab.sspcloud.fr")

df_geolocalized = (
    pq.ParquetDataset(f"{bucket}/{path}", filesystem=s3).read_pandas().to_pandas()
)
df_geolocalized.head(3)

3.2 Reproduire la construction de la base

La première base d’entrée à utiliser est disponible sur data.gouv

import requests
import zipfile

url_download = (
    "https://www.data.gouv.fr/fr/datasets/r/0651fb76-bcf3-4f6a-a38d-bc04fa708576"
)
req = requests.get(url_download)

with open("sirene.zip", "wb") as f:
    f.write(req.content)

with zipfile.ZipFile("sirene.zip", "r") as zip_ref:
    zip_ref.extractall("sirene")

On va importer seulement les colonnes utiles et simplifier la structure pour être en mesure de ne garder que les informations qui nous intéressent (nom de l’entreprise, adresse, commune, code postal…)

import pandas as pd
import numpy as np

list_cols = [
    "siren",
    "siret",
    "activitePrincipaleRegistreMetiersEtablissement",
    "complementAdresseEtablissement",
    "numeroVoieEtablissement",
    "typeVoieEtablissement",
    "libelleVoieEtablissement",
    "codePostalEtablissement",
    "libelleCommuneEtablissement",
    "codeCommuneEtablissement",
    "etatAdministratifEtablissement",
    "denominationUsuelleEtablissement",
    "activitePrincipaleEtablissement",
]

df = pd.read_csv("sirene/StockEtablissement_utf8.csv", usecols=list_cols)

df["numero"] = (
    df["numeroVoieEtablissement"]
    .replace("-", np.NaN)
    .str.split()
    .str[0]
    .str.extract("(\d+)", expand=False)
    .fillna("0")
    .astype(int)
)

df["numero"] = df["numero"].astype(str).replace("0", "")

df["adresse"] = (
    df["numero"]
    + " "
    + df["typeVoieEtablissement"]
    + " "
    + df["libelleVoieEtablissement"]
)

df["adresse"] = df["adresse"].replace(np.nan, "")

df = df.loc[df["etatAdministratifEtablissement"] == "A"]

df.rename(
    {
        "denominationUsuelleEtablissement": "denom",
        "libelleCommuneEtablissement": "commune",
        "codeCommuneEtablissement": "code_commune",
        "codePostalEtablissement": "code_postal",
    },
    axis="columns",
    inplace=True,
)

df["ape"] = df["activitePrincipaleEtablissement"].str.replace("\.", "", regex=True)
df["denom"] = df["denom"].replace(np.nan, "")

df_siret = df.loc[
    :,
    [
        "siren",
        "siret",
        "adresse",
        "ape",
        "denom",
        "commune",
        "code_commune",
        "code_postal",
    ],
]
df_siret["code_postal"] = (
    df_siret["code_postal"]
    .replace(np.nan, "0")
    .astype(int)
    .astype(str)
    .replace("0", "")
)

On importe ensuite les données géolocalisées

import zipfile
import shutil
import os

# os.remove("sirene.zip")
# shutil.rmtree('sirene/')

url_geoloc = "https://files.data.gouv.fr/insee-sirene-geo/GeolocalisationEtablissement_Sirene_pour_etudes_statistiques_utf8.zip"
r = requests.get(url_geoloc)

with open("geoloc.zip", "wb") as f:
    f.write(r.content)

with zipfile.ZipFile("geoloc.zip", "r") as zip_ref:
    zip_ref.extractall("geoloc")

df_geoloc = pd.read_csv(
    "geoloc/GeolocalisationEtablissement_Sirene_pour_etudes_statistiques_utf8.csv",
    usecols=["siret", "epsg", "x_longitude", "y_latitude"],
    sep=";",
)

Il ne reste plus qu’à associer les deux jeux de données

df_geolocalized = df_siret.merge(df_geoloc, on="siret")
df_geolocalized["code_commune"] = df_geolocalized["code_commune"].astype(str)

Si vous avez accès à un espace de stockage cloud de type S3, il est possible d’utiliser pyarrow pour enregister cette base. Afin de l’enregistrer dans un espace de stockage public, nous allons l’enregistrer dans un dossier diffusion1

from pyarrow import fs
import pyarrow as pa
import pyarrow.parquet as pq

bucket = "lgaliana"
path = "diffusion/sirene_geolocalized.parquet"

s3 = fs.S3FileSystem(endpoint_override="http://" + "minio.lab.sspcloud.fr")

table = pa.Table.from_pandas(df_geolocalized)

4 Connexion à ElasticSearch

On va supposer que l’utilisateur dispose déjà d’un serveur Elastic fonctionnel mais désire créer un nouvel index. Si vous utilisez le SSPCloud, la démarche de création d’un service Elastic est disponible dans le chapitre précédent.

from elasticsearch import Elasticsearch

HOST = "elasticsearch-master"


def elastic():
    """Connection avec Elastic sur le data lab"""
    es = Elasticsearch(
        [{"host": HOST, "port": 9200, "scheme": "http"}],
        http_compress=True,
        request_timeout=200,
    )
    return es


es = elastic()
es
<Elasticsearch(['http://elasticsearch-master:9200'])>

5 Indexation de notre base Sirène géolocalisée

5.1 Définition du mapping

On va procéder par étape en essayant d’utiliser la structure la plus simple possible.

:one: On s’occupe d’abord de définir le mapping pour les variables textuelles.

string_var = ["adresse", "denom", "ape", "commune"]
map_string = {
    "type": "text",
    "fields": {"keyword": {"type": "keyword", "ignore_above": 256}},
}
mapping_string = {l: map_string for l in string_var}

:two: Les variables catégorielles sont utilisées par le biais du type keyword:

# keywords
keyword_var = ["siren", "siret", "code_commune", "code_postal"]
map_keywords = {
    "type": "text",
    "fields": {"keyword": {"type": "keyword", "ignore_above": 256}},
}
mapping_keywords = {l: map_keywords for l in keyword_var}

:three: La nouveauté par rapport à la partie précédente est l’utilisation de la dimension géographique. Elastic propose le type geo_point pour cela.

# geoloc
mapping_geoloc = {"location": {"type": "geo_point"}}

On collecte tout cela ensemble dans un dictionnaire:

# mapping
mapping_elastic = {
    "mappings": {"properties": {**mapping_string, **mapping_geoloc, **mapping_keywords}}
}

Il est tout à fait possible de définir un mapping plus raffiné. Ici, on va privilégier l’utilisation d’un mapping simple pour illustrer la recherche par distance géographique en priorité.

5.2 Création de l’index

Pour créer le nouvel index, on s’assure d’abord de ne pas déjà l’avoir créé et on passe le mapping défini précédemment.

if es.indices.exists("sirene"):
    es.indices.delete("sirene")

es.indices.create(index="sirene", body=mapping_elastic)

5.3 Indexation de la base géolocalisée

Pour le moment, l’index est vide. Il convient de le peupler.

Il est néanmoins nécessaire de créer le champ location au format attendu par elastic: lat, lon à partir de nos colonnes.

df_geolocalized["location"] = (
    df_geolocalized["y_latitude"].astype(str)
    + ", "
    + df_geolocalized["x_longitude"].astype(str)
)

La fonction suivante permet de structurer chaque ligne du DataFrame telle qu’Elastic l’attend:

def gen_dict_from_pandas(index_name, df):
    """
    Lit un dataframe pandas Open Food Facts, renvoi un itérable = dictionnaire des données à indexer, sous l'index fourni
    """
    for i, row in df.iterrows():
        header = {"_op_type": "index", "_index": index_name, "_id": i}
        yield {**header, **row}

Enfin, on peut industrialiser l’indexation de notre DataFrame en faisant tourner de manière successive cette fonction :

from elasticsearch.helpers import bulk, parallel_bulk
from collections import deque

deque(
    parallel_bulk(
        client=es,
        actions=gen_dict_from_pandas("sirene", df_geolocalized),
        chunk_size=1000,
        thread_count=4,
    )
)
es.count(index="sirene")
ObjectApiResponse({'count': 13059694, '_shards': {'total': 1, 'successful': 1, 'skipped': 0, 'failed': 0}})

6 Recherche

Pour se faciliter la création de cartes réactives, nous allons régulièrement utiliser la fonction suivante qui s’appuie sur un code déjà présenté dans un autre chapitre.

def plot_folium_sirene(df, yvar, xvar):

    center = df[[yvar, xvar]].mean().values.tolist()
    sw = df[[yvar, xvar]].min().values.tolist()
    ne = df[[yvar, xvar]].max().values.tolist()

    m = folium.Map(location=center, tiles="OpenStreetMap")

    # I can add marker one by one on the map
    for i in range(0, len(df)):
        folium.Marker(
            [df.iloc[i][yvar], df.iloc[i][xvar]],
            popup=df.iloc[i]["_source.denom"]
            + f'<br>(Score: {df.iloc[i]["_score"]:.2f})',
            icon=folium.Icon(icon="home"),
        ).add_to(m)

    m.fit_bounds([sw, ne])

    return m

6.1 Premier exemple de requête géographique

ex1 = es.search(
    index="sirene",
    body="""{
  "query": {
    "bool": {
      "must":
      { "match": { "denom":   "institut national de la statistique"}}
      }
  }
}
""",
)["hits"]["hits"]

echo_insee = pd.json_normalize(ex1)
echo_insee

On remarque déjà que les intitulés ne sont pas bons. Quand est-il de leurs localisations ?

plot_folium_sirene(echo_insee, yvar="_source.y_latitude", xvar="_source.x_longitude")

Ce premier essai nous suggère qu’il est nécessaire d’améliorer notre recherche. Plusieurs voies sont possibles:

  1. Améliorer le preprocessing de nos champs textuels en excluant, par exemple, les stopwords ;
  2. Effectuer une restriction géographique pour mieux cibler l’ensemble de recherche
  3. Trouver une variable catégorielle jouant le rôle de variable de blocage2 pour mieux cibler les paires pertinentes

Concernant la restriction géographique, Elastic fournit une approche très efficace de ciblage géographique. En connaissant une position approximative de l’entreprise à rechercher, il est ainsi possible de rechercher dans un rayon d’une taille plus ou moins grande. En supposant qu’on connaisse précisément la localisation de l’Insee, on peut chercher dans un rayon relativement restreint. Si notre position était plus approximative, on pourrait rechercher dans un rayon plus large.

ex2 = es.search(
    index="sirene",
    body="""{
  "query": {
    "bool": {
      "must":
      { "match": { "denom":   "institut national de la statistique"}}
      ,
      "filter":
        {"geo_distance": {
          "distance": "1km",
          "location": {
            "lat": "48.8168",
            "lon": "2.3099"
          }
        }
      }
    }
  }
}
""",
)["hits"]["hits"]

echo_insee = pd.json_normalize(ex2)
echo_insee

Les résultats sont par construction mieux ciblés. Néanmoins ils sont toujours décevants puisqu’on ne parvient pas à identifier l’Insee dans les dix meilleurs échos.

specificsearch = es.search(
    index="sirus_2020",
    body="""{
  "query": {
    "bool": {
      "should":
          { "match": { "rs_denom":   "CPCU - CENTRALE DE BERCY"}},
      "filter": [
          {"geo_distance": {
                  "distance": "0.5km",
                  "location": {
                        "lat": "48.84329", 
                        "lon": "2.37396"
                              }
                            }
            }, 
            { "prefix":  { "apet": "3530" }}
                ]
            }
          }
}""",
)

Informations additionnelles

environment files have been tested on.

Latest built version: 2024-12-07

Python version used:

'3.12.6 | packaged by conda-forge | (main, Sep 30 2024, 18:08:52) [GCC 13.3.0]'
Package Version
affine 2.4.0
aiobotocore 2.15.1
aiohappyeyeballs 2.4.3
aiohttp 3.10.8
aioitertools 0.12.0
aiosignal 1.3.1
alembic 1.13.3
altair 5.4.1
aniso8601 9.0.1
annotated-types 0.7.0
anyio 4.7.0
appdirs 1.4.4
archspec 0.2.3
asttokens 2.4.1
attrs 24.2.0
babel 2.16.0
bcrypt 4.2.0
beautifulsoup4 4.12.3
black 24.8.0
blinker 1.8.2
blis 0.7.11
bokeh 3.5.2
boltons 24.0.0
boto3 1.35.23
botocore 1.35.23
branca 0.7.2
Brotli 1.1.0
cachetools 5.5.0
cartiflette 0.0.2
Cartopy 0.24.1
catalogue 2.0.10
cattrs 24.1.2
certifi 2024.8.30
cffi 1.17.1
charset-normalizer 3.3.2
click 8.1.7
click-plugins 1.1.1
cligj 0.7.2
cloudpathlib 0.20.0
cloudpickle 3.0.0
colorama 0.4.6
comm 0.2.2
commonmark 0.9.1
conda 24.9.1
conda-libmamba-solver 24.7.0
conda-package-handling 2.3.0
conda_package_streaming 0.10.0
confection 0.1.5
contextily 1.6.2
contourpy 1.3.0
cryptography 43.0.1
cycler 0.12.1
cymem 2.0.10
cytoolz 1.0.0
dask 2024.9.1
dask-expr 1.1.15
databricks-sdk 0.33.0
dataclasses-json 0.6.7
debugpy 1.8.6
decorator 5.1.1
Deprecated 1.2.14
diskcache 5.6.3
distributed 2024.9.1
distro 1.9.0
docker 7.1.0
duckdb 0.10.1
en-core-web-sm 3.7.1
entrypoints 0.4
et_xmlfile 2.0.0
exceptiongroup 1.2.2
executing 2.1.0
fastexcel 0.11.6
fastjsonschema 2.21.1
fiona 1.10.1
Flask 3.0.3
folium 0.17.0
fontawesomefree 6.6.0
fonttools 4.54.1
frozendict 2.4.4
frozenlist 1.4.1
fsspec 2023.12.2
gensim 4.3.2
geographiclib 2.0
geopandas 1.0.1
geoplot 0.5.1
geopy 2.4.1
gitdb 4.0.11
GitPython 3.1.43
google-auth 2.35.0
graphene 3.3
graphql-core 3.2.4
graphql-relay 3.2.0
graphviz 0.20.3
great-tables 0.12.0
greenlet 3.1.1
gunicorn 22.0.0
h11 0.14.0
h2 4.1.0
hpack 4.0.0
htmltools 0.6.0
httpcore 1.0.7
httpx 0.28.1
httpx-sse 0.4.0
hyperframe 6.0.1
idna 3.10
imageio 2.36.1
importlib_metadata 8.5.0
importlib_resources 6.4.5
inflate64 1.0.0
ipykernel 6.29.5
ipython 8.28.0
itsdangerous 2.2.0
jedi 0.19.1
Jinja2 3.1.4
jmespath 1.0.1
joblib 1.4.2
jsonpatch 1.33
jsonpointer 3.0.0
jsonschema 4.23.0
jsonschema-specifications 2024.10.1
jupyter-cache 1.0.0
jupyter_client 8.6.3
jupyter_core 5.7.2
kaleido 0.2.1
kiwisolver 1.4.7
langchain 0.3.10
langchain-community 0.3.9
langchain-core 0.3.22
langchain-text-splitters 0.3.2
langcodes 3.5.0
langsmith 0.1.147
language_data 1.3.0
lazy_loader 0.4
libmambapy 1.5.9
locket 1.0.0
lxml 5.3.0
lz4 4.3.3
Mako 1.3.5
mamba 1.5.9
mapclassify 2.8.1
marisa-trie 1.2.1
Markdown 3.6
markdown-it-py 3.0.0
MarkupSafe 2.1.5
marshmallow 3.23.1
matplotlib 3.9.2
matplotlib-inline 0.1.7
mdurl 0.1.2
menuinst 2.1.2
mercantile 1.2.1
mizani 0.11.4
mlflow 2.16.2
mlflow-skinny 2.16.2
msgpack 1.1.0
multidict 6.1.0
multivolumefile 0.2.3
munkres 1.1.4
murmurhash 1.0.11
mypy-extensions 1.0.0
narwhals 1.16.0
nbclient 0.10.0
nbformat 5.10.4
nest_asyncio 1.6.0
networkx 3.3
nltk 3.9.1
numpy 1.26.4
opencv-python-headless 4.10.0.84
openpyxl 3.1.5
opentelemetry-api 1.16.0
opentelemetry-sdk 1.16.0
opentelemetry-semantic-conventions 0.37b0
orjson 3.10.12
OWSLib 0.28.1
packaging 24.1
pandas 2.2.3
paramiko 3.5.0
parso 0.8.4
partd 1.4.2
pathspec 0.12.1
patsy 0.5.6
Pebble 5.1.0
pexpect 4.9.0
pickleshare 0.7.5
pillow 10.4.0
pip 24.2
platformdirs 4.3.6
plotly 5.24.1
plotnine 0.13.6
pluggy 1.5.0
polars 1.8.2
preshed 3.0.9
prometheus_client 0.21.0
prometheus_flask_exporter 0.23.1
prompt_toolkit 3.0.48
protobuf 4.25.3
psutil 6.0.0
ptyprocess 0.7.0
pure_eval 0.2.3
py7zr 0.20.8
pyarrow 17.0.0
pyarrow-hotfix 0.6
pyasn1 0.6.1
pyasn1_modules 0.4.1
pybcj 1.0.2
pycosat 0.6.6
pycparser 2.22
pycryptodomex 3.21.0
pydantic 2.10.3
pydantic_core 2.27.1
pydantic-settings 2.6.1
Pygments 2.18.0
PyNaCl 1.5.0
pynsee 0.1.8
pyogrio 0.10.0
pyOpenSSL 24.2.1
pyparsing 3.1.4
pyppmd 1.1.0
pyproj 3.7.0
pyshp 2.3.1
PySocks 1.7.1
python-dateutil 2.9.0
python-dotenv 1.0.1
python-magic 0.4.27
pytz 2024.1
pyu2f 0.1.5
pywaffle 1.1.1
PyYAML 6.0.2
pyzmq 26.2.0
pyzstd 0.16.2
querystring_parser 1.2.4
rasterio 1.4.3
referencing 0.35.1
regex 2024.9.11
requests 2.32.3
requests-cache 1.2.1
requests-toolbelt 1.0.0
retrying 1.3.4
rich 13.9.4
rpds-py 0.22.3
rsa 4.9
ruamel.yaml 0.18.6
ruamel.yaml.clib 0.2.8
s3fs 2023.12.2
s3transfer 0.10.2
scikit-image 0.24.0
scikit-learn 1.5.2
scipy 1.13.0
seaborn 0.13.2
setuptools 74.1.2
shapely 2.0.6
shellingham 1.5.4
six 1.16.0
smart-open 7.0.5
smmap 5.0.0
sniffio 1.3.1
sortedcontainers 2.4.0
soupsieve 2.5
spacy 3.7.5
spacy-legacy 3.0.12
spacy-loggers 1.0.5
SQLAlchemy 2.0.35
sqlparse 0.5.1
srsly 2.4.8
stack-data 0.6.2
statsmodels 0.14.4
tabulate 0.9.0
tblib 3.0.0
tenacity 9.0.0
texttable 1.7.0
thinc 8.2.5
threadpoolctl 3.5.0
tifffile 2024.9.20
toolz 1.0.0
topojson 1.9
tornado 6.4.1
tqdm 4.66.5
traitlets 5.14.3
truststore 0.9.2
typer 0.15.1
typing_extensions 4.12.2
typing-inspect 0.9.0
tzdata 2024.2
Unidecode 1.3.8
url-normalize 1.4.3
urllib3 1.26.20
wasabi 1.1.3
wcwidth 0.2.13
weasel 0.4.1
webdriver-manager 4.0.2
websocket-client 1.8.0
Werkzeug 3.0.4
wheel 0.44.0
wordcloud 1.9.3
wrapt 1.16.0
xgboost 2.1.1
xlrd 2.0.1
xyzservices 2024.9.0
yarl 1.13.1
yellowbrick 1.5
zict 3.0.0
zipp 3.20.2
zstandard 0.23.0

View file history

SHA Date Author Description
005d89b 2023-12-20 17:23:04 Lino Galiana Finalise l’affichage des statistiques Git (#478)
3fba612 2023-12-17 18:16:42 Lino Galiana Remove some badges from python (#476)
1f23de2 2023-12-01 17:25:36 Lino Galiana Stockage des images sur S3 (#466)
a06a268 2023-11-23 18:23:28 Antoine Palazzolo 2ème relectures chapitres ML (#457)
b68369d 2023-11-18 18:21:13 Lino Galiana Reprise du chapitre sur la classification (#455)
889a71b 2023-11-10 11:40:51 Antoine Palazzolo Modification TP 3 (#443)
652009d 2023-10-09 13:56:34 Lino Galiana Finalise le cleaning (#430)
154f09e 2023-09-26 14:59:11 Antoine Palazzolo Des typos corrigées par Antoine (#411)
9977c5d 2023-08-28 10:43:36 Lino Galiana Fix bug path pandas (#397)
3bdf3b0 2023-08-25 11:23:02 Lino Galiana Simplification de la structure 🤓 (#393)
29ff3f5 2023-07-07 14:17:53 linogaliana description everywhere
f10815b 2022-08-25 16:00:03 Lino Galiana Notebooks should now look more beautiful (#260)
bacb5a0 2022-07-04 19:05:20 Lino Galiana Enrichir la partie elastic (#241)
Retour au sommet

Notes de bas de page

  1. Concernant la première piste, il aurait fallu mieux définir notre mapping pour autoriser des analyzers. A défaut, nous pourrons utiliser nltk ou spacy pour transformer les champs textuels avant d’envoyer la requête. Cette solution présente l’inconvénient de ne pas formatter de la même manière l’ensemble indexé mais pourrait malgré tout améliorer la pertinence des recherches.↩︎

  2. Concernant la première piste, il aurait fallu mieux définir notre mapping pour autoriser des analyzers. A défaut, nous pourrons utiliser nltk ou spacy pour transformer les champs textuels avant d’envoyer la requête. Cette solution présente l’inconvénient de ne pas formatter de la même manière l’ensemble indexé mais pourrait malgré tout améliorer la pertinence des recherches.↩︎

Citation

BibTeX
@book{galiana2023,
  author = {Galiana, Lino},
  title = {Python pour la data science},
  date = {2023},
  url = {https://pythonds.linogaliana.fr/},
  doi = {10.5281/zenodo.8229676},
  langid = {fr}
}
Veuillez citer ce travail comme suit :
Galiana, Lino. 2023. Python pour la data science. https://doi.org/10.5281/zenodo.8229676.