Zusammenfassen von Datensätzen

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
frosch
User
Beiträge: 10
Registriert: Donnerstag 17. September 2009, 09:17

Hallo,

ich bräuchte mal wieder euer Hilfe. Ich habe als Input ein File welches 3 Spalten enthält:
VON , BIS , ID
('04.07.2013', '31.07.2013', '292'),
('01.08.2013', '11.08.2013', '292'),
('04.03.2013', '31.03.2013', '293'),
('01.04.2013', '05.04.2013', '293'),
('06.04.2013', '02.06.2013', '293'),
('03.06.2013', '07.06.2013', '293'),
('09.09.2013', '20.09.2013', '293'),
('22.05.2013', '24.05.2013', '303')


Mein Programm soll nun die Datensätze mit gleicher ID und durchgehenden Zeitraum zusammenfügen, dass Ergebis sollten dann sein:
('04.07.2013', '11.08.2013', '292'),
('04.03.2013', '07.06.2013', '293'),
('09.09.2013', '20.09.2013', '293'),
('22.05.2013', '24.05.2013', '303')

Bis jetzt kann mein Programm nur 2 Datensätze zusammenfügen, jedoch nicht mehrere:

Code: Alles auswählen

while i < len(idlist)-1:
	if idlist[i][2] != idlist[i+1][2]:
		idlist2.append(idlist[i])
	if idlist[i][2] == idlist[i+1][2]:
		print "i", idlist[i]
		# Wenn Zeile1 Tag+1 und Zeile2 Tag gleich sind und auch der Monat gleich sind
		if ((int((idlist[i][1])[:2])+1 == int((idlist[i+1][0])[:2])) and (((idlist[i][1])[3:])[:2]) == (((idlist[i+1][0])[3:])[:2])):
			idlist2.append((idlist[i][0],idlist[i+1][1],idlist[i][2]))
			i = i+1
		# Wenn Zeile 1 Tag gleich 31 und Zeile 2 Tag 01, dann muss Zeile 1 den Monat "01" or "03" or "05" or "07" or "08" or "10" or "12" haben
		elif (idlist[i][1])[:2] == "31" and (((idlist[i][1])[3:])[:2]) == "01" or "03" or "05" or "07" or "08" or "10" or "12":
			if (idlist[i+1][0])[:2] == '01':
				idlist2.append((idlist[i][0],idlist[i+1][1],idlist[i][2]))
				i = i+1
		# Wenn Zeile 1 Tag gleich 30 und Zeile 2 Tag 01, dann muss Zeile 1 den Monat "04" or "06" or "09" or "11"
		elif (idlist[i][1])[:2] == "30" and (((idlist[i][1])[3:])[:2]) == "04" or "06" or "09" or "11":
			if (idlist[i+1][0])[:2] == '01':
				idlist2.append((idlist[i][0],idlist[i+1][1],idlist[i][2]))
				i = i+1
		# Wenn Zeile 1 Tag 28 oder 29 und Zeile 2 Tag 01, dann muss Zeile 1 den Monat "02"
		elif (((idlist[i][1])[:2]) == "28" or "29") and ((((idlist[i][1])[3:])[:2]) == "02") and ((idlist[i+1][0])[:2]) == '01':
			idlist2.append((idlist[i][0],idlist[i+1][1],idlist[i][2]))
			i = i+1
		else:
			print "else", idlist[i]
			idlist2.append(idlist[i])
	i = i+1
Wie kann ich mehrere Datensätze zusamenfügen??

Dank vorab und Quack
DER FROSCH
BlackJack

@frosch: Das sieht ja gruselig aus. Als allererstes solltest Du mal die Eingabedaten von Zeichenketten in vernünftige Datentypen/Objekte umwandeln. Also mindestens die Datumsangaben in `datetime.date`-Objekte, mit denen kann man nämlich rechnen, also zum Beispiel ermitteln wie viele Tage dazwischen liegen. Dannn braucht man diesen ganzen Zeichenkettenverarbeitungswust nicht, den Du da veranstaltest.

Und dann geht man die Datensätze durch, merkt sich immer den vorhergehenden, und fügt entsprechend solange Datensätze zusammen bis sich die ID ändert, oder mehr als ein Tag zwischen den Zeiträumen liegt. Dann kommt der aktuelle Datensatz ins Ergebnis und der neue wird zum aktuellen.
Sirius3
User
Beiträge: 18335
Registriert: Sonntag 21. Oktober 2012, 17:20

@frosch: als erstes solltest Du mal das datetime-Modul verwenden:

Code: Alles auswählen

