将面膜涂抹在多条线上(语法糖?)

时间:2014-09-15 20:19:07

标签: python numpy

我正在寻找一种优雅(或更优雅)的方式来编写numpy中的特定用例。用例是一个大型数据集(因此效率很重要),包含100多个字段,超过1,000行代码和多个代码段,我只想处理这些字段的子集。只要我正在处理所有的观察结果,这在简单的numpy中是干净而有效的:

wages = np.arange(40000,60000,2000)
cg    = np.arange(0,100000,10000)
ded   = np.repeat([6000,9000],5) 
exem  = np.repeat([2000,4000],5) 

agi  = wages + cg
tinc = agi - ded
tinc = tinc - exem

但是在许多代码小节中,我只想处理30行代码的观察的子集,这是我能想到的最好的:

agi  = wages + cg
mask = wages < 50001
tinc = agi

tinc[mask] = agi[mask] - ded[mask]
tinc[mask] = tinc[mask] - exem[mask]

这并不可怕,不要误解我的意思,而是将这个变量与数百行代码相乘。有没有办法做以下的事情,而不诉诸cython / numba循环?

# fake code, just to convey the desired syntax
agi  = wages + cg
tinc = agi

mask( wages < 50001 ):    # i.e. inside a python loop, would be "if wages < 50001:"
   tinc = agi - ded
   tinc = tinc - exem

换句话说,我想定义代码的子部分,并指定完全相同的掩码应该应用于代码部分中的每个单独的数组,而不是为每个单独的数组显式键入掩码。

(顺便说一下,我知道可能有一些通过熊猫的替代方法,但现在我更愿意通过numpy来探索我的最佳选择。我可能会在稍后用pandas标签重新提出这个问题。)

2 个答案:

答案 0 :(得分:1)

我不推荐这个,但是......你可以用一个可怕的魔术上下文管理器来做这件事。例如:

@contextlib.contextmanager
def masking(namespace, mask):
    # If you don't have a fixed set of maskable variables, make it
    # an instance/global/local variables, like `_names`, or just
    # [name for name, value in namespace.items() if isiinstance(value, np.ndarray)]
    names = 'tinc agi dec exem'.split()
    stash = {name: namespace[name] for name in names}
    for name in names:
        namespace[name] = namespace[name][mask]
    try:
        yield
    finally:
        for name in names:
            namespace[name] = stash[name]

现在你可以这样做:

with masking(globals(), wages < 50001):
    tinc = agi - dec
    tinc = tinc - exem

with masking(self.__dict__, self.wages < 50001):
    self.tinc = self.agi - self.dec
    self.tinc = self.tinc - self.exem

# etc.

答案 1 :(得分:0)

执行此操作的一种方法可以使用Numpy's masked arrays

Numpy的文档进一步详细介绍,我建议在开始之前仔细查看。它可能会增加一些复杂性(你可能需要或不需要使用function under numpy.ma),你必须要小心你的代码中的所有操作仍然是正确的,但如果你使用蒙面数组你可以轻松定义这样的上下文管理器:

@contextlib.contextmanager
def mask(clause, masked_arrays):
    for ma in masked_arrays:
        ma[~clause] = np.ma.masked
    yield
    for ma in masked_arrays:
        ma.mask = np.ma.nomask

请注意,由于屏蔽操作必须就地,因此需要使用的数组是屏蔽数组。然后你可以轻松地使用它:

# Wrap arrays as masked arrays
mwages = np.ma.array(wages)
mcg = np.ma.array(cg)
... etc ...

with mask(mwages < 50001, [mwages, mcg, mded, ...]):
    # np.ma.compressed returns 1D array of non-masked items
    unmasked_tinc = np.ma.compressed(magi - mded - mexem)

    # or must change the masked one directly