【问题标题】:Filter simultaneously by different values of rows Pandas通过不同的行值同时过滤 Pandas
【发布时间】:2018-12-06 06:52:14
【问题描述】:

我有一个带有 product_id 及其 property_id 的巨大数据框。请注意,每个属性都以新索引开头。我需要同时过滤每个product_id 的不同property_id 值。有什么办法可以快速做到吗?

out_df

product_id  property_id
0   3588    1
1   3588    2
2   3588    5
3   3589    1
4   3589    3
5   3589    5
6   3590    1
7   3590    2
8   3590    5

例如,希望通过分配在不同行(如 out_df.loc[(out_df['property_id'] == 1) & (out_df['property_id'] == 2)] 但不是它)的两个属性来过滤每个 product_id)。 我需要类似的东西,但同时为每个 product_id 列的所有行工作。

我知道可以通过groupby 进入列表

3587    [2, 1, 5]
3588    [1, 3, 5]
3590    [1, 2, 5]

并在列表中查找交叉点。

gp_df.apply(lambda r: {1, 2} < (set(r['property_id'])), axis=1)  

但这需要时间,同时 Pandas 的通用过滤在速度上得到了极大的优化(相信在 ElasticSearch、Sphinx 等搜索引擎中使用一些棘手的右索引和逆索引)。

预期输出:{1 和 2} 都有。

3587    [2, 1, 5]
3590    [1, 2, 5]

【问题讨论】:

  • 预期输出是什么?
  • 预期输出是列表?还是像我的回答一样的 DataFrame?

标签: python-3.x performance pandas pandas-groupby


【解决方案1】:

由于这既是一个功能问题,也是一个性能问题,我会采用这样的交叉方法:

df = pd.DataFrame({'product_id': [3588, 3588, 3588, 3589, 3589, 3589, 3590, 3590,3590], 
                   'property_id': [1, 2, 5, 1, 3, 5, 1, 2, 5]})

df = df.set_index(['property_id'])

print("The full DataFrame:")
print(df)

start = time()

for i in range(1000):
    s1 = df.loc[(1), 'product_id']
    s2 = df.loc[(2), 'product_id']

    s_done = pd.Series(list(set(s1).intersection(set(s2))))

print("Overlapping product_id's")
print(time()-start)

在我的 ThinkPad T450 上迭代查找 1000 次需要 0.93 秒。我冒昧地测试了@jezrael 的两个建议,它们分别在 2.11 秒和 2.00 秒出现,groupby 方法在软件工程方面更加优雅。

根据数据集的大小和性能的重要性,您还可以切换到更简单的数据类型,例如经典字典并获得进一步的速度。


Jupyter Notebook 可以在这里找到:pandas_fast_lookup_using_intersection.ipynb

【讨论】:

    【解决方案2】:

    最简单的方法是将GroupBy.transform 与比较集一起使用:

    s = {1, 2}
    a = df[df.groupby('product_id')['property_id'].transform(lambda r: s < set(r))]
    print (a)
       product_id  property_id
    0        3588            1
    1        3588            2
    2        3588            5
    6        3590            1
    7        3590            2
    8        3590            5
    

    另一种解决方案是仅过滤集合的值,首先删除重复项:

    df1 = df[df['property_id'].isin(s) & ~df.duplicated(['product_id', 'property_id'])]
    

    然后有必要检查每个组的长度是否与this solution的集合长度相同:

    f, u = df1['product_id'].factorize()
    ids = df1.loc[np.bincount(f)[f] == len(s), 'product_id'].unique()
    

    最后按条件过滤所有带有product_id的行:

    a = df[df['product_id'].isin(ids)]
    print (a)
       product_id  property_id
    0        3588            1
    1        3588            2
    2        3588            5
    6        3590            1
    7        3590            2
    8        3590            5
    

    【讨论】:

      【解决方案3】:

      你的意思是这样的吗?

      result = out_df.loc[out_df['property_id'].isin([1,2]), :]
      

      如果您愿意,您可以根据 product_id 删除重复项...

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-03-08
        • 2019-06-19
        • 1970-01-01
        • 2016-09-11
        • 1970-01-01
        • 1970-01-01
        • 2022-01-25
        • 2021-07-22
        相关资源
        最近更新 更多