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'))
où 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 Réponses :
Les commentaires étant limités en longueur, au moins les points suivants:
si i , vous n'en avez pas besoin. Remplacez votre range par range (len (df) -1) . 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.
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]
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.
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
Y a-t-il une erreur dans votre code
dates.append (str (get_date (i) .date ()))vsdates.append (str (get_date (i) .date)) code>. Vous appelez beaucoup la méthodeget_date (). Avez-vous essayé d'enregistrer ce résultat dans une variable? Et y a-t-il une raison pour lesupplé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) -1Voulez-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.