web-dev-qa-db-fra.com

Comment puis-je accélérer la lecture de plusieurs fichiers et la mise des données dans un cadre de données?

J'ai un certain nombre de fichiers texte, disons 50, que je dois lire dans une base de données volumineuse. Pour le moment, j'utilise les étapes suivantes.

  1. Lisez chaque fichier et vérifiez quelles sont les étiquettes. Les informations dont j'ai besoin sont souvent contenues dans les premières lignes. Les mêmes étiquettes ne font que répéter pour le reste du fichier, avec différents types de données listés à chaque fois.
  2. Créez une image de données avec ces étiquettes.
  3. Lisez à nouveau le fichier et remplissez le cadre de données avec des valeurs.
  4. Concaténer ce cadre de données avec un cadre de données maître.

Cela fonctionne assez bien pour les fichiers de la taille de 100 Ko - quelques minutes, mais à 50 Mo, cela prend juste des heures et n’est pas pratique. 

Comment puis-je optimiser mon code? En particulier -

  1. Comment identifier les fonctions qui prennent le plus de temps et que je dois optimiser? Est-ce la lecture du fichier? Est-ce l'écriture dans le cadre de données? Où est passé mon programme?
  2. Devrais-je envisager le multithreading ou le multitraitement?
  3. Puis-je améliorer l'algorithme?
    • Peut-être lire tout le fichier en une fois dans une liste, plutôt que ligne par ligne,
    • Analyser les données en morceaux/fichier entier, plutôt que ligne par ligne,
    • Attribuez des données à la structure de données en morceaux/une seule fois, plutôt que ligne par ligne.
  4. Y a-t-il autre chose que je puisse faire pour que mon code soit exécuté plus rapidement?

Voici un exemple de code. Mon propre code est un peu plus complexe, car les fichiers texte sont plus complexes, de sorte que je dois utiliser environ 10 expressions régulières et plusieurs boucles while pour lire les données et les allouer au bon emplacement dans le bon tableau. Pour simplifier le MWE, je n’ai pas non plus utilisé d’étiquettes répétitives dans les fichiers d’entrée du MWE. Par conséquent, je voudrais lire le fichier deux fois sans raison. J'espère que cela à du sens!

import re
import pandas as pd

df = pd.DataFrame()
paths = ["../gitignore/test1.txt", "../gitignore/test2.txt"]
reg_ex = re.compile('^(.+) (.+)\n')
# read all files to determine what indices are available
for path in paths:
    file_obj = open(path, 'r')
    print file_obj.readlines()

['a 1\n', 'b 2\n', 'end']
['c 3\n', 'd 4\n', 'end']

indices = []
for path in paths:
    index = []
    with open(path, 'r') as file_obj:
        line = True
        while line:
            try:
                line = file_obj.readline()
                match = reg_ex.match(line)
                index += match.group(1)
            except AttributeError:
                pass
    indices.append(index)
# read files again and put data into a master dataframe
for path, index in Zip(paths, indices):
    subset_df = pd.DataFrame(index=index, columns=["Number"])
    with open(path, 'r') as file_obj:
        line = True
        while line:
            try:
                line = file_obj.readline()
                match = reg_ex.match(line)
                subset_df.loc[[match.group(1)]] = match.group(2)
            except AttributeError:
                pass
    df = pd.concat([df, subset_df]).sort_index()
print df

  Number
a      1
b      2
c      3
d      4

Mes fichiers d'entrée:

test1.txt

a 1
b 2
end

test2.txt

c 3
d 4
end
36
bluprince13

Il s'avère que créer d'abord un DataFrame vierge, rechercher dans l'index le bon emplacement pour une ligne de données, puis mettre à jour uniquement cette ligne du DataFrame est un processus extrêmement coûteux en temps et en argent.

Une façon beaucoup plus rapide de procéder consiste à lire le contenu du fichier d'entrée dans une structure de données primitive telle qu'une liste de listes ou une liste de plans, puis à la convertir en un DataFrame.

Utilisez des listes lorsque toutes les données que vous lisez sont dans les mêmes colonnes. Sinon, utilisez dicts pour indiquer explicitement à quelle colonne chaque bit de données doit aller.

Mise à jour du 18 janvier: Ceci est lié à Comment analyser des fichiers texte complexes en utilisant Python? J’ai également écrit un article de blog expliquant comment analyser des fichiers complexes pour les débutants .

1
bluprince13

