txt mit 500k Datensätzen einlesen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
winnitouch
User
Beiträge: 21
Registriert: Montag 25. März 2019, 22:33

Hallo zusammen,

ich hab in einem txt-File eine equity-Kurve über die letzten 10 Jahre, mit insgesamt rund 500.000 Zeilen.
Hier ein Ausschnitt:

...
21.08.12 21:19:59 76287,500000
21.08.12 21:24:59 76325,000000
21.08.12 21:29:59 76375,000000
21.08.12 21:34:59 76400,000000
21.08.12 21:39:59 76525,000000
21.08.12 21:44:59 76537,500000
21.08.12 21:49:59 76537,500000
21.08.12 21:54:59 76487,500000
21.08.12 21:59:59 76587,500000
22.08.12 08:04:59 76137,500000
22.08.12 08:09:59 76212,500000
22.08.12 08:14:59 76300,000000
22.08.12 08:19:59 76300,000000
22.08.12 08:24:59 76300,000000
...

Ich möchte nun folgendes mit einem Python-Programm machen:

1. Die Datensätze in jeweils eine Liste einlesen. Die erste Liste für das Datum (ohne Uhrzeit) und die zweite Liste für den Kontostand (ganze Zahl, ohne Komma, ausreichend)
2. Die Daten sollen so reduziert werden, dass für jedes Datum nur ein Wert, nämlich der letzte, genommen wird

Könnt Ihr mir mit einem Codebeispiel weiterhelfen?

Gruss
Benutzeravatar
__blackjack__
User
Beiträge: 14040
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@winnitouch: Das ist eine relativ einfache Aufgabe die auch ein Anfänger nach dem durcharbeiten eines Grundlagentutorials lösen können sollte. In der Pythonn-Dokumentation befindet sich beispielsweise ein solches Tutorial. Wo genau liegt denn Dein Problem bei der Umsetzung?

Warum sollen diese parallelen Daten in getrennten Listen gespeichert werden?
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
winnitouch
User
Beiträge: 21
Registriert: Montag 25. März 2019, 22:33

Hallo blackjack,

ja, ich hab schon auch Tutorials gesehn und ausprobiert. Mein Problem war immer die Durchlaufzeit. Das Programm hängt und generiert kein Ouput minutenlang bis ich es "kill". Ich nehme an, dies liegt an der Menge der Daten.

2 separate Listen, weil ich im Anschluss gerne eine Grafik plotten will und gesehen hab, dass man für X- und Y- Achse am besten jeweils eine Liste übermittelt.

Gruss
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist ein Irrglaube. Folgendes Programm braucht auf meinem 6 Jahre alten Laptop 1.2 Sekunden um 1000000 Zeilen deiner Art einzulesen:

Code: Alles auswählen

import csv
import tempfile
import time

fname = tempfile.mktemp()

with open(fname, "w") as outf:
    for _ in range(1000_000):
        outf.write("21.08.12 21:19:59	76287,500000\n")


start = time.time()
with open(fname) as inf:
    reader = csv.reader(inf)
    lines = list(reader)
    print(len(lines))

print("elapsed:", time.time() - start)
Es liegt also an deiner Programmierung, die wir immer noch nicht zu sehen bekommen haben.
Bolitho
User
Beiträge: 219
Registriert: Donnerstag 21. Juli 2011, 07:01
Wohnort: Stade / Hamburg
Kontaktdaten:

dann arbeite doch mal zur Probe mit einem Ausschnitt der Gesamtdatenmenge. Etwa 5 Tage z.B.
Vielleicht kommst du dann zu einem Ergebnis.

Und wenn du den Code hier postest, gibt es sicher weitere Tipps. Und die Code-Tags bitte nicht vergessen.
Benutzeravatar
__blackjack__
User
Beiträge: 14040
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ich habe mal die vollen 500k Datensätze erstellt:

Code: Alles auswählen

#!/usr/bin/env python3
from datetime import datetime as DateTime, timedelta as TimeDelta
from random import randint


def main():
    count = 500_000
    delta = TimeDelta(minutes=5)
    timestamp = DateTime.now() - delta * count
    for _ in range(count):
        print(f'{timestamp:%d.%m.%Y %H:%M:%S} {randint(70000, 79999)},000000')
        timestamp += delta
    

