Werte sammeln und Häufigkeiten zählen

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
lagerschaden
User
Beiträge: 13
Registriert: Samstag 21. Oktober 2017, 11:34

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.
nezzcarth
User
Beiträge: 1749
Registriert: Samstag 16. April 2011, 12:47

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})
)
lagerschaden
User
Beiträge: 13
Registriert: Samstag 21. Oktober 2017, 11:34

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
Zuletzt geändert von lagerschaden am Samstag 18. Februar 2023, 20:51, insgesamt 1-mal geändert.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

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.
Benutzeravatar
__blackjack__
User
Beiträge: 13998
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@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()
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
lagerschaden
User
Beiträge: 13
Registriert: Samstag 21. Oktober 2017, 11:34

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))
lagerschaden
User
Beiträge: 13
Registriert: Samstag 21. Oktober 2017, 11:34

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))
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

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.
Benutzeravatar
__blackjack__
User
Beiträge: 13998
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

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.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
snafu
User
Beiträge: 6850
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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()
Benutzeravatar
snafu
User
Beiträge: 6850
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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ß.
Benutzeravatar
DeaD_EyE
User
Beiträge: 1219
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

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.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
snafu
User
Beiträge: 6850
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@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...
Benutzeravatar
DeaD_EyE
User
Beiträge: 1219
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

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.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Antworten