Metadata-Version: 2.1
Name: french-cities
Version: 0.1.0a8
Summary: Toolbox on french cities: set vintage, find departments, find cities...
Home-page: https://github.com/tgrandje/french-cities/
License: etalab-2.0
Keywords: france,cities
Author: thomas.grandjean
Author-email: thomas.grandjean@developpement-durable.gouv.fr
Requires-Python: >=3.8,<4.0
Classifier: Development Status :: 3 - Alpha
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering
Requires-Dist: geopandas (>=0.13.2,<0.14.0)
Requires-Dist: pandas (>=2.0.3,<3.0.0)
Requires-Dist: pebble (>=5.0.3,<6.0.0)
Requires-Dist: pynsee (>=0.1.4,<0.2.0)
Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
Requires-Dist: rapidfuzz (>=3.1.1,<4.0.0)
Requires-Dist: requests-cache (>=1.1.0,<2.0.0)
Requires-Dist: tqdm (>=4.65.0,<5.0.0)
Requires-Dist: unidecode (>=1.3.6,<2.0.0)
Project-URL: Bug Tracker, https://github.com/tgrandje/french-cities/issues
Project-URL: Documentation, https://github.com/tgrandje/french-cities/
Project-URL: Repository, https://github.com/tgrandje/french-cities/
Description-Content-Type: text/markdown

# french-cities
Toolbox on french cities: set vintage, find departments, find cities...


# Installation

`pip install french-cities`

Note that at this instant, `pynsee` doesn't support communal projection.
After installing `french-cities` from pypi, please uninstall pynsee and replace
it with the current master:
```
pip uninstall pynsee
pip install git+https://github.com/InseeFrLab/pynsee
```



# Configuration

## Setting INSEE's API keys
`french-cities` uses `pynsee` under the hood. For it to work, you need to set
the credentials up. You can set up to four environment variables:
* insee_key
* insee_secret, 
* http_proxy (if accessing web behind a corporate proxy)
* https_proxy (if accessing web behind a corporate proxy)

