Messdaten aus Unterordner einlesen und in Array speichern?

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
MrRT
User
Beiträge: 2
Registriert: Freitag 13. Mai 2011, 20:28

Hallo alle zusammen,

ich bin neu hier und hab gleich mal eine Frage. Ich habe gerade erst angefangen Python zu skripten und will es primär im wissenschaftlichen Bereich nutzen. Bis jetzt habe ich eigentlich alles in Matlab gemacht und kenn mich da auch sehr gut aus (Die Aufgabe und Datenanalyse ist in Matlab schon gelöst!). Da Python jedoch frei ist, will ich jetzt einfach anfangen es zu lernen.

So nun zu meinem Problem:
Ich möchte Messdaten einlesen und diese Auswerten. Die Messwerte kommen aus LabView (ASCII). Die Messung hat zwei Freiheitsgrade (Temperatur, Strom). Die Dateien sind nach Temperaturen in Unterordner gespeichert. Hier mal die Ordnerstruktur:

Code: Alles auswählen

**************************
basePath
    |-- 10Grad
             |-- Datei 1 100mA
             |-- Datei 2 110mA
             |-- ...
    |-- 11Grad
             |-- Datei 1 100mA
    ...
***************************
Diese Dateien möchte ich in Python Auswerten. Das einlesen klappt schon, auch wenn ich mir ziemlich sicher bin, dass es elegantere möglichkeiten gibt.

Code: Alles auswählen

import numpy as np
import matplotlib.pyplot as plt
import glob
from mpl_toolkits.mplot3d import Axes3D

basePath = '.'
allfolder = []
filename = []
fh = []

for root, dirs, files in os.walk( basePath ):
     allfolder.append( dirs )

searchfolder = allfolder[ 0 ]

for folder in searchfolder:
     for files in glob.glob( folder + '/*.lvm' ):
          filename.append( files )
     fh.append( filename )
     filename = []

P = []

for n in range( 0, len( fh ) ):
     download = fh[ n ]
     P.append([])
     for k in download:
          data = np.genfromtxt( k, skiprows = 22 )
          P[n].append( data[ :, 1 ] )

test = np.array(P)
Mit os.walk finde ich erst alle Unterordner, welche ich dann in einer for-schleife durchsuche. Die ersten zwei for schleifen sind also nur dafür da, die Dateinamen in eine Liste zu speichern. In der letzten for-Schleife lade ich mir dann die Dateien ein. Zum Schluss mach ich aus der Liste ein Array. Dies brauch ich später, da ich noch eine Regression machen muss.

Zum ersten wäre jetzt meine Frage, ob dieser Code vielleicht noch optimierungsfähig ist, da ich später >10.000 Dateien einlesen möchte. In diesem Zusammenhang hätte ich vielleicht auch eine Frage an die allgemeine Leistungsfähigkeit von Python. Die 10.000 Dateien enthalten jeweils 5000 einzelne Messwerte. Das entspricht einem Array mit > 50 Millionen Werten (ca. 1,2 GB). Kann Python das überhaupt schaffen? Selbst in Matlab kommt es dann schon auf die richtige Programmierung an.

Für alle die neben Python auch noch Matlab können, das einlesen der Daten habe ich in Matlab mit folgendem Script gemacht:

Code: Alles auswählen

clear all

% Einlesen der Daten und speichern in einer Matrix und als Vektor
format long                                     % Speicherlänge auf long umstellen
Temp = [ 12 13 14 15 16 17 18 19 20 ];                   % Temperaturschritte !!!
for k = 1 : length( Temp );
    Folder = [ num2str( Temp( k ) ), 'Grad' ];  % Alle Ordner werden durchsucht und ausgelesen
    
    dateien = dir( fullfile( pwd, Folder , '*.lvm' ) );   % Dateien mit der Endung *.lvm werden eingelesen
    for i = 1 : length( dateien )
        messwerte{ i } = dlmread( [ fullfile( pwd, Folder , dateien( i ).name ) ], '\t', 22, 0);  % Hier werden die Daten in eine Matrix gespeichert
        PWR( :, i, k ) = messwerte{ i }( :, 3 );
    end

    WL = messwerte{ 1 }( :, 2 );    % Spaltenvektor
    I  = 1:1:550;                   % Zeilenvektor
