有效地将不均匀的列表列表转换为用 nan 填充的最小包含数组
Posted
技术标签:
【中文标题】有效地将不均匀的列表列表转换为用 nan 填充的最小包含数组【英文标题】:efficiently convert uneven list of lists to minimal containing array padded with nan 【发布时间】:2017-03-26 22:30:30 【问题描述】:考虑列表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]]
我如何以快速、矢量化的方式做到这一点?
时机
设置函数
%%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
【问题讨论】:
【参考方案1】:如果这仅适用于 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
【讨论】:
【参考方案2】:这似乎与this question
很接近,其中填充是zeros
而不是NaNs
。那里发布了有趣的方法,以及基于broadcasting
和boolean-indexing
的mine
。所以,我只需修改我的帖子中的一行来解决这种情况 -
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]])
我在该问答中发布了所有已发布方法的一些运行时结果,这可能很有用。
【讨论】:
@piRSquared 列表理解在性能上会比map
更好吗?
@piRSquared 啊不,没关系。另外,我不知道map
可能存在兼容性问题。所以,我认为这是一个公平的编辑。肯定会看到一些运行时测试会很有趣!
@piRSquared 可爱,非常全面的基准测试!正如我所见,所有数据大小都没有明确的赢家。不过比赛很好。
有趣的是,我制作的子列表越广,你的表现就越好。【参考方案3】:
也许是这样的?不了解您的硬件,但意味着 l2 = [list(range(20)), list(range(30))] * 10000 的 100 个循环需要 16 毫秒。
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]
【讨论】:
我真的很喜欢max(l, key=len)
嗯,我真的很喜欢地图(len, l) ^^.
问题是numpy
没有扩展生成器,所以不是真正的比较。
@piRSquared 我同意。它看起来比我做的要短得多:max(len(v) for v in l)
【参考方案4】:
可能最快的列表版本使用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 '转置':
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_longest
和你的box_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),)
【讨论】:
【参考方案5】:我可以将其写成对每个已填充默认值的子数组进行切片分配的形式:
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_longest
或f2
对于大多数列表来说稍微快一些,但对于较大的列表来说,切片分配(即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
【讨论】:
以上是关于有效地将不均匀的列表列表转换为用 nan 填充的最小包含数组的主要内容,如果未能解决你的问题,请参考以下文章