以下可重现的代码生成了一个示例数据集,该数据集以较小的规模模拟了我的数据。
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()
,但是在没有错误的情况下,它们都无法工作。
答案 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.compress
与map
一起使用,并将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的评论“而且您的范围/附加循环几乎一样好(如果不是更好的话)”是正确的。