CSV Liste zählen wie oft ein verschiedene Nummern vorkommen

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
Benutzeravatar
martinjo
User
Beiträge: 186
Registriert: Dienstag 14. Juni 2011, 20:03

Hallo,
ich habe eine Liste mit verschiedenen Verkäufen, diese haben alle eine Artikelnummer in einer bestimmten Spalte und in einer anderen einen Preis.

Nun würde ich daraus gerne eine Liste erstellen, welche ich dann wieder als CSV speichere, die folgendermaßen aussieht.

Code: Alles auswählen

Artikelnummer | Bezeichnung | Anzahl an Verkäufen | Durchschnittlicher Preis | MaxPreis
An sich wohl nicht so kompliziert - aber ich hänge jetzt schon seit 3 Stunden dran (habe viel wieder verworfen) und wollte das morgen früh fertig haben was gerade nicht danach aussieht das ich es schaffe^^

Ich speichere für len(Stückzahl) pro Verkauf die Informationen in einer Liste, später erstelle ich aus den Artikelnummern ein Set. Wie bekomme ich jetzt aber die Anzahl der Vorkommen, am Anfang hatte ich noch keine verschachtelte Liste aber jetzt und da kann ich nicht mehr counten. Wenn ich die Anzahl jedoch separat speichere Fehlt die passende Zuweisung (bzw. ich bekomme es gerade nicht mehr hin).

Ist noch jemand wach der mir ein Stück weiterhelfen würde?


Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf8 -*-

import csv

reader = csv.DictReader(open("Verkaeufe.csv", "r"))

liste = []
lines = []

print "-----------------------------"

print "Sales:"
for row in reader:
    if row["Teile Nr"]:
        try:
            teilenummer = row["Teile Nr"]
            bezeichnung = row["Bezeichnung"]
            preis_solo = row["Einzelpreis"]
            anzahl = int(row["Anz"])
        except:
            raise
        print anzahl, "x ", teilenummer
        for jedes in range(anzahl):
                liste.append([teilenummer, bezeichnung, anzahl, preis_solo])


print "-----------------------------"

menge = set()
for zeile in liste:
    menge.add(zeile[0])

