2
votes

Comment puis-je parcourir plus efficacement un gros fichier CSV?

J'ai un certain nombre de gros fichiers CSV (chacun d'environ deux millions de lignes), qui ont des lignes d'horodatage ressemblant à ceci:

times = bogie_df["timestamp"]

#got an error when applying map directly into pd.DataFrame, which is why I first converted it into a list
items = ['year', 'month', 'day', 'hour', 'minute', 'second']
df = pd.DataFrame(list(map(operator.attrgetter(*items), pd.to_datetime(times))), columns=items)

#for my desired YYYY-MM-DD format (though attrgetter only return "1" for "January instead of "01"
df["date"] = df['year'].map(str) + "-" + df["month"].map(str) + df["day"].map(str) 

Étant donné qu'il y a une entrée pour chaque seconde (sur un cours d'environ un an), il devrait être compréhensible pourquoi il y a tant de lignes. Je veux être plus flexible, c'est pourquoi je veux diviser les horodatages en trois lignes: date, date + heure, date + heure + minute, date + heure + seconde, afin que je puisse regrouper les horodatages à volonté. Voici comment je fais:

def get_date(i):
  return (dt.datetime.strptime(df["timestamp"][i], '%d.%m.%Y %H:%M:%S'))

get_date () est simplement une fonction renvoyant l'horodatage avec l'index donné: p >

dates = []
hours = []
minutes = []
seconds = []
i = 0


#initial values
dates.append(str(get_date(i).date()))
hours.append(str(get_date(i).hour))
minutes.append(str(get_date(i).minute))
seconds.append(str(get_date(i).second))

for i in range(len(df)):
  if i < len(df) - 1 :
    if str(get_date(i).date) < str(get_date(i+1).date): #dates: YYYY-MM-DD
      dates.append(str(get_date(i+1).date()))
    else:
      dates.append(str(get_date(i).date()))

    if str(get_date(i).hour) < str(get_date(i+1).hour): #dates+hours: YYYY-MM-DD HH
      hours.append(str(get_date(i+1).date()) + " " + str(get_date(i+1).hour))
    else:
      hours.append(str(get_date(i).date()) + " " + str(get_date(i).hour))

    if str(get_date(i).minute) < str(get_date(i+1).minute): #dates+hours+minutes: YYYY-MM-DD HH:mm
      minutes.append(str(get_date(i+1).date()) + " " + str(get_date(i+1).hour) + ":" + str(get_date(i+1).minute))
    else: 
      minutes.append(str(get_date(i).date()) + " " + str(get_date(i).hour) + ":" + str(get_date(i).minute))

    if str(get_date(i).second) < str(get_date(i+1).second): #dates+hours+minutes+seconds: YYYY-MM-DD HH:mm+ss
      seconds.append(str(get_date(i+1).date()) + " " + str(get_date(i+1).hour) + ":" + str(get_date(i+1).minute) + ":" + str(get_date(i+1).second))
    else: 
      seconds.append(str(get_date(i).date()) + " " + str(get_date(i).hour) + ":" + str(get_date(i).minute) + ":" + str(get_date(i).second))


df["dates"] = dates
df["hours"] = hours
df["minutes"] = minutes
df["seconds"] = seconds

En gros, j'itère toutes les entrées, je mets chaque date / heure / minute / seconde dans une liste, puis je les insère chacune dans mon dataframe. et les mets dans où get_date () est simplement une fonction renvoyant l'horodatage avec l'index donné.

Je suppose que cela me mettrait à O (n²) ? Ce qui n'est évidemment pas idéal.

Maintenant, faire cela sur un fichier (~ 60 Mo, 2 millions de lignes) prend une demi-heure. Personnellement, je ne peux pas penser à une autre façon de faire ce que je veux faire, donc je voulais juste voir si je pouvais faire quelque chose pour réduire la complexité.

modifier: Ajuster la réponse de @Chris à mes besoins:

16.01.2019 12:52:22
16.01.2019 12:52:23
16.01.2019 12:52:24


3 commentaires

Y a-t-il une erreur dans votre code dates.append (str (get_date (i) .date ())) vs dates.append (str (get_date (i) .date)) . Vous appelez beaucoup la méthode get_date () . Avez-vous essayé d'enregistrer ce résultat dans une variable? Et y a-t-il une raison pour le supplémentaire si i ? Avec l'objet range (j'espère que vous êtes sur python 3.x) vous n'avez déjà que I de 0, ... len (df) -1


Voulez-vous dire enregistrer get_date () dans une variable au début de chaque itération? Cela pourrait aider, je suppose que oui. J'ai modifié ma question pour montrer ce que fait vraiment get_date.


Puisque je demande également i + 1 à chaque itération, la clause if supplémentaire empêche la méthode de planter à la fin.


3 Réponses :


0
votes

Les commentaires étant limités en longueur, au moins les points suivants:

  1. Supprimez le si i , vous n'en avez pas besoin. Remplacez votre range par range (len (df) -1) .
  2. Enregistrez les résultats de votre fonction get_date :

Avant la boucle: next_time = get_date (0)

Dans la boucle:

current_time = next_time
next_time = get_date(i+1)

Cela devrait vous éviter quelques appels de fonction, mais probablement pandas a une meilleure façon de faire de telles choses.


0 commentaires

4
votes

Utilisez operator.attrgetter avec pd.to_datetime:

0    2019-01-16
1    2019-01-16
2    2019-01-16

Output:

df['dates'] = df['date'].astype(str).str[:10]


3 commentaires

Wow, changer la donne! Je l'ai légèrement modifié, car la fonction de carte ne fonctionnait pas en tant que paramètre pour pd.DataFrame () pour moi (voir ma question modifiée), mais c'est énorme. Je n'ai cependant pas pu atteindre 7 secondes. Cela m'a pris 7 minutes, en fait. Peut-être parce que mes articles étaient plus gros?


@Readler Ma mauvaise, liste était absente de la réponse: P. Pour moi, les deux lignes (faire df et df ['date'] ) prennent moins de 10 secondes, combinées, mais cela variera énormément en fonction du matériel. De plus, la partie df ['date'] semble redondante. Permettez-moi de publier une mise à jour.


Sympa, c'est beaucoup plus élégant que ma solution! Pourriez-vous peut-être expliquer comment fonctionne le remplissage ( .str [: 10] )? J'ai fait une autre boucle et utilisé la compréhension de liste ( map (lambda x: "0" + str (x) if len (str (x)) <2 else x, df [d]) ), qui est beaucoup plus cher que votre solution.



0
votes

Vous n'avez pas besoin de cela, mais à la place, vous devez ajouter une seule colonne de type Horodatage :

df['ts'] = pd.to_datetime(df.timestamp, format='%d.%m.%Y %H:%M:%S')

Ensuite, vous pouvez utiliser directement tous les goodies de temps sur cette colonne :

  • df.ts.dt.date : donne la date sous forme de datetime.date
  • df.ts.dt.strftime (format) : donne la date sous forme de chaîne formatée au format. Par exemple, df.ts.dt.strftime ("AAAA-MM-JJ HH: mm") est votre colonne "minutes"
  • df.ts.dt.floor (freq = 'h') : est un horodatage tronqué au niveau de l'heure, par exemple pour le regroupement
  • ... (il suffit de lire pandas Datetime Data pour référence)


0 commentaires