Groupement de données à partir d'un CSV sur python

Résolu/Fermé
Gsxr2 - Modifié le 17 janv. 2023 à 15:44
 Gsxr2 - 19 janv. 2023 à 09:10

Bonjour,

Je traite un fichier CSV qui dispose de plusieurs colonnes, voici un exemple :

74a6a0a2a;info1;Groupe1
e2646926e;info2;Groupe2
7d72c9d9e;info3;Groupe1
568t12d55;info4;Groupe1
t47110dr5;info5;Groupe2

L'idée serait de séparer les données pour créer des groupes en fonction de la troisième colonne. Dans cette colonne, il peut y avoir n'importe quelle appellation, j'ai mis GroupeX pour l'exemple. Chaque groupe correspondrait à un nouveau fichier .csv différent.

Par exemple, en filtrant les données, je dois créer un fichier Groupe1.csv qui contient les lignes 1,2 et 3.

J'aimerais faire ce travail sur python, le langage que je connais le mieux. Cependant, je voulais avoir une idée des différentes techniques possibles pour faire ceci et choisir la meilleure.

La difficulté, pour moi, est qu'on ne connaît pas forcément le nom qui est donné en 3ème colonne.

Pour vous, quel est le meilleur moyen pour développer cette fonction ?

Est-ce que je suis obligé d'utiliser des algorithmes ML pour cela ?

3 réponses

mamiemando Messages postés 33446 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 20 décembre 2024 7 812
Modifié le 17 janv. 2023 à 19:48

Bonjour,

Réponse courte : fais comme dit yg_be dans #5 ce qui ressemble au final à ceci :

dispatch.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import csv
from io import StringIO

def dispatch_csv(input_data: iter, key_columns: tuple, delimiter: str = ";"):
    map_group_file = dict()
    reader = csv.reader(input_data, delimiter=delimiter)
    for row in reader:
        print(row)
        group = tuple(row[i] for i in key_columns)
        f_out = map_group_file.get(group)
        if f_out is None:
            filename = "_".join(group) + ".csv"
            map_group_file[group] = f_out = open(filename, "w")
        if f_out:
            print(delimiter.join(row), file=f_out)
    for f_out in map_group_file.values():
        f_out.close()

key_columns = (2,) # La colonne 3 sert de clé

# Avec un fichier d'entrée CSV
#with open("test.csv") as f_in:
#    dispatch_csv(f_in, key_columns, ";")

# Avec une chaîne de caractères
input_csv = """74a6a0a2a;info1;Groupe1
e2646926e;info2;Groupe2
7d72c9d9e;info3;Groupe1
568t12d55;info4;Groupe1
t47110dr5;info5;Groupe2"""

dispatch_csv(StringIO(input_csv), key_columns, delimiter=";")

Exemple :

(mando@silk) (~) $ python3 dispatch.py

(mando@silk) (~) $ cat Groupe1.csv 
74a6a0a2a;info1;Groupe1
7d72c9d9e;info3;Groupe1
568t12d55;info4;Groupe1

(mando@silk) (~) $ cat Groupe2.csv 
e2646926e;info2;Groupe2
t47110dr5;info5;Groupe2

Réponse détaillée

