Liste erstellen aus anderer Liste / AttributeError: 'str' object has no attribute 'append

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
krischanb
User
Beiträge: 12
Registriert: Samstag 9. Januar 2021, 18:38

Hi,

ich habe eine Datei mit Messwerten unterschiedlicher Sensoren, die Farbcodiert sind. Diese möchte ich nun auswerten. Nachfolgend der Code mit dem ich begonnen bin das zu bewerkstelligen:

Code: Alles auswählen

import sys
file=open(r"D:/Python/temperaturlog.txt")
inhalt=file.read()
file.close

# Funktion zum Aufteilen der Liste nach Sensoren
def liste(zwliste,sensor):
    sensor.append([zwliste[0],
        zwliste[1],
        zwliste[2],
        float(zwliste[3].replace(",",".")),
        zwliste[4]])

#Trennen am Zeilenumbruch
zeilenliste=inhalt.split(chr(10))

#leere Listen
#Blau=[]
#Schwarz=[]
#Draussen1=[]
Sensoren=[]

#Alle unterschiedlichen Sensoren in eine Liste schreiben
for zeile in zeilenliste:
    zwliste=zeile.split(";")
    if Sensoren.count(zwliste[0])==0 and zwliste[0]!="":
        Sensoren.append(zwliste[0])

# Sortieren der Sonsoren
for j in range(0,len(Sensoren),1):
    #Jede Zeile entsprechend des Sensors verarbeiten
    for zeile in zeilenliste:
        if zeile:
            zwliste=zeile.split(";")
            liste(zwliste,Sensoren[j])
            #if zwliste[0]=="Schwarz":
            #      liste(zwliste,Schwarz)
            #elif zwliste[0]=="Blau":
            #      liste(zwliste,Blau)
            #elif zwliste[0]=="Draussen1":
            #      liste(zwliste,Draussen1)

#Ausgabe
#for p in Blau:
#    print(f"{p[0]} {p[1]} {p[2][:5]} {p[3]:.2f} {p[4]}")

#for p in Schwarz:
#    print(f"{p[0]} {p[1]} {p[2][:5]} {p[3]:.2f} {p[4]}")

#for p in Draussen1:
#    print(f"{p[0]} {p[1]} {p[2][:5]} {p[3]:.2f} {p[4]}")
Mein aktuelles Problem ist der Bereich "#Sortieren der Sensoren".
Ich wollte den Bereich "dynamisch" gestalten, so dass wenn Sensoren hinzukommen diese ohne Aufwand automatisch integriert werden. Das Problem liegt konkret in: " liste(zwliste,Sensoren[j])" Meine Idee war, für jeden Sensor eine neue eigene Liste zu erstellen. Sensor[j] ergibt die Farbe des Sensors.

