Aller au contenu

Projection

Une projection est une modification du schéma d'une table, souvent en retirant des colonnes.

Données inutiles

Supposons que l'on veuille afficher le code postal des communes de France. On peut utiliser les données de la table localisation et effectuer une sélection dessus.

communes_selectionnees = selection(localisation, lambda l : l["code_postal"][0] ==  "0" and l["code_postal"][1] == "1")

On se retrouve avec la table communes_selectionnees suivante :

code_insee nom code_postal latitude longitude
01001 L' Abergement-Clémenciat 01400 46.153721024 4.925850148
01002 L' Abergement-de-Varey 01640 46.009605679 5.428087796
... ... ... ... ...

Cette table comporte des données dont nous n'avons pas besoin : le code insée, la latitude et la longitude. Ces données en trop vont prendre de la place si on veut exporter la table en CSV. Elles rendent aussi la table plus difficile à lire pour un humain, par exemple affichée par le biais d'un tableur.

TODO (prof) ex calculer la place prise par les colonnes en trop ?

On aimerait avoir une table code_postal comme suit :

nom code_postal
L' Abergement-Clémenciat 01400
L' Abergement-de-Varey 01640
... ...

Algorithme

Pour projeter une table sur un nouveau schéma, on parcours chaque ligne de la table, et on crée une nouvelle table en ajoutant des lignes qui ne contiennent que les champs qui nous intéressent.

Par exemple, la simplification ci-dessus peut s'effectuer avec un des deux codes suivants:

code_postal = []
for l in localisation:
    code_postal.append({"nom":l["nom"], "code_postal":l["code_postal"]})
# on omet le if puisqu'on séléctionne toutes les lignes
code_postal = [{"nom":l["nom"], "code_postal":l["code_postal"]} in l for l in localisation]

Quelques projections

Exercice 7

Implémentez la fonction code_insee qui retourne une table, projection de la table localisation sur le schéma (nom, code_insee).

Vous devriez obtenir la sortie suivante dans le programme correspondant :

| nom                      | code_insee |
| ------------------------ | ---------- |
| L' Abergement-Clémenciat | 01001      |
| L' Abergement-de-Varey   | 01002      |
| Ambérieu-en-Bugey        | 01004      |
| Ambérieux-en-Dombes      | 01005      |
| Ambléon                  | 01006      |
| Ambronay                 | 01007      |
| Ambutrix                 | 01008      |
| Andert-et-Condon         | 01009      |
| Anglefort                | 01010      |
| Apremont                 | 01011      |

Le code est très similaire à celui de l'exemple précédent !

def code_insee(localisation):
    codes_insee = []
    for l in localisation:
        codes_insee.append({"nom":l["nom"], "code_insee":l["code_insee"]})
    return codes_insee
def code_insee(localisation):
    return [{"nom":l["nom"], "code_insee":l["code_insee"]} in l for l in localisation]

Exercice 8

Implémentez la fonction coordonnees qui retourne une table, projection de la table localisation sur le schéma (nom, code_insee).

Vous devriez obtenir la sortie suivante dans le programme correspondant :

| nom                      | latitude     | longitude   |
| ------------------------ | ------------ | ----------- |
| L' Abergement-Clémenciat | 46.153721024 | 4.925850148 |
| L' Abergement-de-Varey   | 46.009605679 | 5.428087796 |
| Ambérieu-en-Bugey        | 45.961048852 | 5.372275427 |
| Ambérieux-en-Dombes      | 45.99616357  | 4.911967138 |
| Ambléon                  | 45.749886304 | 5.594584599 |
| Ambronay                 | 46.005690539 | 5.357748739 |
| Ambutrix                 | 45.936683389 | 5.332446671 |
| Andert-et-Condon         | 45.787194157 | 5.657799976 |
| Anglefort                | 45.909091467 | 5.795056945 |
| Apremont                 | 46.205877513 | 5.657980818 |

Le principe est toujours le même.

def coordonnees(localisation):
coords = []
for l in localisation:
    coords.append({"nom":l["nom"], "latitude":l["latitude"], "longitude":l["longitude"]})
return coords
def coordonnees(localisation):
    return[{"nom":l["nom"], "latitude":l["latitude"], "longitude":l["longitude"]} for l in localisation]

Code générique

De la même manière que pour la sélection, on veut écrire le moins de code possible. Seule la transformation ligne par ligne change d'une fonction à l'autre, le code qui permet de parcourir les lignes reste le même.

Exercice 9

Voici une fonction projection.

def projection(t, transformation):
    """t une table
    transformation, une fonction qui prends en entrée une ligne de la table t, et retourne une nouvelle ligne, projection de la ligne sur un nouveau schéma.

    Retourne une table, projection de t sur le schéma de créé par la fonction transformation

    >>> projection([{"a":1, "b":2}, {"a":3, "b":4}], lambda l : {"a":l["a"]})
    [{"a":1}, {"a":3}]
    """
    return [transformation(l) for l in t]

Utilisez-là pour réécrire la projection de l'exercice 8.

def coordonnees(localisation):
    return projection(localisation, lambda l:{"nom":l["nom"], "latitude":l["latitude"], "longitude":l["longitude"]})

Encore plus simple (Difficile)

En fait, on peut faire encore plus simple : on voudrait juste pouvoir spécifier les colonnes à garder dans notre table, un peu comme ça :

p = projection(table, ["colonne", "colonne"])

Essayons d'écrire une telle fonction.

Exercice 10

Implémentez la fonction projection_ligne :

def projection_ligne(ligne, schema):
    """Ligne une ligne d'une table sous forme d'un dictionnaire
    schema une list de chaines, des noms de colonnes présentes dans le schéma de ligne
    Retourne une nouvelle ligne ne comportant que les colonnes de schema

    >>> projection_ligne({"a":1, "b":2, "c":3}, ["a", "c"])
    {"a":1, "c":3}
    """

Puis utilisez là pour implémenter la fonction projection:

def projection(t, schema):
    """t une table, 
    schema une list de chaines, noms de colonnes présentes dans le schéma de t
    Retourne une table, projection de t sur schema

    >>> projection([{"a":1, "b":2, "c":3}, {"a":3, "b":4, "c":4}], ["a", "c"])
    [{"a":1, "c":3}, {"a":3, "c":4}] 
    """

Dans le programme correspondant, vous devriez avoir la sortie suivante :

Pour créer une nouvelle ligne projetée sur un schéma, utilisez une boucle for pour itérer sur chaque élément du schéma :)

def projection_ligne(ligne, schema):
    """Ligne une ligne d'une table sous forme d'un dictionnaire
    schema une list de chaines, des noms de colonnes présentes dans le schéma de ligne
    Retourne une nouvelle ligne ne comportant que les colonnes de schema

    >>> projection_ligne({"a":1, "b":2, "c":3}, ["a", "c"])
    {"a":1, "c":3}
    """
    #vous devez implémenter cette fonction
    nouvelle_ligne = {}
    for c in schema:
        nouvelle_ligne[c] = ligne[c]
    return nouvelle_ligne

def projection(t, schema):
    """t une table, 
    schema une list de chaines, noms de colonnes présentes dans le schéma de t
    Retourne une table, projection de t sur schema

    >>> projection([{"a":1, "b":2, "c":3}, {"a":3, "b":4, "c":4}], ["a", "c"])
    [{"a":1, "c":3}, {"a":3, "c":4}] 
    """
    #vous devez implémenter cette fonction
    proj = []
    for l in t:
        proj.append(projection_ligne(l, schema))
    return proj