有效地将不均匀的列表列表转换为最小的包含用nan填充的数组

时间:2016-11-13 00:25:32

标签: python pandas numpy

考虑列表清单l

l = [[1, 2, 3], [1, 2]]

如果我将其转换为np.array,我会在第一个位置获得一个[1, 2, 3]的一维对象数组,在第二个位置获得[1, 2]

print(np.array(l))

[[1, 2, 3] [1, 2]]

我想要这个

print(np.array([[1, 2, 3], [1, 2, np.nan]]))

[[  1.   2.   3.]
 [  1.   2.  nan]]

我可以通过循环执行此操作,但我们都知道不受欢迎的循环

def box_pir(l):
    lengths = [i for i in map(len, l)]
    shape = (len(l), max(lengths))
    a = np.full(shape, np.nan)
    for i, r in enumerate(l):
        a[i, :lengths[i]] = r
    return a

print(box_pir(l))

[[  1.   2.   3.]
 [  1.   2.  nan]]

如何以快速,矢量化的方式执行此操作?

时间

enter image description here

enter image description here

设置功能

%%cython
import numpy as np

def box_pir_cython(l):
    lengths = [len(item) for item in l]
    shape = (len(l), max(lengths))
    a = np.full(shape, np.nan)
    for i, r in enumerate(l):
        a[i, :lengths[i]] = r
    return a
def box_divikar(v):
    lens = np.array([len(item) for item in v])
    mask = lens[:,None] > np.arange(lens.max())
    out = np.full(mask.shape, np.nan)
    out[mask] = np.concatenate(v)
    return out

def box_hpaulj(LoL):
    return np.array(list(zip_longest(*LoL, fillvalue=np.nan))).T

def box_simon(LoL):
    max_len = len(max(LoL, key=len))
    return np.array([x + [np.nan]*(max_len-len(x)) for x in LoL])

def box_dawg(LoL):
    cols=len(max(LoL, key=len))
    rows=len(LoL)
    AoA=np.empty((rows,cols, ))
    AoA.fill(np.nan)
    for idx in range(rows):
        AoA[idx,0:len(LoL[idx])]=LoL[idx]
    return AoA

def box_pir(l):
    lengths = [len(item) for item in l]
    shape = (len(l), max(lengths))
    a = np.full(shape, np.nan)
    for i, r in enumerate(l):
        a[i, :lengths[i]] = r
    return a

def box_pandas(l):
    return pd.DataFrame(l).values

5 个答案:

答案 0 :(得分:8)

这似乎是this question中的一个,其中填充为zeros而不是NaNs。这里发布了有趣的方法,以及基于broadcastingboolean-indexing的{​​{3}}。所以,我只想修改我的帖子中的一行来解决这种情况,就像这样 -

def boolean_indexing(v, fillval=np.nan):
    lens = np.array([len(item) for item in v])
    mask = lens[:,None] > np.arange(lens.max())
    out = np.full(mask.shape,fillval)
    out[mask] = np.concatenate(v)
    return out

示例运行 -

In [32]: l
Out[32]: [[1, 2, 3], [1, 2], [3, 8, 9, 7, 3]]

In [33]: boolean_indexing(l)
Out[33]: 
array([[  1.,   2.,   3.,  nan,  nan],
       [  1.,   2.,  nan,  nan,  nan],
       [  3.,   8.,   9.,   7.,   3.]])

In [34]: boolean_indexing(l,-1)
Out[34]: 
array([[ 1,  2,  3, -1, -1],
       [ 1,  2, -1, -1, -1],
       [ 3,  8,  9,  7,  3]])

我已经在那个Q& A上发布了所有已发布方法的运行时结果,这可能很有用。

答案 1 :(得分:3)

最快的列表版本可能使用itertools.zip_longest(在Py2中可能是izip_longest):

In [747]: np.array(list(itertools.zip_longest(*ll,fillvalue=np.nan))).T
Out[747]: 
array([[  1.,   2.,   3.],
       [  1.,   2.,  nan]])

普通zip产生:

In [748]: list(itertools.zip_longest(*ll))
Out[748]: [(1, 1), (2, 2), (3, None)]

另一个zip' transposes':

In [751]: list(zip(*itertools.zip_longest(*ll)))
Out[751]: [(1, 2, 3), (1, 2, None)]

通常以列表(甚至是列表的对象数组)开始时,坚持使用列表方法会更快。创建数组或数据帧需要很大的开销。

这不是第一次提出这个问题。

How can I pad and/or truncate a vector to a specified length using numpy?

我的回答包括zip_longestbox_pir

我认为还有使用扁平阵列的快速numpy版本,但我不记得细节。这可能是由Warren或Divakar提供的。