end
Ein weiteres Problem habe ich jetzt mit der Auswertung der Daten. Da ich von Matlab komme, habe ich noch ein paar Probleme mit der Array-Struktur von Python und wie ich diese weiter aufrufe.
Ich würde diese Daten später gerne als 3dPlot und Contour-Plot ausgeben. Aber ich hab irgendwie noch Probleme mit der Struktur des Arrays und wie ich die einzelen Ebenen an die contour-Funktion übergebe.

Es würde mich freuen falls mir jemand mit diesem Problem helfen kann. Bei Bedarf kann ich ernst gemeinten Helfen auch gerne ein paar Dateien inkl. Python-Skript zukommen lassen.


Mit freundlichen Grüßen

MrRT
Benutzeravatar
pillmuncher
User
Beiträge: 1482
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Warum so barock? Lieber so:

Code: Alles auswählen

import numpy as np
import glob
import os

test = np.array([
    [np.genfromtxt(filename, skiprows=22)[:, 1] for filename in glob.iglob(os.path.join(subdir, '*.lvm' ))]
        for subdir in next(os.walk(os.curdir))[1]
])
Zum Plotten kann ich leider nichts sagen.

Gruß,
Mick.
Zuletzt geändert von pillmuncher am Samstag 14. Mai 2011, 00:04, insgesamt 1-mal geändert.
In specifications, Murphy's Law supersedes Ohm's.
BlackJack

@MrRT: Erst einmal eine Anmerkung zur Form: Üblich sind vier Leerzeichen pro Einrückungsebene. Wenn man Quelltext anderen zur Verfügung stellt oder Quelltext von anderen übernimmt, ist das wichtig, weil die Einrückungstiefe eben nicht nur ”Kosmetik”, sondern für den Compiler von Bedeutung ist. Ein paar weitere Konventionen findet man in PEP 8 -- Style Guide for Python Code.

”Pythonistas” legen in der Regel viel Wert auf gute Namen. Abkürzungen die nicht wirklich verbreitet sind, sollte man vermeiden. Also `fh` und `P` sind nicht wirklich schön. Bei `k` würde man einen Laufindex erwarten, aber keinen Dateinamen. Ausnahme bei kurzen Namen sind "list comprehensions" und Generatorausdrücke. Weil dort der Name in einem räumlich nur in einem sehr begrenzten Ausdruck verwendet wird. `allfolder` und `filename` sind überraschend weil der Name im Singular ist, sich aber jeweils ein Container-Objekt dahinter verbirgt. Wenn man `filename` liest, erwartet man üblicherweise *einen* Dateinamen und keine Liste von Dateinamen.