for nummer in menge:
    lines.append([nummer, "hier soll anzahl der vorkommen rein)])


http://www.megafileupload.com/en/file/3 ... e-csv.html
Zuletzt geändert von Anonymous am Samstag 23. Juni 2012, 00:27, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Irgend wie scheint mir Dein CSV korrupt zu sein! Ich bekomme dieses hier:

Code: Alles auswählen

Datum,Rg Nr.,Kunde,Plz, Ort,Teile Nr,Bezeichnung,Hersteller,Anz,Rbest,Einzelpreis,Gesamtpreis,Gebühren 
,,,,,,,,Stück,,NETTO,NETTO,gesamt
,,,,,,,,,,lt Rechnung,lt Rechnung,
,,,,,,,,,,,,
,,,,,,,,,,,,
01.03.12,EFA601,HELMUT,25926,##,2530-112-122,TRITT AUFSTIEG,MB,2,10 N,"57,84","115,69","17,43"
03.03.12,EFA602,NORBERT,90556,##,2540-112-12,SPIEGEL RUND,MB,1,,"14,99","14,99","5,05"
05.03.12,EFA603,MICHAEL,81739,##,2540-112-12,SPIEGEL RUND,MB,3,,"12,39","37,17","10,09"
05.03.12,EFA604,DETLEF,27386,##,12-134-0958,VERSTÄRKER,MB,1,,"247,93","247,93","27,10"
08.06.12,3297,JENS,49086,##,2512-134-0631,UNTERLAGE,MB,10,115 N,"5,88","58,82","1,33"
11.06.12,3298,ANDEREAS,58636,##,2530-0451 F08,VENTIL,MB,1,18 F,"57,14","57,14","6,47"
18.06.12,3305,SABINE,23898,##,12-136-2198,SPIEGEL OVAL,MB,1,,"15,13","15,13",
22.06.12,3310,SVEN,21717,##,2512-134-0631,UNTERLAGE,MB,1,,"50,42","50,42","1,64"
Die Zeilen zwei und drei sehen für mich merkwürdig aus... vier und fünf sind offensichtlich nur "Leerzeilen". Ist das alles so gewollt? Wenn nein, wie soll es korrekt aussehen?

Die "regelmäßigen" Zeilen haben 13 Datenspalten, wenn ich richtig gezählt habe - das entspräche genau diesen Keys:

Code: Alles auswählen

Datum,Rg Nr.,Kunde,Plz, Ort,Teile Nr,Bezeichnung,Hersteller,Anz,Rbest,Einzelpreis,Gesamtpreis,Gebühren 
Kannst Du das bitte noch mal erklären und ggf. korrgieren?

Btw: Solche kleine Samples kannst Du hier direkt im Forum oder einem Pastebin posten ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
martinjo
User
Beiträge: 186
Registriert: Dienstag 14. Juni 2011, 20:03

Die CSV-Datei sieht etwas zerlumpt aus, die Originale sogar noch schlimmer, aber dass ist ok so.

Deswegen teste ich zuerst ob ein Inhalt in der Zeile mit der Artikelnummer ist:

Code: Alles auswählen

if row["Teile Nr"]:
Ich bin jetzt auch schon etwas weiter gekommen:

Code: Alles auswählen

menge = set()
for zeile in liste:
    menge.add(zeile[0])

for nummer in menge:
    anzahl_temp = 0
    preise_temp = []
    for i in liste:
        if nummer == i[0]:   
            anzahl_temp += 1
            preise_temp.append(i[3])
    durchschnitt = sum(preise_temp)/len(preise_temp)
    lines.append([nummer, anzahl_temp, durchschnitt])

for l in lines:
    print l
Die CSV wollte ich irgendwie nicht direkt posten da ich dachte es ist einfacher wenn man die direkt speichern kann.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Also das hier wäre mein Ansatz:

Code: Alles auswählen

#!/usr/bin/env python

import argparse
import csv
from collections import defaultdict, Counter
from pprint import pprint


def avg(numbers):
    return sum(numbers) / len(numbers)


def parse_file(filename):
    prices = defaultdict(list)
    sells = Counter()
    description = dict()
    with open("verkäufe_corrected.csv") as f:
        reader = csv.DictReader(f)
        for row in reader:
            article = row["Teile Nr"]
            description[article] = row["Bezeichnung"]
            prices[article].append(float(row["Einzelpreis"].replace(",", ".")))
            sells.update((article,))
    return [(key, name, sells.get(key), avg(prices[key]), max(prices[key])) 
            for key, name in description.items()]


def main():
    parser = argparse.ArgumentParser(description='CSV-Parser')
    parser.add_argument("filename", metavar="FILE", help='CSV file')
    args = parser.parse_args()

    result = parse_file(args.filename)
    
    # hier muss Du noch eine `save`-Funktion bauen
    pprint(result)


if __name__ == "__main__":
    main()
Ist schon spät, daher ist manches sicher suboptimal ;-) Wichtig sind eigentlich nur `collections.Counter` mit dem ich das Auftreten der Artikel-Nummer zähle und `collections.defaultdict` in dem ich mir die einzelnen Preise der Verkäufe merke. Daraus kann ich später dann den Surchschnittspreis und das Maximum herausziehen.

Ausgabe mit Deinem Beispieldatensatz sieht so aus:

Code: Alles auswählen

[('12-134-0958', 'VERSTÄRKER', 1, 247.93, 247.93),
 ('2530-112-122', 'TRITT AUFSTIEG', 1, 57.84, 57.84),
 ('2530-0451 F08', 'VENTIL', 1, 57.14, 57.14),
 ('2512-134-0631', 'UNTERLAGE', 2, 28.150000000000002, 50.42),
 ('2540-112-12', 'SPIEGEL RUND', 2, 13.690000000000001, 14.99),
 ('12-136-2198', 'SPIEGEL OVAL', 1, 15.13, 15.13)]
Ach so, ich benutze Python 3.2 - für Python 2.7 musst Du vermutlich einige Anpassungen machen (`iteritems` statt `items` fällt mir da spontan ein!)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
martinjo
User
Beiträge: 186
Registriert: Dienstag 14. Juni 2011, 20:03

Gerade bin ich auch fertig geworden :D

Aber das veröffentliche ich lieber nicht direkt^^
http://pastebin.com/8zkCXQi7

Ich werde mir dein Beispiel aber auf jeden Fall noch anschauen - was ich jetzt habe sollte ja erstmal nur funktionieren. Jetzt gönne ich mir erst mal die 4 Stunden Schlaf die mir noch bleiben und mache morgen mal weiter.

Vielen Danke auf jeden Fall!
BlackJack

@martinjo: Deine Namensgebung lässt zu wünschen übrig. `liste` und `menge` (hier im Sinne der mathematischen Menge) sind zu allgemein. Die beschreiben den Datentyp, aber nicht das was man eigentlich wissen möchte: Was haben die Werte dahinter für eine Bedeutung für das Programm. Dann kannst Du Dich anscheinend bei den Datensätzen zwischen `lines` und `rows` nicht entscheiden und hast auch mal ein deutsches `zeile` drin. Bei Datensätzen würde ich mich im Englischen immer für `row`/`rows` entscheiden.

Ein ``except`` in dem einzig ein ``raise`` steht, macht das gesamte ``try``/``except``-Konstrukt sinnfrei. Das kann also raus.

Den Inhalt von `liste` verstehe ich nicht? Warum steckst Du da für jeden Artikel die Zeilen je nach der "Anz"-Spalte mehrfach rein? Das ist doch einfach nur eine unnötige Datenexplosion. Du steckst die Anzahl ja sogar mit in die Elemente — verwendest sie danach aber nie wieder. Statt die Zeilen zu duplizieren kann man später doch einfach die Anzahlen aufaddieren und mit den Einzelpreisen multiplizieren‽ Das spart Arbeit und Speicher.

Du gehst für jede Artikelnummer in `menge` über alle Zeilen um die Zeilen mit dieser Artikelnummer zu finden. Das ist ineffizient. Man sollte diese Liste *einmal* durchgehen und zum Beispiel ein Wörterbuch das Artikelnummer auf eine Liste mit den betroffenen Zeilen abbildet erstellen. Das kann man auch schon beim Einlesen tun. Da bietet sich ein `collections.defaultdict` an.

`i` ist übrigens ein ganz schlechter Name für Werte die keine ganzen Zahlen sind. Insbesondere in Schleifen. Damit rechnet keiner.

Listen als Datenstrukturen wo die einzelnen Elemente bestimmte Bedeutungen haben, sind nicht so gut. Das führt zu Quelltext mit vielen magischen Indizes die man kennen und sich merken muss. Für die Elemente böten sich `collections.namedtuple` an. ``prices.append(row.price)`` ist verständlicher als ``prices.append(row[3])``.

Noch besser wäre IMHO wenn man gleich eine eigene Klasse für Artikel schreibt, in der die Daten akkumuliert werden können. Dann hat man Attributnamen für die Bestandteile und Speicherverbrauch der nur noch von der Anzahl der verschiedenen Artikel, aber nicht mehr von der Gesamtanzahl der Eingabezeilen abhängt.

Dateien die man öffnet, sollte man auch wieder schliessen. Das `open()` im `DictReader()`-Aufruf macht das unmöglich.

Namen sollte man nicht so weit weg vom Einsatzort definieren. `lines` wird erst sehr spät verwendet, aber ziemlich weit am Anfang definiert. Man muss also wenn man es unten findet, erst weit nach oben suchen, wenn man wissen will was es ist. Und bei Programmänderungen bleiben dann auch gerne mal „Leichen” oben vorhanden, wenn man sie unten nicht mehr verwendet, aber vergessen hat, dass sie oben mal angelegt wurden.

Ich würde auf so etwas hier kommen (komplett ungetestet):

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import csv


class Article(object):
    def __init__(self, product_code, description):
        self.product_code = product_code
        self.description = description
        self.count = 0
        self.total = 0.0
    
    def update(self, count, unit_price):
        self.count += count
        self.total += unit_price * count

    def as_row(self):
        return [
            self.product_code,
            self.description,
            self.count,
            round(self.total / self.count, 2),
        ]


def main():
    with open('SALES.csv', 'rb') as lines:
        reader = csv.DictReader(lines)
        
        product_code2article = dict()
        for row in reader:
            product_code = row['Teile Nr']
            unit_price = row['Einzelpreis']
            if product_code and unit_price and row['Rg Nr.']:
                description = row['Bezeichnung']
                unit_price = float(unit_price.replace(',', '.'))
                count = int(row['Anz'])
                
                article = product_code2article.get(product_code)
                if article is None:
                    article = Article(product_code, description)
                    product_code2article[product_code] = article
                article.update(count, unit_price)
        
    for article in product_code2article.itervalues():
        print article.as_row()


if __name__ == '__main__':
    main()
Antworten