Seite 1 von 1

Werte sammeln und Häufigkeiten zählen

Verfasst: Samstag 18. Februar 2023, 19:29
von lagerschaden
Ich hab ein eigentlich einfaches Problem und weiss nicht, wie ich es in Python lösen soll. Mein Level ist ziemlicher Anfänger.

Ein Messgerät spuckt Integer-Werte aus, die sind in einer Datei fortlaufend in jeweils einer eigenen Zeile gespeichert:
430
456
470
456
456
450
450
Mal sind es ein paar hundert Werte, es können aber auch ein paar tausend sein.

Ich möchte nun wissen,, wie oft jeder Wert vorkommt, die Werte sollen sortiert ausgegeben werden, die häufigsten zuerst:
456, 3
450, 2
430, 1
470, 1

Ich weiss nicht, wie ich eine 2-Spaltige Liste o.ä. von Werte schaffe und bearbeite, 1.Spalte mit den Werten, 2.Spalte mit der dazugehörigen Häufigkeit.

Für Hinweise wäre ich dankbar.

Re: Werte sammeln und Häufigkeiten zählen

Verfasst: Samstag 18. Februar 2023, 19:40
von nezzcarth
Grundsätzlich geht so etwas mit collections.Counter:

(
Fließkommazahlen kommen bei dir im Beispiel nicht vor, sind bei Messwerten jedoch ja Grundsätzlich schon ein Thema und können manchmal Probleme machen:

Code: Alles auswählen

In [1]: from collections import Counter

In [2]: Counter((1.2345678912345678, 1.2345678912345679))
Out[2]: Counter({1.234567891234568: 2})
)

Re: Werte sammeln und Häufigkeiten zählen

Verfasst: Samstag 18. Februar 2023, 20:27
von lagerschaden
Alle Werte sind Integer, je Zeile 1 Wert
Ich habe das mal mit Counter probiert:

Code: Alles auswählen

#!/usr/bin/python3

import os
from collections import Counter

daten = open('nr','r')
i = 0

for wert in daten:

  i = i + 1
  c = Counter(wert)

  print(i, c, wert)

daten.close()
ergibt:
1 Counter({'4': 1, '3': 1, '0': 1, '\n': 1}) 430
2 Counter({'4': 1, '5': 1, '6': 1, '\n': 1}) 456
3 Counter({'4': 1, '7': 1, '0': 1, '\n': 1}) 470
4 Counter({'4': 1, '5': 1, '6': 1, '\n': 1}) 456
5 Counter({'4': 1, '5': 1, '6': 1, '\n': 1}) 456
6 Counter({'4': 1, '5': 1, '0': 1, '\n': 1}) 450
7 Counter({'4': 1, '5': 1, '0': 1}) 450
Counter zählt nicht die Werte, sondern die Zeichen incl des Zeilenvoschubs

Re: Werte sammeln und Häufigkeiten zählen

Verfasst: Samstag 18. Februar 2023, 20:40
von __deets__
Weil du die Zeichen (die man nun mal aus Dateien erhält) nicht in Zahlwerte wandelst. Kannst du selbst tun. Oder einfach sowas wie Pandas benutzen, das erledigt das meistens automatisch, und kennt garantiert auch eine Histogramm-Funktion.

Re: Werte sammeln und Häufigkeiten zählen

Verfasst: Samstag 18. Februar 2023, 20:44
von __blackjack__
@lagerschaden: Der `Counter` zählt die Elemente von dem Objekt das übergeben wird. Wenn Du dem eine einzelne Zeile übergibst, dann zählt er die Elemente von der Zeile, also die Zeichen. Du musst *einen* `Counter` erstellen, mit den Elementen die Du zählen willst, also mit den Zahlen.

Weitere Anmerkungen: Eingerückt wird mit vier Leerzeichen pro Ebene.

`os` wird importiert, aber nirgends verwendet.

Dateien öffnet man wo möglich mit der ``with``-Anweisung, und bei Textdateien sollte man immer explizit die Kodierung angeben.