我认为'被压扁了'版本在这一行上起作用:

In [809]: ll
Out[809]: [[1, 2, 3], [1, 2]]
In [810]: sll=np.hstack(ll)     # all values in a 1d array
In [816]: res=np.empty((2,3)); res.fill(np.nan)  # empty target

得到价值变平的指数。这是至关重要的一步。这里使用r_是迭代的;快速版本可能使用cumsum

In [817]: idx=np.r_[0:3, 3:3+2]
In [818]: idx
Out[818]: array([0, 1, 2, 3, 4])
In [819]: res.flat[idx]=sll
In [820]: res
Out[820]: 
array([[  1.,   2.,   3.],
       [  1.,   2.,  nan]])

=====

所以缺少的链接是>np.arange()广播

In [897]: lens=np.array([len(i) for i in ll])
In [898]: mask=lens[:,None]>np.arange(lens.max())
In [899]: mask
Out[899]: 
array([[ True,  True,  True],
       [ True,  True, False]], dtype=bool)
In [900]: idx=np.where(mask.ravel())
In [901]: idx
Out[901]: (array([0, 1, 2, 3, 4], dtype=int32),)

答案 2 :(得分:2)

也许是这样的?不知道你的硬件,但是对于l2 = [list(range(20)),list(range(30))] * 10000的100个循环意味着16ms。

from numpy import nan


def box(l):
    max_lenght = len(max(l, key=len))
    return [x + [nan]*(max_lenght-len(x)) for x in l]

答案 3 :(得分:1)

我可能会在每个已经填充了默认值的子数组上将其写为切片赋值形式:

def to_numpy(LoL, default=np.nan):
    cols=len(max(LoL, key=len))
    rows=len(LoL)
    AoA=np.empty((rows,cols, ))
    AoA.fill(default)
    for idx in range(rows):
        AoA[idx,0:len(LoL[idx])]=LoL[idx]
    return AoA

我在Divakar的Boolean Indexing中添加f4并添加到时间测试中。至少在我的测试中,(Python 2.7和Python 3.5; Numpy 1.11)它并不是最快的。

时间显示大多数列表的izip_longestf2稍快一些,但对于较大的列表,切片分配(f1)更快:

from __future__ import print_function
import numpy as np
try:
    from itertools import izip_longest as zip_longest 
except ImportError:
    from itertools import zip_longest   

def f1(LoL):
    cols=len(max(LoL, key=len))
    rows=len(LoL)
    AoA=np.empty((rows,cols, ))
    AoA.fill(np.nan)
    for idx in range(rows):
        AoA[idx,0:len(LoL[idx])]=LoL[idx]
    return AoA

def f2(LoL):
    return np.array(list(zip_longest(*LoL,fillvalue=np.nan))).T

def f3(LoL):
    max_len = len(max(LoL, key=len))
    return np.array([x + [np.nan]*(max_len-len(x)) for x in LoL])

def f4(LoL):
    lens = np.array([len(item) for item in LoL])
    mask = lens[:,None] > np.arange(lens.max())
    out = np.full(mask.shape,np.nan)
    out[mask] = np.concatenate(LoL)
    return out  

if __name__=='__main__':
    import timeit   
    for case, LoL in (('small', [list(range(20)), list(range(30))] * 1000),
                      ('medium', [list(range(20)), list(range(30))] * 10000),
                      ('big', [list(range(20)), list(range(30))] * 100000),
                      ('huge', [list(range(20)), list(range(30))] * 1000000)):
        print(case)
        for f in (f1, f2, f3, f4):
            print("   ",f.__name__, timeit.timeit("f(LoL)", setup="from __main__ import f, LoL", number=100) )      

打印:

small
    f1 0.245459079742
    f2 0.209980010986
    f3 0.350691080093
    f4 0.332141160965
medium
    f1 2.45869493484
    f2 2.32307982445
    f3 3.65722203255
    f4 3.55545687675
big
    f1 25.8796288967
    f2 26.6177148819
    f3 41.6916451454
    f4 41.3140149117
huge
    f1 262.429639101
    f2 295.129109859
    f3 427.606887817
    f4 441.810388088

答案 4 :(得分:0)

如果这仅适用于 2D 列表,这可能是您的答案:

from numpy import nan

def even(data):
    maxlen = max(len(l) for l in data)
    for l in data:
        l.extend([nan] * (maxlen - len(l)))

如果您不想修改实际列表:

from numpy import nan

def even(data):
    res = data.copy()
    maxlen = max(len(l) for l in res)
    for l in res:
        l.extend([nan] * (maxlen - len(l)))
    return res