>>> date1 = datetime.datetime.strptime('31.07.2013','%d.%m.%Y')
>>> date2 = datetime.datetime.strptime('01.08.2013','%d.%m.%Y')
>>> delta = date2-date1
>>> delta.days
1
Benutzeravatar
/me
User
Beiträge: 3561
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Ich würde etwa so beginnen:

Code: Alles auswählen

from datetime import datetime
import itertools
import operator

data = [('04.07.2013', '31.07.2013', '292'),
        ('01.08.2013', '11.08.2013', '292'),
        ('04.03.2013', '31.03.2013', '293'),
        ('01.04.2013', '05.04.2013', '293'),
        ('06.04.2013', '02.06.2013', '293'),
        ('03.06.2013', '07.06.2013', '293'),
        ('09.09.2013', '20.09.2013', '293'),
        ('22.05.2013', '24.05.2013', '303')]
date_format = '%d.%m.%Y'

# create real dates
data = [(datetime.strptime(date_start, date_format),
         datetime.strptime(date_end, date_format),
         key) for date_start, date_end, key in data]

# sort dates
data.sort(key=lambda x: (x[2], x[0]))

# group by key
groups = itertools.groupby(data, key=operator.itemgetter(2))
Die letzten beiden Schritte kann man auch noch zusammenfassen wenn man mag.

Code: Alles auswählen

groups = itertools.groupby(sorted(data, key=lambda x: (x[2], x[0])), key=operator.itemgetter(2))
BlackJack

Lösungsvorschlag:

Code: Alles auswählen

from datetime import datetime as DateTime, timedelta as TimeDelta
from pprint import pprint

ONE_DAY = TimeDelta(days=1)


def parse_date(string):
    return DateTime.strptime(string, '%d.%m.%Y').date()


def aggregate(rows):
    rows = iter(rows)
    try:
        start, end, id_ = next(rows)
    except StopIteration:
        pass  # Empty iterable.  It's okay to do nothing.
    else:    
        for next_start, next_end, next_id in rows:
            if id_ == next_id and next_start - end == ONE_DAY:
                end = next_end
            else:
                yield start, end, id_
                start, end, id_ = next_start, next_end, next_id
        yield start, end, id_


def main():
    data = [
        ('04.07.2013', '31.07.2013', '292'),
        ('01.08.2013', '11.08.2013', '292'),
        ('04.03.2013', '31.03.2013', '293'),
        ('01.04.2013', '05.04.2013', '293'),
        ('06.04.2013', '02.06.2013', '293'),
        ('03.06.2013', '07.06.2013', '293'),
        ('09.09.2013', '20.09.2013', '293'),
        ('22.05.2013', '24.05.2013', '303'),
    ]
    aggregated = list(
        aggregate((parse_date(s), parse_date(e), i) for s, e, i in data)
    )
    pprint(aggregated)


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

Code: Alles auswählen

[(datetime.date(2013, 7, 4), datetime.date(2013, 8, 11), '292'),
 (datetime.date(2013, 3, 4), datetime.date(2013, 6, 7), '293'),
 (datetime.date(2013, 9, 9), datetime.date(2013, 9, 20), '293'),
 (datetime.date(2013, 5, 22), datetime.date(2013, 5, 24), '303')]
Benutzeravatar
diesch
User
Beiträge: 80
Registriert: Dienstag 14. April 2009, 13:36
Wohnort: Brandenburg a.d. Havel
Kontaktdaten:

Alternative:

Code: Alles auswählen

#!/usr/bin/python
# coding: utf-8

import datetime
from pprint import pprint

VON, BIS, ID = 0, 1, 2

MAX_DELTA = datetime.timedelta(days=1)

def parse_date(string):
    return datetime.datetime.strptime(string, '%d.%m.%Y').date()


idlist = [[parse_date(i[VON]), parse_date(i[BIS]), i[ID]]
          for i in 
          ('04.07.2013', '31.07.2013', '292'),
          ('01.08.2013', '11.08.2013', '292'),
          ('04.03.2013', '31.03.2013', '293'),
          ('01.04.2013', '05.04.2013', '293'),
          ('06.04.2013', '02.06.2013', '293'),
          ('03.06.2013', '07.06.2013', '293'),
          ('09.09.2013', '20.09.2013', '293'),
          ('22.05.2013', '24.05.2013', '303')
          ]

result = []


for item in sorted(idlist, key=lambda x: (x[ID], x[VON])):
    if not result:  # erster Eintrag
        result.append(item)
    elif result[-1][ID] == item[ID] and item[VON] - result[-1][BIS] <= MAX_DELTA:
        result[-1][BIS] = item[BIS]  # "verlängern"
    else:
        result.append(item)

    
pprint(result)
Ausgabe:

Code: Alles auswählen