Please refer to [`pynsee`'s documentation](https://pynsee.readthedocs.io/en/latest/api_subscription.html)
to help configure the API's access.

## Session management
Note that `pynsee` uses it's own web session. Every Session object you will pass
to `french-cities` will **NOT** be shared with `pynsee`. This explains the
possibility to pass a session as an argument to `french-cities` functions,
even if you had to configure the corporate proxy through environment variables
for `pynsee`.

## 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. 

Working from postal codes will make use of the BAN (Base Adresse Nationale)
and should return correct results.

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 
(which is a fast process and 99% accurate) instead of using an API (which is
lengthy though foolproof).

```
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"],
    }
)
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)
```

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
out basic mistakes (up to a certain limit).

The columns used by the algorithm can be (in the order of precedence used by
the algorithm):
* 'x' and 'y' (in that case, epsg must be explicitly given);
* 'postcode' and 'city'
* 'address', 'postcode' and 'city'
* 'department' and 'city'

Note that the algorithm can (and will) make errors using xy coordinates on a 
older vintage (ie different from the current one) in the case of historic 
splitting of cities (the geographic files are not vintaged yet).

The lexical (postcode, city, address, departement) recognition is based on the
BAN (base adresse nationale). The algorithm won't collect underscored
results, but failures may still occure (and will be in accordance with the 
BAN's failures).

```
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)
```

For a complete documentation on `find_city`, please type 
`help(find_city)`.

### Set vintage to cities' codes
`french-cities` can try to project a given dataframe into a set vintage,
starting from an unknown vintage (or even a non-vintaged dataset, which is 
often the case).

Error may occur for splitted cities as the starting vintage is unknown
(or inexistant).

In case of a known starting vintage, you can make use of
INSEE's projection API (with `pynsee`). Note that this might prove slower as
each row will have to induce a request to the API (which allows up to 
30 requests/minute).

Basically, the algorithm of `french-cities` will try to see if a given city
code exists in the desired vintage:
* if yes, it will be kept (we the aforementionned approximation regarding
restored cities);
* if not, it will look in older vintages and make use of INSEE's projection API.

This algorithm will also:
* convert communal districts' into cities' codes;
* convert delegated or associated cities' codes into it's parent's.

```
from french_cities import set_vintage
import pandas as pd

df = pd.DataFrame(
    [
        ["07180", "Fusion"],
        ["02077", "Commune déléguée"],
        ["02564", "Commune nouvelle"],
        ["75101", "Arrondissement municipal"],
        ["59298", "Commune associée"],
        ["99999", "Code erroné"],
        ["14472", "Oudon"],
    ],
    columns=["A", "Test"],
    index=["A", "B", "C", "D", 1, 2, 3],
)
df = set_vintage(df, 2023, field="A")
print(df)
```

For a complete documentation on `set_vintage`, please type 
`help(set_vintage)`.


## Support
In case of bugs, please open an issue [on the repo](https://github.com/tgrandje/french-cities/issues).

## Author
Thomas GRANDJEAN (DREAL Hauts-de-France, service Information, Développement Durable et Évaluation Environnementale, pôle Promotion de la Connaissance).

## Licence
Licence Ouverte version 2.0 [etalab-2.0](https://www.etalab.gouv.fr/wp-content/uploads/2017/04/ETALAB-Licence-Ouverte-v2.0.pdf)

## Project Status
Test phase.
# french-cities
Boîte à outils sur les communes françaises : millésimage, reconnaissance de 
départements ou de communes...


# Installation

`pip install french-cities`

Notez qu'à cette heure, `pynsee` ne supporte pas les projections de codes commune. 
Dans l'immédiat, après avoir installé `french-cities` depuis pypi, il faut donc
désinstaller `pynsee` puis réinstaller la version master courante depuis son repo 
github :
```
pip uninstall pynsee
pip install git+https://github.com/InseeFrLab/pynsee
```

# Configuration

## 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 :
* 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.

## Gestion des sessions web
`pynsee` utilise son propre gestionnaire de session web. 
Ainsi, les objets Session passés en argument à `french-cities` ne seront
**PAS** partagés avec `pynsee`. 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`).

## Utilisation

### Pourquoi french-cities ?
Des packages et des API sont déjà disponibles pour des recherches usuelles. Par
exemple, `pynsee` utilise les API de l'INSEE pour retrouver de multiples données
(comme les départements, les 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.

La différence est que `french-cities` est optimisé pour travailler avec des données
fournies sous la forme de Series ou DataFrames pandas. Ce package gérera mieux
de gros volumes de données que ne le feraient des appels multiples à des API.

### Trouver les départements
`french-cities` peut retrouver un code département à partir de codes postaux ou 
de codes communes officiels (COG/INSEE).

Travailler à partir de codes postaux entraînera l'utilisation de la BAN (Base
Adresse Nationale) et devrait fournir des résultats corrects.

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).
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).

```
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"],
    }
)
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)
```

Pour une documentation complète sur la fonction `find_departements`, merci 
d'utiliser la commande suivante :
`help(find_departements)`.

### Trouver les codes communes
`french-cities` peut retrouver le code commune à partir de champs multiples.
Il est capable de détecter certaines erreurs simples dans les champs (jusqu'à 
une certaine limite).

Les colonnes utilisées par l'algorithme pour cette détection sont (par ordre
de priorité) :
* 'x' et 'y' (dans ce cas, un code EPSG doit être explicitement donné);
* 'postcode' et 'city'
* 'address', 'postcode' et 'city'
* 'department' et 'city'

Il est à noter que l'algorithme peu faire ê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 la BAN (base adresse nationale). L'algorithme ne conservera pas de
résultats insuffisamment fiables, mais des erreurs peuvent subsister (elles 
seront dans ce cas cohérentes avec les résultats de la BAN).

```
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)
```

Pour une documentation complète sur la fonction `find_city`, merci 
d'utiliser la commande suivante :
`help(find_city)`.

### Projection de codes communes dans un millésime donné
`french-cities` peut tenter de "projeter" un dataframe dans un millésime donné,
la date initiale demeurant inconnue (voire inexistante, les cas de fichiers
"multi-millésimés" étant fréquents dans la vie réelle).

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 
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 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).

Cet algorithme va également :
* convertir les codes des éventuels arrondissements municipaux en celui de leur
commune de rattachement;
* convertir les codes des communes associées et déléguées en celui de leur 
commune de rattachement.

```
from french_cities import set_vintage
import pandas as pd

df = pd.DataFrame(
    [
        ["07180", "Fusion"],
        ["02077", "Commune déléguée"],
        ["02564", "Commune nouvelle"],
        ["75101", "Arrondissement municipal"],
        ["59298", "Commune associée"],
        ["99999", "Code erroné"],
        ["14472", "Oudon"],
    ],
    columns=["A", "Test"],
    index=["A", "B", "C", "D", 1, 2, 3],
)
df = set_vintage(df, 2023, field="A")
print(df)
```

Pour une documentation complète sur la fonction `set_vintage`, merci 
d'utiliser la commande suivante :
`help(set_vintage)`.


## Support
En cas de bugues, merci d'ouvrir un ticket [sur le repo](https://github.com/tgrandje/french-cities/issues).

## Auteur
Thomas GRANDJEAN (DREAL Hauts-de-France, service Information, Développement Durable et Évaluation Environnementale, pôle Promotion de la Connaissance).

## Licence
Licence Ouverte version 2.0 [etalab-2.0](https://www.etalab.gouv.fr/wp-content/uploads/2017/04/ETALAB-Licence-Ouverte-v2.0.pdf)

## État du projet
Phase de test.