if __name__ == '__main__':
    main()
Wenn die Datensätze alle wie im Beispiel 5 Minuten auseinander liegen, komme ich übrigens nur ca. 5 Jahre in die Vergangenheit. :-)

Einlesen, inklusive Datum und Zahl parsen, und filtern nach letztem Datensatz pro Tag dauert hier ca. 3,3 Sekunden mit Python auf »Intel(R) Core(TM) i3-4170 CPU @ 3.70GHz«. Ist jetzt keine 6 Jahre alt wie __deets__'s Laptop, aber Oberliga ist das jetzt auch nicht gerade. Für das filtern habe ich ein `collections.OrderedDict` verwendet.

Python ist zwar ”langsam”, aber für die paar Daten schnell genug. Spasseshalber das Filtern in FreeBASIC:

Code: Alles auswählen

Dim s As String, i As Integer, j As Integer, tmp As String
Dim pd As String, d As String, v As Integer

On Error Goto ErrorHandler
Open "test.txt" For Input As #1
Do Until Eof(1)
  Line Input #1, s
  i = InStr(s, " ")
  If i = 0 Then Error 1000
  d = Left(s, i)
  i = InStrRev(s, " ")
  If i = 0 Then Error 1001
  j = InStrRev(s, ",")
  If j = 0 And j <= i Then Error 1002
  tmp = Mid(s, i + 1, j - i - 1)
  For i = 1 To Len(tmp)
    If InStr(Mid(tmp, i, 1), Any "0123456789") = 0 Then Error 1003
  Next
  v = ValInt(tmp)
  If d <> pd Then
    pd = d
    Print d, v
  End If
Loop
If d = pd And pd <> "" Then Print d, v
Close #1
End

ErrorHandler:
  On Error Goto 0
  Select Case Err
    Case 2
      Print "Datei konnte nicht gefunden werden!"
    Case 3
      Print "I/O Fehler!"
    Case 1000
      Print "Leerzeichen nach Datum nicht gefunden!"
    Case 1001
      Print "Leerzeichen vor Wert nicht gefunden!"
    Case 1002
      Print "Komma in Wert nicht gefunden!"
    Case 1003
      Print "Wert ist keine ganze Zahl!"
    Case Else
      Error Err
  End Select
  System 1
Das braucht nur 0,5 Sekunden, ist aber hässlicher/umständlicher im Code und Plotten geht in Python auch einfacher/besser.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
winnitouch
User
Beiträge: 21
Registriert: Montag 25. März 2019, 22:33

Hallo,

also ich mach es mittlerweile so:

Code: Alles auswählen

#Werte aus txt in Liste schreiben
print ("Step 1: Rohdaten übernehmen...")
list_raw = open("datenfile.txt").readlines()
print ("...done")

#Werte in Liste umdrehen, damit ältester Eintrag zuerst
print ("Step 2: Werte in Liste drehen...")
list_raw = list_raw[::-1]
print ("...done")

#Finale Listen anlegen für Datum und Kontostand
list_date = []
list_cash = []

#Prüfe ob die ersten 8 Zeichen (Datum ohne Uhrzeit) schon in Finalliste enthalten
#Wenn nicht, hinzufügen. So werden Doppeleinträge entfernt.
print ("Step 3: Datum und Kontostand extrahieren und in eigene Listen schreiben...")
for i in list_raw:
    if i[:8] not in list_date:
        list_date.append(i[:8])
        list_cash.append(i[18:])
list_cash = list_cash[::-1] #Älteste Element zuerst, jüngstes Element zum Schluss
list_date = list_date[::-1] #Älteste Element zuerst, jüngstes Element zum Schluss
print ("...done")

#Zeilenumbrüche am Ende entfernen
print ("Step 4: Zeilenumbrüche entfernen und Komma durch Punkt ersetzten...")
for i, line in enumerate(list_cash):
    list_cash[i] = line.replace('\n', '')

#Komma durch Punkt ersetzen
for i, line in enumerate(list_cash):
    list_cash[i] = line.replace(',', '.')
    list_cash[i] = int(float(list_cash[i]))
