【问题标题】:Mathematica fast 2D binning algorithmMathematica 快速二维分箱算法
【发布时间】:2011-12-31 23:57:29
【问题描述】:

我在 Mathematica 中开发适当快速的分箱算法时遇到了一些麻烦。我有一个大型(~100k 元素)数据集的形式 T={{x1,y1,z1},{x2,y2,z2},....} 我想将它分箱成一个大约 100x100 箱的二维数组,箱值由落入每个箱的 Z 值的总和给出。

目前我正在遍历表的每个元素,使用 Select 根据 bin 边界列表选择它应该在哪个 bin 中,并将 z 值添加到占据该 bin 的值列表中。最后,我将 Total 映射到 bin 列表,对它们的内容求和(我这样做是因为我有时想做其他事情,比如最大化)。

我曾尝试使用 Gather 和其他此类功能来执行此操作,但上述方法速度快得离谱,尽管我可能使用 Gather 很差。无论如何,按照我的方法进行排序仍然需要几分钟,我觉得 Mathematica 可以做得更好。有没有人手边有一个很好的高效算法?

【问题讨论】:

  • 请发布您已经在使用的代码,否则很难知道是否有解决方案,例如Gather其实是一种改进。
  • 让我看看我是否有这个权利:您是通过相应的 X 和 Y 值对 Z 值进行分级,对吗?
  • x,y,z 是实数还是整数?如果z 是整数,可能有更简单的方法:BinCounts[Join @@ (ConstantArray[{#1, #2}, #3] & @@@ data)]
  • @Szabolcs,我不明白你上面的评论。
  • @Mr.Wizard 我的意思是 如果 z 的意思是某物的“计数”,那么我们不妨将每个条目乘以z 倍和使用内置的快速BinCounts函数。

标签: performance algorithm wolfram-mathematica bin binning


【解决方案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,3}->1, {2,3}->10 并且,比如说,{2,3}->2 mat[[2,3 ]] 将是 1+10+2; “TreatRepeatedEntries”表示如果位置条目重复,则将值相加。 SparseArray 的默认行为是不这样做。 TreatRepeatedEntries"->1 激活该功能 - 使用它您可以编写非常高效的代码。
  • 为什么这些事情没有记录在案?您之前展示了 SparseArray 属性,我相信我问过是否还有其他类似的有用的未记录功能,但这并没有出现。由于官方文档中没有这些,但您愿意透露它们,请您考虑将这些隐藏但强大的方法放在一起的索引?
  • Id 确实多次展示了这一点,至少在 MathGroup 上展示过一次:forums.wolfram.com/mathgroup/archive/2010/Dec/msg00335.html
  • 我现在才注意到这个回复。我希望这是 SparseArray 的一个选项,而不是系统选项。
【解决方案2】:

由于 Szabolcs 的可读性问题,我打算重写以下代码。在那之前,要知道如果您的垃圾箱是常规的,并且您可以使用RoundFloorCeiling(带有第二个参数)代替Nearest,那么下面的代码会快得多。在我的系统上,它的测试速度比同时发布的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, Tr@#2 &][[2]] &
][[2]] ~Flatten~ 1 ~Total~ {3} // MatrixForm

重构:

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]

【讨论】:

  • @Szabolcs,你能建议我如何使它更具可读性吗?
  • 一旦我弄清楚为什么它没有给出与我相同的结果......bins 中的那些数字,它们是垃圾箱的中心,对吗?
  • @Szabolcs,如您所见,我使用的是 Nearest,因此您可以自己推断出该部分,但如果我没记错的话,是的。此外,它完全有可能只是功能失调。 :o)
  • 我的第一条评论只是沮丧的呐喊,并不是直接针对你 :-) 但说真的,我确实认为客观上很难阅读(尽管“易于阅读”是一种主观和文化的东西,取决于我们习惯的)。以Reap[Sow[#1, bins~Nearest~#2] & @@@ #2, bins, Tr@#2 &] 行为例。 #2 出现了 3 次,每一次都是不同函数的参数。将Tr 用作Total 的简写存在误导性。内联符号的非正统使用。
  • 我想建议一个改变。使用Nearest 是可以的,但是每次使用都必须做大量的预处理。我建议用With[{nn=Nearest[bins]}, ...]f 中的所有内容包装起来,并使用nn[#] 而不是bins ~Nearest~ #。这完成了一次所需的所有预处理,在我的机器上超过 10^6 点,它减少了近 7 秒。
【解决方案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

这两种方法(res1res2)在这台机器上每秒可以分别处理 100k 和 200k 个元素。这是否足够快,还是需要循环运行整个程序?

【讨论】:

  • 您使用AddToPart 的长格式是否有原因?
  • 我修复了Part。使用AddTo,它只是避免了一些括号。当我将Set 放在Function 中时,我倾向于使用这种形式,但我对这种风格没有强烈的感觉。
  • 这似乎比我做的要快得多,但是你能轻松处理不规则的垃圾箱吗? (出于这个原因,我使用了Nearest。)
  • Total /@ gb[[All, All, 2]] 等价于 Total[gb[[All, All, 2]], {2}]。
【解决方案4】:

这是我使用 What is in your Mathematica tool bag? 中定义的函数 SelectEquivalents 的方法,非常适合此类问题。

data = RandomReal[100, {75, 3}];
bins = Range[0, 100, 20];
binMiddles = (Most@bins + Rest@bins)/2;
nearest = Nearest[binMiddles];

SelectEquivalents[
   data
   ,
   TagElement -> ({First@nearest[#[[1]]], First@nearest[#[[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]]];

【讨论】:

  • 正如我在 Mr.Wizard 的解决方案中提到的,您需要使用 NearestFunctions 而不是每次都重新调用 Nearest。换句话说,首先计算Nearest[xbins]Nearest[ybins],你会得到不小的加速。
  • 另外,为什么是TransformResults -> (Append[#1, Total[#2]] &) 而不是TransformResults -> ({#1, Total[#2]} &)
  • 因为 #1 是一个列表,我想要一个“可绘制”的结果。
  • 当然。我自己应该看到的。
  • OP 正在讨论 bin boundaries 列表。您正在使用 bin midpoints 列表。
猜你喜欢
  • 1970-01-01
  • 2012-05-28
  • 1970-01-01
  • 1970-01-01
  • 2012-01-25
  • 1970-01-01
  • 1970-01-01
  • 2011-09-07
  • 1970-01-01
相关资源
最近更新 更多