Datenauswertung mittels Python

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.
Larusso
User
Beiträge: 14
Registriert: Dienstag 25. Februar 2014, 10:44

Hallo liebe Pythone,

ich stehe for folgendem Problem. Aus einer Datenbank lese ich mittels eines Skripts gefilterte Daten als .csv aus, welche ich nun mittels Python weiter aufbereiten möchte. Leider bin ich kein versierter Programmierer, daher mein Post.

Die .csv besteht aus ca 500000 Zeilen, mit einer Headerzeile für die Spaltenbezeichnungen (zwischen 5 bis 12 Spalten) und den dann folgenden Werten. Nun möchte ich allerdings die meisten Zeilen löschen, welche an irgendeiner Stelle innerhalb jeder Zeile den Wert "nan" stehen hat.

Die übrig bleibenden Zeilen sollen dann nach 2 Merkmalen (also 2 der Spalten) eine Matrix aufspannen und die Häufigkeiten über diese 2 Merkmale zählen (eine Art Zählschleife für jede mögliche Kombination der 2 Merkmale)

Bisher bin leider nur soweit gekommen, dass ich die Datei eingelesen habe mit:

Code: Alles auswählen

f = open("datei.csv")

zeilen = f.readlines()

f.close()
Ich scheitere bereits daran eine passende Schleife zu bauen um die entsprechenden Zeilen in Abhängigkeit ihrer Werte zu löschen.
Ich vermute es müsste irgendwie so aussehen:

Code: Alles auswählen

for zeile in zeilen:
    for index in zeile:
        if index == "nan":
            del zeilen[zeile]
        else:
            continue
Für eure Hilfe bin ich euch sehr dankbar!

Viele Grüße

Larusso
Zuletzt geändert von Anonymous am Dienstag 25. Februar 2014, 11:27, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Nutze hierzu besser das in Python enthaltene `csv`-Modul. Details zur Nutzung kannst du in dessen Doku nachlesen. Das Pattern für den einfachen Fall lautet:

Code: Alles auswählen

import csv

with open('datei.csv') as csv_file:
    reader = csv.reader(csv_file)
    for row in reader:
        do_something(row)
`row` ist hier eine Liste mit Elementen für die jeweilige Zeile.

Zum Testen bietet sich übrigens islice() an, weil man damit prima nur ein paar Datensätze zum Angucken rausfischen kann. Das oben genannte Pattern sähe gekürzt dann zum Beispiel so aus:

Code: Alles auswählen

from itertools import islice
[...]
for row in islice(reader, 5):
    print(row)  # zeigt nur die ersten 5 Zeilen an
BlackJack

@Larusso: Schau Dir doch mal an was `zeile` und `index` in Deinen Schleifen für Werte haben. Das sind nicht die, die Du wahrscheinlich denkst, denn der Name `index` passt so gar nicht zu den Werten die daran gebunden werden.

Ein ``continue`` am Ende des Kontrollflusses in einer Schleife hat keinen Effekt — der nächste Schleifendurchlauf würde ja sowieso der nächste Schritt sein. Der ``else``-Zweig ist also überflüssig.

In der Standardbibliothek gibt es ein Modul zum lesen und schreiben von CSV-Dateien: das `csv`-Modul.

Zum generellen Vorgehen: Um mit ``del some_list[index]`` ein Element aus einer Liste zu löschen muss `index` eine Zahl sein. Innerhalb einer Schleife über eine Liste Elemente aus genau dieser Liste zu löschen geht aber sowieso nicht, weil sich durch das löschen der Index aller folgenden Elemente verschiebt, wovon die ``for``-Schleife aber nichts mitbekommt und dadurch Elemente übersprungen werden.

Deshalb erstellt man üblicherweise eine neue Liste wo man nur die Elemente aufnimmt, die man haben möchte. Bei der Gelegenheit kann man dann auch gleich nur die beiden Spalten selektieren die für das Endergebnis wichtig sind. Und man kann das gleich beim Einlesen machen, damit man nicht die Werte von allen 500.000 Zeilen auf einmal im Speicher haben muss, wenn man sowieso einen Grossteil davon gar nicht haben will.

Das zählen der Kombinationen könnte man dann mit einem `collections.Counter` sehr einfach machen.

Und statt mit Listen zu operieren könnte man auch Generatorfunktionen und -Ausdrücke verwenden, beziehungsweise Funktionen aus dem `itertools`-Modul. Dann kann man einlesen und filtern mit konstantem Speicherverbrauch schreiben und nur das erstellen der Kombinationszähler belegt dann Speicher der abhängig von der Anzahl der Kombinationen in den Daten ist.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Larusso hat geschrieben:Ich scheitere bereits daran eine passende Schleife zu bauen um die entsprechenden Zeilen in Abhängigkeit ihrer Werte zu löschen.
Ich vermute es müsste irgendwie so aussehen:

