Pour mon application, je dois lire plusieurs fichiers de 15 M lignes chacun, les stocker dans un DataFrame et enregistrer le DataFrame au format HDFS5.
J'ai déjà essayé différentes approches, notamment pandas.read_csv avec les spécifications chunksize et dtype, et dask.dataframe. Ils prennent tous les deux environ 90 secondes pour traiter 1 fichier, et j'aimerais donc savoir s'il existe un moyen de traiter efficacement ces fichiers de la manière décrite. Dans ce qui suit, je montre un code des tests que j'ai effectués.
import pandas as pd
import dask.dataframe as dd
import numpy as np
import re
# First approach
store = pd.HDFStore('files_DFs.h5')
chunk_size = 1e6
df_chunk = pd.read_csv(file,
sep="\t",
chunksize=chunk_size,
usecols=['a', 'b'],
converters={"a": lambda x: np.float32(re.sub(r"[^\d.]", "", x)),\
"b": lambda x: np.float32(re.sub(r"[^\d.]", "", x))},
skiprows=15
)
chunk_list = []
for chunk in df_chunk:
chunk_list.append(chunk)
df = pd.concat(chunk_list, ignore_index=True)
store[dfname] = df
store.close()
# Second approach
df = dd.read_csv(
file,
sep="\t",
usecols=['a', 'b'],
converters={"a": lambda x: np.float32(re.sub(r"[^\d.]", "", x)),\
"b": lambda x: np.float32(re.sub(r"[^\d.]", "", x))},
skiprows=15
)
store.put(dfname, df.compute())
store.close()
Voici à quoi ressemblent les fichiers (les espaces se composent d'un onglet littéral):
a b
599.998413 14.142895
599.998413 20.105534
599.998413 6.553850
599.998474 27.116098
599.998474 13.060312
599.998474 13.766775
599.998596 1.826706
599.998596 18.275938
599.998718 20.797491
599.998718 6.132450)
599.998718 41.646194
599.998779 19.145775
Tout d'abord, permet de répondre au titre de la question
Je vous suggère d'utiliser modin :
import modin.pandas as mpd
import pandas as pd
import numpy as np
frame_data = np.random.randint(0, 10_000_000, size=(15_000_000, 2))
pd.DataFrame(frame_data*0.0001).to_csv('15mil.csv', header=False)
!wc 15mil*.csv ; du -h 15mil*.csv
15000000 15000000 480696661 15mil.csv
459M 15mil.csv
%%timeit -r 3 -n 1 -t
global df1
df1 = pd.read_csv('15mil.csv', header=None)
9.7 s ± 95.1 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)
%%timeit -r 3 -n 1 -t
global df2
df2 = mpd.read_csv('15mil.csv', header=None)
3.07 s ± 685 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)
(df2.values == df1.values).all()
True
Donc, comme nous pouvons le voir, modin était approximativement 3 fois plus rapide sur ma configuration.
Maintenant pour répondre à votre problème spécifique
Comme les gens l'ont remarqué, votre goulot d'étranglement est probablement le convertisseur. Vous appelez ces lambdas 30 millions de fois. Même la surcharge de l'appel de fonction devient non triviale à cette échelle.
Attaquons ce problème.
!sed 's/.\{4\}/&)/g' 15mil.csv > 15mil_dirty.csv
Tout d'abord, j'ai essayé d'utiliser modin avec l'argument converters. Ensuite, j'ai essayé une approche différente qui appelle moins souvent l'expression rationnelle:
Je vais d'abord créer un objet de type fichier qui filtre tout à travers votre expression régulière:
class FilterFile():
def __init__(self, file):
self.file = file
def read(self, n):
return re.sub(r"[^\d.,\n]", "", self.file.read(n))
def write(self, *a): return self.file.write(*a) # needed to trick pandas
def __iter__(self, *a): return self.file.__iter__(*a) # needed
Ensuite, nous le passons à pandas comme premier argument dans read_csv:
with open('15mil_dirty.csv') as file:
df2 = pd.read_csv(FilterFile(file))
%%timeit -r 1 -n 1 -t
global df1
df1 = pd.read_csv('15mil_dirty.csv', header=None,
converters={0: lambda x: np.float32(re.sub(r"[^\d.]", "", x)),
1: lambda x: np.float32(re.sub(r"[^\d.]", "", x))}
)
2min 28s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
%%timeit -r 1 -n 1 -t
global df2
df2 = mpd.read_csv('15mil_dirty.csv', header=None,
converters={0: lambda x: np.float32(re.sub(r"[^\d.]", "", x)),
1: lambda x: np.float32(re.sub(r"[^\d.]", "", x))}
)
38.8 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
%%timeit -r 1 -n 1 -t
global df3
df3 = pd.read_csv(FilterFile(open('15mil_dirty.csv')), header=None,)
1min ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
On dirait que Modin gagne à nouveau! Malheureusement, Modin n'a pas encore implémenté la lecture à partir des tampons, j'ai donc conçu l'approche ultime.
%%timeit -r 1 -n 1 -t
with open('15mil_dirty.csv') as f, open('/dev/shm/tmp_file', 'w') as tmp:
tmp.write(f.read().translate({ord(i):None for i in '()'}))
df4 = mpd.read_csv('/dev/shm/tmp_file', header=None)
5.68 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
Cela utilise translate
qui est considérablement plus rapide que re.sub
, et utilise également /dev/shm
qui est un système de fichiers en mémoire qu'ubuntu (et d'autres linux) fournissent habituellement. Tout fichier qui y est écrit n'ira jamais sur le disque, il est donc rapide. Enfin, il utilise modin pour lire le fichier, en contournant la limitation de tampon de modin. Cette approche est environ 30 fois plus rapide que votre approche, et elle est également assez simple.
Eh bien, mes découvertes ne sont pas très liées aux pandas, mais plutôt à certains pièges courants.
Your code:
(genel_deneme) ➜ derp time python a.py
python a.py 38.62s user 0.69s system 100% cpu 39.008 total
Replace re.sub(r"[^\d.]", "", x) with precompiled version and use it in your lambdas
Result :
(genel_deneme) ➜ derp time python a.py
python a.py 26.42s user 0.69s system 100% cpu 26.843 total
replace np.float32 with float and run your code.
My Result:
(genel_deneme) ➜ derp time python a.py
python a.py 14.79s user 0.60s system 102% cpu 15.066 total
Trouvez une autre façon d'obtenir le résultat avec les flotteurs. Plus d'informations sur ce problème https://stackoverflow.com/a/6053175/37491