Avant de retirer le marteau multitraitement, vous devez d’abord effectuer un profilage. Utilisez cProfile pour parcourir rapidement et identifier les fonctions qui prennent beaucoup de temps. Malheureusement, si vos lignes sont toutes dans un seul appel de fonction, elles apparaîtront comme des appels de bibliothèque. line_profiler est meilleur mais prend un peu plus de temps d'installation. 

REMARQUE. Si vous utilisez ipython, vous pouvez utiliser% timeit (commande magique pour le module timeit) et% prun (commande magique pour le module de profil) à la fois pour chronométrer vos instructions et fonctions. Une recherche sur Google montrera quelques guides.

Pandas est une bibliothèque merveilleuse, mais j’ai été victime occasionnellement de l’utiliser mal avec des résultats atroces. En particulier, méfiez-vous des opérations append ()/concat (). Cela pourrait être votre goulot d’étranglement, mais vous devriez profiler pour être sûr. En règle générale, les opérations numpy.vstack () et numpy.hstack () sont plus rapides si vous n'avez pas besoin d'aligner l'index/la colonne. Dans votre cas, il semblerait que vous puissiez vous débrouiller avec des séries ou des Numpy ndarys 1D qui permettent de gagner du temps.

En passant, un bloc try en python est beaucoup plus lent, souvent 10 fois ou plus que la recherche d’une condition non valide. Veillez donc à en avoir absolument besoin lorsque vous le collez dans une boucle pour chaque ligne. C’est probablement l’autre grossier du temps; J'imagine que vous avez bloqué le bloc try pour vérifier AttributeError en cas d'échec de match.group (1). Je vérifierais d'abord pour une correspondance valide. 

Même ces petites modifications devraient être suffisantes pour que votre programme s'exécute beaucoup plus rapidement avant d'essayer quelque chose de radical comme le multitraitement. Ces bibliothèques Python sont géniales mais apportent un nouvel ensemble de défis à relever.

15
clocker

Je l'ai souvent utilisé car il s'agit d'une mise en œuvre particulièrement simple du multitraitement. 

import pandas as pd
from multiprocessing import Pool

def reader(filename):
    return pd.read_Excel(filename)

def main():
    pool = Pool(4) # number of cores you want to use
    file_list = [file1.xlsx, file2.xlsx, file3.xlsx, ...]
    df_list = pool.map(reader, file_list) #creates a list of the loaded df's
    df = pd.concat(df_list) # concatenates all the df's into a single df

if __== '__main__':
    main()

En utilisant cela, vous devriez pouvoir augmenter considérablement la vitesse de votre programme sans trop de travail. Si vous ne savez pas combien de processeurs vous avez, vous pouvez vérifier en extrayant votre Shell et en tapant

echo %NUMBER_OF_PROCESSORS%

EDIT: Pour accélérer encore plus cette opération, envisagez de modifier vos fichiers au format csv et d’utiliser la fonction pandas pandas.read_csv

11
Некто

Tout d'abord, si vous lisez le fichier plusieurs fois, il semble que ce soit le goulot d'étranglement. Essayez de lire le fichier en un objet chaîne, puis d’utiliser cStringIO dessus plusieurs fois. 

Deuxièmement, vous n'avez pas vraiment montré de raison de construire les index avant de lire tous les fichiers. Même si vous le faites, pourquoi utilisez-vous des Pandas pour l'IO? Il semble que vous puissiez le construire dans des structures de données python normales (peut-être en utilisant __slots__), puis le placer dans le cadre de données maître. Si vous n'avez pas besoin de l'index X du fichier avant de lire le fichier Y (comme le suggère votre deuxième boucle), il vous suffit de parcourir les fichiers une fois.

Troisièmement, vous pouvez soit utiliser la variable simple split/strip sur les chaînes pour extraire les jetons séparés par des espaces, ou si c'est plus compliqué (il existe des guillemets), utilisez le module CSV de la bibliothèque standard de Python. Tant que vous n’indiquez pas comment vous construisez réellement vos données, il est difficile de proposer une solution à ce problème.

Ce que vous avez montré jusqu’à présent peut être fait assez rapidement avec le simple 

for path in paths:
    data = []
    with open(path, 'r') as file_obj:
        for line in file_obj:
            try:
                d1, d2 = line.strip().split()
            except ValueError:
                pass
            data.append(d1, int(d2)))
    index, values = Zip(*data)
    subset_df = pd.DataFrame({"Number": pd.Series(values, index=index)})