Code: Alles auswählen

for zeile in zeilen:
    for index in zeile:
        if index == "nan":
            del zeilen[zeile]
        else:
            continue
Vergiss am Besten sofort, dass man Listenelemente löschen kann. Das hat aus technischen Gründen meistens eine sauschlechte Performance und führt zu unerwartetem Verhalten, wenn man zudem noch die Liste, aus der man löscht, gerade durchläuft. Der übliche Weg ist, eine neue Liste zu erstellen, die dann nur noch diejenigen Elemente enthält, die man behalten wollte.

Das `continue` ist hier übrigens überflüssig. Der Programmfluss würde an dieser Stelle so oder so mit dem nächsten Iterationsschritt beginnen. `continue` macht nur dann Sinn, wenn man mitten in einem Iterationsschritt die Abarbeitung für das aktuelle Elemente beenden und sofort mit dem nächsten Element weitermachen möchte. Es steht sinngemäß also für ein "brich aktuellen Iterationsschritt ab" und wird relativ selten verwendet.
Larusso
User
Beiträge: 14
Registriert: Dienstag 25. Februar 2014, 10:44

Danke für eure schnellen Antworten!

Ich werde mich direkt einmal damit beschäftigen und überdenken. Insbesondere danke für den Hinweis, dass der del-Befehl in Kombination mit einer For-Schleife keinen Sinn macht!

MfG

Larusso
Larusso
User
Beiträge: 14
Registriert: Dienstag 25. Februar 2014, 10:44

Moin Moin,

bin gerade dabei einige eurer Tipps umzusetzen, sieht wie folgt aus:

Code: Alles auswählen

import csv

rohdatenliste=[]
Ausgabeliste=[]
datei ="complete.csv"

with open(datei) as csv_file:
    reader = csv.reader(csv_file)
    for row in reader:
        rohdatenliste.append(row)
        
    for a in range(len(rohdatenliste)):
        if (rohdatenliste[a][5] != "nan") and (rohdatenliste[a][5] < 0.5):
            Ausgabeliste.append(rohdatenliste[a])
Mir haut der Index a scheinbar ab, da ich die Fehlermeldung line 13. index out of range erhalte, kann mir aber nicht erklären warum dies so ist, Anregungen?

Danke!
BlackJack

@Larusso: `a` kann gar nicht das Problem sein, denn dafür werden ja nur die Werte 0 bis len(rohdatenliste) - 1 verwendet. Dann muss es die 5 sein. Es gibt offenbar Zeilen in der Datei die weniger als 6 Spalten haben.

Die `rohdatenliste` scheint mir überflüssig zu sein. Wäre auch einfacher zu erstellen. In Zeile 8 einfach ``rohdatenliste = list(csv.reader(csv_file))``. Und wenn man die Daten erst einmal komplett einliest, braucht der Rest der Verarbeitung nicht mehr im ``with``-Block stehen.

``for i in range(len(sequence)):`` ist in Python ein „anti pattern”. Man braucht keinen Index um über die Elemente von einem Sequenztyp zu iterieren weil man *direkt* über diese Elemente gehen kann. Also ``for item in sequence:``.

Der konkrete Typ der Datenstruktur sollte nicht im Namen stehen. Wenn man den Typ dann mal ändert, muss man entweder überall den Namen ändern, oder man hat falsche, verwirrende Namen im Programm stehen.

Du vergleichst den gleichen Wert einmal mit einer Zeichenkette und einmal mit einer Zahl, da der Wert eine Zeichenkette ist, macht das vergleichen mit einer Zahl keinen Sinn. Ich würde die Daten in `float`\s wandeln und den ersten Test mit `math.isnan()` machen.

Wie sehen die Daten denn aus? Sind in den Zeilen nur Gleitkommazahlen, oder auch andere Daten?

Edit: Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python
import csv
from math import isnan


def main(): 
    filename = 'complete.csv'
    result = list()
    with open(filename) as csv_file:
        reader = csv.reader(csv_file)
        for row in reader:
            value = float(row[5])
            if not isnan(value) and value < 0.5:
                result.append(row)


if __name__ == '__main__':
    main()
Larusso
User
Beiträge: 14
Registriert: Dienstag 25. Februar 2014, 10:44

Hi Blackjack!

Die Daten sehen folgendermaßen aus:

Entweder: 2010-10-22;23:21:00;nan;nan;nan;nan;nan;nan;nan;nan;nan

