У меня есть программа, которая работает с большим набором экспериментальных данных. Данные хранятся в виде списка объектов, которые являются экземплярами класса со следующими атрибутами:
- time_point - время выборки
- cluster - имя кластера узлов, из которого была взята выборка
- узел - имя узла, из которого была взята проба
- qty1 = значение образца для первой величины
- Кол-во2 = значение образца для второй величины
Мне нужно извлечь некоторые значения из набора данных, сгруппированных тремя способами - один раз для выборки в целом, один раз для каждого кластера узлов и один раз для каждого узла. Значения, которые мне нужно вывести, зависят от (отсортированных по времени) совокупных сумм qty1 и qty2: максимального значения поэлементной суммы накопленных сумм qty1 и qty2, момента времени, когда это максимальное значение произошло, и значения qty1 и qty2 в этот момент времени.
Я придумал следующее решение:
dataset.sort(key=operator.attrgetter('time_point'))
# For the whole set
sys_qty1 = 0
sys_qty2 = 0
sys_combo = 0
sys_max = 0
# For the cluster grouping
cluster_qty1 = defaultdict(int)
cluster_qty2 = defaultdict(int)
cluster_combo = defaultdict(int)
cluster_max = defaultdict(int)
cluster_peak = defaultdict(int)
# For the node grouping
node_qty1 = defaultdict(int)
node_qty2 = defaultdict(int)
node_combo = defaultdict(int)
node_max = defaultdict(int)
node_peak = defaultdict(int)
for t in dataset:
# For the whole system ######################################################
sys_qty1 += t.qty1
sys_qty2 += t.qty2
sys_combo = sys_qty1 + sys_qty2
if sys_combo > sys_max:
sys_max = sys_combo
# The Peak class is to record the time point and the cumulative quantities
system_peak = Peak(time_point=t.time_point,
qty1=sys_qty1,
qty2=sys_qty2)
# For the cluster grouping ##################################################
cluster_qty1[t.cluster] += t.qty1
cluster_qty2[t.cluster] += t.qty2
cluster_combo[t.cluster] = cluster_qty1[t.cluster] + cluster_qty2[t.cluster]
if cluster_combo[t.cluster] > cluster_max[t.cluster]:
cluster_max[t.cluster] = cluster_combo[t.cluster]
cluster_peak[t.cluster] = Peak(time_point=t.time_point,
qty1=cluster_qty1[t.cluster],
qty2=cluster_qty2[t.cluster])
# For the node grouping #####################################################
node_qty1[t.node] += t.qty1
node_qty2[t.node] += t.qty2
node_combo[t.node] = node_qty1[t.node] + node_qty2[t.node]
if node_combo[t.node] > node_max[t.node]:
node_max[t.node] = node_combo[t.node]
node_peak[t.node] = Peak(time_point=t.time_point,
qty1=node_qty1[t.node],
qty2=node_qty2[t.node])
Это дает правильный вывод, но мне интересно, можно ли сделать его более читабельным / Pythonic и / или быстрее / более масштабируемым.
Вышеупомянутое привлекательно тем, что оно перебирает (большой) набор данных только один раз, но непривлекательно тем, что я по существу скопировал / вставил три копии одного и того же алгоритма.
Чтобы избежать проблем с копированием / вставкой, описанных выше, я пробовал также:
def find_peaks(level, dataset):
def grouping(object, attr_name):
if attr_name == 'system':
return attr_name
else:
return object.__dict__[attrname]
cuml_qty1 = defaultdict(int)
cuml_qty2 = defaultdict(int)
cuml_combo = defaultdict(int)
level_max = defaultdict(int)
level_peak = defaultdict(int)
for t in dataset:
cuml_qty1[grouping(t, level)] += t.qty1
cuml_qty2[grouping(t, level)] += t.qty2
cuml_combo[grouping(t, level)] = (cuml_qty1[grouping(t, level)] +
cuml_qty2[grouping(t, level)])
if cuml_combo[grouping(t, level)] > level_max[grouping(t, level)]:
level_max[grouping(t, level)] = cuml_combo[grouping(t, level)]
level_peak[grouping(t, level)] = Peak(time_point=t.time_point,
qty1=node_qty1[grouping(t, level)],
qty2=node_qty2[grouping(t, level)])
return level_peak
system_peak = find_peaks('system', dataset)
cluster_peak = find_peaks('cluster', dataset)
node_peak = find_peaks('node', dataset)
Для (не сгруппированных) вычислений на уровне системы я также придумал это, что довольно:
dataset.sort(key=operator.attrgetter('time_point'))
def cuml_sum(seq):
rseq = []
t = 0
for i in seq:
t += i
rseq.append(t)
return rseq
time_get = operator.attrgetter('time_point')
q1_get = operator.attrgetter('qty1')
q2_get = operator.attrgetter('qty2')
timeline = [time_get(t) for t in dataset]
cuml_qty1 = cuml_sum([q1_get(t) for t in dataset])
cuml_qty2 = cuml_sum([q2_get(t) for t in dataset])
cuml_combo = [q1 + q2 for q1, q2 in zip(cuml_qty1, cuml_qty2)]
combo_max = max(cuml_combo)
time_max = timeline.index(combo_max)
q1_at_max = cuml_qty1.index(time_max)
q2_at_max = cuml_qty2.index(time_max)
Тем не менее, несмотря на то, что в этой версии использовались классные списки и zip (), он трижды просматривает набор данных только для вычислений на системном уровне, и я не могу придумать хорошего способа сделать уровень кластера и расчеты на уровне узла без медленной работы, например:
timeline = defaultdict(int)
cuml_qty1 = defaultdict(int)
#...etc.
for c in cluster_list:
timeline[c] = [time_get(t) for t in dataset if t.cluster == c]
cuml_qty1[c] = [q1_get(t) for t in dataset if t.cluster == c]
#...etc.
У кого-нибудь здесь, в Stack Overflow, есть предложения по улучшению? Первый приведенный выше фрагмент хорошо работает для моего начального набора данных (порядка миллиона записей), но в последующих наборах данных будет больше записей и кластеров / узлов, поэтому масштабируемость является проблемой.
Это мое первое нетривиальное использование Python, и я хочу убедиться, что я правильно использую язык (это заменяет очень запутанный набор SQL-запросов, и более ранние версии версии Python были по существу очень неэффективные прямые переводы того, что это сделало). Обычно я мало занимаюсь программированием, поэтому я могу упустить что-то элементарное.
Большое спасибо!