Voici la différence de minutage lorsque je tourne sur une machine virtuelle avec un espace disque non préalloué (les fichiers générés ont une taille d'environ 24 Mo):

import pandas as pd
from random import randint
from itertools import combinations
from posix import fsync


outfile = "indexValueInput"

for suffix in ('1', '2'):
    with open(outfile+"_" + suffix, 'w') as f:
        for i, label in enumerate(combinations([chr(i) for i in range(ord('a'), ord('z')+1)], 8)) :
            val = randint(1, 1000000)
            print >>f, "%s %d" % (''.join(label), val)
            if i > 3999999:
                break
        print >>f, "end"
        fsync(f.fileno())

def readWithPandas():
    data = []
    with open(outfile + "_2", 'r') as file_obj:
        for line in file_obj:
            try:
                d1, d2 = str.split(line.strip())
            except ValueError:
                pass
            data.append((d1, int(d2)))
    index, values = Zip(*data)
    subset_df = pd.DataFrame({"Numbers": pd.Series(values, index=index)})

def readWithoutPandas():
    data = []
    with open(outfile+"_1", 'r') as file_obj:
        for line in file_obj:
            try:
                d1, d2 = str.split(line.strip())
            except ValueError:
                pass
            data.append((d1, int(d2)))
    index, values = Zip(*data)

def time_func(func, *args):
    import time
    print "timing function", str(func.func_name)
    tStart = time.clock()
    func(*args)
    tEnd = time.clock()
    print "%f seconds " % (tEnd - tStart)

time_func(readWithoutPandas)
time_func(readWithPandas)

Les temps qui en résultent sont:

timing function readWithoutPandas
4.616853 seconds 
timing function readWithPandas
4.931765 seconds 

Vous pouvez essayer ces fonctions avec votre accumulation d'index et voir quelle serait la différence de temps. Il est presque certain que le ralentissement provient de plusieurs lectures de disque. Et comme Pandas ne mettra pas beaucoup de temps à construire votre base de données à partir d’un dictionnaire, il est préférable de comprendre comment construire votre index en Python pur avant de transmettre les données à Pandas. Cependant, les données lues et la constitution de l'index sur 1 disque sont-elles lues.

J'imagine qu'un autre inconvénient est que, si vous imprimez depuis l'intérieur de votre code, attendez-vous à ce que cela prenne énormément de temps. Le temps qu'il faut pour écrire du texte en clair sur un tty est inférieur au temps qu'il faut pour lire/écrire sur le disque.

3

Considérations générales sur le python:

Tout d’abord sur la mesure du temps, vous pouvez utiliser un tel extrait:

from time import time, sleep


class Timer(object):
    def __init__(self):
        self.last = time()


    def __call__(self):
        old = self.last
        self.last = time()
        return self.last - old

    @property
    def elapsed(self):
        return time() - self.last



timer = Timer()

sleep(2)
print timer.elapsed
print timer()
sleep(1)
print timer()

Ensuite, vous pouvez analyser le code en cours d’exécution plusieurs fois et vérifier le diff.

À propos de cela, je commente en ligne:

with open(path, 'r') as file_obj:
    line = True
    while line: #iterate on realdines instead.
        try:
            line = file_obj.readline()
            match = reg_ex.match(line)
            index += match.group(1)
            #if match:
            #    index.extend(match.group(1)) # or extend

        except AttributeError:
            pass

Votre code précédent n'était pas vraiment Pythonic, vous pouvez essayer/sauf . Ensuite, essayez uniquement de faire dans le minimum de lignes possibles.

Les mêmes avis s'appliquent au deuxième bloc de code.

Si vous avez besoin de lire les mêmes fichiers plusieurs fois. vous pouvez les stocker dans RAM à l'aide de StringIO ou plus facilement conserver un dict {path: content} que vous ne lisez qu'une fois.

Les regex Python sont connus pour être lents, vos données semblent assez simples, vous pouvez envisager d’utiliser des méthodes de division et de bande sur vos lignes d’entrée.

 striped=[l.split() for l in [c.strip() for c in file_desc.readlines()] if l] 

Je vous recommande de lire ceci: https://Gist.github.com/JeffPaine/6213790 la vidéo correspondante est ici https://www.youtube.com/watch?v=OSGv2VnC0go

2
cgte

Vous pouvez importer le modèle de multitraitement et utiliser un pool de processus de travail pour ouvrir simultanément plusieurs fichiers en tant qu'objets de fichier, ce qui accélère le chargement de votre code. Pour tester l'heure, importez la fonction datetime et utilisez le code suivant:

import datetime
start=datetime.datetime.now()

#part of your code goes here

execTime1=datetime.datetime.now()
print(execTime1-start)

#the next part of your code goes here

execTime2=datetime.datetime.now()
print(execTime2-execTime1)

En ce qui concerne la lecture de chaque fichier une seule fois, envisagez d'utiliser un autre script de multitraitement pour créer une liste de lignes dans chaque fichier, afin de pouvoir rechercher une correspondance sans opération d'entrée-sortie de fichier.

1
Ron Distante

Lisez les fichiers directement dans une base de données pandas à l’aide de pd.read_csv. Pour créer votre sous-ensemble_df. Utilisez des méthodes telles que skipfooter pour ignorer les lignes à la fin du fichier dont vous n’avez pas besoin. Il existe de nombreuses autres méthodes disponibles pouvant remplacer certaines des fonctions de boucle regex que vous utilisez, telles que error_bad_lines et skip_blank_lines.

Ensuite, utilisez les outils fournis par les pandas pour nettoyer les données inutiles.

Cela vous permettra de lire l’ouverture et de lire le fichier une seule fois.

1
blindChicken

Votre code ne fait pas ce que vous décrivez. 

Question : 1. Lisez tous les fichiers et vérifiez les étiquettes. Les informations dont j'ai besoin sont souvent contenues dans les premières lignes. 

Mais vous lisez le fichier entier , pas seulement quelques lignes .Cela vous permet de lire les fichiers deux fois !

Question : 2. Relisez le fichier et remplissez le cadre de données avec des valeurs. 

Vous écrasez df['a'|'b'|'c'|'d'] dans la boucle encore et encore, ce qui est inutile
Je crois que ce n'est pas ce que vous voulez.
Cela fonctionne pour les données données dans Question, mais pas si vous devez traiter n valeurs. 


Proposition avec une logique différente:

data = {}
for path in paths:
    with open(path, 'r') as file_obj:
        line = True
        while line:
            try:
                line = file_obj.readline()
                match = reg_ex.match(line)
                if match.group(1) not in data:
                    data[ match.group(1) ] = []

                data[match.group(1)].append( match.group(2) )
            except AttributeError:
                pass

print('data=%s' % data)
df = pd.DataFrame.from_dict(data, orient='index').sort_index()
df.rename(index=str, columns={0: "Number"}, inplace=True)  

Sortie

data={'b': ['2'], 'a': ['1'], 'd': ['4'], 'c': ['3']}
<class 'pandas.core.frame.DataFrame'>
Index: 4 entries, a to d
Data columns (total 1 columns):
Number    4 non-null object
dtypes: object(1)
memory usage: 32.0+ bytes
  Number
a      1
b      2
c      3
d      4  

Horaire :

             Code from Q:   to_dict_from_dict
    4 values 0:00:00.033071 0:00:00.022146
 1000 values 0:00:08.267750 0:00:05.536500
10000 values 0:01:22.677500 0:00:55.365000

Testé avec Python: 3.4.2 - pandas: 0.19.2 - re: 2.2.1

1
stovfl

1 créer un modèle de sortie pour les fichiers (comme le bloc de données de résultat doit avoir les colonnes A, B C)

2 lisez chaque fichier, transformez-le en modèle de sortie (établi à l’étape 1) et enregistrez le fichier tel que temp_idxx.csv; vous pouvez le faire en parallèle :)

3 concaténez ces fichiers temp_idxx.csv en un seul fichier volumineux et supprimez les fichiers temporaires

l’avantage de cette procédure est qu’elle peut être exécutée en parallèle et ne consommera pas toute la mémoire. Les inconvénients sont la création du format de sortie et le respect de celui-ci, ainsi que l’utilisation de l’espace disque.

1
quester

Tout d’abord, utilisez un profileur pour votre script ( voir cette question) . Analysez exactement quelle partie consomme plus de temps. Voyez si vous pouvez l'optimiser.

Deuxièmement, j'estime que la lecture du fichier d'opération d'E/S est probablement le goulot d'étranglement. Il peut être optimisé en utilisant une approche simultanée. Je suggère de lire les fichiers simultanément et de créer un cadre de données. Chaque thread peut transférer le cadre de données nouvellement créé dans une file d'attente. Une file d'attente de surveillance de thread principale peut extraire des trames de données de la file d'attente et les fusionner avec un cadre de données maître.

J'espère que cela t'aides. 

1
EngineeredBrain