Wczytywanie z pliku, poprawienie wydajności.

Wczytywanie z pliku, poprawienie wydajności.
IS
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 49
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:

Kopiuj
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?

lion137
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 5025
1

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

enedil
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1028
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.

Guaz
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Częstochowa
  • Postów: 220
1

Spróbuj tak:

Kopiuj
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:

Kopiuj
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.

enedil
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1028
2

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

Kopiuj
with open(nazwapliku, 'r') as f:
    for _ in range(3):
        next(f)
    for line in f:
        #cośtam
enedil
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1028
3

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

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

wystarczy go zamienić na taki:

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

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

Kopiuj
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:

Kopiuj
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)
IS
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 49
0

Rzeczywiście wystarczyło tylko napisać

Kopiuj
	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.

Guaz
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Częstochowa
  • Postów: 220
1

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

Kopiuj
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 :).

Pyxis
  • Rejestracja: dni
  • Ostatnio: dni
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:

Kopiuj
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()

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.