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_pathcome "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:
    1. raccogliere file di input
    2. gestire i tipi di colonna
    3. 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_namese dtypesparametri oggetto attributi di un class 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 con infer_datetime_formatabilitato 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.concatper non creare una lista non necessaria.