print ("...done")
Vor allem die for-Schleife in Step 3 ist zeitintensiv, würde das evtl eleganter, schneller gehn?
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wie hier gezeigt nimmt man für eine solche Aufgabe das csv Modul. Damit spart man sich die fragilen Indizes. Und das ganze String-gerödel. Und für einen Test auf schon enthalten sollte man eine geeignete Datenstruktur verwenden, wie zb eine Menge.
Sirius3
User
Beiträge: 18270
Registriert: Sonntag 21. Oktober 2012, 17:20

Es ist schlecht, dass Du Daten in zwei Listen parallel hältst. Dir scheint die Reihenfolge der Einträge wichtig zu sein, aber list_date benutzt Du gar nicht weiter?
Statt Listen zu ändern, erzeugt man in Python eine neue Liste, dann ist es auch unnötig, mit enumerate sich einen Index mitliefern zu lassen. die Umwandlungen würde man auch gleich in einem Schritt machen.

Code: Alles auswählen

#Werte aus txt in Liste schreiben
print ("Step 1: Rohdaten übernehmen...")
with open("datenfile.txt") as lines:
    lines = list(lines)
print ("...done")

#Finale Listen anlegen für Datum und Kontostand
dates = set()
cashes = []
for line in reversed(lines):
    date, time, value = line.split()
    if date not in dates:
        dates.add(date)
        caches.append(int(float(value.replace(',', '.'))))
caches = caches[::-1]
print ("...done")
winnitouch
User
Beiträge: 21
Registriert: Montag 25. März 2019, 22:33

ok danke. Die zwei Listen pfleg ich parallel, da ich wie gesagt etwas später im Code noch eine Kennlinie plotte....und da geb ich eben X-Achse und Y-Achse jeweils als Liste rein. Deswegen ist auch die Reihenfolge in der Liste relevant (ältester Eintrag zuerst), sonst sieht die Kennlinie falsch aus.

Mein Code macht noch einiges mehr, was ich aktuell nicht gepostet habe, das mach ich mal, wenn ich fertig bin. Ich hab das Gefühl, da kann noch einiges optimiert werden :) Bin halt kein Vollblut-Programmierer. Ich überleg mir, was für eine Teilaufgabe ich lösen möchte, google danach und adaptier brauchbare Beispiele. Meist sinds halt die einfachen Beispiele, die man natürlich auch viel eleganter lösen könnte, aber ich muss ja auch was übernehmen, was ich verstehe :)
Benutzeravatar
__blackjack__
User
Beiträge: 14040
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wie gesagt ich habe zum ausfiltern ein `collections.OrderedDict` genommen. Damit wird der Code IMHO deutlich einfacher. Man muss sich die gesehenen Datumsangaben nicht einzeln merken, nicht alle Zeilen erst komplett in den Speicher lesen um dan rückwärts darüber iterieren zu können und am Ende das Ergebnis deswegen noch einmal umdrehen. Man steckt einfach *alle* Datum/Wert-Paare in dieses Wörterbuch und dadurch bleibt ja automatisch das letzte erhalten. Und die Datumsangaben und die Werte kann man nach dem einlesen auch ganz einfach als getrennte Listen von dem Wörterbuch erhalten. Oder auch gemeinsam darüber iterieren wenn man weitere Verarbeitungsschritte hat, welche das jeweilige Paar benötigen.

Das CSV-Modul würde ich wahrscheinlich nicht verwenden, aber auch nicht mit festen Indexwerten arbeiten, sondern an Leerzeichen aufteilen wie Sirius3 das gemacht hat. Insbesondere das CSV-Modul und das Komma als Trenner würde ich hier als etwas undurchsichtigen Hack sehen, denn das sind ja keine durch Komma getrennte Spalten, sondern man zerteilt damit den Wert der letzten Spalte wo das Komma nicht als Trennzeichen sondern als Dezimalkomma verwendet wird. *Wenn* man dafür das CSV-Modul verwendet, sollte man das deutlich in einen Kommentar schreiben, damit Leser keinen falschen Eindruck von den Daten bekommen.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Benutzeravatar
__blackjack__
User
Beiträge: 14040
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ein kleines C-Programm (mit glib als Abhängigkeit) das Daten erzeugt, die mehr nach einem Kursverlauf aussehen:

Code: Alles auswählen

#include <glib.h>
#include <glib/gprintf.h>

/** Number of records to generate. */
#define COUNT       500000
/** Interval between records. */
#define INTERVAL    (G_TIME_SPAN_MINUTE * 5)


static void in_place_add(GDateTime **dt, GTimeSpan delta)
{
    GDateTime *tmp = g_date_time_add(*dt, delta);
    g_date_time_unref(*dt);
    *dt = tmp;
}


int main(int argc, char const *argv[])
{
    gint value = 70000;
    GDateTime *timestamp = g_date_time_new_now_local();
    in_place_add(&timestamp, -(INTERVAL * COUNT));

    for (int i = 0; i < COUNT; ++i) {
        gchar *tmp = g_date_time_format(timestamp, "%d.%m.%Y %H:%M:%S");
        g_printf("%s %d,000000\n", tmp, value);
        g_free(tmp);
        
        in_place_add(&timestamp, INTERVAL);
        value += g_random_int_range(-10, 11);
    }
    
    g_date_time_unref(timestamp);
    return 0;
}
Und das FreeBASIC-Programm um einen simplen Plot erweitert:

Code: Alles auswählen

Const MaxCount = 4096

Type DataSet
  Count As Integer
  Dates(MaxCount) As String
  Values(MaxCount) As Integer
End Type

Sub DataSetAppend (ds As DataSet, d As String, v As Integer)
  ds.Dates(ds.Count) = d
  ds.Values(ds.Count) = v
  ds.Count = ds.Count + 1
End Sub

Sub LoadData (ds As DataSet, filename As String)
  Dim s As String, i As Integer, j As Integer, tmp As String
  Dim prevD As String, d As String, v As Integer
  
  ds.Count = 0
  Open filename For Input As #1
  Do Until Eof(1)
    Line Input #1, s
    i = InStr(s, " ")
    If i = 0 Then Error 1000
    d = Left(s, i - 1)
    i = InStrRev(s, " ")
    If i = 0 Then Error 1001
    j = InStrRev(s, ",")
    If j = 0 And j <= i Then Error 1002
    tmp = Mid(s, i + 1, j - i - 1)
    For i = 1 To Len(tmp)
      If InStr(Mid(tmp, i, 1), Any "0123456789") = 0 Then Error 1003
    Next
    v = ValInt(tmp)
    If d <> prevD Then
      prevD = d
      DataSetAppend ds, d, v
    End If
  Loop
  If d = prevD And prevD <> "" Then DataSetAppend ds, d, v
  Close #1
End Sub

Sub DataSetGetMinMax (ds As DataSet, ByRef min As Integer, ByRef max As Integer)
  Dim i As Integer, v As Integer
  
  min = &H7fffffffffffffff
  max = &H8000000000000000
  For i = 0 To ds.Count - 1
    v = ds.Values(i)
    If min > v Then min = v
    If max < v Then max = v
  Next
End Sub

Sub PlotData (ds As DataSet)
  Dim i As Integer, min As Integer, max As Integer
  Dim prevY As String, y As String
  
  DataSetGetMinMax ds, min, max
  Screen 20
  Window (0, max)-(ds.Count, min)
  For i = 0 To ds.Count - 2
    Line (i, ds.Values(i))-(i + 1, ds.Values(i + 1))
    y = Right(ds.Dates(i), 4)
    If y <> prevY Then
      prevY = y
      Draw String (i, max), y, 6
    End If
  Next
  GetKey
  Screen 0
End Sub

On Error Goto ErrorHandler
Dim ds As DataSet

LoadData ds, "test.txt"
PlotData ds
End

ErrorHandler:
  On Error Goto 0
  Select Case Err
    Case 2
      Print "Datei konnte nicht gefunden werden!"
    Case 3
      Print "I/O Fehler!"
    Case 1000
      Print "Leerzeichen nach Datum nicht gefunden!"
    Case 1001
      Print "Leerzeichen vor Wert nicht gefunden!"
    Case 1002
      Print "Komma in Wert nicht gefunden!"
    Case 1003
      Print "Wert ist keine ganze Zahl!"
    Case Else
      Error Err
  End Select
  System 1
Beispielausgabe:
Bild
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Antworten