Oder: 2010-10-22;23:22:00;11;4.09836;3.85604;0.384319;0.361596;32.6727;3.99455;7.24545;1026.41

Jede Zeile verfügt über die gleiche Anzahl an Spalten (meistens 11), lediglich deren Werte sind unterschiedlich, "nan", Datum, Uhrzeit oder Fließkommazahl.
Das ich 2 Vergleiche auf auf unterschiedliche Datentypen vornehme, war mir auch schon in den Kopf gekommen, danke nochmal für die Klarstellung.
Du vergleichst den gleichen Wert einmal mit einer Zeichenkette und einmal mit einer Zahl, da der Wert eine Zeichenkette ist, macht das vergleichen mit einer Zahl keinen Sinn. Ich würde die Daten in `float`\s wandeln und den ersten Test mit `math.isnan()` machen.
Das mache ich dann am besten so:

Code: Alles auswählen

 map(float, rohdatenliste)
?

Was genau meinst du mit den ersten test: math.isnan() ?

Danke im Voraus
Larusso
User
Beiträge: 14
Registriert: Dienstag 25. Februar 2014, 10:44

Ok, map geht schonmal nicht. Liegt denke ich an den ersten Einträgen, die Uhrzeit bzw. Datum darstellen. Auf Zeit und Datum würde ich eigentlich nur ungern verzichten, da sie schon ne gewisse Bedeutung für die spätere Auswertung haben
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Larusso: map geht nicht, weil Du Listen nicht in Floats umwandeln kannst. Und math.isnan tut genau das was der Name sagt, prüfen ob der Wert "nan" ist.
Larusso
User
Beiträge: 14
Registriert: Dienstag 25. Februar 2014, 10:44

Mir is klar, dass es dies abprüfen wird.

Ich bin mir nur nicht ganz sicher an welcher Stelle ich dies tun soll, vorm casten der Liste oder danach. Ich probiere mal weiter rum.
BlackJack

@Larusso: `math.isnan()` ist eine Funktion die testet ob eine Gleitkommazahl den Wert „not a number” hat. Genau so wenig wie es Sinn macht zu testen ob eine Zeichenkette kleiner als 0.5 ist, macht es umgekehrt keinen Sinn zu testen ob eine Zahl ungleich der Zeichenkette 'nan' ist, denn das ist sie immer, selbst wenn der Wert der Zahl „not a number” ist.

Das `map()` nicht geht ist ja klar weil sich eine Zeichenkette mit einem Darum nicht mit `float()` in eine Zahl umwandeln lässt. Man könnte aus jeder Zeilenliste eine neue erstellen aus den ersten beiden Elementen und den restlichen Elementen in Zahlen umgewandelt. Je nach dem was man später mit dem Datum und der Uhrzeit anfangen will, würde ich die auch umwandeln und zwar in *ein* `datetime`-Objekt aus dem `datetime`-Modul.
Larusso
User
Beiträge: 14
Registriert: Dienstag 25. Februar 2014, 10:44

Ok,
also zum Vorgang konkret:

Ich lese meine Rohdatendatei als Liste ein.

Nun erstelle ich eine weitere Liste die, welche die ersten beiden Spalteneinträge jeder Zeile aufnimmt (in datetime umwandeln) und eine weitere welche die restlichen Zahlenwerte aufnimmt (in float umwandeln).

Code: Alles auswählen

DTliste = []
Wertliste = []
Fr DTListe:

Code: Alles auswählen

with open(datei) as csv_file:
    rohdatenliste = list(csv.reader(csv_file))
    for a in rohdatenliste:
        for b in range(2):
            DTliste.append(rohdatenliste[a][b])
Fehler
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "csvnanfilter.py", line 5, in <module>
DTliste.append(rohdatenliste[a])
IndexError: list index out of range

Ärgerlich! Es hakt bei meinen Indizes
Larusso
User
Beiträge: 14
Registriert: Dienstag 25. Februar 2014, 10:44

Liegt es evtl. daran, dass jede Zeile als eine Zeichenkette gespeichert wurde und somit keine Index b existiert? Also keine Unterlisten pro Listeneintrag in der Hauptliste
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Bei "for a in rohdatenliste" enthält "a" schon eine Zeile und nicht den Index auf eine Zeile. Warum machst Du zwei Listen, wenn die Listeneinträge der beiden Listen doch zusammengehören?
Larusso
User
Beiträge: 14
Registriert: Dienstag 25. Februar 2014, 10:44

Weil die Einträge in den jeweiligen Zeilenspalten verschiedene Datentypen beeinhalten und ich diese separieren will.

Ich denke meine Überlegung war richtig. Meine "rohdatenliste" enthält als Einträge keine weitere Liste sondern eine Zeichenkette. Dies muss ich ändern um überhaupt Unterindizes zu bekommen,