Folgende Fehlermeldung bekomme ich:
Traceback (most recent call last):
File "D:\Python\Temeraturauswertung.py", line 35, in <module>
liste(zwliste,Sensoren[1])
File "D:\Python\Temeraturauswertung.py", line 8, in liste
sensor.append([zwliste[0],
AttributeError: 'str' object has no attribute 'append'
Wenn ich den auskommentierten Bereich "#if zwliste[0]=="Schwarz": #liste(zwliste,Schwarz)" klappt das wie ich mir das vorstelle. Ich verstehe das so, dass meine Funktion eine Liste als Parameter erwartet. Meine Gedanke war, dass ich so eine neue Liste, welche ich über die Farbe anspreche, generiere.

Hier komme ich nicht mehr weiter, ich stecke noch in den Anfängen mit Python. Ich bin noch nicht so firm mit Dictionarys, vielleicht wäre das die elegantere Lösung. Kann mir da jemand einen Tip geben.

Vielen Dank und Gruß
Krischanb
Caskuda
User
Beiträge: 26
Registriert: Sonntag 15. März 2020, 22:09

Code: Alles auswählen

#Alle unterschiedlichen Sensoren in eine Liste schreiben
for zeile in zeilenliste:
    zwliste=zeile.split(";")
    if Sensoren.count(zwliste[0])==0 and zwliste[0]!="":
        Sensoren.append(zwliste[0])
Aus obigem Abschnitt stammt dein Problem:

zwliste ist eine Liste von Strings
zwliste[0] ist ein String
Alle Elemente in Sensoren sind somit Strings, wegen: Sensoren.append(zwliste[0])

Wäre hilfreich noch ein paar Zeilen von der Log-Datei zu sehen, um Verbesserungsvorschläge geben zu können.
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Man sollte keine Funktionsdefinitionen mitten in ausführbarem Code haben, am besten steht auf oberster Ebene gar kein ausführbarer Code.
`sys` wird importiert aber nicht benutzt.
Dateien, die man öffnet sollte man auch wieder schließen, indem man close auch aufruft (), oder noch besser, das with-Statement benutzt.
Datentypen sollten in Variablennamen nicht vorkommen, weil die sich während der Entwicklung auch mal ändern, die Namen sollten aussagekräftig sein und keine Abkürzungen enthalten. Was soll zw denn bedeuten?
Funktionsnamen sollten nach Tätigkeiten benannt werden, `liste` ist aber keine Tätigkeit.
Statt count == 0 könnte man den in-Operator nutzen.
`Sensoren` ist eine Liste mit Strings, und Strings haben nunmal keine append-Methode.
Was Du mit `Sensoren` überhaupt machen willst, ist mir nicht klar.
Ich glaube, Du möchtest ein Wörterbuch mit Listen verwenden, dazu bietet sich defaultdict an.

Du scheinst eine csv-Datei zu haben, da bietet es sich an, das csv-Modul zu benutzen.

Code: Alles auswählen

import csv
from collections import defaultdict

LOG_FILENAME = "D:/Python/temperaturlog.txt"

# Funktion zum Aufteilen der Liste nach Sensoren
def append_sensor(sensor, row):
    sensor.append([row[0],
        row[1],
        row[2],
        float(row[3].replace(",",".")),
        row[4]])

def main():
    sensors = defaultdict(list)
    with open(LOG_FILENAME, encoding="utf8", newline="") as input_file:
        rows = csv.reader(input_file)
        for row in rows:
            if row and row[0]:
                append_sensor(sensors[row[0]], row)

    #Ausgabe
    for data in sensors['Blau']:
        print(f"{data[0]} {data[1]} {data[2][:5]} {data[3]:.2f} {data[4]}")

if __name__ == '__main__':
    main()
krischanb
User
Beiträge: 12
Registriert: Samstag 9. Januar 2021, 18:38

Hallo,
@Caskuda:
Wäre hilfreich noch ein paar Zeilen (in Summe sind es ca. 16.000) von der Log-Datei zu sehen, um Verbesserungsvorschläge geben zu können.
So sieht der Log aus:

Sensor;Datum;Uhrzeit;Temperatur;Einheit
Schwarz;01.12.2020;11:40:02; 18,50;°C
Draussen1;01.12.2020;11:40:03; 4,69;°C
Blau;01.12.2020;11:40:04; 18,31;°C

@Sirius3:
Du scheinst eine csv-Datei zu haben, da bietet es sich an, das csv-Modul zu benutzen.
Dem ist nicht ganz so, siehe obiger Aufbau der Rohdaten. Das Trennzeichen ist ein ";" und kein ",". Aber das ist ja kein Problem, sondern lässt sich so "rows = csv.reader(input_file,delimiter=";")" anpassen.
Was Du mit `Sensoren` überhaupt machen willst, ist mir nicht klar.
Ich wollte die Messwerte, welche alle in einer Datei stehen, trennen um dann damit beginnen zu erst die Maxima und Minima zu bestimmen, diese dann auf Tage runterbrechen, Grafen erstellen. (Dies möchte ich mir selber erarbeiten und nicht vorgefertigten Code benutzen, sonst lerne ich es ja nicht.)
`Sensoren` ist eine Liste mit Strings, und Strings haben nunmal keine append-Methode.
Meine Idee war:
  • Besorge dir alle unterschiedlichen Sensorennamen (String), welche in der Liste auftauchen und schreibe diese in eine "Zwischenliste" als Interimsspeicher.
  • Erstelle anhand der Namen (String) aus der Zwischenliste neue Listen, welche ich über die Namen ansprechen kann.
  • Mit den Listen für jeden Sensor weitere Auswertungen (Min., Max, usw.) durchführen.
Nach meinem Verständnis müsste das mit einem Dictionary eleganter gehen, hier hatte ich aber einen Gedankenklemmer, mangels Erfahrung.
`sys` wird importiert aber nicht benutzt.
Wollte ich benutzen um Fehler abzufangen, wenn die Datei nicht existiert. Ist aktuell in der Tat unbenutzt.
Man sollte keine Funktionsdefinitionen mitten in ausführbarem Code haben, am besten steht auf oberster Ebene gar kein ausführbarer Code.
Funktionsnamen sollten nach Tätigkeiten benannt werden, `liste` ist aber keine Tätigkeit.
Datentypen sollten in Variablennamen nicht vorkommen, weil die sich während der Entwicklung auch mal ändern, die Namen sollten aussagekräftig sein und keine Abkürzungen enthalten. Was soll zw denn bedeuten?
Im letzten Zitat stehe ich bei "Datentypen sollten in Variablennamen nicht vorkommen," etwas auf dem Schlauch. Ansonsten vielen Dank für die Hinweise. Ich werde mich bemühen diese zu beherzigen.

Danke und Gruß
Krischanb
Benutzeravatar
__blackjack__
User
Beiträge: 14053
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@krischanb: Das mit den (Grund)Datentypen betrifft die Namen die `liste` enthalten. Also statt `zeilenliste` einfach nur `zeilen`. Denn bei ``for zeile in zeilenliste:`` in Deinem Code würde man beispielsweise eher nicht erst die gesamte Datei als eine grosse Zeichenkette einlesen und dann anden Zeilenenden aufteilen, sondern direkt über das geöffnete Dateiobjekt iterieren. Und dann ist `zeilenliste` als Name falsch. Der Name `zeilen` würde dagegen für jedes Objekt zutreffend sein das etwas iterierbares über Zeilen repräsentiert. Also sowohl eine Liste mit Zeilen als auch ein Dateiobjekt.

Du behandelst in Deinem Code die Kopfzeile der Datei nicht. Das wird Probleme geben "Temperatur" im ersten Datensatz in eine Zahl umzuwandeln.

Die beiden Spalten Datum und Zeit sollte man beim einlesen schon zu `datetime`-Objekten zusammenfassen.

Code: Alles auswählen

#!/usr/bin/env python3
import csv
from collections import defaultdict, namedtuple
from datetime import datetime as DateTime
from statistics import mean

LOG_FILENAME = "D:/Python/temperaturlog.txt"
HEADER = ["Sensor", "Datum", "Uhrzeit", "Temperatur", "Einheit"]
UNIT = "°C"

Record = namedtuple("Record", "sensor_name timestamp temperature")


def convert_row(row):
    sensor_name, date_text, time_text, temperature_text, unit = row
    if unit != UNIT:
        raise ValueError(f"expected unit {UNIT!r}, got {unit!r}")

    return Record(
        sensor_name,
        DateTime.strptime(f"{date_text} {time_text}", "%d.%m.%Y %H:%M:%S"),
        float(temperature_text.replace(",", ".")),
    )


def load_records(filename):
    sensor_name_to_records = defaultdict(list)
    with open(filename, encoding="utf-8", newline="") as file:
        rows = csv.reader(file, delimiter=";")
        header = next(rows)
        if header != HEADER:
            raise ValueError(f"expected {HEADER}, got {header}")

        for record in map(convert_row, rows):
            sensor_name_to_records[record.sensor_name].append(record)

    for records in sensor_name_to_records.values():
        records.sort()

    return sensor_name_to_records


def print_records(records):
    for record in records:
        print(f"{record.timestamp}  {record.temperature:.2f}{UNIT}")

    print()

    for label, value in [
        ("min", records[0].temperature),
        ("max", records[-1].temperature),
        ("mean", mean(record.temperature for record in records)),
    ]:
        print(f"{label:>5}: {value:.2f}{UNIT}")



def main():
    sensor_name_to_records = load_records(LOG_FILENAME)
    print_records(sensor_name_to_records["Blau"])


if __name__ == "__main__":
    main()
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
krischanb
User
Beiträge: 12
Registriert: Samstag 9. Januar 2021, 18:38

Hi,
@__blackjack__
Du behandelst in Deinem Code die Kopfzeile der Datei nicht. Das wird Probleme geben "Temperatur" im ersten Datensatz in eine Zahl umzuwandeln.
Ist hier konkret nicht problematisch, da ich dies nur für das Verständnis hier ergänzt habe. Ist in meinem Post unglücklich und von mir nicht richtig dargestellt. Aber grundsätzlich finde ich den Header sinvoll und dieser wird eingebaut. Auch wenn es hier sicherlich eindeutig ist, so wird es Anwendungsfälle geben, wo dies nicht der Fall ist.
Du hast mir, Sirius3 auch, viel Input gegeben. Da muss ich mich jetzt in Ruhe hinsetzen und das nachvollziehen. Ich glaube, ich habe die Texte weitestgehend verstanden, nun geht es an den Code. Das wird aber sicherlich erst im Laufe der Woche etwas werden.

Gruß
Krischanb
krischanb
User
Beiträge: 12
Registriert: Samstag 9. Januar 2021, 18:38

Hi,
@ __blackjack__
ich musste deinen Code ein wenig abändern. Die Liste im Dictionary ist nicht nach den Temperaturen aufsteigend oder absteigend sortiert.
for label, value in [
("min", records[0].temperature),
("max", records[-1].temperature),
("mean", mean(record.temperature for record in records)),
]:
ersetzt durch:
for label, value in [
("min", min(record.temperature for record in records)),
("max", max(record.temperature for record in records)),
("mean", mean(record.temperature for record in records)),
]:
Wenn ich diese Auswertung für jeden Tag im Monat und im Monat machen möchte, wie wäre dann der richtige Weg? Würde ich da ein Nested Dictionary mit mehreren namedtuple aufbauen und so verschachteln?
Record = namedtuple("Record", Record_Date)
Record_Date = namedtuple("Record_Date", Record_Day)
Record_Day = namedtuple("Record_Day", "sensor_name timestamp temperature")
print_records(sensor_name_to_records[key])
übergibt ja eine Liste die ausgewertet wird.

Grüße
Krischanb
Benutzeravatar
__blackjack__
User
Beiträge: 14053
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@krischanb: Ups, stimmt, die sind nach Sensorname, Zeitstempel, und Tempearatur sortiert, also mit der Tempearatur als letztes Kriterium.

Für diese Art von Auswertungen würde man sich eine Funktion schreiben die Records bekommt und die entsprechend nach Tag oder Monat gruppiert.

Letztlich solltest Du aber dringend überlegen ob Du nicht genug durch das selbst erfinden des Rades bis zu diesem Punkt gelernt hast und/oder nicht lieber an etwas anderem weiterlernen möchtest. Denn für diese Aufgabe(n) verwendet man das passende Werkzeug: die Pandas-Bibliothek.

Das gesamte bisherhige Programm schrumpft dabei auf das hier zusammen:

Code: Alles auswählen

#!/usr/bin/env python3
import pandas as pd

LOG_FILENAME = "D:/Python/temperaturlog.txt"
LOG_FILENAME = "test.csv"
UNIT = "°C"


def main():
    data = pd.read_csv(
        LOG_FILENAME,
        delimiter=";",
        index_col=["Sensor", "Zeitpunkt"],
        parse_dates={"Zeitpunkt": ["Datum", "Uhrzeit"]},
        decimal=",",
        encoding="utf-8",
    )
    if (data["Einheit"] != UNIT).any():
        raise ValueError(f"expected unit {UNIT!r} for all records")

    data = data.drop(columns=["Einheit"])

    blau_data = data.loc["Blau"]
    print(blau_data)
    print(blau_data.agg(["min", "max", "mean"]))


if __name__ == "__main__":
    main()
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
krischanb
User
Beiträge: 12
Registriert: Samstag 9. Januar 2021, 18:38

@__blackjack__ ,

Das Rad neu erfinden muss ich sicherlich nicht. Deine Art, wie du mir Hilfe geleistet hast, fand ich wirklich gut und geduldig. Du hast mir wertvolle Anregungen gegeben um selber zum Ziel zu finden.
Letztendlich ist das dabei rausgekommen:

Code: Alles auswählen

import pandas as pd

LOG_FILENAME = "D:/Python/temperaturlog.txt"
UNIT = "°C"


def main():
    data = pd.read_csv(
        LOG_FILENAME,
        delimiter=";",
        index_col=["Sensor","Zeitpunkt"],
        parse_dates={"Zeitpunkt": ["Datum"]},
        decimal=",",
        encoding="utf-8",
    )
    if (data["Einheit"] != UNIT).any():
        raise ValueError(f"expected unit {UNIT!r} for all records")

    df=pd.DataFrame(data)


    df_pivot = df.pivot_table(
        values="Temperatur",
        index="Zeitpunkt",
        columns="Sensor",
        aggfunc=["max","min","mean"]
        )

    
    ser_unstacked=df_pivot.unstack()
    df_unstacked=ser_unstacked.unstack()
    df_unstacked.index.names=["Werte","Sensor"]
    df_reorderd = df_unstacked.reorder_levels(["Sensor","Werte"])
    df_sorted = df_reorderd.sort_index(axis=0)
    

    print(df_sorted)


if __name__ == "__main__":
    main()
Sicherlich bietet das an der einen oder anderen Stelle noch Potential für eine schönere Programmierung.

Gruß
Krischanb
krischanb
User
Beiträge: 12
Registriert: Samstag 9. Januar 2021, 18:38

Hi,

Code: Alles auswählen

df.index
liefert mir als Ergebnis:
MultiIndex([( 'Schwarz', '2020-12-01 11:40:02'),
('Draussen1', '2020-12-01 11:40:03'),
( 'Blau', '2020-12-01 11:40:04'),
( 'Schwarz', '2020-12-01 11:50:02'),
('Draussen1', '2020-12-01 11:50:02'),
( 'Blau', '2020-12-01 11:50:03'),
( 'Schwarz', '2020-12-01 12:00:02'),
('Draussen1', '2020-12-01 12:00:03'),
( 'Blau', '2020-12-01 12:00:04'),
( 'Schwarz', '2020-12-01 12:10:02'),
...
( 'Blau', '2021-01-07 14:30:04'),
( 'Schwarz', '2021-01-07 14:40:02'),
('Draussen1', '2021-01-07 14:40:03'),
( 'Blau', '2021-01-07 14:40:04'),
( 'Schwarz', '2021-01-07 14:50:02'),
('Draussen1', '2021-01-07 14:50:03'),
( 'Blau', '2021-01-07 14:50:04'),
( 'Schwarz', '2021-01-07 15:00:02'),
('Draussen1', '2021-01-07 15:00:02'),
( 'Blau', '2021-01-07 15:00:03')],
names=['Sensor', 'Zeitpunkt'], length=16044)
Kann ich jetzt direkt aus diesem Multiindex mit dem Datum aus dem Timestamp witerarbeiten, oder muss ich diesen aufteilen in Uhrzeit und Datum und daraus zwei getrennte Indezes machen? Hier komme ich nicht weiter und müsste den Timestamp auftrennen.

Danke und Gruß
Krischanb
Benutzeravatar
__blackjack__
User
Beiträge: 14053
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@krischanb: Was willst Du denn machen?
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
krischanb
User
Beiträge: 12
Registriert: Samstag 9. Januar 2021, 18:38

Hi,

die Daten sind Temperaturmessungen (Draussen, Raumluft und die größte innenliegende Wand) um schlussendlich den Wärmestrom und die Wärmeverluste zu bestimmen.
Neben den Tagesextrema, die habe ich wenn ich nur die Datmusspalte parse

Code: Alles auswählen

parse_dates={"Zeitpunkt": ["Datum"]},

und nicht

Code: Alles auswählen

parse_dates={"Zeitpunkt": ["Datum", "Uhrzeit"]},
, Hier werden ja die zwei Spalten (Datum und Uhrzeit zusammengefügt), eventuell muss ich die doch getrennt lassen.
Bei der letzteren Codzeile fehlt mir irgendwie der Kniff in diesem Index nur auf das Datum und oder die Uhrzeit zuzugreifen. Ich habe versucht mit timestamp.date zu arbeiten, alledings ohne Erfolg.
Zudem sind auch bestimmte Zeitintervalle interessant. Also was passiert beispielsweise zwischen 22 Uhr, wenn die Heizung aus ist, und 6 Uhr des Folgetages. Hier möchte ich dann später auch Graphen zeichnen.
Ich hoffe mein Anliegen wird so etwas deutlicher.

Danke und Gruß
Krischanb
krischanb
User
Beiträge: 12
Registriert: Samstag 9. Januar 2021, 18:38

Hi,
beim Versuch mein obiges Problem zu lösen und mich dem Thema indizieren zu nähern bin ich auf folgendes Problem gestoßen, das ich so gerade nicht verstehe.

Code: Alles auswählen

df.loc[:,:]
zeigt mir wie erwartet den ganzen df,

Code: Alles auswählen

df.loc["Schwarz",:]
, zeigt mir wie erwartet alle Schwarzen Werte zu dem Index "Schwarz" an,

Code: Alles auswählen

df.loc[:,"2020-12-01 11:40:02"]
, erzeugt einen Key Error, heißt für mich, es gibt den Eintrag nicht,

Code: Alles auswählen

df.loc["Schwarz","2020-12-01 11:40:02"]
, zeigt mir die Werte zu dem Sensor zum gegebenen Timestamp an.

Hat jemand eine Idee, wie ich mich meinem Problem aus dem vorangegangenen Post nähern kann?

Danke
Krischanb
Antworten