Effizienter Weg um aus Listen eine Teilliste auszuwählen?

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
Vash
User
Beiträge: 5
Registriert: Mittwoch 30. Mai 2018, 18:50

Hallo,

ich würde gerne aus einem Liste von Messdaten nur einen bestimmten Zeitbereich auswählen. Habe das momentan gelöst wie unten angegeben. Aber ich habe gelesen, dass die slice Operation für lange Listen ineffizient ist, da sie jedesmal die referenzierten Listen Elemente in eine neue Liste kopieren muss. Gibt es da auch eine elegantere/schnellere Lösung, da bei mir die Liste schon so um 10000 Elemente lang ist?

Code: Alles auswählen

data.analysis.staw = statistics.stdev(data.analysis.delta[data.start_index:data.end_index]) 
Noch eine kleine Frage:
Wenn ich eine Auswahl treffe über b = [0:3] wenn a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] dann ist b = [0, 1, 2]. Intuitiv hätte ich gedacht b = [0, 1, 2, 3]. Heißt das wenn ich einen Startzeitpunkt und Endzeitpunkt festlege ich zum Endzeitpunktindex eigentlich immer +1 nehmen muss um die richtige Auswahl zu treffen?
Der Fehler ist wahrscheinlich bei extrem vielen Elementen vernachlässigbar klein aber würde mich trotzdem interessieren.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

Intuitiv ist der Startindex enthalten und der Endindex nicht. Und bei vielen Zahlen nimmt man numpy.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@Vash:
Der Start ist der erste Index, dessen Element der Beginn der Unterliste sein soll. Als Stop nimmt man den ersten Index, der nicht mehr enthalten sein soll. Hätte man auch anders lösen können, ist aber in Python eben so implementiert worden. Also im Grunde Slice-Ende + 1 (wie du auch schon meintest).
Benutzeravatar
ThomasL
User
Beiträge: 1366
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

Also mich hat mal interessiert, wie das Python Slicing sich zeitlich so verhält und habe in einem Jupyter Notebook etwas rumgespielt.
Da das Slicing ja wohl auf C-Ebene realisiert ist, ist es 20x schneller als wenn man es per List Comprehension nachbaut.
Beides hat O(n).

Numpy Slicing hat O(1), da keine Kopie der Daten erzeugt werden.
Ist also extrem schnell. Je nach Slicegröße Faktor 15 bis 160. (in deiner range(10000))
Benötigt man eine Kopie der Daten, dann haben wir wieder O(n) und "nur" noch einen Faktor 12x.

Es hängt also davon ab, was du mit den Slices machen willst.
Aber grundsätzlich bist du mit Numpy im Turbo-Modus 8)

Code: Alles auswählen

arr = [_ for _ in range(10000)]

%timeit sli = arr[0:1000]
%timeit sli = arr[9000:10000]
# gleich schnell ob am Anfang oder Ende der Liste

    3 µs ± 49.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    2.96 µs ± 10.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    
# slicing nachbilden durch list comprehension
%timeit sli = [arr[i] for i in range(0, 1000)]
%timeit sli = [arr[i] for i in range(9000, 10000)]
# ca 20x langsamer

    60.4 µs ± 1.34 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
    61.7 µs ± 543 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
    
# verdopplung der slice grösse
%timeit sli = arr[0:2000]
%timeit sli = arr[8000:10000]
# ca. verdopplung der zeit

    6.31 µs ± 123 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    6.27 µs ± 55.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    
%timeit sli = [arr[i] for i in range(0, 2000)]
%timeit sli = [arr[i] for i in range(8000, 10000)]
# gleiches gilt für die list comprehension


    122 µs ± 3.82 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
    125 µs ± 4.02 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
    
%timeit sli = arr[0:9000]

    35.7 µs ± 183 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
    
	
import numpy as np
arr = np.arange(10000)

# Numpy slicing hat O(1)? wie das?

%timeit sli = arr[0:1000]     # 15x schneller bei 1000 Elementen
%timeit sli = arr[2000:4000]  # 30x schneller bei 2000 Elementen
%timeit sli = arr[0:9000]     # 160x schneller bei 9000 Elementen


    249 ns ± 3.23 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
    242 ns ± 10.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
    235 ns ± 3.26 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
    
# Numpy slicing erzeugt nur ein Abbild/View

arr = np.arange(10)
sli = arr[:5]
sli[0] = 100
print(sli, arr)
# Veränderungen des Slices verändern das ursprüngliche Array

    [100   1   2   3   4] [100   1   2   3   4   5   6   7   8   9]
    

# Python slicing erzeugt ein neues Objekt

arr = [_ for _ in range(10)]
sli = arr[:5]
sli[0] = 100
print(sli, arr)

    [100, 1, 2, 3, 4] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    

# numpy.copy() erzeugt ein neues Objekt

arr = np.arange(10)
sli = arr[:5].copy()
sli[0] = 100
print(sli, arr)

    [100   1   2   3   4] [0 1 2 3 4 5 6 7 8 9]
    

arr = np.arange(10000)
%timeit sli = arr[0:9000].copy()

    2.56 µs ± 17 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    
# und ist dann nur noch 12x schneller als Python slicing

Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

In CPython werden die Objekte mittels Schleife in die neue Liste gesetzt. Auf jedes Objekt wird zudem ein Py_INCREF ausgeführt, da es ja eine zusätzliche Referenz darstellt. Kann man nachlesen, wenn man in listobject.c nach der Funktion list_slice() sucht. In Numpy hat man entweder ein View auf den gewünschten Auschnitt und damit entfällt die nötige Arbeit zum Kopieren. Und falls wirklich eine Kopie in Numpy angefordert wird, dann kann dies intern direkt auf den C-Datentypen passieren. Für den Kopiervorgang bieten sich dann entsprechende Optimierungen an, die bessere Performance bieten als eine "normale" Schleife. Die C-Funktion memcpy() wäre eine Möglichkeit, aber wie Numpy hier tatsächlich vorgeht, weiß ich aus dem Stand nicht.
Antworten