Renommer données_en_table -> donnees_en_table
Suppression des accents pour éviter les problèmes d'encodage Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
327
donnees_en_table/Exercices/CORRECTION.md
Normal file
327
donnees_en_table/Exercices/CORRECTION.md
Normal file
@@ -0,0 +1,327 @@
|
||||
## Correction - Données en table
|
||||
|
||||
---
|
||||
|
||||
### 1. Manipulation de fichiers CSV
|
||||
|
||||
- On peut représenter un enregistrement par un dictionnaire.
|
||||
- Une virgule (CSV : Comma Separated Values - Valeurs séparées par une virgule). Néanmoins, le format CSV autorise d'autres séparateurs.
|
||||
- La table étant une liste de dictionnaires, on obtient donc le premier élément de la liste, c'est à dire un dictionnaire, donc une ligne de la table.
|
||||
|
||||
---
|
||||
|
||||
### 2. Opérations sur les tables
|
||||
|
||||
- Pour sélectionner des colonnes selon un critère donné, on va utiliser la fonction projection.
|
||||
|
||||
```python
|
||||
def projection(table, liste_attributs):
|
||||
return [{clé:ligne[clé] for clé in ligne if clé in liste_attributs} for ligne in table]
|
||||
```
|
||||
|
||||
- La fonction *select* ici va sélectionner les lignes dont une des valeurs vaut 19. On obtient donc :
|
||||
|
||||
```python
|
||||
[{'Prénom': 'Chandler', 'Math': '19', 'Anglais': '15', 'NSI': '17'},
|
||||
{'Prénom': 'Ross', 'Math': '14', 'Anglais': '19', 'NSI': '13'}]
|
||||
```
|
||||
|
||||
- Il y a deux (2) noms qui sont communs aux tables : on aura donc deux (2) lignes. De plus, la table U rajoute ses deux (2) colonnes (Âge, Mail) aux quatre de la table T : on aura donc 6 (six) colonnes.
|
||||
|
||||
---
|
||||
|
||||
### 3. Déterminer des fonctions basiques
|
||||
|
||||
- Puisqu'il s'agit de compter le nombre de lignes, donc d'enregistrements, on peut donc utiliser la fonction *len* pour obtenir la longueur de la liste de dictionnaires.
|
||||
|
||||
```python
|
||||
def cardinalite(table):
|
||||
return len(table)
|
||||
```
|
||||
|
||||
- La liste des attributs d'une table correspond aux clés du premier dictionnaire (en supposant que la table est cohérente) :
|
||||
|
||||
```python
|
||||
def attributs(table):
|
||||
if len(table) == 0:
|
||||
return []
|
||||
return list(table[0].keys())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Reconnaître une fonction
|
||||
|
||||
```python
|
||||
def mystère(t, cs):
|
||||
t_p = []
|
||||
for l in t :
|
||||
nvlle_l = {}
|
||||
for c in l:
|
||||
if c in cs:
|
||||
nvelle_l[c] = l[c]
|
||||
t_p.append(nvlle_l)
|
||||
return t_p
|
||||
```
|
||||
|
||||
Cette fonction réalise une **projection** : elle crée une nouvelle table ne contenant que les colonnes (attributs) spécifiés dans la liste `cs`.
|
||||
|
||||
- `t` est la table d'origine (liste de dictionnaires)
|
||||
- `cs` est la liste des colonnes à conserver
|
||||
- Pour chaque ligne `l` de la table, on crée un nouveau dictionnaire `nvlle_l` ne contenant que les clés présentes dans `cs`
|
||||
- On retourne la nouvelle table `t_p`
|
||||
|
||||
**Remarque** : Il y a une erreur dans le code original (`nvelle_l` au lieu de `nvlle_l`). La version corrigée serait :
|
||||
|
||||
```python
|
||||
def projection(table, colonnes):
|
||||
table_projetee = []
|
||||
for ligne in table:
|
||||
nouvelle_ligne = {}
|
||||
for colonne in ligne:
|
||||
if colonne in colonnes:
|
||||
nouvelle_ligne[colonne] = ligne[colonne]
|
||||
table_projetee.append(nouvelle_ligne)
|
||||
return table_projetee
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. Tester la cohérence d'une table
|
||||
|
||||
- Fonction `coherence` qui teste si chaque ligne a le même ensemble d'attributs :
|
||||
|
||||
```python
|
||||
def coherence(table):
|
||||
"""
|
||||
Teste si chaque ligne de la table a le même ensemble d'attributs
|
||||
:param table: (list) une liste de dictionnaires
|
||||
:return: (bool) True si la table est cohérente, False sinon
|
||||
"""
|
||||
if len(table) == 0:
|
||||
return True
|
||||
|
||||
attributs_reference = set(table[0].keys())
|
||||
|
||||
for ligne in table:
|
||||
if set(ligne.keys()) != attributs_reference:
|
||||
return False
|
||||
|
||||
return True
|
||||
```
|
||||
|
||||
- Fonction `doublons` qui vérifie si un attribut apparaît deux fois avec la même valeur :
|
||||
|
||||
```python
|
||||
def doublons(table, attribut):
|
||||
"""
|
||||
Vérifie si un attribut apparaît deux fois avec la même valeur
|
||||
:param table: (list) une liste de dictionnaires
|
||||
:param attribut: (str) le nom de l'attribut à vérifier
|
||||
:return: (bool) True s'il y a des doublons, False sinon
|
||||
"""
|
||||
valeurs_vues = []
|
||||
|
||||
for ligne in table:
|
||||
valeur = ligne[attribut]
|
||||
if valeur in valeurs_vues:
|
||||
return True
|
||||
valeurs_vues.append(valeur)
|
||||
|
||||
return False
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. Lier tableur, fichier CSV et liste de dictionnaires
|
||||
|
||||
```python
|
||||
PlanningTwitch = [
|
||||
{'NomStream': 'AntoineDaniel', 'Genre': 'M', 'Jeu': 'Fall_Guys', 'Numéro': '1'},
|
||||
{'NomStream': 'MV', 'Genre': 'M', 'Jeu': 'Isaac', 'Numéro': '2'},
|
||||
{'NomStream': 'AngleDroit', 'Genre': 'F', 'Jeu': 'Fall_Guys', 'Numéro': '3'},
|
||||
{'NomStream': 'BagheraJones', 'Genre': 'F', 'Jeu': 'Fall_Guys', 'Numéro': '4'}
|
||||
]
|
||||
```
|
||||
|
||||
- La première ligne de la feuille de calcul contient les en-têtes (attributs) :
|
||||
`NomStream | Genre | Jeu | Numéro`
|
||||
|
||||
- Pour obtenir le fichier CSV correspondant :
|
||||
```python
|
||||
vers_csv('PlanningTwitch', ['NomStream', 'Genre', 'Jeu', 'Numéro'])
|
||||
```
|
||||
|
||||
- La deuxième ligne du fichier CSV :
|
||||
`AntoineDaniel;M;Fall_Guys;1`
|
||||
|
||||
- La cellule C8 de la feuille correspondante : la table n'a que 5 lignes (1 en-tête + 4 données), donc C8 est **vide**.
|
||||
|
||||
- Pour obtenir la valeur d'une cellule (par exemple C2, le jeu d'AntoineDaniel) :
|
||||
```python
|
||||
PlanningTwitch[0]['Jeu'] # 'Fall_Guys'
|
||||
```
|
||||
|
||||
- Pour modifier le jeu de MV :
|
||||
```python
|
||||
PlanningTwitch[1]['Jeu'] = 'Worms'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. Ajouter une ligne ou une colonne
|
||||
|
||||
- Pour obtenir la liste de dictionnaires depuis le fichier CSV :
|
||||
```python
|
||||
Groupe1 = import_csv('Groupe1')
|
||||
```
|
||||
|
||||
- Pour ajouter Rachel :
|
||||
```python
|
||||
Groupe1.append({'Prénom': 'Rachel', 'Math': '17', 'Anglais': '19', 'NSI': '18'})
|
||||
```
|
||||
|
||||
- Fonction pour ajouter une colonne moyenne :
|
||||
|
||||
```python
|
||||
from copy import deepcopy
|
||||
|
||||
def ajouter_moyenne(table):
|
||||
"""
|
||||
Ajoute une colonne 'Moyenne' à chaque ligne de la table
|
||||
:param table: (list) une liste de dictionnaires avec des notes
|
||||
:return: (list) une nouvelle table avec la colonne Moyenne
|
||||
"""
|
||||
nouvelle_table = deepcopy(table)
|
||||
|
||||
for ligne in nouvelle_table:
|
||||
math = int(ligne['Math'])
|
||||
anglais = int(ligne['Anglais'])
|
||||
nsi = int(ligne['NSI'])
|
||||
moyenne = (math + anglais + nsi) / 3
|
||||
ligne['Moyenne'] = '{:.1f}'.format(moyenne)
|
||||
|
||||
return nouvelle_table
|
||||
```
|
||||
|
||||
- Pour ajouter une ligne contenant les moyennes par matière :
|
||||
|
||||
```python
|
||||
from copy import deepcopy
|
||||
|
||||
def ajouter_ligne_moyennes(table):
|
||||
"""
|
||||
Ajoute une ligne contenant les moyennes par matière
|
||||
:param table: (list) une liste de dictionnaires avec des notes
|
||||
:return: (list) une nouvelle table avec la ligne des moyennes
|
||||
"""
|
||||
nouvelle_table = deepcopy(table)
|
||||
|
||||
nb_eleves = len(nouvelle_table)
|
||||
total_math = 0
|
||||
total_anglais = 0
|
||||
total_nsi = 0
|
||||
|
||||
for ligne in nouvelle_table:
|
||||
total_math = total_math + int(ligne['Math'])
|
||||
total_anglais = total_anglais + int(ligne['Anglais'])
|
||||
total_nsi = total_nsi + int(ligne['NSI'])
|
||||
|
||||
ligne_moyennes = {
|
||||
'Prénom': 'Moyenne',
|
||||
'Math': '{:.1f}'.format(total_math / nb_eleves),
|
||||
'Anglais': '{:.1f}'.format(total_anglais / nb_eleves),
|
||||
'NSI': '{:.1f}'.format(total_nsi / nb_eleves)
|
||||
}
|
||||
|
||||
nouvelle_table.append(ligne_moyennes)
|
||||
return nouvelle_table
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8. Sélectionner, trier, joindre
|
||||
|
||||
On définit les tables :
|
||||
|
||||
```python
|
||||
Hero = [
|
||||
{'NumHero': '0', 'NomHero': 'Sangoku', 'VilleHero': 'Kyoto'},
|
||||
{'NumHero': '1', 'NomHero': 'Naruto', 'VilleHero': 'Konoha'},
|
||||
{'NumHero': '2', 'NomHero': 'Luffy', 'VilleHero': 'Fuchsia'},
|
||||
{'NumHero': '3', 'NomHero': 'Ryo Saeba', 'VilleHero': 'Tokyo'},
|
||||
{'NumHero': '4', 'NomHero': 'Saitama', 'VilleHero': 'Ville Z'},
|
||||
{'NumHero': '5', 'NomHero': 'Onizuka', 'VilleHero': 'Tokyo'}
|
||||
]
|
||||
|
||||
Armes = [
|
||||
{'NumHero': '0', 'NomHero': 'Sangoku', 'Arme': 'Ki'},
|
||||
{'NumHero': '1', 'NomHero': 'Naruto', 'Arme': 'Chakra'},
|
||||
{'NumHero': '2', 'NomHero': 'Luffy', 'Arme': 'Corps'},
|
||||
{'NumHero': '3', 'NomHero': 'Ryo Saeba', 'Arme': 'Magnum'},
|
||||
{'NumHero': '4', 'NomHero': 'Saitama', 'Arme': 'Poing'},
|
||||
{'NumHero': '5', 'NomHero': 'Onizuka', 'Arme': 'Tout est une arme'}
|
||||
]
|
||||
```
|
||||
|
||||
- **HeroTokyo** : héros dont la ville est Tokyo
|
||||
|
||||
```python
|
||||
HeroTokyo = select(Hero, "ligne['VilleHero'] == 'Tokyo'")
|
||||
# Résultat :
|
||||
# [{'NumHero': '3', 'NomHero': 'Ryo Saeba', 'VilleHero': 'Tokyo'},
|
||||
# {'NumHero': '5', 'NomHero': 'Onizuka', 'VilleHero': 'Tokyo'}]
|
||||
```
|
||||
|
||||
- **HeroAlpha** : héros triés par ordre alphabétique du nom
|
||||
|
||||
```python
|
||||
HeroAlpha = tri(Hero, 'NomHero')
|
||||
# Résultat :
|
||||
# [{'NumHero': '2', 'NomHero': 'Luffy', 'VilleHero': 'Fuchsia'},
|
||||
# {'NumHero': '1', 'NomHero': 'Naruto', 'VilleHero': 'Konoha'},
|
||||
# {'NumHero': '5', 'NomHero': 'Onizuka', 'VilleHero': 'Tokyo'},
|
||||
# {'NumHero': '3', 'NomHero': 'Ryo Saeba', 'VilleHero': 'Tokyo'},
|
||||
# {'NumHero': '4', 'NomHero': 'Saitama', 'VilleHero': 'Ville Z'},
|
||||
# {'NumHero': '0', 'NomHero': 'Sangoku', 'VilleHero': 'Kyoto'}]
|
||||
```
|
||||
|
||||
- **HeroComplet** : nom, ville et arme des héros (fusion puis projection)
|
||||
|
||||
```python
|
||||
HeroFusion = fusion(Hero, Armes, 'NumHero')
|
||||
HeroComplet = projection(HeroFusion, ['NomHero', 'VilleHero', 'Arme'])
|
||||
# Résultat :
|
||||
# [{'NomHero': 'Sangoku', 'VilleHero': 'Kyoto', 'Arme': 'Ki'},
|
||||
# {'NomHero': 'Naruto', 'VilleHero': 'Konoha', 'Arme': 'Chakra'},
|
||||
# {'NomHero': 'Luffy', 'VilleHero': 'Fuchsia', 'Arme': 'Corps'},
|
||||
# {'NomHero': 'Ryo Saeba', 'VilleHero': 'Tokyo', 'Arme': 'Magnum'},
|
||||
# {'NomHero': 'Saitama', 'VilleHero': 'Ville Z', 'Arme': 'Poing'},
|
||||
# {'NomHero': 'Onizuka', 'VilleHero': 'Tokyo', 'Arme': 'Tout est une arme'}]
|
||||
```
|
||||
|
||||
- **HeroVille** : numéro et ville des héros
|
||||
|
||||
```python
|
||||
HeroVille = projection(Hero, ['NumHero', 'VilleHero'])
|
||||
# Résultat :
|
||||
# [{'NumHero': '0', 'VilleHero': 'Kyoto'},
|
||||
# {'NumHero': '1', 'VilleHero': 'Konoha'},
|
||||
# {'NumHero': '2', 'VilleHero': 'Fuchsia'},
|
||||
# {'NumHero': '3', 'VilleHero': 'Tokyo'},
|
||||
# {'NumHero': '4', 'VilleHero': 'Ville Z'},
|
||||
# {'NumHero': '5', 'VilleHero': 'Tokyo'}]
|
||||
```
|
||||
|
||||
- **HeroImpair** : nom et ville des héros ne venant pas de Tokyo et dont le numéro est impair
|
||||
|
||||
```python
|
||||
HeroImpair = select(Hero, "ligne['VilleHero'] != 'Tokyo' and int(ligne['NumHero']) % 2 == 1")
|
||||
HeroImpair = projection(HeroImpair, ['NomHero', 'VilleHero'])
|
||||
# Résultat :
|
||||
# [{'NomHero': 'Naruto', 'VilleHero': 'Konoha'}]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Source : Florian Mathieu - Licence CC BY NC SA
|
||||
236
donnees_en_table/Exercices/README.md
Normal file
236
donnees_en_table/Exercices/README.md
Normal file
@@ -0,0 +1,236 @@
|
||||
## Exercices
|
||||
|
||||
|
||||
|
||||
### 1. Manipulation de fichiers CSV
|
||||
|
||||
- Par quoi peut-on représenter un enregistrement en Python ?
|
||||
|
||||
- Dans un fichier CSV, quel élément sépare les différents attributs ?
|
||||
|
||||
- On dispose d'une table de données *Table* représentée par une liste de dictionnaires. Qu'obtient-on en entrant :
|
||||
|
||||
```python
|
||||
Table[0]
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 2. Opérations sur les tables
|
||||
|
||||
On dispose de la table T représentant les notes d'élèves dans trois matières :
|
||||
|
||||
| Prénom | Math | Anglais | NSI |
|
||||
| -------- | ---- | ------- | ---- |
|
||||
| Joey | 16 | 17 | 18 |
|
||||
| Chandler | 19 | 15 | 17 |
|
||||
| Ross | 14 | 19 | 13 |
|
||||
|
||||
|
||||
|
||||
- Pour selectionner des colonnes selon un critère donné, laquelle de ces deux fonctions peut-on utiliser ?
|
||||
|
||||
```python
|
||||
def select(table, critère):
|
||||
def test(ligne):
|
||||
return eval(critère)
|
||||
return [ligne for ligne in table if test(ligne)]
|
||||
```
|
||||
|
||||
```python
|
||||
def projection(table, liste_attributs):
|
||||
return [{clé:ligne[clé] for clé in ligne if clé in liste_attributs} for ligne in table]
|
||||
```
|
||||
|
||||
- La fonction *select* vu au dessus peut-être utilisée comme ceci :
|
||||
|
||||
```python
|
||||
select(T,"'19' in ligne.values()")
|
||||
```
|
||||
|
||||
Que renvoie cet appel de fonction ?
|
||||
|
||||
|
||||
|
||||
- Soit U la table suivante :
|
||||
|
||||
| Prénom | Âge | Mail |
|
||||
| -------- | ---- | ---------------------- |
|
||||
| Joey | 29 | howyoudoin@friends.com |
|
||||
| Chandler | 31 | joke@friends.com |
|
||||
|
||||
En réutilisant la fonction fusion suivante:
|
||||
|
||||
```python
|
||||
from copy import deepcopy
|
||||
def fusion(table_1, table_2, cle_1, cle_2=None):
|
||||
if cle_2 is None:
|
||||
cle_2 = cle_1
|
||||
table_finale = []
|
||||
for ligne_1 in table_1:
|
||||
for ligne_2 in table_2:
|
||||
if ligne_1[cle_1] == ligne_2[cle_2]:
|
||||
ligne_finale = deepcopy(ligne_1)
|
||||
for cle in ligne_2:
|
||||
if cle != cle_2:
|
||||
ligne_finale[cle] = ligne_2[cle]
|
||||
table_finale.append(ligne_finale)
|
||||
return table_finale
|
||||
```
|
||||
|
||||
donnez le nombre de lignes et de colonnes renvoyées par :
|
||||
|
||||
```python
|
||||
fusion(T,U, 'Prénom')
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 3. Determiner des fonctions basiques
|
||||
|
||||
- Determiner une fonction qui calcule la **cardinalité** d'une table, c'est à dire son nombre de lignes.
|
||||
- Determiner une fonction qui renvoie la liste des attributs d'une table.
|
||||
|
||||
|
||||
|
||||
### 4. Reconnaître une fonction
|
||||
|
||||
Quel est le principe de la fonction suivante :
|
||||
|
||||
```python
|
||||
def mystère(t, cs):
|
||||
t_p = []
|
||||
for l in t :
|
||||
nvlle_l = {}
|
||||
for c in l:
|
||||
if c in cs:
|
||||
nvelle_l[c] = l[c]
|
||||
t_p.append(nvelle_l)
|
||||
return t_p
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 5. Tester la cohérence d'une table
|
||||
|
||||
- Determiner une fonction *coherence*(table) qui teste si chaque ligne a le même ensemble d'attributs.
|
||||
- Determiner une fonction *doublons*(table, attribut) qui vérifie si un attribut de référerence apparaît deux fois avec la même valeur dans une table.
|
||||
|
||||
### 6. Lier tableur, fichier CSV et liste de dictionnaires
|
||||
|
||||
On dispose d'une liste de dictionnaires suivante :
|
||||
|
||||
```python
|
||||
PlanningTwitch =[{'NomStream' : 'AntoineDaniel', 'Genre' : 'M', 'Jeu' : 'Fall_Guys','Numéro' : '1'},{'NomStream' : 'MV', 'Genre' : 'M', 'Jeu' : 'Isaac','Numéro' : '2'}, {'NomStream' : 'AngleDroit', 'Genre' : 'F', 'Jeu' : 'Fall_Guys','Numéro' : '3'}, {'NomStream' : 'BagheraJones', 'Genre' : 'F', 'Jeu' : 'Fall_Guys','Numéro' : '4'}]
|
||||
```
|
||||
|
||||
- On travaille avec le tableur LibreOffice Calc de la suite LibreOffice qui produit des fichiers au format ont (alors qu'Excel de la suite Microsoft Office produit des fichiers au format xlsx). Quelle est la première ligne de la feuille de calcul obtenue dans un tableau à partir de cette liste ?
|
||||
- Quelle commande lancer pour obtenir le fichier CSV correspondant ?
|
||||
- Quelle est la deuxième ligne du fichier CSV correspondant ?
|
||||
- Quelle valeur trouve t-on à la cellule C8 de la feuille correspondante ?
|
||||
- Par quelle commande obtient-on cette valeur ?
|
||||
- Une erreur de saisie a lieu : MV joue à worms en fait. Quelle commande permet de modifier le fichier correpondant du tableur ?
|
||||
|
||||
### 7. Ajouter une ligne ou une colonne
|
||||
|
||||
On dispose de la table suivante au format CSV, dans le repertoire courant sous le nom ***'./Groupe1.csv'***
|
||||
|
||||
| Prénom | Math | Anglais | NSI |
|
||||
| -------- | ---- | ------- | ---- |
|
||||
| Joey | 16 | 17 | 18 |
|
||||
| Chandler | 19 | 15 | 17 |
|
||||
| Ross | 14 | 19 | 13 |
|
||||
|
||||
- Comment obtenir la liste de dictionnaires correspondante en utilisant une fonction déjà vue ?
|
||||
- Ajouter les notes de l'élève Rachel qui a eu 17 en Maths, 18 en NSI et 19 en anglais.
|
||||
- On voudrait ajouter une colonne contenant les moyennes de chaque élève afin d'obtenir le tableau suivant :
|
||||
|
||||
| Prénom | Math | Anglais | NSI | Moyenne |
|
||||
| -------- | ---- | ------- | ---- | ------- |
|
||||
| Joey | 16 | 17 | 18 | 17 |
|
||||
| Chandler | 19 | 15 | 17 | 17 |
|
||||
| Ross | 14 | 19 | 13 | 15,3 |
|
||||
| Rachel | 17 | 19 | 18 | 18 |
|
||||
|
||||
On doit envoyer une nouvelle table qui ne modifie pas la table d'origine. Pour effectuer une copie d'une liste d'objets complexes (ici une liste de dictionnaires), on peut utiliser la fonction *deepcopy* de la bibliothèque copy. La fonction à créer pourra donc avoir la structure suivante qu'il faudra compléter :
|
||||
|
||||
```python
|
||||
from copy import deepcopy
|
||||
def ajouter_moyenne(table):
|
||||
nvelle_table = deepcopy(table)
|
||||
pass
|
||||
|
||||
return nvelle_table
|
||||
```
|
||||
|
||||
Pour obtenir l'affichage d'un nombre flottant arrondi à 2 chiffres derrière la virgule, on peut utiliser la méthode *format*
|
||||
|
||||
Par exemple :
|
||||
|
||||
```python
|
||||
>> '{:.2f}'.format(314/100) #indique un flottant avec 2 chiffres après la virgule
|
||||
'3,14'
|
||||
```
|
||||
|
||||
Ajouter une ligne qui contient les moyennes par matières.
|
||||
|
||||
Cela devrait donner un tableau du genre :
|
||||
|
||||
| Prénom | Math | Anglais | NSI |
|
||||
| -------- | ---- | ------- | ---- |
|
||||
| Joey | 16 | 17 | 18 |
|
||||
| Chandler | 19 | 15 | 17 |
|
||||
| Ross | 14 | 19 | 13 |
|
||||
| Rachel | 17 | 19 | 18 |
|
||||
| Moyenne | 16,5 | 17,5 | 16,5 |
|
||||
|
||||
|
||||
|
||||
### 8. Selectionner, trier, joindre
|
||||
|
||||
On dispose de la table Hero suivante
|
||||
|
||||
| NumHero | NomHero | VilleHero |
|
||||
| ------- | --------- | --------- |
|
||||
| 0 | Sangoku | Kyoto |
|
||||
| 1 | Naruto | Konoha |
|
||||
| 2 | Luffy | Fuchsia |
|
||||
| 3 | Ryo Saeba | Tokyo |
|
||||
| 4 | Saitama | Ville Z |
|
||||
| 5 | Onizuka | Tokyo |
|
||||
|
||||
Ainsi que celle- ci :
|
||||
|
||||
| NumHero | NomHero | Arme |
|
||||
| ------- | --------- | ----------------- |
|
||||
| 0 | Sangoku | Ki |
|
||||
| 1 | Naruto | Chakra |
|
||||
| 2 | Luffy | Corps |
|
||||
| 3 | Ryo Saeba | Magnum |
|
||||
| 4 | Saitama | Point |
|
||||
| 5 | Onizuka | Tout est une arme |
|
||||
|
||||
|
||||
|
||||
- Renvoyer HeroTokyo, une table extraite de Hero ne contenant que les lignes dont l'attribut VilleHero vaut "Tokyo"
|
||||
|
||||
- Renvoyer HeroAlpha, une table extraite de Hero triée selon l'ordre alphabétique du nom des héros.
|
||||
|
||||
- Renvoyer HeroComplet, une table contenant le nom, la ville ainsi que l'arme favorite des héros.
|
||||
|
||||
- Renvoyer HeroVille, la table contenant le numéro ainsi que la ville des héros.
|
||||
|
||||
- Renvoyer HeroImpair, la table contenant le nom et la ville des hero ne venant pas de Tokyo, et dont le numéro est impair.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
------------
|
||||
|
||||
Auteur : Florian Mathieu
|
||||
|
||||
Licence CC BY NC
|
||||
|
||||
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||
|
||||
63
donnees_en_table/FUSION.md
Normal file
63
donnees_en_table/FUSION.md
Normal file
@@ -0,0 +1,63 @@
|
||||
## Fusion et jointures de tables
|
||||
|
||||
|
||||
|
||||
> Lorsque l'on a de grandes quantités de données à traiter, il arrive que celles-ci soient réparties entre plusieurs tables. Il est donc parfois utile voir necessaire de regrouper toutes ces données dans une seule table. On appelle cela "fusion de tables" ou "jointure de tables"
|
||||
|
||||
### Le programme
|
||||
|
||||

|
||||
|
||||
-----
|
||||
|
||||
- On veut fusionner deux tables selon un attribut commun.
|
||||
- On va donc selectionner dans chaque table la ligne ayant la même valeur pour l'attribut choisi.
|
||||
|
||||
Reprenons une table *Notes* vu précédemment :
|
||||
|
||||
| Prénom | DS1 | DS2 | Projet |
|
||||
| ------------ | ---- | ---- | ------ |
|
||||
| Michelangelo | 12 | 14 | B |
|
||||
| Leonardo | 15 | 16 | A |
|
||||
| Raphael | 10 | 12 | C |
|
||||
| Donatello | 13 | 15 | B |
|
||||
|
||||
Admettons une seconde table et appellons la *Infos* :
|
||||
|
||||
| Prénom | Âge | Mail |
|
||||
| ------------ | ---- | ------------------- |
|
||||
| Michelangelo | 16 | pizza@turtle.com |
|
||||
| Leonardo | 15 | devinci@turtle.com |
|
||||
| Raphael | 14 | chocolat@turtle.com |
|
||||
| Donatello | 17 | donut@turtle.com |
|
||||
|
||||
- On souhaite regrouper les données des deux tables : on peut utiliser l'attribut "Prénom" pour cela.
|
||||
- Il nous faut donc une fonction qui permettent de fusionner les tables:
|
||||
|
||||
```python
|
||||
from copy import deepcopy
|
||||
def fusion(table_1, table_2, cle_1, cle_2=None):
|
||||
if cle_2 is None:
|
||||
cle_2 = cle_1
|
||||
table_finale = []
|
||||
for ligne_1 in table_1:
|
||||
for ligne_2 in table_2:
|
||||
if ligne_1[cle_1] == ligne_2[cle_2]:
|
||||
ligne_finale = deepcopy(ligne_1)
|
||||
for cle in ligne_2:
|
||||
if cle != cle_2:
|
||||
ligne_finale[cle] = ligne_2[cle]
|
||||
table_finale.append(ligne_finale)
|
||||
return table_finale
|
||||
```
|
||||
|
||||
|
||||
|
||||
--------------
|
||||
|
||||
Auteur : Florian Mathieu
|
||||
|
||||
Licence CC BY NC
|
||||
|
||||
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||
|
||||
138
donnees_en_table/MANIPULATION.md
Normal file
138
donnees_en_table/MANIPULATION.md
Normal file
@@ -0,0 +1,138 @@
|
||||
## Operation sur les tables
|
||||
|
||||
|
||||
|
||||
> Une fois que l'on dispose de données en table, nous pouvons alors manipuler ces données et effectuer des recherches ou des tris.
|
||||
|
||||
### Le programme
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### Interrogation de table
|
||||
|
||||
Il arrive fréquemment que l'on souhaite accéder à différentes informations contenues dans une table de données, selon différents critères.
|
||||
|
||||
On suppose que la table en question est une *liste de dictionnaires* - comme vu précédemment - et que cette table sera appelée ***table*** dans la suite de ce cours.
|
||||
|
||||
Chaque ligne est donc un dictionnaire et chaque clé correspondra au nom d'une colonne.
|
||||
|
||||
✏ *Avant de commencer* ✏
|
||||
|
||||
> Pour comparer et vérifier différents critères, nous utiliserons les **opérateurs booléns habituels**
|
||||
>
|
||||
> Nous aurons donc <, >, <=, >=, ==, !=, in, not, and, or, is...
|
||||
|
||||
*Quand on interroge une table, on en construit une nouvelle contenant uniquement les lignes satisfaisant une condition donnée sous la forme d'une **fonction booléenne***.
|
||||
|
||||
#### Selection de lignes
|
||||
|
||||
Pour selectionner des lignes, on peut simplifier l'instruction à :
|
||||
|
||||
```python
|
||||
[ligne for ligne in table if (condition)]
|
||||
```
|
||||
|
||||
Cette instruction va donc générer une liste de dictionnaires verifiant la condition.
|
||||
|
||||
|
||||
|
||||
#### Selection de colonnes
|
||||
|
||||
Selectionner certaines colonnes revient à selectionner certaines clés dans les dictionnaires.
|
||||
|
||||
L'instruction ressemblera donc à :
|
||||
|
||||
```python
|
||||
[{clé:ligne[clé] for clé in ligne if (condition sur clé)} for ligne in table]
|
||||
```
|
||||
|
||||
|
||||
|
||||
--------------
|
||||
|
||||
### Exemples
|
||||
|
||||
Suivant le tableau vu précédemment
|
||||
|
||||
| Prénom | DS1 | DS2 | Projet |
|
||||
| ------------ | ---- | ---- | ------ |
|
||||
| Michelangelo | 12 | 14 | B |
|
||||
| Leonardo | 15 | 16 | A |
|
||||
| Raphael | 10 | 12 | C |
|
||||
| Donatello | 13 | 15 | B |
|
||||
|
||||
On peut écrire une fonction qui va selectionner les personnes selon un critère précis :
|
||||
|
||||
```python
|
||||
def select(table, critere):
|
||||
def test(ligne):
|
||||
return eval(critere)
|
||||
return [ligne for ligne in table if test(ligne)]
|
||||
```
|
||||
|
||||
On peut donc tester cela en sélectionnant les élèves ayant obtenu plus de 15 au DS n°2 :
|
||||
|
||||
- la fonction *eval* permet d'évaluer l'expression contenue dans la cellule *ligne* sous forme d'une chaine de caractères dans un entier.
|
||||
- il est necessaire de bien le préciser dans l'appel de la fonction *select*
|
||||
|
||||
```python
|
||||
>> select(table, "eval(ligne['DS2']) > 15")
|
||||
```
|
||||
|
||||
Quel est le résultat obtenu ?
|
||||
|
||||
----
|
||||
|
||||
Quand on selectionne une ou plusieurs colonnes - attributs - d'une table, on appelle cela une ***projection***.
|
||||
On va donc recréer une table qui ne contiendra que les attributs selectionnés :
|
||||
|
||||
```python
|
||||
def projection (table, liste_attributs):
|
||||
return [{clé:ligne[clé] for clé in ligne if clé in liste_attributs} for ligne in table]
|
||||
```
|
||||
|
||||
Admettons que l'on souhaite ne retenir uniquement que les groupes de projet ainsi que les prénoms des élèves.
|
||||
|
||||
Afin de faire la recherche, on peut écrire l'instruction suivante:
|
||||
|
||||
```python
|
||||
projection (Notes, ['Prénom', 'Projet'])
|
||||
```
|
||||
|
||||
-------
|
||||
|
||||
### Manipulation de tables
|
||||
|
||||
#### Tri de table
|
||||
|
||||
On peut trier une liste avec la fonction ***sorted*** qui possède un argument ***key*** précisant le critère de tri et un argument ***reverse***, un booléen qui permet de choisir un tri croissant (par défaut ) ou décroissant (en précisant reverse = True).
|
||||
|
||||
On peut donc créer une fonction *tri* qui va trier n'importer quelle table en donnant l'attribut choisir pour le tri et en précisant si l'on veut obtenir le tri dans l'ordre décroissant.
|
||||
|
||||
```python
|
||||
def tri (table, attribut, decroit = False):
|
||||
def critere (ligne):
|
||||
return ligne[attribut]
|
||||
return sorted(table, key = critere, reverse =decroit)
|
||||
```
|
||||
|
||||
Exemple : Pour trier dans l'ordre décroissant la table Notes selon les notes du DS n°1, on peut écrire :
|
||||
|
||||
```python
|
||||
>> tri (Notes, 'DS1', True)
|
||||
```
|
||||
|
||||
Quel est le résultat affiché ?
|
||||
|
||||
|
||||
|
||||
---------
|
||||
|
||||
Auteur : Florian Mathieu
|
||||
|
||||
Licence CC BY NC
|
||||
|
||||
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||
|
||||
39
donnees_en_table/PANDAS.md
Normal file
39
donnees_en_table/PANDAS.md
Normal file
@@ -0,0 +1,39 @@
|
||||
## La bibliothèque Pandas
|
||||
|
||||
|
||||
|
||||
La bibliothèque pandas est l'une des plus utilisées pour la gestion de données - appelée également *Data Science*.
|
||||
|
||||
Comme pour toutes les bibliothèques python, on doit l'importer en début de fichier :
|
||||
|
||||
```python
|
||||
import pandas
|
||||
```
|
||||
|
||||
Elle nous permet de lire un fichier CSV de manière beaucoup plus rapide :
|
||||
|
||||
```python
|
||||
pandas.read_csv('nom_du_fichier.csv', delimiter = ';')
|
||||
```
|
||||
|
||||
Mais également un fichier Excel :
|
||||
|
||||
```python
|
||||
pandas.read_excel('nom_du_fichier.xlsx', delimiter = ';')
|
||||
```
|
||||
|
||||
On obtient alors un objet caractéristique de cette bibliothèque qu'on appellera *dataframe* et qu'on peut représenter par un tableau de p-uplets nommés (on utilise alors des noms au lieu d'indices).
|
||||
|
||||
De plus, Pandas permet d'effectuer les manipulations de base que l'on a vu précédemment et même plus :
|
||||
|
||||
Exemple, une table nommée 'table' pourra être utilisée :
|
||||
|
||||
- pour afficher les 10 premières lignes : table.head(10)
|
||||
- pour afficher les en-têtes des colonnes (aussi appelés champs) : table.columns( )
|
||||
|
||||
Enfin, on peut également fusionner deux tables ensemble via cette instruction:
|
||||
|
||||
```python
|
||||
pandas.merge(table_1,table_2)
|
||||
```
|
||||
|
||||
174
donnees_en_table/README.md
Normal file
174
donnees_en_table/README.md
Normal file
@@ -0,0 +1,174 @@
|
||||
## Données en table & Fichiers CSV
|
||||
|
||||
Un chapitre Excel-lent !
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
> Le format CSV est fréquemment utilisé pour échanger des données traitées à l'aide de tableurs ou de logiciels de traitement de bases de données. Ici, notre objectif sera d'apprendre à importer et exporter des données dans Python à l'aide du format CSV.
|
||||
|
||||
En informatique, il est courant de traiter d'importantes quantités d'informations, c'est d'ailleurs le modèle économique de bon nombre de sites et services que vous utilisez quotidiennement : *réseaux sociaux, magasins en ligne, cabinets d'analyses...*
|
||||
|
||||
> **Contenu**: Définition d'un fichier CSV, de données en table, recherche dans une table, fonction de tri <br>**Compétences**: Savoir exporter et importer des données dans un programme Python depuis un fichier CSV
|
||||
|
||||
### Le programme
|
||||
|
||||
<br>
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
### <span style="color:green"> Apport de connaissances </span>
|
||||
|
||||
Les données en table sont une composante essentielle de l'informatique : de très nombreuses entreprises utilisent des tableurs afin de traiter un gros volume de données. <p>
|
||||
|
||||
<p>
|
||||
En maîtrisant cet aspect, nous pourrons commencer à utiliser de petites bases de données pour nos activités, mais également travailler sur des projets plus intéressants.
|
||||
|
||||
---------------
|
||||
|
||||
### <span style="color: green" > Enregistrements </span>
|
||||
|
||||
Un **enregistrement** est une structure de données (différentes ou non) auxquelles on accède grâce à un nom.
|
||||
|
||||
On peut donc représenter les notes d'une élève dans différentes disciplines à l'aide d'un **enregistrement**
|
||||
|
||||
```python
|
||||
{'Nom' : 'Jean-Yves', 'Anglais' : '17', 'NSI' : '18', 'Maths' : '16'}
|
||||
```
|
||||
|
||||
À quelle structure de données python cela vous fait-il penser ?
|
||||
|
||||
✏ *Les clés sont parfois appelées champs ou attributs quand on parle de base de données* ✏
|
||||
|
||||
-----------
|
||||
|
||||
### <span style="color: green" > Fichiers CSV</span>
|
||||
|
||||
Le format CSV (***Comma Separated Value***) est employé poour importer / exporter des données depuis ou vers un tableur.
|
||||
|
||||
C'est une sorte de fichier texte dans lequel chaque ligne correspond à une ligne du tableau.
|
||||
|
||||
Comme son nom l'indique, on sépare les colonnes (et donc les valeurs) par une **virgule**, ou par un **point-virgule**.
|
||||
|
||||
Il permet donc de représenter une liste d'enregistrement ayant les même **champs**.
|
||||
|
||||
→ Exemple de feuille de calcul<br>
|
||||
| Prénom | DS1 | DS2 | Projet |
|
||||
| ------------ | ---- | ---- | ------ |
|
||||
| Michelangelo | 12 | 14 | B |
|
||||
| Leonardo | 15 | 16 | A |
|
||||
| Raphael | 10 | 12 | C |
|
||||
| Donatello | 13 | 15 | B |
|
||||
|
||||
On peut donc représenter ce tableau sous forme d'un fichier "notes.csv" :
|
||||
|
||||
```
|
||||
Prenom; DS1; DS2; Projet
|
||||
Michelangelo; 12; 14; B
|
||||
Leonardo; 15; 16; A
|
||||
Raphael; 10; 12; C
|
||||
Donatello; 13; 15; B
|
||||
```
|
||||
|
||||
Chaque ligne est un ***enregistrement***. La première ligne définit les ***attributs*** de chaque enregistrement.
|
||||
|
||||
------------------
|
||||
|
||||
### <span style="color: green" > Implémentation en Python</span>
|
||||
|
||||
On peut choisir de représenter en Python les fichiers **CSV** par des listes de dictionnaires dont les clés sont les noms des colonnes.
|
||||
|
||||
Par exemple, avec le tableau du dessus, cela donne :
|
||||
|
||||
```python
|
||||
Notes = [{'Prénom' : 'Michelangelo', 'DS1' : '12', 'DS2' : '14', 'Projet' : 'B'},
|
||||
{'Prénom' : 'Leonardo', 'DS1' : '15', 'DS2' : '16', 'Projet' : 'A'},
|
||||
{'Prénom' : 'Raphael', 'DS1' : '10', 'DS2' : '12', 'Projet' : 'C'},
|
||||
{'Prénom' : 'Donatello', 'DS1' : '13', 'DS2' : '15', 'Projet' : 'B'}]
|
||||
```
|
||||
|
||||
On peut utiliser le vocabulaire décrivant une feuille de calcul de tableau :
|
||||
|
||||
- Une table est une liste de dictionnaire, ici **Notes**
|
||||
- Chaque ligne est un dictionnaire et correspond à un enregistrement, par exemple **Notes[0]**
|
||||
- Chaque cellule contient la valeur d'une clé du dictionnaire, par exemple **Notes[0] ['DS2']**
|
||||
|
||||
-----------------
|
||||
|
||||
### <span style = "color : green">Import d'un fichier CSV </span>
|
||||
|
||||
Il existe un module Python nommé *CSV* qui permet de manipuler ces fichiers.
|
||||
|
||||
On va donc créer une liste de dictionnaires, soit un par ligne de la table.
|
||||
|
||||
```python
|
||||
import csv
|
||||
|
||||
def import_csv(fichier):
|
||||
lecteur = csv.DictReader(open(fichier + '.csv', 'r'))
|
||||
return [dict(ligne) for ligne in lecteur ]
|
||||
```
|
||||
|
||||
Ce qui nous donne :
|
||||
|
||||
```python
|
||||
[{'Prénom' : 'Michelangelo', 'DS1' : '12', 'DS2' : '14', 'Projet' : 'B'},
|
||||
{'Prénom' : 'Leonardo', 'DS1' : '15', 'DS2' : '16', 'Projet' : 'A'},
|
||||
{'Prénom' : 'Raphael', 'DS1' : '10', 'DS2' : '12', 'Projet' : 'C'},
|
||||
{'Prénom' : 'Donatello', 'DS1' : '13', 'DS2' : '15', 'Projet' : 'B'}]
|
||||
```
|
||||
|
||||
|
||||
|
||||
On peut également manipuler les fichiers afin de lire puis de le transformer en tableau :
|
||||
|
||||
```python
|
||||
import csv
|
||||
def import_csv2():
|
||||
resultat = []
|
||||
with open('notes.csv',newline = '') as csvfile:
|
||||
s = csv.reader(csvfile,delimiter = ';')
|
||||
for i in s:
|
||||
resultat.append(i)
|
||||
return resultat
|
||||
```
|
||||
|
||||
On obtient donc ici la liste suivante :
|
||||
|
||||
```python
|
||||
resultat = [['Prenom', 'DS1', 'DS2', 'Projet'], ['Michelangelo', '12', '14', 'B'],
|
||||
['Leonardo', '15','16', 'A'], ['Raphael', '10', '12', 'C'], ['Donatello' '13', '15', 'B']]
|
||||
```
|
||||
|
||||
-------
|
||||
|
||||
### <span style = "color : green">Export d'un fichier CSV </span>
|
||||
|
||||
Pour exporter une table vers un **fichier CSV** - *comprendre, créer un fichier csv depuis une table python* - on va entrer le nom de la table sous forme de chaine de caracteres. On donnera l'ordre des colonnes sous forme de liste d'attributs.
|
||||
|
||||
```python
|
||||
def vers_csv(nom, ordre):
|
||||
with open(nom + '.csv', 'w') as fic:
|
||||
dic = csv.DictWriter(fic, fieldnames = ordre)
|
||||
table = eval(nom)
|
||||
dic.writeheader() # première ligne, celle des attributs
|
||||
for ligne in table:
|
||||
dic.writerow(ligne) # ajoute les lignes de la table
|
||||
return None
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----------
|
||||
|
||||
Auteur : Florian Mathieu
|
||||
|
||||
Licence CC BY NC
|
||||
|
||||
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||
|
||||
BIN
donnees_en_table/assets/bo.png
Normal file
BIN
donnees_en_table/assets/bo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 403 KiB |
BIN
donnees_en_table/assets/bo_1.png
Normal file
BIN
donnees_en_table/assets/bo_1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 132 KiB |
BIN
donnees_en_table/assets/bo_2.png
Normal file
BIN
donnees_en_table/assets/bo_2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 188 KiB |
BIN
donnees_en_table/assets/bo_3.png
Normal file
BIN
donnees_en_table/assets/bo_3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
BIN
donnees_en_table/assets/meme.gif
Normal file
BIN
donnees_en_table/assets/meme.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
11
donnees_en_table/import_csv.py
Normal file
11
donnees_en_table/import_csv.py
Normal file
@@ -0,0 +1,11 @@
|
||||
import csv
|
||||
|
||||
def import_csv(fichier):
|
||||
lecteur = csv.DictReader(open(fichier + '.csv', 'r'))
|
||||
return [dict(ligne) for ligne in lecteur ]
|
||||
|
||||
|
||||
def select(table, critere):
|
||||
def test(ligne):
|
||||
return eval(critere)
|
||||
return [ligne for ligne in table if test(ligne)]
|
||||
BIN
donnees_en_table/memo.jpeg
Normal file
BIN
donnees_en_table/memo.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
5
donnees_en_table/tortues.csv
Normal file
5
donnees_en_table/tortues.csv
Normal file
@@ -0,0 +1,5 @@
|
||||
Prénom;DS1;DS2;Projet
|
||||
Michelangelo;12;14;B
|
||||
Leonardo;15;16;A
|
||||
Raphael;10;12;C
|
||||
Donatello;13;15;B
|
||||
|
Reference in New Issue
Block a user