pandas系列中的矢量化索引numpy数组与pandas系列中的布尔numpy数组

时间:2018-08-12 22:24:52

标签: python pandas numpy

以下可重现的代码生成了一个示例数据集,该数据集以较小的规模模拟了我的数据。

import numpy as np 
import pandas as pd

np.random.seed(142536)

df = pd.DataFrame({
        "vals": list(np.arange(12).reshape(3,4)),
        "idx" : list(np.random.choice([True, False], 12).reshape(3,4))})
df

                           idx            vals
0   [False, True, True, False]    [0, 1, 2, 3]
1    [True, True, False, True]    [4, 5, 6, 7] 
2  [False, True, False, False]  [8, 9, 10, 11] 

以下可重现的代码返回我想要的结果,但是对于大型数据集而言效率很低。
我将如何更有效地做到这一点?

sel = []
for i in range(len(df.vals)):
    sel.append(df.vals[i][df.idx[i]])

df['sel'] = sel
df

                           idx            vals        sel
0   [False, True, True, False]    [0, 1, 2, 3]     [1, 2]
1    [True, True, False, True]    [4, 5, 6, 7]  [4, 5, 7]
2  [False, True, False, False]  [8, 9, 10, 11]        [9]

我已经尝试过np.apply_along_axis()np.where()df.apply()df.transform(),但是在没有错误的情况下,它们都无法工作。

6 个答案:

答案 0 :(得分:3)

前提很糟糕,因为您不应该这样存储数据。您至少可以通过以下方法来加快速度:将数据与itertools.chain合并,建立索引,然后使用np.array_split拆分结果。

from itertools import chain

fn = lambda x: np.array(list(chain.from_iterable(x)))
df['sel'] = np.array_split(
    fn(df.vals)[fn(df.idx)], np.cumsum([sum(x) for x in df.idx][:-1]))

                           idx            vals      sel
0   [True, False, True, False]    [0, 1, 2, 3]   [0, 2]
1  [False, False, False, True]    [4, 5, 6, 7]      [7]
2   [False, True, True, False]  [8, 9, 10, 11]  [9, 10]

答案 1 :(得分:2)

使用列表理解和numpy索引:

SELECT  Table_Users.UserID , Stories.StoryID, Images.imgID
FROM  dbo.Images
  INNER JOIN dbo.imagesInStories ON Images.imgID = imagesInStories.imgID3
    INNER JOIN dbo.Stories ON imagesInStories.StoryID3 = Stories.StoryID
      INNER JOIN dbo.Users_Stories ON Stories.StoryID = Users_Stories.StoryID2 
        INNER JOIN dbo.Table_Users ON Users_Stories.ID2 = Table_Users.UserID

df.assign(sel=[x[y] for x, y in zip(df.vals, df.idx)])

答案 2 :(得分:2)

如果这是df

             vals                         idx
0    [0, 1, 2, 3]   [True, False, True, True]
1    [4, 5, 6, 7]  [False, True, False, True]
2  [8, 9, 10, 11]   [True, True, True, False]

那么您的sel是:

In [21]: sel
Out[21]: [array([0, 2, 3]), array([5, 7]), array([ 8,  9, 10])]

这是大小不同的数组的列表。

df列作为数组是:

In [7]: vals = df['vals'].values
In [8]: idx = df['idx'].values

都是数组的对象数组。但是我们可以使用stack(或vstack)将它们转换为2d数组:

In [23]: vals = np.stack(vals)
In [24]: idx = np.stack(idx)
In [25]: vals
Out[25]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
In [26]: idx
Out[26]: 
array([[ True, False,  True,  True],
       [False,  True, False,  True],
       [ True,  True,  True, False]])

我们可以简单地用布尔掩码进行索引-但结果是一维数组:

In [27]: vals[idx]
Out[27]: array([ 0,  2,  3,  5,  7,  8,  9, 10])
where上的

idx产生等效的索引数组元组:

In [28]: np.where(idx)
Out[28]: (array([0, 0, 0, 1, 1, 2, 2, 2]), array([0, 2, 3, 1, 3, 0, 1, 2]))

