如何选择对数组的多个轴求和?

时间:2018-10-28 11:52:25

标签: arrays j

J中选择性地求和数组的多个轴的首选方法是什么?

例如,假设a是以下第3级数组:

   ]a =: i. 2 3 4
 0  1  2  3
 4  5  6  7
 8  9 10 11

12 13 14 15
16 17 18 19
20 21 22 23

我的目标是定义一个dyad“ sumAxes”以对我选择的多个轴求和:

   0 1 sumAxes a      NB. 0+4+8+12+16+20 ...
60 66 72 78

   0 2 sumAxes a      NB. 0+1+2+3+12+13+14+15 ...
60 92 124

   1 2 sumAxes a      NB. 0+1+2+3+4+5+6+7+8+9+10+11 ...
66 210

我当前尝试实现此动词的方式是使用二元组|:首先置换a的轴,然后使用{{1}散布必要等级的项目}(其中,"n是我要求和的数字轴),然后对结果项求和:

n

这似乎可以按我的意愿工作,但是作为J的初学者,我不确定是否忽略了排名或某些动词的某些方面,从而使定义更清晰。更笼统地说,我想知道在这种语言中置换轴,撕碎和求和是惯用的还是有效的。

对于上下文,我以前在数组编程方面的大部分经验是使用Python的NumPy库。

NumPy没有J的等级概念,而是希望用户显式标记数组的轴以减少过度:

   sumAxes =: dyad : '(+/ @ ,"(#x)) x |: y'

作为一个脚注,当仅指定一个轴时(因为等级不能与“轴”互换),与NumPy相比,我当前的>>> import numpy >>> a = numpy.arange(2*3*4).reshape(2, 3, 4) # a =: i. 2 3 4 >>> a.sum(axis=(0, 2)) # sum over specified axes array([ 60, 92, 124]) 实现方式具有“不正确”的缺点。

1 个答案:

答案 0 :(得分:9)

动机

J具有令人难以置信的功能,可以处理任意排列的数组。但是,该语言的一个方面几乎同时具有普遍用途和合理性,但与这种与维数无关的性质却有些相反。

主轴(实际上通常是引导轴)被隐式特权。这是基础概念,例如#是商品的数量(即第一轴的尺寸),+/的低调优雅和通用性,无需进一步修改,以及语言。

但这也是导致您在尝试解决此问题时遇到的障碍的原因。

标准方法

因此,解决问题的一般方法就是您所拥有的:转置或以其他方式重新排列数据,使您感兴趣的轴成为引导轴。您的方法是经典且无懈可击的。您可以出于良心使用它。

替代方法

但是,像您一样,这让我有点ni不安,因为我们在相似的情况下被迫跳过这样的篮球。我们正在与语言的本质背道而驰的一个线索是连接词"(#x)的动态论证;通常,连接的参数是固定的,并且在运行时对其进行计算通常会迫使我们使用显式代码(如您的示例)或dramatically more complicated code。当语言使某些事情变得难以执行时,通常是您不愿接受的信号。

另一个是那个奇迹(,)。不仅仅是我们要移调一些轴;是我们要专注于一个特定的轴,然后运行将其尾随的所有元素转换为平面向量。尽管我实际上认为这更多地反映了我们对问题的框架方式所施加的约束,而不是符号上的约束。有关更多信息,请参见本文的最后一部分。

有了这一点,我们可能有理由希望直接解决非领先轴。而且,J到处都提供了使我们能够精确执行此操作的原语,这可能暗示该语言的设计人员还认为有必要包括对主导轴首要性的某些例外。

入门示例

例如,二进位|. rotate )的排名为1 _,即它的左侧为矢量。

对于使用多年的人来说,这有时是令人惊讶的,从来没有超过左边的标量。这与未约束的右秩一起是J的超前​​轴偏差的另一个微妙结果:我们将右参数视为项目的向量,而将左参数视为简单的标量旋转值该向量的

因此:

   3 |. 1 2 3 4 5 6
4 5 6 1 2 3

   1 |. 1 2 , 3 4 ,: 5 6
3 4
5 6
1 2

但是在后一种情况下,如果我们不想将表视为行的向量,而是将列视为列的向量,怎么办?

当然,经典方法是使用等级,以明确表示我们感兴趣的轴(因为将其保留为 implicit 总是选择引导轴) :

   1 |."1 ] 1 2 , 3 4 ,: 5 6
2 1
4 3
6 5

现在,这在J代码中完全是惯用法,标准和无处不在的:J鼓励我们根据等级来考虑。没有人会眨眼阅读这段代码。

但是,正如开头所述,从另一种意义上讲,它感觉像是在进行自动调整或手动调整。尤其是当我们想在运行时动态选择排名时。从符号上来说,我们现在不再对整个数组进行寻址,而是对每一行进行寻址

这是|.左行的位置:它是can address non-leading axes directly的少数几个原语之一。

   0 1 |. 1 2 , 3 4 ,: 5 6
2 1
4 3
6 5

看妈,没有等级!当然,我们现在必须为每个轴分别指定一个旋转值,但这不仅可以,而且有用,因为 now 剩下的参数听起来更像是可以按照真正的J精神从输入中计算出

直接汇总非引导轴

因此,既然我们知道J在某些情况下可以让我们处理非引导轴,我们只需to survey those cases并在此处标识出一个适合我们目的的轴。