[[datetime.date(2013, 7, 4), datetime.date(2013, 8, 11), '292'],
 [datetime.date(2013, 3, 4), datetime.date(2013, 6, 7), '293'],
 [datetime.date(2013, 9, 9), datetime.date(2013, 9, 20), '293'],
 [datetime.date(2013, 5, 22), datetime.date(2013, 5, 24), '303']]
http://www.florian-diesch.de
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Ich hätte auch noch was:

Code: Alles auswählen

In [18]: data = [
   ....: (datetime.date(2013, 7, 4), datetime.date(2013, 7, 31), '292'), 
   ....: (datetime.date(2013, 8, 1), datetime.date(2013, 8, 11), '292'), 
   ....: (datetime.date(2013, 3, 4), datetime.date(2013, 3, 31), '293'), 
   ....: (datetime.date(2013, 4, 1), datetime.date(2013, 4, 5), '293'), 
   ....: (datetime.date(2013, 4, 6), datetime.date(2013, 6, 2), '293'), 
   ....: (datetime.date(2013, 6, 3), datetime.date(2013, 6, 7), '293'), 
   ....: (datetime.date(2013, 9, 9), datetime.date(2013, 9, 20), '293'), 
   ....: (datetime.date(2013, 5, 22), datetime.date(2013, 5, 24), '303')
   ....: ]

In [19]: for bundle in data:
    grouped_data[bundle[-1]].extend(bundle[:-1])
   ....:     

In [20]: sorted(
   ....: ((min(value), max(value), key) for key, value in grouped_data.iteritems()), 
   ....: key=lambda b: b[-1]
   ....: )
Out[20]: 
[(datetime.date(2013, 7, 4), datetime.date(2013, 8, 11), '292'),
 (datetime.date(2013, 3, 4), datetime.date(2013, 9, 20), '293'),
 (datetime.date(2013, 5, 22), datetime.date(2013, 5, 24), '303')]
mutetella


EDIT: Ok, ich sehe gerade: Themaverfehlung! Zeiträume sollten ja zusammenhängend sein...
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
p90
User
Beiträge: 198
Registriert: Donnerstag 22. Juli 2010, 17:30

Hier noch was nicht ganz so hüpsches aber wollte doch auch mal eine Lösung geben und nicht immer nru BlackJack mit Fragen Löchern:

Code: Alles auswählen

from datetime import datetime
data = [('04.07.2013', '31.07.2013', '292'),
        ('01.08.2013', '11.08.2013', '292'),
        ('04.03.2013', '31.03.2013', '293'),
        ('01.04.2013', '05.04.2013', '293'),
        ('06.04.2013', '02.06.2013', '293'),
        ('03.06.2013', '07.06.2013', '293'),
        ('09.09.2013', '20.09.2013', '293'),
        ('22.05.2013', '24.05.2013', '303')]
date_format = '%d.%m.%Y'

# as we only are looking for similarities in datasets with
# same id, sort by ids first, then look at dates

id_to_dates_list = dict()
for entry in data:
	date_start = datetime.strptime(entry[0], date_format)
	date_end = datetime.strptime(entry[1], date_format)
	id = entry[2]
	try:
		id_to_dates_list[id].append([date_start, date_end])
	except KeyError:
		id_to_dates_list[id] = [[date_start, date_end]]

solutions = dict()
for id in id_to_dates_list:
	tmp = []
	while (len(id_to_dates_list[id])>1):
		check_event = id_to_dates_list[id][0]
		changed = False
		for event in id_to_dates_list[id][1:]:
			if check_event[0] > event[1] and (check_event[0] - event[1]).days == 1:
				changed = True
				check_event[0] = event[0]
				id_to_dates_list[id].remove(event)
			if check_event[1] < event[0] and (event[0] - check_event[1]).days == 1:
				changed = True
				check_event[1] = event[1]
				id_to_dates_list[id].remove(event)
		if not changed:
			tmp.append(id_to_dates_list[id].pop(0))
	else:
		tmp.append(id_to_dates_list[id][0])
	solutions[id] = tmp

for id in solutions:
	for solution_group in solutions[id]:
		start = solution_group[0]
		end = solution_group[1]
		print(start, end, id)
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

@p90: Bitte verwende doch vier Spaces als Einrückung - und keine Tabs ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

@p90: Da geht aber noch ein wenig was an deinem Code:

Zeilen 20 bis 23 könnten gut durch ``collections.defaultdict`` ersetzt werden, dann sparst du dir den händischen Test. Dafür hast du aber ein schönes Beispiel gebracht, warum in Namen keine Datentypen verwendet werden sollen. "id_to_dates_list" hört sich zwar nach Liste an, enthält aber ein Dictionary ;-)