我们还可以从这些数组中生成一个蒙版数组:

In [34]: mvals = np.ma.MaskedArray(vals, ~idx)
In [35]: mvals
Out[35]: 
masked_array(
  data=[[0, --, 2, 3],
        [--, 5, --, 7],
        [8, 9, 10, --]],
  mask=[[False,  True, False, False],
        [ True, False,  True, False],
        [False, False, False,  True]],
  fill_value=999999)
In [36]: mvals.compressed()
Out[36]: array([ 0,  2,  3,  5,  7,  8,  9, 10])

但是要逐行获取值,我们必须进行某种迭代:

In [37]: [row[i] for row,i in zip(vals, idx)]
Out[37]: [array([0, 2, 3]), array([5, 7]), array([ 8,  9, 10])]

为此,In[7]In[8]的对象数组与堆叠的2d数组一样好,甚至没有。

In [40]: [row[i] for row,i in zip(df['vals'], df['idx'])]
Out[40]: [array([0, 2, 3]), array([5, 7]), array([ 8,  9, 10])]

您的range/append循环几乎一样好(如果不是更好的话)。

sel数组的大小不同(或者至少在理论上可以变化),这很好地表明了“矢量化”,整个数组的操作是不可能的。但是您需要这样的清单吗?如果不能通过快速数组操作生成它,则也不能将其与一个数组一起使用。在创建和使用时,都必须在“行”上进行迭代。

答案 3 :(得分:1)

您不应该真正使用Pandas系列存储列表。但是,如果不可避免,则可以将itertools.compressmap一起使用,并将df['vals']df['idx']作为单独的参数输入:

from itertools import compress

df['sel'] = list(map(list, map(compress, df['vals'], df['idx'])))

print(df)

             vals                         idx        sel
0    [0, 1, 2, 3]   [False, True, True, True]  [1, 2, 3]
1    [4, 5, 6, 7]   [False, True, True, True]  [5, 6, 7]
2  [8, 9, 10, 11]  [True, False, False, True]    [8, 11]

如果您的df['vals']系列确实是NumPy数组,则可以使用NumPy索引:

df['sel'] = [vals[idx] for vals, idx in zip(df['vals'], df['idx'])]

答案 4 :(得分:0)

如果您稍微将其解压缩到一个函数中,应用程序应该可以正常工作。至于速度的提高,请报告您的用例/数据,因为一遍又一遍调用该函数可能会非常昂贵:

def return_indices(row):
    row_vals = row['vals']
    row_idx = row['idx']
    true_rows = np.where(row_idx == True)
    return list(row_vals[true_rows])

df['sel'] = df.apply(lambda x: return_indices(x), axis=1)

答案 5 :(得分:0)

谢谢大家的回答。

下面是我的想法。我尚未将计时与其他解决方案进行比较。

tmp = np.where(
        np.concatenate(df.idx.values).reshape(df.idx.values.shape[0],df.idx[0].shape[0] ), 
        np.concatenate(df.vals.values).reshape(df.vals.values.shape[0],df.vals[0].shape[0] ),
        np.nan)

df['sel'] = [*map(lambda a: [x for x in a if ~np.isnan(x)], tmp)]

df

                           idx            vals              sel
0   [False, True, True, False]    [0, 1, 2, 3]       [1.0, 2.0]
1    [True, True, False, True]    [4, 5, 6, 7]  [4.0, 5.0, 7.0]
2  [False, True, False, False]  [8, 9, 10, 11]            [9.0]

我认为这比我在OP中首次提出的for循环要好(尽管我尚未测试),因为应该将此lambda函数并行映射(应用)到tmp np.array并且不需要跟踪i的内部状态。除非那是python对于for循环所做的事情。

编辑:

原始帖子中的for循环 明显 更快。我没有确切的时间安排,但是对于我的大数据集,此答案中的map函数需要几分钟才能完成,而OP中的for循环则需要几秒钟。

@hpaulj的评论“而且您的范围/附加循环几乎一样好(如果不是更好的话)”是正确的。