2016-06-24 13 views
2

Dies ist meine erste Frage, also bitte verzeihen Sie alle Fehler.Python: Viele-zu-viele-Vergleich, um erforderliche Daten zu finden

Ich habe eine große Datei (CSV) mit mehreren (~ 10000000 +) Linien Informationen wie das folgende Beispiel:

date;box_id;box_length;box_width;box_height;weight;type 
--snip-- 
1999-01-01 00:00:20;nx1124;10;4;5.5;2.1;oversea 
1999-01-01 00:00:20;np11r4;8;3.25;2;4.666;local 
--snip-- 

Mein Ziel ist es, durch jede Zeile zu lesen und die Büchse der Lautstärke und innerhalb von 1 berechnen Stundenfenster (zB 00:00:00 - 00:00:59) Ich muss aufzeichnen, wenn 2 oder mehr Boxen ein ähnliches Volumen haben (+ -10% Unterschied) und dann ihren Zeitstempel sowie ihren Typ aufzeichnen.

Im Moment ich bin mit einem Brute-Force-Ansatz:

  • laufen durch jede Zeile
  • berechnen Volumen
  • zur nächsten Zeile und Rechenvolumen gehen
  • vergleichen
  • wiederholen bis 1 Std. Zeitdifferenz festgestellt
  • Entfernen Sie die erste Box aus der Liste
  • ein anderes Feld in die Liste
  • wiederholen Sie den Vorgang mit dem zweiten Feld

Zum Beispiel hinzufügen, wenn mein 1 Stunde Fenster 1,2,3,4 hat, ich bin diesen

1 
2 == 1 
3 == 1 then == 2 
4 == 1 then == 2 then == 3 
5 == 2 then == 3 then == 4 # removed 1 from list(1hr window moved down) 
6 == 2 then == 3 then == 4 then == 5 
7 == 2 then == 3 then == 4 then == 5 then == 6 
.... so on .... 
tun

Das ist das Beste, was ich mir vorstellen kann, da ich jede Box innerhalb eines bestimmten Zeitfensters mit anderen vergleichen muss. Aber das ist im Moment sehr sehr langsam.

Ich bin auf der Suche nach einem besseren Algorithmus, aber ich bin mir unsicher, in welche Richtung ich gehen muss. Ich versuche, einige ausgezeichnete Werkzeuge zu lernen (bis jetzt ist Pandas mein Favorit), aber ich gehe davon aus, dass ich zuerst einen Algorithmus implementieren muss, damit diese Werkzeuge mit den Daten so umgehen können, wie ich es brauche.

Wenn es hilft, werde ich meinen Python-Code (Quelle) veröffentlichen.

Aktualisierung Folgende sind mein Code. Ich habe mehrere Zeilen weggelassen (wie try/catch block für ungültigen Dateipfad/-format, Typ Conversion Error Handling usw.). Ich habe den Code ein bisschen angepasst, um für 5 Sekunden Fenster zu arbeiten.

Im Anschluss an die Box Klasse

from datetime import datetime 
from time import mktime 

class Box(object): 
    """ Box model """ 

    def __init__(self,data_set): 
     self.date = data_set[0] 
     self.timestamp = self.__get_time() 
     self.id = data_set[1] 
     self.length = float(data_set[2]) 
     self.width = float(data_set[3]) 
     self.height = float(data_set[4]) 
     self.weight = int(data_set[5]) 
     self.volume = self.__get_volume() 

    def __get_time(self): 
     """ convert from date string to unix-timestamp """ 
     str_format = '%Y-%m-%d %H:%M:%S' 
     t_tuple = datetime.strptime(self.date, str_format).timetuple() 
     return mktime(t_tuple) 

    def __get_volume(self): 
     """ calculate volume of the box """ 
     return (self.length * self.width * self.height) 

Es folgt das eigentliche Programm den Vergleich durchführen. Ich habe die Utility-Datei und die main.py-Datei zur Vereinfachung zusammengefasst.

from csv import reader 
from io import open as open_file 
from os import path 
from sys import argv, exit 
from time import time 

# custom lib 
from Box import Box 

def main(): 

    file_name = str.strip(argv[1]) 
    boxes_5s = [] 
    diff = 0 

    similar_boxes = [] 

    for row in get_file(file_name): 
     if row: 
      box = Box(row) 

      if len(boxes_5s) > 0: 
       diff = box.timestamp - boxes_5s[0].timestamp 
       if diff < 6: 
        boxes_5s.append(box) 
       else: 
        similar_boxes += get_similar(boxes_5s) 
        del boxes_5s[0] # remove the oldest box 
        boxes_5s.append(box) 
      else: 
       boxes_5s.append(box) 

     print(similar_boxes) 


