Seite 1 von 1
sorted mean
Verfasst: Freitag 26. Juli 2013, 14:24
von pythy
Liebe Python-Freunde,
ich möchte die Performance meines Skriptes optimieren. Ziel des Skriptes ist es, den Mittelwerte mehrerer Einträge eines bestimmten Schlüssels zu berechnen, möglichst ohne viele Schleifen. Dafür liegt eine Liste vor, in der sich die jeweiligen Monatsnamen (ohne Tag) sowie die jeweilige Tagestemperatur als Spalten abgebildet sind. Leider habe ich keine spezielle Funktion dafür gefunden.
Code: Alles auswählen
#compute avgTemp
for i in range(0, len(key), 1):
for j in range (0, len(symbols), 1):
if key[i] == symbols[j]:
sum[j] = sum[j]+float(lines[i][5])
numEntries[j] =numEntries[j] +1
result=[]
for ind in range (0, len(symbols), 1):
result.append([symbols[ind],'%.2f' %(round((float(sum[ind])/float(numEntries[ind])),2))])
Viele grüße
Re: sorted mean
Verfasst: Freitag 26. Juli 2013, 14:58
von snafu
Re: sorted mean
Verfasst: Freitag 26. Juli 2013, 15:00
von pillmuncher
Der Hinweis von snafu zeigt auch, warum es sehr schlecht ist, eine Liste sum zu nennen.
Re: sorted mean
Verfasst: Freitag 26. Juli 2013, 15:02
von /me
Der Code ist äußerst schwer zu lesen, da du auf eine für Python höchst umständliche Weise auf die Listenelemente zugreifst.
Dein Code sieht etwa so aus:
Code: Alles auswählen
elements = [1, 2, 3, 4, 5]
for i in range(0, len(elements), 1):
print(elements[i] ** 2)
Du kannst direkt über die Elemente einer Liste (und anderer Datentypen iterieren):
Code: Alles auswählen
elements = [1, 2, 3, 4, 5]
for value in elements:
print(value ** 2)
Re: sorted mean
Verfasst: Freitag 26. Juli 2013, 15:13
von pillmuncher
Zu dem, was /me völlig zurecht anmerkt noch dies: falls du zusätzlich zu den Werten der Liste auch die Indizes derselben benötigst, verwende enumerate().
Code: Alles auswählen
elements = [1, 2, 3, 4, 5]
for index, value in enumerate(elements):
print(index, value ** 2)
Python 3.x, übrigens.
Re: sorted mean
Verfasst: Freitag 26. Juli 2013, 15:14
von pythy
Vielen Dank für eure Antworten. Mein Problem ist, dass meine Liste in der ersten Spalte Monatsnamen umfasst (welche für einen Monat mehrmals auftreten) und in der zweiten Spalte Temperaturangaben.
Daher muss ich
1. gleiche Monate zusammen gruppieren
2. aus den Temperaturangeben gleicher Monate den entsprechenden Durchschnitt berechnen
Also funktioniert sum(...)/len(...) nicht, jedenfalls nicht direkt
Re: sorted mean
Verfasst: Freitag 26. Juli 2013, 15:41
von BlackJack
@pythy: Dann solltest Du das erst einmal umorganisieren, zum Beispiel zu einem Wörterbuch (`dict`) welches Monatsnamen auf eine Liste mit Temperaturwerten abbildet. Damit kanst Du dann mit `sum()` und `len()` die Mittelwerte berechnen. Ein `collections.defaultdict()` bietet sich eventuell an. Oder Du erstellst am Anfang ein Wörterbuch was alle Monatsnamen jeweils auf eine leere Liste abbildet. Oder Du erstellst Dir einen Datentyp der die Summe und die Anzahl kapselt und nach dem Durchschnitt gefragt werden kann.
Re: sorted mean
Verfasst: Freitag 26. Juli 2013, 15:42
von pillmuncher
Na, wenn es nicht direkt funktioniert, dann eben indirekt (was auch immer das heißen mag...):
Code: Alles auswählen
from random import randint
from itertools import groupby
from operator import itemgetter
temperatures = [
(month, day, randint(100, 300) / 10.)
for month in range(1, 13)
for day in range(1, 31)] # der Einfachheit halber haben alle Monate 30 Tage
for month, grouped in groupby(temperatures, itemgetter(0)):
grouped = list(grouped)
avg = sum(temperature for month, day, temperature in grouped) / len(grouped)
print(month, '{0:.2f}'.format(round(avg, 2)))
Die
Standard Lib ist dein Freund. Schau sie dir an.
Re: sorted mean
Verfasst: Freitag 26. Juli 2013, 15:44
von BlackJack
Anmerkung zu pillmuncher's Code: Die Liste muss in diesem Fall nach Monaten sortiert/gruppiert vorliegen. Also ggf. müsste man die Daten vorher nach der ersten Spalte sortieren.
Re: sorted mean
Verfasst: Freitag 26. Juli 2013, 15:54
von Sirius3
Um die Diskussion zu erleichtern hab ich mir erlaubt pythys Code nach Python zu konvertieren:
Code: Alles auswählen
from collections import defaultdict
def mean(values):
return sum(values)/len(values)
grouped_entries = defaultdict(list)
for k, line in zip(key, lines):
grouped_entries[k].append(float(line[5]))
result = [(k, '%.2f'%mean(grouped_entries[k])) for k in symbols]
Re: sorted mean
Verfasst: Samstag 27. Juli 2013, 11:03
von pythy
Hallo,
vielen Dank für die lehrreiche Diskussion, ich habe mir die von euch vorgeschlagenen Funktionen angeschaut und - es hat jetzt geklappt!
Anbei findet ihr meinen Code:
Code: Alles auswählen
from collections import defaultdict
# define: extract columns
def column(matrix, i):
return [row[i] for row in matrix]
# dictionary
data = zip(column(lines, 0), column(lines, 5))
# group values by key
dict = defaultdict(list)
for v, k in data:
dict[v].append(float(k))
# define mean fct
def mean(values):
return sum(values)/len(values)
# compute average value
avgVals = [(k, '%.2f'%mean(dict[k])) for k in dict]
Viele Grüße und ein schönes WE!
Re: sorted mean
Verfasst: Samstag 27. Juli 2013, 11:56
von cofi
Damit dein Ansatz noch stimmt, wenn du nur Ganzzahlen hast, solltest du noch `from __future__ import division` benutzen.
Und, noch weit wichtiger, nenne dein Dictionary nicht `dict`, wenn dir nicht klar ist, warum nicht, dann schau dir mal das Highlighting an. Und wenn es dir dann immernoch nicht klar ist, probiere mal
Re: sorted mean
Verfasst: Samstag 27. Juli 2013, 11:58
von BlackJack
@pythy: Die Kommentare sind teilweise unpassend oder überflüssig. Bei ``# define…`` steht ja schon im Code, dass dort Funktionen definiert werden und wie die heissen. ``# dictionary`` ist falsch, weil dort überhaupt kein Wörterbuch definiert wird.
`dict` ist der Name des eingebauten Datentyps, den sollte man nicht an andere Werte binden, denn dann kann man das Objekt nicht mehr aufrufen oder anderweitig verwenden.
Alternativ mit weniger selbst geschriebenen Funktionen und mehr aus der Standardbibliothek (ungetestet):
Code: Alles auswählen
from itertools import imap
from operator import itemgetter
from collections import defaultdict
def main():
#
# ...
#
month2temperatures = defaultdict(list)
for month, temperature in imap(itemgetter([5, 0]), rows):
month2temperatures[month].append(float(temperature))
month2average_temperature = dict(
(m, sum(ts) / len(ts)) for m, ts in month2temperatures.iteritems()
)
if __name__ == '__main__':
main()
Und die Idee keine Listen aufzubauen, sondern einen Aggregator-Datentyp zu schreiben (ungetestet):
Code: Alles auswählen
from itertools import imap
from operator import itemgetter
from collections import defaultdict
class Averager(object):
def __init__(self, value=0.0, count=0):
self.sum = value
self.count = count
def __iadd__(self, value):
self.sum += value
self.count += 1
return self
@property
def average(self):
return self.sum / self.count
def main():
#
# ...
#
month2temperatures_averager = defaultdict(Averager)
for month, temperature in imap(itemgetter([5, 0]), rows):
month2temperatures_averager[month] += float(temperature)
for month, aggregate in sorted(month2temperatures_averager.iteritems()):
print 'Average temperature in {0} was {1:.2f}.'.format(
month, aggregate.average
)
if __name__ == '__main__':
main()