Concatena diversi file CSV in un unico dataframe
Aug 24 2020
Al momento ho 600 file CSV (e questo numero crescerà) di 50.000 linee ciascuno che vorrei inserire in un singolo dataframe. L'ho fatto, funziona bene e ci vogliono 3 minuti:
colNames = ['COLUMN_A', 'COLUMN_B',...,'COLUMN_Z']
folder = 'PATH_TO_FOLDER'
# Dictionnary of type for each column of the csv which is not string
dictTypes = {'COLUMN_B' : bool,'COLUMN_D' :int, ... ,'COLUMN_Y':float}
try:
# Get all the column names, if it's not in the dict of type, it's a string and we add it to the dict
dictTypes.update({col: str for col in colNames if col not in dictTypes})
except:
print('Problem with the column names.')
# Function allowing to parse the dates from string to date, we put in the read_csv method
cache = {}
def cached_date_parser(s):
if s in cache:
return cache[s]
dt = pd.to_datetime(s, format='%Y-%m-%d', errors="coerce")
cache[s] = dt
return dt
# Concatenate each df in finalData
allFiles = glob.glob(os.path.join(folder, "*.csv"))
finalData = pd.DataFrame()
finalData = pd.concat([pd.read_csv(file, index_col=False, dtype=dictTypes, parse_dates=[6,14],
date_parser=cached_date_parser) for file in allFiles ], ignore_index=True)
Ci vuole un minuto in meno senza la cosa della data di analisi. Quindi mi chiedevo se potevo migliorare la velocità o era un tempo standard per quanto riguarda il numero di file. Grazie !
Risposte
2 ojdo Aug 26 2020 at 14:49
Ecco il mio feedback non testato sul tuo codice. Alcune osservazioni:
- Incapsula la funzionalità come una funzione denominata. Ho assunto
folder_path
come "variante" principale il tuo codice chiamante potrebbe voler variare, ma il tuo caso d'uso potrebbe "richiedere" un primo argomento diverso. - Utilizzare le raccomandazioni PEP8 per i nomi delle variabili.
- Pettina / separa le diverse preoccupazioni all'interno della funzione:
- raccogliere file di input
- gestire i tipi di colonna
- leggere CSV e analizzare le date
- A seconda di quanto ciascuna di queste preoccupazioni cresce di dimensione nel tempo, più funzioni separate potrebbero svilupparsi organicamente da questi paragrafi separati, portando alla fine a un intero pacchetto di utilità o classe (a seconda di quanta configurazione di "istanza" dovresti conservare, spostando la
column_names
edtypes
parametri oggetto attributi di unclass XyzCsvReader
's__init__
metodo.) - Per quanto riguarda l'analisi della data: probabilmente il collo di bottiglia non è causato dal caching o meno, ma dalla frequenza con cui si richiama il macchinario pesante dietro
pd.to_datetime
. La mia ipotesi è che chiamarlo solo una volta alla fine, ma coninfer_datetime_format
abilitato sarà molto più veloce che chiamarlo una volta per riga (anche con la cache manuale).
import glob
import os
import pandas as pd
def read_xyz_csv_folder(
folder_path,
column_names=None,
dtypes=None):
all_files = glob.glob(os.path.join(folder_path, "*.csv"))
if column_names is None:
column_names = [
'COLUMN_A',
'COLUMN_B', # ...
'COLUMN_Z']
if dtypes is None:
dtypes = {
'COLUMN_B': bool,
'COLUMN_D': int,
'COLUMN_Y': float}
dtypes.update({col: str for col in column_names
if col not in dtypes})
result = pd.concat((
pd.read_csv(file, index_col=False, dtype=dtypes)
for file in all_files),
ignore_index=True)
# untested pseudo-code, but idea: call to_datetime only once
result['date'] = pd.to_datetime(
result[[6, 14]],
infer_datetime_format=True,
errors='coerce')
return result
# use as
read_xyz_csv_folder('PATH_TO_FOLDER')
Modifica: come suggerito dall'utente FMc nel suo commento, passa da una comprensione della lista a un'espressione generatrice all'interno pd.concat
per non creare una lista non necessaria.