Wczytywanie z pliku, poprawienie wydajności.

0

Witam, chciałem napisać sobie skrypt do wczytywania danych z pliku .txt i na ich podstawie tworzyć wykres.
W plikach dane są rodzieszlone " ; ". Poszczególne kolumny mają inne dane(float) i każdą tablicę chcę wypełnić właśnie tymi danymi.
Napisałem taką funkcję, tylko jest ona mało wydajna, domyślam się że to przez konwersję na float, ale bez tego wyskakuje mi błąd że na stringu nie można wykonywać operacji.
Dopiero zaczynam zabawę z pythonem.
Oto co napisałem:

from matplotlib.pyplot import figure
import matplotlib.pyplot as plt 
import numpy as np
import math

nazwapliku = "wyniki.txt"

with open( nazwapliku, "r" ) as f:
    lista_linii = [ line.rstrip( "\n" ) for line in f ]

skala = 0.85 

x1=[] 
x2=[]
xw=[]
y=[]
it = 0 #iterator liczący, która jest linijka w pliku txt, pomijam pierwsze 3 linijki

#wczytywanie z pliku
for linia in lista_linii:
    if it > 3:
        #print(linia)
        itCzesc = 0 #odliczanie, która dana jest czytana
        for pole in linia.split( " ; " ): 
            if itCzesc == 0:
                yw = (float(pole) - skala)/0.5*100
                xw.append(yw)
                itCzesc = itCzesc+1
            elif itCzesc == 1:
                x1.append(pole)
                itCzesc = itCzesc+1
            elif itCzesc == 2:
                x2.append(pole)
                itCzesc = itCzesc+1
            elif itCzesc == 3:
                y.append(pole)
                itCzesc = itCzesc+1
    if it <= 4:
        it = it+1

xw = np.array(xw)
y = np.array(y)

plt.xlabel("Czas [ms]")
plt.ylabel("Wynik")
plt.grid(True)
plt.plot(y, xw)
plt.show()

Przy pliku txt z około 6000 linijek danych wczytywało mi się to ok 53 minuty, a zdarza mi się że tych danych mam znacznie więcej(czasami nawet 300tys linijek). Jak mogę usprawnić swój kod?

1

Jakbyś zamiast pliku tekstowego, Użył pliku csv, to Python ma do tego super szybkie narzędzie: pandas .

0

Możesz wrzucić przykład pliku wyniki.txt?

Poza tym, zastanawia mnie to zdanie:

domyślam się że to przez konwersję na float

Skąd te domysły? Bo z moich pomiarów wychodzi, że jest tak tragicznie wolno, bo właśnie nie dokonujesz konwersji w odpowiednim miejscu.

1

Spróbuj tak:

from matplotlib.pyplot import figure
import matplotlib.pyplot as plt 
import numpy as np
import math

nazwapliku = "wyniki.txt"

skala = 0.85 

x1=[] 
x2=[]
xw=[]
y=[]
with open(nazwapliku, "r") as f:
    #Passing three first lines:
    for enum, line in enumerate(f, 1):
        if enum < 4:
            continue
        else:
            pola = linia.split(" ; ")
            
            #Pole pierwsze:
            yw = (float(pola.pop(0)) - skala)*50 #Po co dzielić i mnożyć, skoro działania są rozłączne i następujące po sobie.
            xw.append(yw)
            
            #Pole drugie:
            x1.append(pola.pop(0))
            
            #Pole trzecie
            x2.append(pola.pop(0))
            
            #Pole czwarte
            y.append(pola.pop(0))
        

xw = np.array(xw)
y = np.array(y)

plt.xlabel("Czas [ms]")
plt.ylabel("Wynik")
plt.grid(True)
plt.plot(y, xw)
plt.show()

Przez brak danych wejściowych ciężko mi to sprawdzić, ale dla dużych plików bez wątpienia się sprawdzi dużo lepiej, nie musisz przechowywać całego pliku w zmiennej, w dodatku nie potrzebna ci dodatkowa wewnątrz, skoro z pól robisz listy splitem.

Dodatkowe ułamki prędkości jakbyś chciał uzyskać, możesz przygotować append do użycia (różnica jest tak nieznacząca, że tak się nie robi gdy problem skali nie jest przytłaczający):
@Edit:

from matplotlib.pyplot import figure
import matplotlib.pyplot as plt 
import numpy as np
import math

nazwapliku = "wyniki.txt"

skala = 0.85 
xw = []
y = []

ap = list.append
with open(nazwapliku, "r") as f:
    for enum, line in enumerate(f, 1):
        if enum < 4:
            continue
        else:
            pola = line.split(" ; ")
            
            yw = (float(pola[0]) - skala)*50
            ap(yw, xw)
            ap(float(pola[-1]), y)