daher auch mein Index out of range fehler!
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Dann lass dir doch mal "a" (was besser "row") heißen sollte, ausgeben. Dann siehst du schon, warum es nicht funktioniert. Wenn du mittls ``for i in range(...)`` auf Listenelemente zugreifst, dann ist das übrigens sehr unpythonisch. Du kannst direkt über Listen iterieren und solltest nicht den Umweg über Indizes gehen.

Du muss hier auch keinen Umweg über eine unnütze Liste gehen:

Code: Alles auswählen

with open(dateiname) as fp:
    for row in csv.reader(fp):
        for element in row:
            ...
Statt "DTliste" solltest du dir auch noch einen besseren Namen einfallen lassen. Zum einen sollten Namen von Variablen mit Kleinbuchstaben anfangen (siehe PEP 8), beginnen sie mit einem Großbuchstaben, dann deutet das auf eine Klasse hin. Dann solltest du auch keine Datentypen in Namen einbauen. Unter Umständen ändert sich mal der Typ und dann müsstest du alle Namen ändern. Wähle also einfach den Plural als Namen. Statt "tier_liste" also einfach "tiere". (Sellbiges gilt für "rohdatenliste"). Der einzig aussagekräftige Teil von "DTliste" ist das DT. Leider weiß niemand, nicht einmal du in vier Woche, was "DT" eigentlich bedeutet. Überlege dir also etwas vernünftiges und nicht selbstgemachte Abkürzungen.
Das Leben ist wie ein Tennisball.
Benutzeravatar
Balmung
User
Beiträge: 44
Registriert: Sonntag 17. März 2013, 18:36

Wie Sirius bereits sagte, das "a" referenziert das Element der Liste, nicht den Index. d.h. a würde auf das jeweilige subelement zugreifen. (eine bessere Benennung der Variablen wäre angebracht)

Anmerken sollte man vielleicht noch, dass das csv modul standardmäßig das Komma als Trenner nutzt (das C in CSV steht für "Comma" *schock*), du aber das Semikolon.
Um das zu berücksichtigen, füge einfach den delimiter Parameter hinzu:

Code: Alles auswählen

csv.reader(csv_file, delimiter=';')
»Honk Honk«
Larusso
User
Beiträge: 14
Registriert: Dienstag 25. Februar 2014, 10:44

So hallo und schoene neue Woche!

Mein Miniprogramm sieht nun folgendermaßen aus:

Code: Alles auswählen

# -*- coding: utf-8 -*-

import csv

datei = open("rda002_complete.csv", "rb")

Ausgabe = []

rohdaten = csv.reader(datei, delimiter = ";")


for row in rohdaten:
    if row[5] != "nan":
        Ausgabe.append(row)

datei.close()

neuedatei = open("_rda002_gefiltert.csv","wb")
writer = csv.writer(neuedatei)
writer.writerows(Ausgabe)
Funktioniert soweit, vielen Dank für eure Hilfe! Der eher anspruchsvolle Teil steht mir allerdings noch bevor (kommt noch ^^)

Einige Fragen hätte ich allerdings noch:
-In euren Vorschlägen arbeitet ihr mit with .. as zum öffnen, welchen Grund hat das genau?
- Ist datei.close() bei csv-reader überhaupt erforderlich?

Die Idee mit math.isnan in meiner Bedingung werde ich noch einbauen, hatte nur das Problem, dass die erste Zeile (meine Headerzeile) nicht floatbar ist, da Spaltenname. Funktioniert derzeit auch so wie oben abgebildet. Das wichtigste ist erstmal, dass nur die gültigen Zeilen mit werten ausgegeben werden. Sehr gut!
BlackJack

@Larusso: ``with`` sorgt bei Dateiobjekten dafür das Datei die in jedem Fall geschlossen wird sobald der Kontrollfluss den ``with``-Block verlässt. Und zwar egal auf welche weise das passiert, ob wegen einer Ausnahme, einem ``break`` oder ``continue`` falls der ``with``-Block in einer Schleife steht, oder durch eine ``return``-Anweisung.

Schliessen von Dateien ist wichtig, weil Dateideskriptoren keine unendliche Ressource sind. Die werden vom Betriebssystem üblicherweise begrenzt, darum sollte man sie durch Schliessen der Dateiobjekte wieder freigeben. Und beim Schreiben ist es ausserdem noch wichtig, weil erst nach dem im Schliessen impliziten `flush()`-Aufruf die Daten auch garantiert alle geschrieben werden und nicht noch etwas im Dateipuffer liegen bleibt.
Antworten