【问题标题】:Pandas filter smallest by groupPandas 按组过滤最小
【发布时间】:2021-07-27 17:18:22
【问题描述】:

我有一个格式如下的数据框:

d = {'id1': ['a', 'a', 'b', 'b',], 'id2': ['a', 'b', 'b', 'c'], 'score': ['1', '2', '3', '4']}
    df = pd.DataFrame(data=d)


print(df)
     id1    id2    score
0     a      a       1        
1     a      b       2             
3     b      b       3        
4     b      c       4

数据框有超过 10 亿行,它表示 id1 和 id2 列中对象之间的成对距离分数。我不需要所有对象对组合,对于 id1 中的每个对象(大约有 40k 个唯一 ID),我只想保留前 100 个最接近(最小)的距离分数

我正在运行的代码如下:

df = df.groupby(['id1'])['score'].nsmallest(100)

这段代码的问题是我每次尝试运行时都会遇到内存错误

MemoryError: Unable to allocate 8.53 GiB for an array with shape (1144468900,) and data type float64

我假设这是因为在后台 pandas 现在正在为 group by 的结果创建一个新的数据框,但现有的数据框仍然保存在内存中。

我只取每个 id 的前 100 个的原因是为了减小数据框的大小,但我似乎在执行该过程时实际上占用了更多空间。

有什么方法可以过滤掉这些数据而不占用更多内存?

所需的输出将是这样的(假设前 1 名而不是前 100 名)

     id1    id2    score
0     a      a       1        
1     b      b       3            

关于原始 df 的一些附加信息:

df.count()
permid_1    1144468900
permid_2    1144468900
distance    1144468900
dtype: int64

df.dtypes
permid_1      int64
permid_2      int64
distance    float64

df.shape
dtype: object
(1144468900, 3)

id1 & id2 unique value counts: 33,830

【问题讨论】:

  • 如果您没有太多不同的id1s,您可以为每个唯一值使用循环,按该值过滤df并对其进行排序,并将前100个附加到一个新的df
  • 我们很清楚,score 不仅仅是id1 的最近邻居的排名顺序,对吗?这是一些正的实值距离度量吗?
  • 您能否打印df.count()df.dtypesdf.shape 以获得您拥有的全部数据,并将其添加到问题中。
  • @KyleParsons 是的,这是正确的,它是余弦相似度
  • @ThePyGuy 刚刚添加

标签: python pandas dataframe


【解决方案1】:

由于缺少您的数据,我无法测试此代码,但也许可以尝试以下方法:

indicies = []
for the_id in df['id1'].unique():
    scores = df['score'][df['id1'] == the_id]
    min_subindicies = np.argsort(scores.values)[:100]  # numpy is raw index only
    min_indicies = scores.iloc[min_subindicies].index  # convert to pandas indicies
    indicies.extend(min_indicies)

df = df.loc[indicies]

描述性地,在每个唯一 ID (the_id) 中提取匹配分数。然后找到最小的 100 的原始索引。选择这些索引,然后从原始索引映射到 Pandas 索引。将 Pandas 索引保存到您的列表中。然后在最后,熊猫索引的子集。

iloc 确实接受列表输入。 some_series.iloc 应该与 some_series.values 正确对齐,这应该允许它工作。像这样间接存储索引应该会大大提高内存效率。

df['score'][df['id1'] == the_id] 应该比df.loc[df['id1'] == the_id, 'score'] 更有效。它不是获取整个数据框并将其屏蔽,而是仅获取数据框的分数列并将其屏蔽以匹配 ID。如果您想立即释放更多内存,您可能需要在每个循环结束时del scores

【讨论】:

    【解决方案2】:

    您可以尝试以下方法:

    df.sort_values(["id1", "scores"], inplace=True)
    df["dummy_key"] = df["id1"].shift(100).ne(df["id1"])
    
    df = df.loc[df["dummy_key"]]
    
    1. 您按升序(最小的在顶部)排序,首先是分组,然后是分数。

    2. 您添加列以指示当前id1 是否与后面的 100 行不同(如果不是 - 您的行按顺序是 101+)。

    3. 您从 2 中按列过滤。

    【讨论】:

      【解决方案3】:

      正如 Aryerez 在评论中概述的那样,您可以执行以下操作:

      closest = pd.concat([df.loc[df['id1'] == id1].sort_values(by = 'score').head(100) for 
          id1 in set(df['id1'])])
      

      你也可以

      def get_hundredth(id1):
          sub_df = df.loc[df['id1'] == id1].sort_values(by = 'score')
          return sub_df.iloc[100]['score']
      
      hundredth_dict = {id1: get_hundredth(id1) for id1 in set(df['id1'])}
      
      def check_distance(row):
          return row['score'] <= hundredth_dict[row['id1']]
      
      closest = df.loc[df.apply(check_distance, axis = 1)
      

      另一种策略是查看过滤超过阈值的距离如何影响数据帧。也就是拿

         low_scores = df.loc[df['score']<threshold]
      

      对于某个合理的阈值,这是否会显着减小数据框的大小?您需要一个阈值,使数据框足够小以供使用,但每个id1 留下最低的 100 分。

      您可能还想研究在给定距离指标的情况下可以进行何种优化。可能有专门针对余弦相似度的算法。

      【讨论】:

      • 您确定第一个解决方案可以吗?我试过了,我得到一个语法错误
      • @MustardTiger 我很可能在某个地方搞砸了。什么错误?
      • closest = pd.concat([sub_df = df.loc[df['id1'] == id1].sort_values(by = 'score').head(100) for id1 in set( df['id1'])]) 它在 sub_df 后面的 = 符号后表示无效语法
      • @MustardTiger 是的,我最初分配给sub_df,但后来我改为列表理解。感谢您指出。希望我的编辑能解决它。
      【解决方案4】:

      对于给定形状 (1144468900, 3)33,830 唯一值计数,id1id2 列是分类列的良好候选者,将它们转换为分类数据类型,这将减少 @987654325 的内存需求@ 时间大约为这两列,然后执行您想要的任何聚合。

      df[['id1', 'id2']] = df[['id1', 'id2']].astype('category')
      out = df.groupby(['id1'])['score'].nsmallest(100)
      

      【讨论】:

        猜你喜欢
        • 2013-11-30
        • 1970-01-01
        • 2019-04-02
        • 1970-01-01
        • 2017-08-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-09-13
        相关资源
        最近更新 更多