2 votes

Python : Lire un CSV vers une liste n'est pas plus rapide qu'un CSV vers un dict avec vérification des clés ?

En cherchant une alternative au tri SAS, j'ai décidé d'essayer Python 2.6 (les deux sur le même serveur Unix). Le tri d'une table étroite de 500mln lignes prend 20 minutes en SAS. J'ai exporté 20% de la table (100mln lignes) vers un fichier CSV qui ressemble à ceci :

X|||465097434|912364420|0.00|0.00|0.00|0.00|1.00|01FEB2016|X|0|0
X|||465097434|912364420|0.00|0.00|0.00|0.00|0.00|02FEB2016|X|0|0
X|||465097434|912364420|0.00|0.00|0.00|0.00|2.00|03FEB2016|X|0|0
X|||465097434|912364421|0.00|0.00|0.00|0.00|3.00|04FEB2016|X|0|0
X|||465097434|912364421|0.00|0.00|0.00|0.00|6.00|05FEB2016|X|0|0
X|||965097411|912364455|0.00|0.00|0.00|0.00|4.00|04FEB2016|X|0|0
X|||965097411|912364455|0.00|0.00|0.00|0.00|1.00|05FEB2016|X|0|0

Le but est de le trier par 5e et 11e colonnes. J'ai d'abord vérifié la vitesse de lecture du fichier par python avec le code :

from __future__ import print_function
import csv
import time
linesRead=0
with open ('/path/to/file/CSV_FILE.csv','r') as dailyFile:
    allLines=csv.DictReader(dailyFile, delimiter='|')
    startTime=time.time()
    for row in allLines:
        linesRead += 1
        if (linesRead) % 1000000 == 0:
            print(linesRead, ": ", time.time()-startTime, " sec.")
            startTime=time.time()

Et le résultat est qu'il faut 6 secondes pour lire chaque million de lignes.

1000000 :  6.6301009655  sec.
2000000 :  6.33900094032  sec.
3000000 :  6.26246404648  sec.
4000000 :  6.56919789314  sec.
5000000 :  6.17433309555  sec.
...
98000000 :  6.61627292633  sec.
99000000 :  7.14683485031  sec.
100000000 :  7.08069109917  sec.

J'ai donc étendu le code pour le charger dans un dictionnaire (clé=colonne 5 (identifiant du compte)) et la valeur est une liste de listes (lignes) pour ce compte. C'est là que je me suis rendu compte que le chargement des listes dans un dictionnaire ralentit lorsque le dictionnaire s'agrandit (ce qui est assez logique puisqu'il y a un nombre croissant de clés à vérifier) :

import csv
import time
myDictionary = {}
linesRead=0
with open ('/path/to/file/CSV_FILE.csv','r') as dailyFile:
    allLines=csv.DictReader(dailyFile, delimiter='|')
    startTime=time.time()
    for row in allLines:
        accountID=row['account_id'].strip('\'')
        linesRead += 1
        if accountID in myDictionary:
            myDictionary[accountID].append([row['date'].strip('\''), row['balance1'], row['balance2'], row['balance3']])
        else:
            myDictionary[accountID]=[]
        if (linesRead) % 1000000 == 0:
            print(linesRead, ": ", time.time()-startTime, " sec.")
            startTime=time.time()

Et les heures sont :

1000000, ': ', 8.9685721397399902, ' sec.')
(2000000, ': ', 10.344831943511963, ' sec.')
(3000000, ': ', 11.637137889862061, ' sec.')
(4000000, ': ', 13.024128913879395, ' sec.')
(5000000, ': ', 13.508150815963745, ' sec.')
(6000000, ': ', 14.94166088104248, ' sec.')
(7000000, ': ', 16.307464122772217, ' sec.')
(8000000, ': ', 17.130259990692139, ' sec.')
(9000000, ': ', 17.54616379737854, ' sec.')
(10000000, ': ', 20.254321813583374, ' sec.')
...
(39000000, ': ', 55.350741863250732, ' sec.')
(40000000, ': ', 56.762171983718872, ' sec.')
(41000000, ': ', 57.876702070236206, ' sec.')
(42000000, ': ', 54.548398017883301, ' sec.')
(43000000, ': ', 60.040227890014648, ' sec.')

ce qui signifie qu'il n'y a aucune chance de charger 500 millions de lignes dans un temps raisonnable (le dernier million sur 500 millions serait chargé en 600 secondes). Je pense que la partie la plus lente de chaque itération est la vérification de l'existence des clés dans le dictionnaire :

if accountID in myDictionary:

J'ai donc transformé le dictionnaire en liste en espérant que l'ajout simple sera beaucoup plus rapide :

with open ('/path/to/file/CSV_FILE.csv','r') as dailyFile:
    allLines=csv.DictReader(dailyFile, delimiter='|')
    startTime=time.time()
    for row in allLines:
        linesRead += 1
        myList.append([row['account_id'].strip('\''), row['date'].strip('\''), row['balance1'], row['balance2'], row['balance3']])
        if (linesRead) % 1000000 == 0:
            print(linesRead, ": ", time.time()-startTime, " sec.")
            startTime=time.time()

Malheureusement, les performances n'ont pas augmenté du tout :

1000000 :  9.15476489067  sec.
2000000 :  10.3512279987  sec.
3000000 :  12.2600080967  sec.
4000000 :  13.5473120213  sec.
5000000 :  14.8431830406  sec.
6000000 :  16.5556428432  sec.
7000000 :  17.6754620075  sec.
8000000 :  19.1299819946  sec.
9000000 :  19.7615978718  sec.
10000000 :  22.5903761387  sec.

Le chargement d'une liste ne devrait-il pas être beaucoup plus rapide que le chargement d'un dictionnaire avec vérification des clés à l'entrée ?

Est-ce que j'utilise mal python pour traiter ce type de données ? Pour comparer, j'ai trié le fichier avec la commande unix sort :

$ date ; sort  -t'|' -k5,9 CSV_FILE.csv > delete.txt; date;
Sun Jul 23 18:46:16 CEST 2017
Sun Jul 23 19:06:53 CEST 2017

Et cela a pris 20 minutes pour faire le travail. Alors qu'en python je n'arrivais pas à charger en mémoire les données.

1voto

zipa Points 16012

Je suggère pandas car il devrait être plus rapide. Voici le code pour la lecture csv fichier :

import pandas as pd
df = pd.read_csv('/path/to/file/CSV_FILE.csv', sep='|')

Et pour le trier, vous pouvez utiliser :

df.sort_values([4, 10], ascending=[True,True], inplace=True)

Note : la première liste est constituée des noms de colonnes et les autres arguments sont explicites.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X