为数组中的每个唯一元素创建布尔掩码

3
我有一个数字列表,想为该列表的每个唯一元素创建一个布尔掩码(或数组,无所谓)。在下面的示例中,我想创建四个长度为len(labels)的掩码。第一个掩码在位置 i 处为True,如果labels [i] == 0 ,第二个掩码在位置 i 处为True,如果labels [i] == 1 等等。
我尝试使用pandas和循环中的 .isin 方法来实现此目的。然而,由于我的算法中需要多次调用此方法,并且标签列表可能非常长,因此循环不够有效,速度太慢。如何使此过程更快?
labels = [0,0,1,1,3,3,3,1,2,1,0,0]
d = dict()
y = pd.Series(labels)
for i in set(labels):
    d[i] = y.isin([i])
3个回答

4

方法1

使用listset

In [989]: {x: [x==l for l in labels] for x in set(labels)}
Out[989]:
{0: [True, True, False, False, False, False, False, False, False, False, True, True],
 1: [False, False, True, True, False, False, False, True, False, True, False, False],
 2: [False, False, False, False, False, False, False, False, True, False, False, False],
 3: [False, False, False, False, True, True, True, False, False, False, False, False]}

如果您希望将其作为 数据帧(dataframe)
In [994]: pd.DataFrame({x: [x==l for l in labels] for x in set(labels)})
Out[994]:
        0      1      2      3
0    True  False  False  False
1    True  False  False  False
2   False   True  False  False
3   False   True  False  False
4   False  False  False   True
5   False  False  False   True
6   False  False  False   True
7   False   True  False  False
8   False  False   True  False
9   False   True  False  False
10   True  False  False  False
11   True  False  False  False

方法二

使用 pd.get_dummies,如果您有一个 series ,那么您可以

In [997]: pd.get_dummies(y).astype(bool)
Out[997]:
        0      1      2      3
0    True  False  False  False
1    True  False  False  False
2   False   True  False  False
3   False   True  False  False
4   False  False  False   True
5   False  False  False   True
6   False  False  False   True
7   False   True  False  False
8   False  False   True  False
9   False   True  False  False
10   True  False  False  False
11   True  False  False  False

基准测试

小型

In [1002]: len(labels)
Out[1002]: 12

In [1003]: %timeit pd.get_dummies(y).astype(bool)
1000 loops, best of 3: 476 µs per loop

In [1004]: %timeit pd.DataFrame({x: [x==l for l in labels] for x in set(labels)})
1000 loops, best of 3: 580 µs per loop

In [1005]: %timeit pd.DataFrame({x : (y == x) for x in y.unique()})
1000 loops, best of 3: 1.15 ms per loop

大的

In [1011]: len(labels)
Out[1011]: 12000

In [1012]: %timeit pd.get_dummies(y).astype(bool)
1000 loops, best of 3: 875 µs per loop

In [1013]: %timeit pd.DataFrame({x: [x==l for l in labels] for x in set(labels)})
100 loops, best of 3: 4.97 ms per loop

In [1014]: %timeit pd.DataFrame({x : (y == x) for x in y.unique()})
1000 loops, best of 3: 1.32 ms per loop

这种写法的for循环比我的写法更快吗? - Merlin1896

1
你可以使用statsmodels.tools.tools.categorical,这应该会很快,尤其是如果你已经有一个NumPy数组可以使用。
categorical(np.array(labels), drop=True).astype(bool)

如果您想要在生成的数组中每个列与其相应标签之间进行显式映射,请将dictnames=True传递给category演示
>>> from statsmodels.tools.tools import categorical
>>> labels = np.array([0,0,1,1,3,3,3,1,2,1,0,0])
>>> categorical(labels, drop=True).astype(bool)
array([[ True, False, False, False],
       [ True, False, False, False],
       [False,  True, False, False],
       [False,  True, False, False],
       [False, False, False,  True],
       [False, False, False,  True],
       [False, False, False,  True],
       [False,  True, False, False],
       [False, False,  True, False],
       [False,  True, False, False],
       [ True, False, False, False],
       [ True, False, False, False]], dtype=bool)

>>> res, d = categorical(np.array(labels), drop=True, dictnames=True)
>>> d
{0: 0, 1: 1, 2: 2, 3: 3}

初步基准测试(假设已经是NumPy数组)

您的数据集:

>>> %timeit categorical(labels, drop=True).astype(bool)
14.1 µs ± 519 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

更大的数据集:labels = np.random.randint(0, 4, 10000)
%timeit categorical(labels, drop=True).astype(bool)
360 µs ± 9.08 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

这似乎不能产生所需的输出格式,特别是如果标签数组中的数字不连续,例如:labels = [1,1,0,1,5,6,12,,12,4,5]。 - Merlin1896
@Merlin1896,能否请您概述一下在这种情况下输出格式有什么问题?对我来说,它看起来符合我的预期。 - miradulo
使用标签 = np.array([0,0,2,3,2,12]),执行 a=categorical(labels, drop=True).astype(bool) 后,输出结果并没有给出原始标签的参考。a[:,0] 是标签 0 的期望输出,但是 a[:,1] 是标签 2 的输出。 - Merlin1896
1
@Merlin1896 请看我的编辑,我意识到 category 函数有一个 dictnames 参数可以帮助你获取你想要的映射。如果你想要反向映射,只需使用 {v: k for k, v in d.items()} - miradulo

0
创建一个由 False 值组成的数组。通过使用 groupby 进行迭代,以获取标签的索引位置,并将这些位置设置为 True
d = {}
empty_labels = np.array([False] * len(labels))
for label, group in pd.DataFrame(labels, columns=['labels']).groupby('labels'):
    d[label] = empty_labels.copy()
    d[label][group] = True
>>> d
{0: array([ True, False, False, False, False, False, False, False, False,
        False, False, False], dtype=bool),
 1: array([False,  True, False, False, False, False, False, False, False,
        False, False, False], dtype=bool),
 2: array([False, False,  True, False, False, False, False, False, False,
        False, False, False], dtype=bool),
 3: array([False, False, False,  True, False, False, False, False, False,
        False, False, False], dtype=bool)}

速度应该与 pd.get_dummies 相当。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接