【问题标题】:Convert numpy rows into columns based on ID根据 ID 将 numpy 行转换为列
【发布时间】:2018-05-28 20:29:43
【问题描述】:

假设我有一个numpy 数组,它在两种项目类型的 ID 之间进行映射:

[[1, 12],
 [1, 13],
 [1, 14],
 [2, 13],
 [2, 14],
 [3, 11]]

我想重新排列这个数组,使新数组中的每一行代表与原始数组中相同 ID 匹配的所有项目。在这里,每一列将代表原始数组中的一个映射,直到对新数组中的列数有一个指定的形状限制。如果我们想从上面的数组中获得这个结果,确保我们只有 2 列,我们将获得:

[[12, 13],  #Represents 1 - 14 was not kept as only 2 columns are allowed
 [13, 14],  #Represents 2
 [11,  0]]  #Represents 3 - 0 was used as padding since 3 did not have 2 mappings

这里最简单的方法是使用 for 循环来填充新数组,因为它遇到原始数组中的行。使用numpy 的功能是否有更有效的方法来完成此任务?

【问题讨论】:

标签: python arrays numpy scipy


【解决方案1】:

这是一种通用且主要是 Numpythonic 的方法:

In [144]: def array_packer(arr):
     ...:     cols = arr.shape[1]
     ...:     ids = arr[:, 0]
     ...:     inds = np.where(np.diff(ids) != 0)[0] + 1
     ...:     sp = np.split(arr[:,1:], inds)
     ...:     result = [np.unique(a[: cols]) if a.shape[0] >= cols else
     ...:                    np.pad(np.unique(a), (0, (cols - 1) * (cols - a.shape[0])), 'constant')
     ...:                 for a in sp]
     ...:     return result
     ...:     
     ...:     

演示:

In [145]: a = np.array([[1, 12, 15, 45],
     ...:  [1, 13, 23, 9],
     ...:  [1, 14, 14, 11],
     ...:  [2, 13, 90, 34],
     ...:  [2, 14, 23, 43],
     ...:  [3, 11, 123, 53]])
     ...:  

In [146]: array_packer(a)
Out[146]: 
[array([ 9, 11, 12, 13, 14, 15, 23, 45,  0,  0,  0]),
 array([13, 14, 23, 34, 43, 90,  0,  0,  0,  0,  0,  0]),
 array([ 11,  53, 123,   0,   0,   0,   0,   0,   0,   0,   0,   0])]

In [147]: a = np.array([[1, 12, 15],
     ...:  [1, 13, 23],
     ...:  [1, 14, 14],
     ...:  [2, 13, 90],
     ...:  [2, 14, 23],
     ...:  [3, 11, 123]])
     ...: 
     ...:   
     ...:  

In [148]: array_packer(a)
Out[148]: 
[array([12, 13, 14, 15, 23]),
 array([13, 14, 23, 90,  0,  0]),
 array([ 11, 123,   0,   0,   0,   0])]

