Математический алгоритм двумерного бинарного алгоритма
У меня возникли проблемы с разработкой подходящего алгоритма биннинга в Mathematica. У меня есть большой (~ 100 тыс. Элементов) набор данных формы Т = {{x1, y1, z1}, {x2, y2, z2},....}
и я хочу объединить его в 2D-массив размером около 100x100 бит, причем значение bin задается суммой значений Z, которые попадают в каждую ячейку.
В настоящее время я повторяю каждый элемент таблицы, используя Select, чтобы выбрать, в каком бункере он должен быть основан на списках границ bin, и добавив значение z в список значений, занимающих этот бит. В конце я нарисую Total на список ящиков, суммируя их содержимое (я делаю это, потому что иногда хочу делать другие вещи, например максимизировать).
Я попытался использовать Gather и другие подобные функции для этого, но вышеупомянутый метод был смехотворно быстрее, хотя, возможно, я плохо использую Gather. Во всяком случае, по-прежнему требуется несколько минут, чтобы выполнить сортировку по моему методу, и я чувствую, что Mathematica может сделать лучше. У кого-нибудь есть хороший эффективный алгоритм?
Ответы
Ответ 1
Вот метод, основанный на постулате Szabolcs, который примерно на порядок выше.
data = RandomReal[5, {500000, 3}];
(*500k values*)
zvalues = data[[All, 3]];
epsilon = 1*^-10;(*prevent 101 index*)
(*rescale and round (x,y) coordinates to index pairs in the 1..100 range*)
indexes = 1 + Floor[(1 - epsilon) 100 Rescale[data[[All, {1, 2}]]]];
res2 = Module[{gb = GatherBy[Transpose[{indexes, zvalues}], First]},
SparseArray[
gb[[All, 1, 1]] ->
Total[gb[[All, All, 2]], {2}]]]; // AbsoluteTiming
Дает {2.012217, Null}
AbsoluteTiming[
System`SetSystemOptions[
"SparseArrayOptions" -> {"TreatRepeatedEntries" -> 1}];
res3 = SparseArray[indexes -> zvalues];
System`SetSystemOptions[
"SparseArrayOptions" -> {"TreatRepeatedEntries" -> 0}];
]
Дает {0.195228, Null}
res3 == res2
True
"TreatRepeatedEntries" → 1 добавляет повторяющиеся позиции вверх.
Ответ 2
Я намерен переписать код ниже из-за соображений удобочитаемости Сабольча. До тех пор, знайте, что если ваши корзины являются регулярными, и вместо Nearest
вы можете использовать Round
, Floor
или Ceiling
(со вторым аргументом), код ниже будет намного быстрее. В моей системе он тестируется быстрее, чем решение GatherBy
.
Предполагая, что я понимаю ваши требования, я предлагаю:
data = RandomReal[100, {75, 3}];
bins = {0, 20, 40, 60, 80, 100};
Reap[
Sow[{#3, #2}, bins ~Nearest~ #] & @@@ data,
bins,
Reap[Sow[#, bins ~Nearest~ #2] & @@@ #2, bins, [email protected]#2 &][[2]] &
][[2]] ~Flatten~ 1 ~Total~ {3} // MatrixForm
Refactored:
f[bins_] := Reap[Sow[{##2}, bins ~Nearest~ #]& @@@ #, bins, #2][[2]] &
bin2D[data_, X_, Y_] := f[X][data, f[Y][#2, #2~Total~2 &] &] ~Flatten~ 1 ~Total~ {3}
Использование:
bin2D[data, xbins, ybins]
Ответ 3
Здесь мой подход:
data = RandomReal[5, {500000, 3}]; (* 500k values *)
zvalues = data[[All, 3]];
epsilon = 1*^-10; (* prevent 101 index *)
(* rescale and round (x,y) coordinates to index pairs in the 1..100 range *)
indexes = 1 + Floor[(1 - epsilon) 100 Rescale[data[[All, {1, 2}]]]];
(* approach 1: create bin-matrix first, then fill up elements by adding zvalues *)
res1 = Module[
{result = ConstantArray[0, {100, 100}]},
Do[
AddTo[result[[##]], zvalues[[i]]] & @@ indexes[[i]],
{i, Length[indexes]}
];
result
]; // Timing
(* approach 2: gather zvalues by indexes, add them up, convert them to a matrix *)
res2 = Module[{gb = GatherBy[Transpose[{indexes, zvalues}], First]},
SparseArray[gb[[All, 1, 1]] -> (Total /@ gb[[All, All, 2]])]
]; // Timing
res1 == res2
Эти два подхода (res1
и res2
) могут обрабатывать соответственно 100k и 200k элементов в секунду на этой машине. Это достаточно быстро или вам нужно запустить всю эту программу в цикле?
Ответ 4
Здесь мой подход, использующий функцию SelectEquivalents, определенную в Что входит в ваш пакет инструментов Mathematica?, который идеально подходит для такой проблемы.
data = RandomReal[100, {75, 3}];
bins = Range[0, 100, 20];
binMiddles = ([email protected] + [email protected])/2;
nearest = Nearest[binMiddles];
SelectEquivalents[
data
,
TagElement -> ({[email protected][#[[1]]], [email protected][#[[2]]]} &)
,
TransformElement -> (#[[3]] &)
,
TransformResults -> (Total[#2] &)
,
TagPattern -> Flatten[Outer[List, binMiddles, binMiddles], 1]
,
FinalFunction -> (Partition[Flatten[# /. {} -> 0], Length[binMiddles]] &)
]
Если вы хотите группировать в соответствии с более чем двумя измерениями, вы можете использовать в функции FinalFunction эту функцию, чтобы придать результатом списка желаемое измерение (я не помню, где я его нашел).
InverseFlatten[l_,dimensions_]:= Fold[Partition[#, #2] &, l, Most[Reverse[dimensions]]];