Das manuelle hochzählen von `i` würde man in Python mit der `enumerate()`-Funktion erledigen.

Wenn man Zahlen zählen will, sollte man die auch Texte/Zeilen aus der Datei auch besser tatsächlich in Zahlen umwandeln.

`c` ist kein guter Name, wie die meisten einbuchstabigen, nichtssagenden Namen. `x`, `y`, und `z` für allgemeine Gleitkommawerte und Koordinaten und `i`, `j`, `k` für ganze Zahlen die als (Lauf)Index verwendet werden sind fast die einzigen Ausnahmen wo ein einzelner Buchstabe okay ist, falls man keinen besseren findet. Hier beispielsweise `line_number` statt `i`.

Das Ursprungsprogramm überarbeitet, aber immer noch mit dem Fehler:

Code: Alles auswählen

#!/usr/bin/env python3
from collections import Counter


def main():
    with open("nr", encoding="ascii") as daten:
        for line_number, wert in enumerate(map(int, daten), 1):
            counter = Counter(wert)
            print(line_number, counter, wert)


if __name__ == "__main__":
    main()
Denn wenn man den beseitigt, verschwindet auch die Zeilennummer (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
from collections import Counter


def main():
    with open("nr", encoding="ascii") as daten:
        counter = Counter(map(int, daten))
    print(counter)


if __name__ == "__main__":
    main()

Re: Werte sammeln und Häufigkeiten zählen

Verfasst: Samstag 18. Februar 2023, 21:23
von lagerschaden
Danke für die Hinweise, habe jetzt selbst eine Lösung mit Counter gefunden.

Code: Alles auswählen

#!/usr/bin/python3

from collections import Counter

daten = open('nr', 'r')
i = 0
a=[]

for wert in daten:
    wert = wert[:wert.find(' ')]
    a = a + [wert]
    i = i + 1
daten.close()
print(Counter(a))

Re: Werte sammeln und Häufigkeiten zählen

Verfasst: Samstag 18. Februar 2023, 21:43
von lagerschaden
und noch etwa kürzer

Code: Alles auswählen

#!/usr/bin/python3

from collections import Counter

daten = open('access.log','r')
a=[]

for wert in daten:
  wert = wert[:wert.find(' ')]  # schneidet alles nach den Werten ab, da können noch Zusätze stehen, die für die Zählung irrelevant sind
  a.append(wert)
daten.close()

print(Counter(a), len(a))

Re: Werte sammeln und Häufigkeiten zählen

Verfasst: Samstag 18. Februar 2023, 21:52
von __deets__
Das ist ungeschickt und zu kompliziert. Statt mit Zahlwerten arbeitest du jetzt mit Strings. Das mag unmittelbar dein Problem lösen, aber man sollte schon die richtigen Datentypen für sowas benutzen.

Re: Werte sammeln und Häufigkeiten zählen

Verfasst: Samstag 18. Februar 2023, 23:12
von __blackjack__
Das mit dem `find()` ist auch nicht so richtig robust. Falls da kein Leerzeichen ist, dann gibt die Methode -1 zurück, das heisst das letzte Zeichen wird abgeschnitten. Das ist recht ”magisch” und man hat ein Problem falls die letzte Zeile nicht durch ein Zeilenendezeichen abgeschlossen ist. Ich würde das expliziter, beispielsweise mit der `partition()`-Methode lösen.

Re: Werte sammeln und Häufigkeiten zählen

Verfasst: Sonntag 19. Februar 2023, 06:12
von snafu
Hier mal eine robustere Variante, die auch mit leeren Zeilen klarkommt:

Code: Alles auswählen

#!/usr/bin/env python3
from collections import Counter

FILENAME = "access.log"

def get_number_counter(stream):
    return Counter(
        int(line.partition(" ")[0])
        for line in stream if line.strip()
    )

def show_number_counts(counter):
    for number, count in counter.items():
        print(f"{count}x\t{number}")

def main():
    with open(FILENAME, encoding="ascii") as stream:
        counter = get_number_counter(stream)
    show_number_counts(counter)

if __name__ == "__main__":
    main()

Re: Werte sammeln und Häufigkeiten zählen

Verfasst: Sonntag 19. Februar 2023, 16:00
von snafu
Und wenn man das ganze noch ausbauen will:

Code: Alles auswählen

#!/usr/bin/env python3
from collections import Counter

FILENAME = "access.log"

def get_number_counter(stream):
    return Counter(
        int(parts[0]) for parts in (
            line.split() for line in stream
        ) if parts and parts[0].isdigit()
    )

def show_number_counts(counter):
    for number, count in sorted(counter.items()):
        print(f"{count}x\t{number}")

def main():
    with open(FILENAME, encoding="ascii") as stream:
        counter = get_number_counter(stream)
    show_number_counts(counter)

if __name__ == "__main__":
    main()
Damit werden nur Zeilen gezählt, die mit einer Nummer anfangen. Somit kommt das Skript auch mit Kommentar-Zeilen klar. Außerdem ist die Ausgabe der Zahlen jetzt sortiert von klein nach groß.

Re: Werte sammeln und Häufigkeiten zählen

Verfasst: Montag 20. Februar 2023, 20:22
von DeaD_EyE
Noch schwerer für Anfänger zu verstehen:

Code: Alles auswählen

from collections import Counter
from random import choices


# 100 Zufällige Messwerte zwischen 430 und 450 schreiben
with open("messwerte.txt", "w") as fd:
     for value in choices(range(430, 451), k=100):
         fd.write(f"{value}\n")


with open("messwerte.txt", "rb") as fd:
    # innerhalb dieses Blocks ist die Datei geöffnet und
    # kann gelesen werden
    messwerte = [
        int(digits) for line in fd if (digits := line.rstrip()) and digits.isdigit()
    ]

messwerte_c = Counter(messwerte)
print(messwerte_c)

Anfängerfreundlicher:

Code: Alles auswählen

from collections import Counter


with open("messwerte.txt", "rb") as fd:
    messwerte = []

    for line in fd:
        try:
            messwerte.append(int(line.rstrip()))
        except ValueError:
            pass

messwerte_c = Counter(messwerte)
print(messwerte_c)
Im zweiten Beispiel nutze ich das Prinzip: 'Ask for Forgiveness, Not Permission'
Im ersten Beispiel ist es nicht möglich, da ich eine List Comprehension nutze und dort keine Fehler abgefangen werden können.

Oftmals ist es einfacher einen Fehler abzufangen, als vorher alle möglichen Prüfungen durchzuführen.
Beim int ist es noch recht einfach. Bei einem float gibt es viele Möglichkeiten der Darstellung, was z.B. diese Prüfungen noch länger gestaltet. Aber schon beim int kann man erkennen, dass der Code etwas kürzer ist.


Anmerkung zu Bytes: In beiden Beispielen öffne ich die Dateien im RAW-Modus "rb". Dann bekommt man `bytes` anstatt `str`. Die Funktion `int()` kann auch `bytes` in eine Ganzzahl umwandeln.

PS: Ich habe jetzt nicht so viel gemacht wie die anderen. Du sollst ja programmieren lernen.

Re: Werte sammeln und Häufigkeiten zählen

Verfasst: Dienstag 21. Februar 2023, 06:31
von snafu
@DeaD_EyE
Laut OP steht in den Zeilen nicht zwingend nur eine Zahl. Es können noch Zusatzinformationen dabei sein, die aber nicht verarbeitet werden sollen. Damit käme dein Code nicht klar...

Re: Werte sammeln und Häufigkeiten zählen

Verfasst: Mittwoch 22. Februar 2023, 09:59
von DeaD_EyE
snafu hat geschrieben: Dienstag 21. Februar 2023, 06:31 @DeaD_EyE
Laut OP steht in den Zeilen nicht zwingend nur eine Zahl. Es können noch Zusatzinformationen dabei sein, die aber nicht verarbeitet werden sollen. Damit käme dein Code nicht klar...
Ja, am Ende käme eine leere Liste bei raus und der OP weiß dann nicht warum.