【讨论】:

    【解决方案2】:

    这是一种使用稀疏矩阵的方法:

    def pp(map_, maxitems=2):
        M = sparse.csr_matrix((map_[:, 1], map_[:, 0], np.arange(map_.shape[0]+1)))
        M = M.tocsc()
        sizes = np.diff(M.indptr)
        ids, = np.where(sizes)
        D = np.concatenate([M.data, np.zeros((maxitems - 1,), dtype=M.data.dtype)])
        D = np.lib.stride_tricks.as_strided(D, (D.size - maxitems + 1, maxitems),
                                            2 * D.strides)
        result = D[M.indptr[ids]]
        result[np.arange(maxitems) >= sizes[ids, None]] = 0
        return result
    

    使用@crisz 的代码进行计时,但已修改为使用较少重复的测试数据。我还添加了一些“验证”:chrisz 和我的解决方案给出了相同的答案,其他两个输出不同的格式,所以我无法检查它们。

    代码:

    from scipy import sparse
    import numpy as np
    from collections import defaultdict, deque
    
    def pp(map_, maxitems=2):
        M = sparse.csr_matrix((map_[:, 1], map_[:, 0], np.arange(map_.shape[0]+1)))
        M = M.tocsc()
        sizes = np.diff(M.indptr)
        ids, = np.where(sizes)
        D = np.concatenate([M.data, np.zeros((maxitems - 1,), dtype=M.data.dtype)])
        D = np.lib.stride_tricks.as_strided(D, (D.size - maxitems + 1, maxitems),
                                            2 * D.strides)
        result = D[M.indptr[ids]]
        result[np.arange(maxitems) >= sizes[ids, None]] = 0
        return result
    
    def chrisz(a):
      return [[*a[a[:,0]==i,1],0][:2] for i in np.unique(a[:,0])]
    
    def piotr(a):
      d = defaultdict(lambda: deque((0, 0), maxlen=2))
      for key, val in a:
        d[key].append(val)
      return d
    
    def karams(arr):
      cols = arr.shape[1]
      ids = arr[:, 0]
      inds = np.where(np.diff(ids) != 0)[0] + 1
      sp = np.split(arr[:,1:], inds)
      result = [a[:2].ravel() if a.size >= cols else np.pad(a.ravel(), (0, cols -1 * (cols - a.size)), 'constant')for a in sp]
      return result
    
    def make(nid, ntot):
        return np.c_[np.random.randint(0, nid, (ntot,)),
                     np.random.randint(0, 2**30, (ntot,))]
    
    from timeit import timeit
    import pandas as pd
    import matplotlib.pyplot as plt
    
    res = pd.DataFrame(
           index=['pp', 'chrisz', 'piotr', 'karams'],
           columns=[10, 50, 100, 500, 1000, 5000, 10000],# 50000],
           dtype=float
    )
    
    for c in res.columns:
    #        l = np.repeat(np.array([[1, 12],[1, 13],[1, 14],[2, 13],[2, 14],[3, 11]]), c, axis=0)
        l = make(c // 2, c * 6)
        assert np.all(chrisz(l) == pp(l))
        for f in res.index:
            stmt = '{}(l)'.format(f)
            setp = 'from __main__ import l, {}'.format(f)
            res.at[f, c] = timeit(stmt, setp, number=30)
    
    ax = res.div(res.min()).T.plot(loglog=True)
    ax.set_xlabel("N");
    ax.set_ylabel("time (relative)");
    
    plt.show()
    

    【讨论】:

    • 我怀疑我的时间安排在重复结果上很好,因为它实际上只循环了 3 次。不错的方法!
    【解决方案3】:

    对于这个问题,朴素的 for 循环实际上是一个非常有效的解决方案:

    from collections import defaultdict, deque
    d = defaultdict(lambda: deque((0, 0), maxlen=2))
    
    %%timeit
    for key, val in a:
        d[key].append(val)
    4.43 µs ± 29.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    
    # result: {1: deque([13, 14]), 2: deque([13, 14]), 3: deque([0, 11])}
    

    为了比较,这个线程中提出的 numpy 解决方案慢了 4 倍:

    %timeit [[*a[a[:,0]==i,1],0][:2] for i in np.unique(a[:,0])]
    18.6 µs ± 336 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
    

    Numpy 很棒,我自己也经常使用它,但我认为这种情况下它很麻烦。

    【讨论】:

    • 不错的算法视觉!
    • 关于您的基准测试,请同时使用大型阵列运行基准测试。另请注意,您需要用零而不是无填充数组。 Plus padding 应该从右边开始。
    • 我将填充从无更改为 0。感谢@Kasramvd 指出这一点!
    • 我刚刚在一个 10000 倍大的列表上计时,并且 numpy 方法要快得多,我希望其他人能够验证计时,因此它似乎没有偏见。
    【解决方案4】:

    从几乎重复的部分稍微改编为 pad 并只选择两个元素:

    [[*a[a[:,0]==i,1],0][:2] for i in np.unique(a[:,0])]
    

    输出:

    [[12, 13], [13, 14], [11, 0]]
    

    如果您想跟踪密钥:

    {i:[*a[a[:,0]==i,1],0][:2] for i in np.unique(a[:,0])}
    
    # {1: [12, 13], 2: [13, 14], 3: [11, 0]}
    

    函数

    def chrisz(a):
      return [[*a[a[:,0]==i,1],0][:2] for i in np.unique(a[:,0])]
    
    def piotr(a):
      d = defaultdict(lambda: deque((0, 0), maxlen=2))
      for key, val in a:
        d[key].append(val)
      return d
    
    def karams(arr):
      cols = arr.shape[1]
      ids = arr[:, 0]
      inds = np.where(np.diff(ids) != 0)[0] + 1
      sp = np.split(arr[:,1:], inds)
      result = [a[:2].ravel() if a.size >= cols else np.pad(a.ravel(), (0, cols -1 * (cols - a.size)), 'constant')for a in sp]
      return result
    

    时间

    from timeit import timeit
    import pandas as pd
    import matplotlib.pyplot as plt
    
    res = pd.DataFrame(
           index=['chrisz', 'piotr', 'karams'],
           columns=[10, 50, 100, 500, 1000, 5000, 10000, 50000],
           dtype=float
    )
    
    for f in res.index:
        for c i
    
    n res.columns:
            l = np.repeat(np.array([[1, 12],[1, 13],[1, 14],[2, 13],[2, 14],[3, 11]]), c, axis=0)
            stmt = '{}(l)'.format(f)
            setp = 'from __main__ import l, {}'.format(f)
            res.at[f, c] = timeit(stmt, setp, number=30)
    
    ax = res.div(res.min()).T.plot(loglog=True)
    ax.set_xlabel("N");
    ax.set_ylabel("time (relative)");
    
    plt.show()
    

    结果(显然@Kasramvd 是赢家):

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-25
      • 1970-01-01
      相关资源
      最近更新 更多