熊猫计数列表列中包含的列表的出现

Jur*_*rgy 5 python vectorization pandas

我有这个Pandas DataFrame,其中有一个带有列表的列:

>>> df = pd.DataFrame({'m': [[1,2,3], [5,3,2], [2,5], [3,8,1], [9], [2,6,3]]})
>>> df
           m
0  [1, 2, 3]
1  [5, 3, 2]
2     [2, 5]
3  [3, 8, 1]
4        [9]
5  [2, 6, 3]
Run Code Online (Sandbox Code Playgroud)

我想计算一个列表v = [2, 3]包含在DataFrame列表中的次数。因此,在此示例中,正确答案为3。现在这只是一个例子,在我的实际数据中,df['m']可以包含超过900万行,而列表实际上是最多包含20个元素的字符串列表。如果需要,请提供更多详细信息:的元素不v包含重复项,的列表也不包含m,因此可以设置它们而不是列表。

我的程序的第一次迭代遍历每一行并检查all(e in data['m'][i] for e in v),如果是True,则增加一个计数器。但是,正如许多SO问题和博客文章所述,在DataFrame的行上进行迭代很慢,并且可以更快地完成。

因此,对于下一次迭代,我在DataFrame中添加了一个列,该列包含列表的副本v:

>>> df['V'] = [[2, 3]] * len(df)
>>> df
        V          m
0  [2, 3]  [1, 2, 3]
1  [2, 3]  [5, 3, 2]
2  [2, 3]     [2, 5]
3  [2, 3]  [3, 8, 1]
4  [2, 3]        [9]
5  [2, 3]  [2, 6, 3]
Run Code Online (Sandbox Code Playgroud)

和一个辅助函数,它像我之前所做的那样简单地返回包含布尔值:

def all_helper(l1, l2):
    return all(v in l1 for v in l2)
Run Code Online (Sandbox Code Playgroud)

然后可以np.vectorize用来添加具有布尔值的列:

df['bool'] = np.vectorize(all_helper)(df['m'], df['V'])
Run Code Online (Sandbox Code Playgroud)

最后,用一个简单的算式计算这些布尔值的总和 df['bool'].sum()

我也尝试使用.apply():

df['bool'] = df.apply(lambda row: all(w in row['m'] for w in v), axis=1)
count = df['bool'].sum()
Run Code Online (Sandbox Code Playgroud)

但这比矢量化要慢。

现在这些方法都可以使用,矢量化比初始方法快得多,但是感觉有点笨拙(使用辅助函数以这种方式创建具有相同值的列)。所以我的问题是性能是关键,是否有更好/更快的方法来计算列表包含在列表列中的次数?由于列表中没有重复项,因此可能需要检查是否有问题len(union(df['m'], df['V'])) == len(df['m']),但是我不知道如何以及是否是最好的解决方案。

编辑:由于有人问;这是一个使用字符串而不是整数的示例:

>>> df = pd.DataFrame({'m': [["aa","ab","ac"], ["aa","ac","ad"], ["ba","bb"], ["ac","ca","cc"], ["aa"], ["ac","da","aa"]]})
>>> v = ["aa", "ac"]
>>> df
                    m
0  ["aa", "ab", "ac"]
1  ["aa", "ac", "ad"]
2        ["ba", "bb"]
3  ["ac", "ca", "cc"]
4              ["aa"]
5  ["ac", "da", "aa"]

>>> count_occurrence(df, v)
3
Run Code Online (Sandbox Code Playgroud)

但是,如果您需要更广泛的DataFrame,可以使用以下代码生成它:

import string

n = 10000
df = pd.DataFrame({'m': [list(set([''.join(np.random.choice(list(string.ascii_lowercase)[:5], np.random.randint(3, 4))) for _ in range(np.random.randint(1, 10))])) for _ in range(n)]})
v = ["abc", 'cde']
print(count_occurrence(df, v))
Run Code Online (Sandbox Code Playgroud)

编辑:Divakar或Vaishali的解决方案都没有比使用的解决方案快np.vectorize。想知道是否有人可以击败它。

乔恩克莱门茨想出了一个解决方案,大约30%的速度更快,更清洁:df.m.apply(set(v).issubset).sum()。我一直在寻找更快的实现方式,但这是朝正确方向迈出的一步。

Jon*_*nts 5

您可以DataFrame.apply与内置set.issubset方法一起使用,然后.sum()它们都在比 Python 等价物更低的级别(通常是 C 级别)运行。

subset_wanted = {2, 3}
count = df.m.apply(subset_wanted.issubset).sum()
Run Code Online (Sandbox Code Playgroud)

我看不出比编写自定义 C 级函数节省更多的时间,该函数相当于sum带有检查的自定义,其中有一个子集可以逐行确定 0/1。在这一点上,无论如何你都可以运行成千上万次。