def get_file(file_name): 
    """ open and return csv file pointer line by line """ 
    with open_file(file_name,'rb') as f: 
     header = f.readline() 
     print(header) 
     rows = reader(f, delimiter=';') 

     for r in rows: 
      yield r 
     else: 
      yield '' 


def get_similar(box_list): 
    """ compare boxes for similar volume """  

    num_boxes = len(box_list) 

    similar_boxes = [] 
    record_str = "Box#{} Volm:{} and #{} Volm:{}" 
    for i in xrange(num_boxes): 
     box_1 = box_list[i] 

     for j in xrange(i+1, num_boxes): 
      box_2 = box_list[j] 

      vol_diff = abs((box_1.volume - box_2.volume)/box_1.volume) <= 0.1 


      if vol_diff: similar_boxes.append(record_str.format(box_1.id,box_1.volume,box_2.id, box_2.volume)) 

    return similar_boxes 

if __name__ == "__main__": 
    main() 

Vielen Dank.

+0

sollten Sie wahrscheinlich zuerst bestellen und dann finden Sie die ähnlichen Felder – armonge

+2

es hilft. Siehe [Erstellen eines minimalen, vollständigen und überprüfbaren Beispiels] (http://stackoverflow.com/help/mcve) – rll

+0

Es tut mir leid für die vage Natur meiner Frage, ich werde später etwas Code veröffentlichen, wenn ich zurück bin Zuhause –

Antwort

1

Den ersten Zeitstempel als Start eines einstündigen Fensters nehmen (anstelle von Uhr-Stunden-Bins immer auf Stunde: 00: 00) Ich denke, eine durchaus machbare Implementierung für Datenmengen so klein wie ein paar zehn Millionen Zeilen Daten sein (mitbestellt entriesin Datei erwarten):

#! /usr/bin/env python 
from __future__ import print_function 

import csv 
import datetime as dt 
import math 
import collections 


FILE_PATH_IN = './box_data_time_ordered_100k_sparse.csv' 
TS_FORMAT = '%Y-%m-%d %H:%M:%S' 
TS_TOKEN = 'date' 
SIMILAR_ENOUGH = 0.1 
BoxEntry = collections.namedtuple(
    'BoxEntry', ['start_ts', 'a_ts', 't_type', 'b_volume']) 


def box_volume(box_length, box_width, box_height): 
    """Volume in cubic of length units given.""" 
    return box_length * box_width * box_height 


def filter_similar_box_volumes(box_entries): 
    """Ordered binary similarity comparator using complex algorithm 
    on a medium large slice of data.""" 

    def _key(r): 
     """sort on volume.""" 
     return r.b_volume 

    entries_volume_ordered = sorted(box_entries, key=_key) 
    collector = [] 
    for n, box_entry in enumerate(entries_volume_ordered[1:], start=1): 
     one = box_entry.b_volume 
     prev_box_entry = entries_volume_ordered[n] 
     previous = prev_box_entry.b_volume 
     if one and math.fabs(one - previous)/one < SIMILAR_ENOUGH: 
      if box_entry not in collector: 
       collector.append(box_entry) 
      if prev_box_entry not in collector: 
       collector.append(prev_box_entry) 
    return collector 


def hourly_boxes_gen(file_path): 
    """Simplistic generator, yielding hour slices of parsed 
    box data lines belonging to 1 hour window per yield.""" 

    csv.register_dialect('boxes', delimiter=';', quoting=csv.QUOTE_NONE) 
    start_ts = None 
    cx_map = None 
    hour_data = [] 
    an_hour = dt.timedelta(hours=1) 
    with open(file_path, 'rt') as f_i: 
     for row in csv.reader(f_i, 'boxes'): 
      if cx_map is None and row and row[0] == TS_TOKEN: 
       cx_map = dict(zip(row, range(len(row)))) 
       continue 
      if cx_map and row: 
       a_ts = dt.datetime.strptime(row[cx_map[TS_TOKEN]], TS_FORMAT) 
       t_type = row[cx_map['type']] 
       b_length = float(row[cx_map['box_length']]) 
       b_width = float(row[cx_map['box_width']]) 
       b_height = float(row[cx_map['box_height']]) 
       b_volume = box_volume(b_length, b_width, b_height) 
       if start_ts is None: 
        start_ts = a_ts 
        hour_data.append(
         BoxEntry(start_ts, a_ts, t_type, b_volume)) 
       elif a_ts - an_hour < start_ts: 
        hour_data.append(
         BoxEntry(start_ts, a_ts, t_type, b_volume)) 
       else: 
        yield filter_similar_box_volumes(hour_data) 
        hour_data = [BoxEntry(start_ts, a_ts, t_type, b_volume)] 
        start_ts = a_ts 
     if hour_data: 
      yield filter_similar_box_volumes(hour_data) 


def main(): 
    """Do the thing.""" 
    for box_entries in hourly_boxes_gen(FILE_PATH_IN): 
     for box_entry in box_entries: 
      print(box_entry.start_ts, box_entry.a_ts, box_entry.t_type) 

if __name__ == '__main__': 
    main() 

Mit Beispieleingabedatei:

date;box_id;box_length;box_width;box_height;weight;type 
1999-01-01 00:00:20;nx1124;10;4;5.5;2.1;oversea 
1999-01-01 00:00:20;np11r4;8;3.25;2;4.666;local 
1999-01-01 00:10:20;np11r3;8;3.25;2.1;4.665;local 
1999-01-01 00:20:20;np11r2;8;3.25;2.05;4.664;local 
1999-01-01 00:30:20;np11r1;8;3.23;2;4.663;local 
1999-01-01 00:40:20;np11r0;8;3.22;2;4.662;local 
1999-01-01 00:50:20;dp11r4;8;3.24;2;4.661;local 
1999-01-01 01:00:20;cp11r3;8;3.25;2;4.666;local 
1999-01-01 01:01:20;bp11r2;8;3.26;2;4.665;local 
1999-01-01 01:02:20;ap11r1;8;3.22;2;4.664;local 
1999-01-01 01:03:20;zp11r0;12;3.23;2;4.663;local 
1999-01-01 02:00:20;yp11r4;8;3.24;2;4.662;local 
1999-01-01 04:00:20;xp11r4;8;3.25;2;4.661;local 
1999-01-01 04:00:21;yy11r4;8;3.25;2;4.661;local 
1999-01-01 04:00:22;xx11r4;8;3.25;2;4.661;oversea 
1999-01-01 04:59:19;zz11r4;8;3.25;2;4.661;local 

ergibt:

1999-01-01 00:00:20 1999-01-01 00:30:20 local 
1999-01-01 00:00:20 1999-01-01 00:50:20 local 
1999-01-01 00:00:20 1999-01-01 00:00:20 local 
1999-01-01 00:00:20 1999-01-01 00:20:20 local 
1999-01-01 00:00:20 1999-01-01 00:10:20 local 
1999-01-01 00:00:20 1999-01-01 00:00:20 oversea 
1999-01-01 00:00:20 1999-01-01 01:00:20 local 
1999-01-01 01:00:20 1999-01-01 01:01:20 local 
1999-01-01 01:00:20 1999-01-01 01:03:20 local 
1999-01-01 04:00:20 1999-01-01 04:00:21 local 
1999-01-01 04:00:20 1999-01-01 04:00:22 oversea 
1999-01-01 04:00:20 1999-01-01 04:59:19 local 

Einige Anmerkungen:

  1. csv-Modul zum Lesen, mit einem bestimmten Dialekt verwendet (als Semikolon-Trennzeichen nicht standardmäßig ist)

  2. Importdatetime mit Alias, ohne Datetime-Klasse für strptime Methode für den Zugriff auf die übergeordneten Modulname - YMMV

  3. die chunked Stunden-Fenster Leser in einem Funktionsgenerator einzukapseln

  4. Volumen und ähnliche Berechnung in separaten Funktionen.

  5. Volume bestellt einfachen Filter-Algorithmus, der irgendwie O (m) sein sollte für m ist die Anzahl der Kandidaten Übereinstimmungen.

  6. Benennen Sie Tupel für kompakten Speicher, aber auch sinnvolle Adressierung.

  7. Um eine Uhr eingestellt 1 Stunde Fenster (nicht den ersten Zeitstempel mit Bootstrap) zu implementieren, anpassen braucht man den Code ein wenig, aber soll

Ansonsten neugierig wartet auf das Codebeispiel aus dem Trivial sein OP ;-)

aktualisiert der ähnlich genug Filteralgorithmus, so dass das Ereignis reiche Stunden, kein O machen (n^2) Algorithmus alles Zeit essen ... (die _naive eines mit einer verschachtelten Schleife entfernt).

Das Hinzufügen eines Tages voller Einträge pro Sekunde zur Probe mit 3600 Kandidaten für die Ähnlichkeitsprüfung dauerte ca. 10 Sekunden für diese ca. 100k Zeilen (86400+).

+0

Danke! Ja, das ist sehr hilfreich. Ich benutze etwas in dieser Richtung, aber im Moment ist es sehr grob. Ich werde diese Lösung morgen ausprobieren. :) –