我发现对非引导轴工作最有用的原语是;.,带有 boxed 左手参数。所以我的直觉是首先做到这一点。

让我们从您的示例开始,稍做修改以了解我们的总结。

    ]a =: i. 2 3 4
    sumAxes =: dyad : '(< @ ,"(#x)) x |: y'

     0 1 sumAxes a
+--------------+--------------+---------------+---------------+
|0 4 8 12 16 20|1 5 9 13 17 21|2 6 10 14 18 22|3 7 11 15 19 23|
+--------------+--------------+---------------+---------------+ 
     0 2 sumAxes a
+-------------------+-------------------+---------------------+
|0 1 2 3 12 13 14 15|4 5 6 7 16 17 18 19|8 9 10 11 20 21 22 23|
+-------------------+-------------------+---------------------+
    1 2 sumAxes a
+-------------------------+-----------------------------------+
|0 1 2 3 4 5 6 7 8 9 10 11|12 13 14 15 16 17 18 19 20 21 22 23|
+-------------------------+-----------------------------------+

the definition of for dyads derived from ;.1和朋友的相关部分是:

  

二元情况1_12_2中的品格由布尔向量x中的1决定; 空向量x和非零的#y表示整个y 如果x是原子01,则将其视为(#y)#x 。通常,布尔向量>j{x指定如何切割轴j,并将原子视为(j{$y)#>j{x

这是什么意思:如果我们只是试图沿数组的维度进行切片,而没有内部分段,则可以简单地使用带有仅由1和{ {1}} 。向量中a:的数量(即和)确定结果数组的等级。

因此,重现上面的示例:

1

瞧。另外,注意左手参数中的模式吗?这两个Ace恰好在您对 ('';'';1) <@:,;.1 a +--------------+--------------+---------------+---------------+ |0 4 8 12 16 20|1 5 9 13 17 21|2 6 10 14 18 22|3 7 11 15 19 23| +--------------+--------------+---------------+---------------+ ('';1;'') <@:,;.1 a +-------------------+-------------------+---------------------+ |0 1 2 3 12 13 14 15|4 5 6 7 16 17 18 19|8 9 10 11 20 21 22 23| +-------------------+-------------------+---------------------+ (1;'';'') <@:,;.1 a +-------------------------+-----------------------------------+ |0 1 2 3 4 5 6 7 8 9 10 11|12 13 14 15 16 17 18 19 20 21 22 23| +-------------------------+-----------------------------------+ 的原始调用的索引处。明白我的意思是,按照J精神为每个维度提供一个闻起来像一件好事的值吗?

因此,要使用这种方法来提供具有相同接口的sumAxe类似物:

sumAxe

为简洁起见,结果省略了,但与您的 sax =: dyad : 'y +/@:,;.1~ (1;a:#~r-1) |.~ - {. x -.~ i. r=.#$y' NB. Explicit sax =: ] +/@:,;.1~ ( (] (-@{.@] |. 1 ; a: #~ <:@[) (-.~ i.) ) #@$) NB. Tacit 相同。

最终注意事项

我还要指出一件事。从Python调用的sumAxe调用的接口命名了您要“一起运行”的两个轴。那绝对是一种看待它的方式。

另一种看待它的方法(借鉴了我在这里所提到的J哲学)是命名要沿其求和的轴。这是我们实际关注的事实,因为我们不关心每个“切片”这一事实而得到证实,因为我们不在乎其形状,仅在乎其值。

这种谈论您感兴趣的事物的观点发生了变化,其优势在于它始终是单个事物,并且这种奇异性允许我们代码中的某些简化(再次,尤其是在J,我们通常谈论[新的,即转置后的]引导轴)¹。

让我们再次看一下sumAxe的向量矢量参数,以说明我的意思:

;.

现在将三个带括号的参数视为三行的单个矩阵。什么让您脱颖而出?对我来说,就是沿着对角线的那些。它们数量较少,具有价值。相反,ace构成矩阵的“背景”(零)。那些才是真正的内容。

('';'';1) <@:,;.1 a ('';1;'') <@:,;.1 a (1;'';'') <@:,;.1 a 接口现在的状态相反:它要求我们指定Ace(零)。取而代之的是,我们指定1,即我们真正感兴趣的轴

如果这样做,我们可以这样重写函数:

sumAxe

而不是呼叫 xas =: dyad : 'y +/@:,;.1~ (-x) |. 1 ; a: #~ _1 + #$y' NB. Explicit xas =: ] +/@:,;.1~ -@[ |. 1 ; a: #~ <:@#@$@] NB. Tacit ,而是呼叫0 1 sax a,而不是2 xas a,而是呼叫0 2 sax a,等等。

这两个动词相对简单,表明J同意这种焦点转换。


¹在此代码中,我假设您始终希望折叠除1以外的所有轴。此假设在我使用1 xas a来生成前后向量的方法中进行了编码。

但是,脚注 |.与仅指定单个轴的NumPy相比具有“不正确”工作的缺点。建议有时您只希望折叠一个轴。

这是完全有可能的,sumAxes方法可以获取任意(原位)切片;我们只需要更改指导它的方法(生成1s-aces向量)即可。如果您提供了一些概括的示例,我将在此处更新该帖子。可能只是使用;.(<1) x} a: #~ #$y而不是((1;'') {~ (e.~ i.@#@$))的问题。