Tout d'abord pas besoin de ML et de K-means. J'invite d'ailleurs à toujours réfléchir avant d'envisager du ML car ce sont des algorithmes souvent très énergivores, pas toujours adaptés, et souvent appliqués sur des cas d'usages qui n'en ont pas le besoin (c'est ton cas).

Pour traiter ton problème, il y a plusieurs manières de faire :

Concernant la lecture du fichier, tu peux :

  • lire le fichier ligne par ligne, faire un split sur le séparateur du csv (voir #3)
  • utiliser le module csv (voir #4)
  • utiliser pandas.read_csv (mais ça c'est dans les grands jours, pandas c'est l'artillerie lourde)

Concernant la formation des groupes (sous réserve qu'on veuille en faire, voir plus bas) :

  • utiliser un dictionnaire si la donnée dans la colonne est hashable (c'est le cas des entiers et des chaînes de caractères). Si tu considères plusieurs colonnes pour former tes agrégat, tu peux rassembler les valeurs impliquées dans un tuple (mais pas dans une liste un ou un set car ils ne sont pas hashable)
  • utiliser pandas.groupby (mais ça c'est dans les grands jours, pandas c'est l'artillerie lourde, mince je l'ai déjà dit)

Concernant l'écriture des csv deux stratégies :

  • Sans agrégat : on lit chaque ligne et on les dispatche vers le bon fichier en fonction de la colonne (ou les colonnes) permettant de déterminer le groupe.
    • écrire les différents fichiers en les ouvrant / fermant chaque fois que le besoin s'en fait sentir. Ce n'est pas terrible comme le souligne à juste titre yg_be dans #5.
    • écrire les différents fichiers en les ouvrant de manière opportuniste, sans les fermer (ce qui impose d'utiliser open sans "with", car le contextmanager correspondant ferme le descripteur de fichier quand on quitte le with) pendant toute le traitement du fichier, et les fermer tous une fois le fichier d'entrée complètement lu
  • Avec agrégat :
    • Une fois les agrégats constitués après avoir lu le fichier d'entrée intégralement, créer un fichier par agrégat en y écrivant les valeurs concernées

La méthode d'yg_be reste la meilleure pour de gros volume de données, car elle évite de maintenir une structure d'agrégat en mémoire pendant la lecture du fichier. En terme d'entrée sortie elle minimise les ouvertures / fermetures de fichier. Elle force en contrepartie à maintenir un dictionnaire qui à chaque identifiant d'agrégat associe un descripteur de fichier.

Bonne chance

1

Bonjour,

Super intéressant, merci cela m'a beaucoup apporté !

C'est d'ailleurs ce type de réflexion que j'aimerais avoir, afin de pouvoir comparer chaque possibilité avec les avantages/inconvénients.

0
mamiemando Messages postés 33446 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 20 décembre 2024 7 812 > Gsxr2
18 janv. 2023 à 17:49

Merci pour ce retour très positif. As-tu d'autres questions liées au sujet initial, ou pouvons nous basculer ce sujet en résolu ?

0
Gsxr2 > mamiemando Messages postés 33446 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 20 décembre 2024
19 janv. 2023 à 09:10

C'est tout bon pour moi :)

0
yg_be Messages postés 23405 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 20 décembre 2024 Ambassadeur 1 557
17 janv. 2023 à 15:45

bonjour,

Quels moyens as-tu envisagé?

0

Bonjour,

A vrai dire, je m'étais dit que ça allait être facile, alors j'avais déjà commencé à coder quelques lignes. Mon idée était de récupérer les valeurs en colonne 3 et de créer un groupe lorsque la valeur n'a pas encore été vue.

Par exemple, si je reprends l'exemple :

74a6a0a2a;info1;Groupe1
e2646926e;info2;Groupe2
7d72c9d9e;info3;Groupe1
568t12d55;info4;Groupe1
t47110dr5;info5;Groupe2

Sur la première ligne, je créé un fichier .csv avec la ligne correspondante, ensuite je créé un deuxième fichier .csv pour la deuxième ligne car la valeur de la 3ème colonne ne correspond pas avec la première et ainsi de suite.

Je pense que cela peut fonctionner, mais je me posais quand-même la question de, est-ce que c'est vraiment la meilleure solution ?

Ensuite, je sais que je peux utiliser des algorithmes de ML pour faire ceci, mais même chose est-ce vraiment la bonne manière de faire ? (j'ai déjà utilisé k-means et rcf dans d'autres projets)

A savoir que le fichier CSV ne fera pas 5 lignes mais plutôt 500.

0

Salut, un dictionnaire convient à faire cela.

.

En partant de ton texte.

.

text = '''
74a6a0a2a;info1;Groupe1
e2646926e;info2;Groupe2
7d72c9d9e;info3;Groupe1
568t12d55;info4;Groupe1
t47110dr5;info5;Groupe2
'''

groups = {}
for line in text.strip().splitlines():
   rows = line.split(';')
   try:
      groups[rows[2]].append(tuple(rows[0:2]))
   except KeyError:
      groups[rows[2]] = [(tuple(rows[0:2]))]

from pprint import pprint
pprint(groups)

.

Il te faut l'adapter à la lecture de ton csv, ce qui n'est pas bien difficile.

Puis ensuite, il te faudra écrire tes nouveaux csv avec pour nom de fichier, les clés du dict, et pour contenu les valeurs. Cela ne devrait pas être très difficile à réaliser.

0

Merci pour votre réponse, de mon côté j'avais fait ceci qui fonctionne :

with open('test.csv') as f:
    reader = csv.reader(f,delimiter=';')
    value_observed = []
    for rows in reader:
        if rows:
            tag = rows[9]
            if tag not in value_observed:
                value_observed.append(tag)
                with open(tag + '.csv', 'w') as f:
                    writer = csv.writer(f, delimiter=';')
                    writer.writerow(rows)
            else:
                with open(tag + '.csv', 'a') as f:
                    writer = csv.writer(f, delimiter=';')
                    writer.writerow(rows)

Mais je vais étudier votre réponse :)

0
yg_be Messages postés 23405 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 20 décembre 2024 1 557 > Gsxr2
17 janv. 2023 à 17:13

Le désavantage de ton approche, c'est que tu ouvres et refermes à chaque fois un de tes fichiers de destination.  Il serait préférable, pour chaque tag, de mémoriser, par exemple dans un dictionaire, le writer associé.

Un détails, ta variable "rows" mémorise une seule ligne, pourquoi pas "row".

0
Gsxr2 > yg_be Messages postés 23405 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 20 décembre 2024
17 janv. 2023 à 17:44

Ok, je vois !

Merci pour ces explications

0