Wenn Dir die Verzeichnisstruktur bekannt ist, warum gehst Du dann rekursiv durch alle Verzeichnisse? Wenn das nur eine Ebene ist, würde ich einfach mit `os.listdir()` und `os.path.isdir() arbeiten. Oder auch dort schon mit `glob`, falls die Verzeichnisnamen einem bestimmten Muster folgen. Und schau Dir mal den Inhalt von `allfolder` an — das ist so eine etwas sinnlos verschachtelte Struktur.

Wenn das Einlesen so klappt wie gezeigt wäre ich sehr verwundert, denn Du benutzt das `os`-Modul ohne es zu importieren.

Dann funktioniert das auch nur weil `basePath` das aktuelle Verzeichnis ist und es nur eine Verzeichnisebene gibt. `os.walk()` gibt nur dir Verzeichnisnamen zurück und nicht den kompletten Pfad dort hin.

Man sollte Quelltextwiederholungen vermeiden. Zum Beispiel die Schleife über die Ordnernamen so organisieren, dass man `filename` nur an einer Stelle eine leere Liste zuweisen muss und nicht vor der Schleife *und* in der Schleife. Das könnte man zum Beispiel am Anfang der Schleife tun. Die Initialisierung von Namen die nur in einem sehr begrenzten Quelltextabschnitt eine Bedeutung haben gehört auch nicht an den Anfang. Das macht das Programm nur unübersichtlicher und erschwert es später diese Teile in eigene Funktionen auszugliedern, weil man sich dann erst einmal die verteilten Code-Stücke zusammen suchen muss.

Die Zuweisung einer leeren Liste kann man sich auch gänzlich sparen, denn `glob()` gibt ja schon eine Liste zurück. Und damit kann man sich den Namen `filename` eigentlich auch komplett sparen. Damit hätte die ``for``-Schleife nur noch eine Zeile im Schleifenkörper. In der werden Elemente an eine anfangs leere Liste angehängt. Also bietet sich an dieser Stelle eine "list comprehension" an und damit entfällt eine weitere Zeile: Das binden einer leeren Liste an `fh`.

Pfadnamen sollte man mit `os.path.join()` zusammen setzen.

Das Muster ``for i in xrange(len(obj)):`` ist ein ”code smell”. Man kann in Python *direkt* über die Elemente einer Liste/Sequenz iterieren, ohne den Umweg über einen Index. Falls man *zusätzlich* einen Index benötigt, gibt es die `enumerate()`-Funktion. In Deinem Fall ist der Index aber nicht nötig. An das letzte Element einer Liste kommt man mit dem Index -1. Schöner wäre hier aber die Liste die `P` hinzugefügt werden soll an einen Namen zu binden und nachdem sie befüllt wurde an `P` anzuhängen.

Ungetestet:

Code: Alles auswählen

import glob
import os
import numpy as np

base_path = '.'

folders = filter(
    os.path.isdir,
    (os.path.join(base_path, fn) for fn in os.listdir(base_path))
)
fh = [glob.glob(os.path.join(folder, '*.lvm')) for folder in folders]
P = [[np.genfromtxt(fn, skiprows=22)[:,1] for fn in dl] for dl in fh]

test = np.array(P)
Hat `test` am Ende denn die gewünschte Form?

Was die Datenmenge angeht könnte es bei einem 32-Bit-System/-Programm eng werden, weil da pro Prozess ”nur” 2 GiB Arbeitsspeicher verwendet werden können.
Benutzeravatar
pillmuncher
User
Beiträge: 1482
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Übrigens leiden alle bisher gezeigten Varianten daran, dass os.listdir() die Namen laut Dokumentation in arbitrary order ausgibt. os.walk() verwendet os.listdir(), also gilt da vermutlich dasselbe. In der Doku zu os.walk() steht leider nicht viel dazu, und im code nachzusehen ist mir grade zu viel Mühe. Bei mir (WinXP/cygwin) stimmt die Sortierung zwar, aber wenn in der Doku steht, ich soll mich nicht drauf verlassen, dann tu ich das besser auch nicht.

Jedenfalls vermute ich, dass MrRT sein Array gerne geordnet hätte, Vertikal nach Temperatur und horizontal nach "Datei 1 ...", "Datei 2 ...", ... .

Desweiteren gefällt mir keine der gezeigten Varianten besonders gut. Meine zB. steht im eklatanten Widerspruch zu Zen of Python's "Sparse is better then dense" und "Explicit is better then implicit". Wenn es ordent- und übersichtlich sein soll, dann vielleicht so:

Code: Alles auswählen

import numpy as np
import glob
import os
import itertools

def subdirs(path):
    return itertools.ifilter(os.path.isdir, (os.path.join(path, fn) for fn in os.listdir(path)))

def globjoined(path, pattern):
    return glob.iglob(os.path.join(path, pattern))

def extract_field(filename):
    return np.genfromtxt(filename, skiprows=22)[:, 1]

def filekey(filename):
    """ homework """

def dirkey(dirname):
    """ homework """

test = np.array([
    map(extract_field, sorted(globjoined(subdir, '*.lvm'), key=filekey))
        for subdir in sorted(subdirs(os.curdir), key=dirkey)
])
In specifications, Murphy's Law supersedes Ohm's.
BlackJack

Alternativ zum sortieren kann man auch den Weg aus dem Matlab-Skript beschreiten und sich die Verzeichnisnamen nicht auflisten lassen, sondern sie in der richtigen Reihenfolge generieren. Wie die aussehen ist ja anscheinend bekannt. Ungetesteter Ansatz:

Code: Alles auswählen

import os
from glob import glob
import numpy as np


def filename_key(filename):
    return filename     # TODO


def main():
    data = list()
    for temperature in xrange(12, 21):
        filenames = glob(os.path.join('%dGrad' % temperature, '*.lvm'))
        filenames.sort(key=filename_key)
        data.append([np.genfromtxt(fn, skiprows=22)[:,1] for fn in filenames])
    # ...


if __name__ == '__main__':
    main()
MrRT
User
Beiträge: 2
Registriert: Freitag 13. Mai 2011, 20:28

Hallo,

erst einmal vielen Dank für die schnellen Antworten. Grundsätzlich funktionieren alle Ansätze gleich, sodass in test das gleiche steht. Wobei mir der Ansatz von pillmuncher am besten gefällt.

Da ich wie gesagt aus der Matlab-Welt komme, muss ich manchmal noch ein bisschen umdenken.
Aber leider muss ich anmerken, dass ich die Skripte etwas verschachtelt und unübersichtlich finde. Ich bin totaler Python Anfänger und muss jetzt erst einmal schauen dass ich die geschriebenen Skripte versteh. Ich hab ehrlich gesagt Null Ahnung was die einzelnen Skripte machen.

@BlackJack: Ich weiß das es mir eigentlich nicht zusteht, aber wenn du mir schon etwas von Form erzählst (ich nehm es dankend an), dann würde ich es vielleicht begrüßen, wenn "Pythonistas" vielleicht etwas mehr Leerzeichen verwenden würden. Ab und zu ein Leerzeichen zu setzen macht einen Code deutlich lesbarer (besonders bei Klammern und vielen Objekten)

Code: Alles auswählen

# Anstatt
data.append([np.genfromtxt(fn, skiprows=22)[:,1] for fn in filenames])

data.append( [ np.genfromtxt( fn, skiprows=22 )[:,1] for fn in filenames ] )
Ich setz mich jetzt erstmal hin und versuch einen der Codes zu verstehen...
Benutzeravatar
pillmuncher
User
Beiträge: 1482
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

MrRT hat geschrieben:Grundsätzlich funktionieren alle Ansätze gleich, sodass in test das gleiche steht. Wobei mir der Ansatz von pillmuncher am besten gefällt.
Mir dagegen gefällt BlackJacks neueste Lösung am besten, weil sie ihre Annahmen über die Umwelt (= Verzeichnisnamen) explizit macht, und explizit ist besser als implizit. Dadurch wird auch der Code kürzer und einfacher.
MrRT hat geschrieben:[...] würde ich es vielleicht begrüßen, wenn "Pythonistas" vielleicht etwas mehr Leerzeichen verwenden würden. Ab und zu ein Leerzeichen zu setzen macht einen Code deutlich lesbarer (besonders bei Klammern und vielen Objekten)

Code: Alles auswählen

# Anstatt
data.append([np.genfromtxt(fn, skiprows=22)[:,1] for fn in filenames])

data.append( [ np.genfromtxt( fn, skiprows=22 )[:,1] for fn in filenames ] )
Im PEP8, auf den BlackJack ja schon hingewiesen hat, steht als erster Punkt unter Whitespace in Expressions and Statements:

Code: Alles auswählen

Avoid extraneous whitespace in the following situations:

    - Immediately inside parentheses, brackets or braces.

      Yes: spam(ham[1], {eggs: 2})
      No:  spam( ham[ 1 ], { eggs: 2 } )
Ich kann nur empfehlen, PEP8 zu lesen. Auch wenn einem manche der Regeln nicht gefallen, sollte man versuchen, sie zu beherzigen. Weil sie bei den Pythonistas überwiegend Konsens sind, kann man regelmäßig Fremd-Code leicht verstehen, weil man sich nicht erst mit den Manierismen der Programmierer auseinandesetzen muss.

Konsens ist auch PEP20, The Zen of Python. Das kann man auch lesen, wenn man am Python-Prompt import this eingibt.

Gruß,
Mick.
In specifications, Murphy's Law supersedes Ohm's.
Antworten