1 Numerical Methods in Extraterrestrial Physics - Introduction to Python II 2 Datei Input/Output Beim Arbeiten mit dem Computer ist es unerlsslich auf Dateien zu zugreifen um z.B. Messdaten einlesen oder Ergebnisse speichern zu können. Hierzu gibt es die eingebaute Funktion ’open’ die ein Objekt erzeugt über dass die gewählte Datei bearbeitet werden kann. # Oeffnen einer Datei mit L e s e z u g r i f f >>> file1 = open ( " datei . txt " ," r " ) # Erzeugen einer Datei mit S c h r e i b z u g r i f f >>> file1 = open ( " datei . txt " ," w " ) # V o r s i c h t ! Eine ggf . b e s t e h e n d e Datei " datei . txt " # wird g e l o e s c h t # Oeffnen / erzeuge n einer Datei mit S c h r e i b z u g r i f f >>> file1 = open ( " datei . txt " ," a " ) # Falls Datei " datei . txt " e x i s t i e r t wird sie # g e o e f f n e t mit der M o e g l i c h k e i t Daten ans Ende # der Datei a n z u h a e n g e n . A n s o n s t e n wird eine # neue Dtai erzeugt >>> file1 . close () # S c h l i e s s t die a k t u e l l e Datei Das oben erzeugte Objekt ’file1’ kann jetzt dazu verwendet werden um aus der Datei zu lesen oder in sie zu schreiben. Das auslesen erfolgt über die Methode ’readline’ (mehr zu Objekten und Methoden folgt spter). datei . txt ascii - datei x y z 1 2 3 >>> file1 = open ( " datei . txt " ," r " ) >>> file1 . readline () # gibt die 1. Zeile aus datei . txt als String zurueck ’x y z {\ backslash } n ’ >>> file1 . readline () # gibt die 2. Zeile aus datei . txt als String zurueck ’\ n ’ >>> file1 . readline () # gibt die 3. Zeile aus datei . txt als String zurueck ’1 2 3 ’ Beachte : Nach dem Ende der Datei gibt ’ readline ’ einen leeren String zurueck >>> file1 . readline () # gibt einen leeren String zurueck ’’ # A n m e r k u n g : die S t r i n g m e t h o d e ’ split ’ kann dafuer v e r w e n d e t werden e i n g e l e s e n e # Zeilen weiter zu v e r a r b e i t e n . Hierbei sind auch die T y p e n k o n v e r t i e r u n g e n # int ( arg ) , float ( arg ) h i l f r e i c h . >>> ’1 2 3 ’. split () [ ’1 ’ , ’2 ’ , ’3 ’] >>> int ( ’1 2 3 ’. split ()[0]) 1 Um in ’file1’ zu schreiben gibt es die Methode ’write’. >>> file1 = open ( " datei2 . txt " ," w " ) # erzeugt leere Datei mit S c h r e i b z u g r i f f >>> file1 . write ( " x y z " ) # s c h r e i b t ’x y z ’ ans Ende von ’ datei2 . txt ’ >>> file1 . write ( " 1 2 3 " ) # s c h r e i b t ’1 2 3 ’ ans Ende von ’ datei2 . txt ’ # Beachte : Z e i l e n u m b r u e c h e muessen e x p l i z i t m i t g e s c h r i e b e n werden # datei2 . txt haette f o l g e n d e n Inhalt x y z1 2 3 >>> file1 = open ( " datei2 . txt " ," w " ) # Die b e s t e h e n d e Datei ’ datei2 . txt ’ >>> file1 . write ( " x y z \ n " ) # wird dabei g e l o e s c h t >>> file1 . write ( " 1 2 3 " ) datei2 . txt haette nun folgenden Inhalt x y z 1 2 3 Wichtig ist zu beachten dass Änderungen in der nicht sofort beim Aufruf von ’write’ geschrieben werden. Um sicher zu stellen dass alles wie gewünscht in die Datei geschrieben wird ist es wichtig die Datei am Ende mit der Methode ’close’ zu schliessen. Gleichzeitig wird auch das zugehörige obj ’file1’ zerstört. Im obige Beispiel >>> file1 = open ( " datei2 . txt " ," w " ) >>> file1 . write ( " x y z \ n " ) >>> file1 . write ( " 1 2 3 " ) Beachte : der folgende Aufruf wuerde noch eine leere Datei ’ datei2 . txt ’ anzeigen >>> less datei2 . txt Erst bei Aufruf >>> file1 . close () hat datei2 . txt den erzeugten Inhalt >>> less datei2 . txt x y z 1 2 3 1 3 Module Ein wichtiges Konzept von Python ist die Auslagerung von Funktionen, Klassen und Variablen in sogenannte Module. Der Vorteil der Modularisierung ist, dass komplexe Projekte in mehrere kleinere Strukturen aufgeteilt werden knnen, welche unabhängig voneinander weiter entwickelt und gepflegt werden können. 3.1 Import von Modulen Die Sprache Python bietet bereits einige Standard Module, welche direkt nach dem Start des Interpreters verfügbar sind, sowie einige ”third-party”Module, welche zuerst installiert werden müssen (siehe Installation.pdf). Um auf Module zugreifen zu können, müssen diese zuerst dem Namensraum des Python Skriptes mittels des keywords import hinzugefügt werden. >>> import random # i m p o r t i e r t das Modul random >>> dir ( random ) # zeigt die v e r f u e g b a r e n F u n k t i o n e n des Moduls [ ... , ’ randint ’ , ... , ’ uniform ’ , ... ] Um auf die verfügbaren Funktionen und Variablen des Moduls zuzugreifen, verwendet man die Syntax, die auch schon für Methoden eingesetzt wurden. Syntax : Modulname . Funktion () Beispiel : >>> a = random . randint (0 ,10) # Zugriff auf die randint F u n k t i o n des Moduls >>> print a 4 >>> b = random . uniform (0 ,10) # Zugriff auf die uniform F u n k t i o n des Moduls >>> print b 5 .0 08 7 11 0 42 7 73 79 2 4 Funktionen und Variablen des Moduls können auch direkt in den Namensraum des aktuellen Skriptes eingepflegt werden. >>> >>> 0.0 >>> >>> 0.0 from math import sin , pi print sin ( pi ) # import sin and pi from module math from math import sin as Sinus # Import sin and add it to the n a m e s p a c e as Sinus print Sinus ( pi ) 4 NumPy Arrays Zwei der nützlichsten Module für das wissenschaftliche Arbeiten mit Python sind NumPy und SciPy. NumPy stellt dem Benutzer als wichtigstes Feature leistungsstarke multidimensionale Arrays und Matrizen und dazugehörige Operationen zur Verfügung, während SciPy unter anderem Module zur wissenschaftlichen Analyse und Simulation bietet. Für den Gebrauch von NumPy und SciPy is es empfehlenswert, die jeweiligen OnlineReferenzen zu den Paketen anzuschauen und griffbereit zu halten: • http://docs.scipy.org/doc/numpy/reference/ • http://docs.scipy.org/doc/scipy/reference/ Vorsicht ist geboten, da diese Dokumentation in der Regel den aktuellen Entwicklungsstand von NumPy/SciPy widerspiegelt und nicht zwangsläufig die auf dem Rechner installierte Version. Sollten beim Arbeiten mit NumPy/SciPy Diskrepanzen gegenüber der Referenz auftreten, findet man auf http://docs.scipy.org/doc/ Links zu den Referenzen vorheriger Versionen. 4.1 Initialisierung Das NumPy Arrays ist das wichtigste Konstrukt in NumPy. NumPy Arrays sind im wesentlichen ähnlich zu den bereits bekannten Listen, jedoch mit einigen Einschränkungen und Vorzügen. Nachteilig im Vergleich zu Listen ist die Einschränkung, dass die Elemente eines NumPy Arrays alle denselben Datentypen haben müssen. Dies wird aber durch die deutlich höhere Zugriffs- und Verarbeitungsgeschwindigkeit bei Arrayoperationen gegenüber Listen wettgemacht. Um Arrays benutzen zu können müssen wir erst das Modul numpy importieren, also dem Namensraum bekanntmachen. 2 >>> import numpy # Import des Modules numpy Um jetzt ein NumPy-Array aus z.B. einer Liste von Ganzzahlen zu erzeugen kann man man die Funktion array() aus dem Modul numpy benutzen: >>> myList = range (5) >>> myArr = numpy . array ( myList ) >>> print myArr , type ( myArr ) [0 1 2 3 4] < type ’ numpy . ndarray ’ > # Liste der G a n z z a h l e n 0 bis 4 # Array der G a n z z a h l e n 0 bis 4 Die Konvertierung von Listen mit gemischten Typen schlägt unter Umständen fehl, wenn keine einheitliche Konvertierung der Datentypen möglich ist: >>> myList = [4 , ’ spam ’ ,4.5 ,(4 ,6)] >>> myArr = numpy . array ( myList ) (...) ValueError : setting an array element with a sequence >>> myList = myList [: -1] # loesche Tupel >>> myArr = numpy . array ( myList ) >>> print myArr [ ’4 ’ ’ spam ’ ’ 4.5 ’] Im ersten Fall schlug die Konvertierung fehl, da kein gemeinsamer Datentyp für alle Elemente gefunden werden konnte (wegen des Tupels). Im zweiten Fall wurde als gemeinsamer Datentyp von int, string und float der string-Datentyp gewählt und für das Array festgelegt. Natürlich muss man nicht vorher Tupel oder Listen definieren, um Arrays zu erzeugen, es gibt eine Reihe nützlicher Funktionen zur Erzeugung von Arrays. Ähnlich zu range() für Listen gibt es arange() zur Erzeugung von auf- oder absteigenden Arrays von Zahlen. Praktischer an arange() ist hierbei, dass sich auch Ranges von Float-Zahlen generieren lassen: >>> myArr = numpy . arange (5) >>> print myArr [0 1 2 3 4] >>> myArr = numpy . arange ( -1 , -0.5 ,0.1) >>> print myArr [ -1. -0.9 -0.8 -0.7 -0.6] # drittes A r g u m e n t ist S c h r i t t w e i t e Dazu ähnlich ist die Funktion linspace(), die jedoch als drittes Argument die Anzahl an Elementen nimmt, und standardmäßig den Endwert enthält. >>> myArr = numpy . linspace (10 ,20 ,5) >>> print myArr [ 10. , 12.5 , 15. , 17.5 , 20. ] # drittes A r g u m e n t ist Anzahl E l e m e n t e Weitere praktische Funktionen zur Generierung von Arrays sind zeros(N), ones(N) und empty(N), die jeweils Arrays aus N zu null, eins oder uninitialisierten Elementen erzeugen. 4.2 Elementweise Operationen Ein wichtiger Vorteil von Arrays gegenber Listen ist es, dass arithmetische und Vergleichs-Operationen elementweise angewandt werden. Somit hat das Array eher Ähnlichkeiten zu einem Vektor als zu einer Liste, bei der dies nur ber Listen oder die noch nicht vorgestellen List Comprehensions möglich ist. Zu beachten ist, dass die elementweisen Funktionen aus dem Namensraum numpy statt aus math importiert werden müssen, um richtig auf Arrays zu funktionieren: >>> myArr >>> print [0 2 4 >>> print [ 0. = numpy . arange (5) myArr *2 6 8] numpy . sqrt ( myArr ) # nicht aus math 1. 1.41421356 1.73205081 2. ] Mathematische Verknüpfungen zweier Arrays werden im Gegensatz zu Listen ebenfalls elementweise abgehandelt: >>> myArr = numpy . arange (5) >>> myBrr = numpy . arange (0 ,1 ,0.2) >>> print myArr ** myBrr [ 1. , 1. , 1.31950791 , 1.93318204 , 3.03143313] >>> myBrr = numpy . arange (0 ,1 ,0.4) >>> print myArr ** myBrr # falsche Anzahl E l e m e n t e ... ValueError : shape mismatch : objects cannot be broadcast to a single shape 3 Dies bedeutet aber auch, dass sich Arrays im Gegensatz zu Listen nicht mit dem Additionsoperator vergrößern lassen. Hierzu muss das Array vergrößert werden, dies kann mit den im nächsten Abschnitt vorgestellten Funktionen geschehen. Auch Vergleichsoperatoren arbeiten elementweise auf Arrays und geben sogenannte Masken zurück (dazu mehr im nächsten Abschnitt): >>> myMask = myArr > 2 >>> print myMask [ False False False True 4.3 True ] Multidimensionalität und Indizierung Wie eingangs erwähnt ist das NumPy Array ein multidimensionaler Container. Zur Erzeugung von mehrdimensionalen Arrays gibt es etliche Möglichkeiten, eine einfache ist es z.B. die Funktionalität der von numpy bereitgestellten Erzeugungsfunktionen zu nutzen, ein sogenanntes shape-Tupel als Argument zu nehmen: >>> mdArray = numpy . zeros ((2 ,3)) >>> print mdArray [[ 0. 0. 0.] [ 0. 0. 0.]] # (2 ,3) gibt die shape an Diese Funktion erzeugt ein 2x3-Array und initialisiert jedes Element zu 0. Das Argument hierbei ist ein Tupel, der jeweils die Anzahl Elemente jeder Dimension angibt. Die Länge des Tupels gibt somit die Dimensionalität des Arrays an. Überprüfen kann man die Form (shape) des Arrays mit dem Attribut shape des Arrays, die Dimensionalität mit ndim, und die Gesamtzahl an Elementen mit size. >>> print mdArr . shape (2 ,3) >>> print mdArr . ndim 2 >>> print mdArr . size 6 Die Indizierung von Arrays funktioniert genau wie bei Listen: >>> myArr = numpy . arange (5) >>> print myArr [2] 2. Genauso ist das Slicen von Arrays möglich: >>> print myArr [1: -1] [1 2 3] Indizierung von multidimensionale Arrays lässt sich entweder über mehrere Indizierungsoperatorklammern oder ber Komma-getrennte Indizierungsanweisungen in einer Operatorklammer realisieren: >>> >>> >>> [[0 [3 >>> 3 >>> 2 mdArr mdArr print 1 2] 4 5]] print = numpy . arange (6) = mdArr . reshape ((2 ,3)) mdArr mdArr [1][0] print mdArr [0 ,2] Die Methode reshape() konvertiert das Array in die angegebene Form. Eine ähnliche Methode ist resize(), die darüber hinaus as Array auch vergrößern kann, wenn die im Tupel angegebene Anzahl Elemente größer als das Ausgangsarray ist. Natrlich sind auch hierbei in jeder Dimension Slices möglich: >>> print mdArr [0 ,:] # erste Zeile [0 1 2] >>> print mdArr [: , -1] # letzte Spalte [2 5] print mdArr [: -1 ,1:] [[1 , 2]] 4 Ein entscheidender Vorteil von NumPy Arrays ist es, dass zur Indizierung auch Listen/Arrays (allg. Sequenzen) oder Masken (True/False Arrays gleicher Form) benutzt werden können. Im ersten Fall besitzt die Index-Liste weniger Elemente als die Ursprugsliste, und jedes Element gibt an welche Indizes des Ursprungsarrays in das Ausgabearrays übernommen werden. >>> >>> >>> [10 myArr = numpy . arange (10 ,20) indArr = numpy . arange (0 ,10 ,2) # I n d i z i e r e jedes zweite Element print myArr [ indArr ] 12 14 16 18] Die bereits oben erwähnten Masken können ebenfalls zur Indizierung genutzt werden, und bieten eine natürliche Schreibweise zur Reduktion von Arrays. Zu beachten ist jedoch, dass die Masken die gleiche Form wie die zu indizierenden Arrays haben müssen. Daraus folgt auch, dass die Masken auf alle Arrays dieser Form angewandt werden können. Dies ist z.B. sehr nützlich zum gleichzeitigen Slicen mehrerer Spalten eines Datensatzes >>> >>> >>> >>> [16 myArr = numpy . arange (10 ,20) myBrr = numpy . arange (40 ,50) myMask = myArr > 15 print myArr [ myMask ] , myBrr [ myMask ] 17 18 19] [46 47 48 49] Masken lassen sich ebenfalls einfach über die Multiplikations- und Additionsoperatoren schneiden oder vereinigen: >>> myMask = ( myArr > 15) * ( myBrr < 49) >>> print myArr [ myMask ] , myBrr [ myMask ] [16 17] [46 47] Masken können auch zur Erzeugung von Indexlisten genutzt werden, indem sie an die Funktion where() übergeben werden: >>> myInd = numpy . where ( myMask ) >>> print myArr [ myInd ] , myBrr [ myInd ] [16 17] [46 47] 5 Übungen Für die Durchführung der Übungen nutzen Sie bitte die Online-Referenzen zu NumPy oder die help-Funktion, um sich mit der Funktionsweise der genannten Funktionen vertraut zu machen 1. Lesen Sie die drei Spalten der Datei ’Data.dat’ als Listen Typ ein. Die erste und zweite Spalte enthält die Koordinaten x und y (Längen- und Breitengrad der Marsoberfläche) und die dritte zugehörige Höhenmessung der Oberfläche, H(x, y). Konvertieren sie die Listen in NumPy arrays. 2. Entfernen Sie fehlerhafte Messungen der Höhe (z = −999) aus den in x, y, z eingelesenen Daten. Benutzen sie hierfür die Masken Funktionalität des NumPy arrays. 3. Erstellen Sie aus den drei eingelesenen und korrigierten NumPy Arrays ein zwei-dimensionales NumPy Array, Z. Hierbei soll Z die Dimension (1000, 1000) besitzen und die (gemittelten) Messwerte H(x, y) enthalten. Benutzen sie hierfür die Funktion histogram2d aus dem NumPy Modul. Zum Überprüfen des erzeugten Histograms, stellen sie dieses Grafisch dar: >>> import pylab >>> Z , x , y = histogram2d (...) >>> pylab . pcolormesh ( x [: -1] , y [: -1] , Z . T ) 5