Zeilen 32 bis 39 können sicher eleganter formuliert werden und der else-Teil der while-Schleife hängt da auch nur so rum. Da du kein break in Schleifenkörper hast, wird das else so oder so immer ausgeführt. Dann kannst du es auch weglassen und die enthaltenen Anweisungen direkt hinschreiben. Unschön sind natürlich auch die Klammern bei der Bedingung des while.
Das Leben ist wie ein Tennisball.
Sirius3
User
Beiträge: 18335
Registriert: Sonntag 21. Oktober 2012, 17:20

@p90: zusätzlich zu dem was EyDu schreibt:

Zeile 32 und 36: der Vergleich ob größer oder kleiner ist überflüssig, da wenn die Differenz 1 Tag ist, ist implizit die erste Bedingung immer erfüllt.

Du merkst selbst, wie umständlich es ist, eine Liste solange zu verändern, bis kein Element mehr in dieser Liste ist. Mit diesem Vorgehen muß man einen ziemlich großen Codebereich komplett überblicken, um zu verstehen, was dort passiert. Python macht es sehr einfach (mit Generatoren) Listen Schritt für Schritt von einer Menge Eingangsdaten in eine Menge Ausgangsdaten zu transformieren, wobei aber die Listen nie geändert, sondern nur neue Listen neu erzeugt werden. Solch ein lineare Kette von Operationen viel einfacher zu verstehen, als Dein while-Schleife.
frosch
User
Beiträge: 10
Registriert: Donnerstag 17. September 2009, 09:17

Hallo,

danke für die vielen Antworten in der raschen Zeit. Leider mußte ich auf Grund einer Grippe eine Woche im Bett verbringen :-(
Hab mal wieder viel Neues gelernt!!

@diesch: Ich habe mich für dein Programm entschieden, da ich es ab besten verstanden habe. Doch leider bekomme ich folgende Fehlermeldung:
result[-1][BIS] = item[BIS] # "verlängern"
TypeError: 'tuple' object does not support item assignment
Wie liegt der Fehler?

Hier mein Programm:

Code: Alles auswählen

 for line in in_f1.readlines():
    linelist = line.split("|")
    if len(linelist) == 16: 
		if (linelist[5]).find('2013') != -1:
			lolist.append((linelist[1],linelist[2],linelist[3]))
			
  
  
 VON, BIS, ID = 0, 1, 2
     
  MAX_DELTA = datetime.timedelta(days=1)
     
  date_format = '%d.%m.%Y'
     
     
  idlist = [(datetime.datetime.strptime(date_start, date_format), datetime.datetime.strptime(date_end, date_format), key) for date_start, date_end, key in lolist]
 
     
  result = []
     
     
  for item in sorted(idlist, key=lambda x: (x[ID], x[VON])):
    if not result:  # erster Eintrag
        result.append(item)
    elif result[-1][ID] == item[ID] and item[VON] - result[-1][BIS] <= MAX_DELTA:
        result[-1][BIS] = item[BIS]  # "verlängern"
    else:
        result.append(item)
     
       
  pprint(result)

Dank vorab und Quack
DER FROSCH
Sirius3
User
Beiträge: 18335
Registriert: Sonntag 21. Oktober 2012, 17:20

@frosch: Du erzeugst ja auch Tuple in Zeile 16, und keine Listen wie diesch, und Tuple sind unveränderlich.

In Zeile 1 ist »readlines« überflüssig, ebenso die Klammern um »linelist« in Zeile 4, obwohl man das besser als »'2013' in linelist[5]« schreibt.
Du hast viel zu viele Leerzeilen, die das Lesen sehr erschweren.
frosch
User
Beiträge: 10
Registriert: Donnerstag 17. September 2009, 09:17

und wie erzeuge ich dann eine Liste? :K
BlackJack

@frosch: In der Python-Dokumentation gibt es ein Tutorial wo die Grunddatentypen vorkommen.
Benutzeravatar
diesch
User
Beiträge: 80
Registriert: Dienstag 14. April 2009, 13:36
Wohnort: Brandenburg a.d. Havel
Kontaktdaten:

Ändere Zeile 16 in

Code: Alles auswählen

idlist = [[datetime.datetime.strptime(date_start, date_format), datetime.datetime.strptime(date_end, date_format), key] for date_start, date_end, key in lolist]
Beachte die [] statt ()
http://www.florian-diesch.de
frosch
User
Beiträge: 10
Registriert: Donnerstag 17. September 2009, 09:17

@diesch: Danke für die Antwort. Die unterschiedliche Klammerung habe ich total übersehen! :oops:
Antworten