plt.xlabel("Czas [ms]")
plt.ylabel("Wynik")
plt.grid(True)
plt.plot(y, xw)
plt.show()

Ale to już sztuka dla sztuki, możesz sobie porównać wyniki, drugi powinien być szybszy :)
Chociaż nie sprawdzałem pop'a w porównaniu do indexu, skoro i tak przy nadpisaniu pola, będzie usuwana zawartość w kolejnej iteracji. Ciężko powiedzieć co się lepiej sprawdzi bez rzetelnego testu :)

Główna zaleta tego rozwiązania, to działanie na każdej wczytanej linii pokolei zamiast przechowywania ich.

2

W kwestii pomijania iluś pierwszych elementów generatora (tutaj 3):

with open(nazwapliku, 'r') as f:
    for _ in range(3):
        next(f)
    for line in f:
        #cośtam
3

Otóż w oryginalnym kodzie była taki fragment:

            elif itCzesc == 3:
                y.append(pole)

wystarczy go zamienić na taki:

            elif itCzesc == 3:
                y.append(float(pole))

Dlaczego? Otóż później, dzieje się

plt.xlabel("Czas [ms]")
plt.ylabel("Wynik")
plt.grid(True)
plt.plot(y, xw)
plt.show()

w szczególności, jeśli y i xw są listami (bądź tablicami numpy) floatów, matplotlib radzi sobie sprawnie. Jeśli jednak y jest tablicą stringów, włączają się jakieś domyślne konwersje, które Bóg raczy wiedzieć z czego wynikają. To one odpowiadają właśnie za 99.9% czasu wykonania.

Tutaj krótki test potwierdzający:

In [3]: x = [random.random() for _ in range(1000)]

In [4]: y = [random.random() for _ in range(1000)]

In [5]: %timeit plt.plot(x, y); plt.savefig('/tmp/z.png')
262 ms ± 69.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [6]: %timeit plt.plot([str(c) for c in x], y); plt.savefig('/tmp/i.png')
22.2 s ± 2.21 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
0

Rzeczywiście wystarczyło tylko napisać

	elif itCzesc == 3:
                y.append(float(pole))

Sprawdziłem także metodę Guaza, też działa, ale jest odrobinę wolniejsza.

Z csv też jeszcze spróbuję.

O tej konwersji stringów nie wiedziałem, że w takim miejscu mogą mieć znaczenie, ale teraz już wiem :D

Teraz jeszcze zostało mi automatyczne zapisywanie wykresu do pliku i będę w domu.

1

Finalnie podsumowując moje wypociny i treściwe uwagi enedila, chyba najszybsze powinno być takie rozwiązanie:

from matplotlib.pyplot import figure
import matplotlib.pyplot as plt 
import numpy as np
import math
 
nazwapliku = "wyniki.txt"
 
skala = 0.85 
xw = []
y = []
with open(nazwapliku, 'r') as f:
    for _ in range(3):
        next(f)
    for line in f:
        pola = line.split(" ; ")
        xw.append(float(pola[0]))
        y.append(float(pola[-1]))
 
xw = (np.array(xw) - skala) * 50
y = np.array(y)
plt.xlabel("Czas [ms]")
plt.ylabel("Wynik")
plt.grid(True)
plt.plot(y, xw)
plt.show()

I nie wiem czy coś więcej tu można naczarować :). csv jak już wcześniej napisał enedil, też jest tylko plikiem tekstowym, nic to raczej nie powinno zmienić :D

Ewentualnie możesz sobie dodać x1 i x2 jeśli je do czegoś potrzebujesz gdzieś dalej, bo w przedstawionym kodzie ich nie używasz :).

1

Przejrzałem pobieżnie pozostałe posty i wg mnie każde z rozwiązań można jeszcze ulepszyć.

Isild napisał(a):

Przy pliku txt z około 6000 linijek danych wczytywało mi się to ok 53 minuty, a zdarza mi się że tych danych mam znacznie więcej(czasami nawet 300tys linijek). Jak mogę usprawnić swój kod?

Ja do takich rzeczy używam genfromtxt. To jest Python, zrób to wg zasad ZEN:

import numpy as np
import matplotlib.pyplot as plt 

filename = "wyniki.txt"
skala = 0.85
data = np.genfromtxt(filename, skip_header=3, delimiter=";", usecols=(0, 3), dtype="f8")
data[:, 0] = (data[:, 0] - skala)/0.5*100

plt.xlabel("Czas [ms]")
plt.ylabel("Wynik")
plt.grid(True)
plt.plot(data[:, 1], data[:, 0])
plt.show()

1 użytkowników online, w tym zalogowanych: 0, gości: 1