【问题标题】:Select 1 True, 1 False per group in a pandas DataFrame在 pandas DataFrame 中每组选择 1 个 True,1 个 False
【发布时间】:2018-06-26 15:46:43
【问题描述】:

我有一个大约 100 万行的 DataFrame,以及大约 10 万个独特事件。有 1 列 Won 每个事件 1 行设置为 True,事件中的每一行都设置为 False。

即,

Event ID  Runner ID  Won
 E1        R1        True
 E1        R2        False
 E1        R3        False
 E2        R4        True
 E2        R5        False
 E2        R6        False

我想最终得到一个平衡的 DataFrame,每组只有 1 个获胜者,并且只有 1 个非获胜者。

即,

Event ID  Runner ID  Won
 E1        R1        True
 E1        R3        False
 E2        R4        True
 E2        R5        False

我不在乎每场比赛选出哪个非获胜者,只要有 1 名获胜者,1 名非获胜者即可。

对于 pandas,我尝试了一些方法,选择获胜者和非获胜者,

_won = df.Won
winners = df[_won]

non_winners = df[~_won]

但是我看到的每个过程,并尝试在每场比赛中选择 1 名非获胜者都非常缓慢 - 每场比赛只需几秒钟(当你有 10 万场比赛时,这在 IMO 是不合理的)。

groupapply 合一,

new_df = winners.append(
    non_winners
    .groupby('Event ID')
    .apply(lambda grp: grp.sample(1))

遍历 groupby,

for event_id, grp in non_winners.groupby('Event ID'):
    winners.append(grp.sample(1))

遍历获胜者的事件 ID,

event_ids = set(winners['Event ID'].drop_duplicates())
for event_id in event_ids:
    winners.append(
        non_winners[non_winners['Event ID'] == event_id].sample(1))

但在处理 ~100 万和 ~100k 事件时,每个选项似乎都非常慢。

【问题讨论】:

    标签: python pandas


    【解决方案1】:

    使用groupbyhead

    df.groupby(['Event ID', 'Won']).head(1)
    
      Event ID Runner ID    Won
    0       E1        R1   True
    1       E1        R2  False
    3       E2        R4   True
    4       E2        R5  False
    

    只要您对输出中保留的内容不挑剔,因为输出是平衡的。


    还有drop_duplicates

    df.drop_duplicates(subset=['Event ID', 'Won'], keep='last') 
    # or keep='first', it doesn't matter
    
      Event ID Runner ID    Won
    0       E1        R1   True
    2       E1        R3  False
    3       E2        R4   True
    5       E2        R6  False
    

    最后,如果你想实现洗牌,请事先致电sample

    (df.sample(frac=1)
       .sort_values(by=['Event ID'])
       .drop_duplicates(['Event ID', 'Won'])
    )
    
      Event ID Runner ID    Won
    2       E1        R3  False
    0       E1        R1   True
    4       E2        R5  False
    3       E2        R4   True
    

    【讨论】:

    • 完美!非常感谢您提供多种选择。我实际上使用了 drop_duplicates 的一种变体,但无论出于何种原因,我一定是被自己的脚绊倒了/感到困惑,并且没有继续下去。
    【解决方案2】:

    我想出的一个让我对速度感到满意的选项是在非获胜行上使用随机播放和复制的组合,

    _won = df.Won
    winners = df[_won]
    _non_winners = shuffle(df[~_won])
    
    non_winners = _non_winners[~_non_winners.duplicated('Event ID')]
    
    new_df = winners.append(non_winners)
    

    在其他选项中,每组需要花费大约一秒钟的时间,这是不可持续的,因为它在这么多的组中运行,而据我所知,这个解决方案给出了完全相同的结果,但在10秒左右,不知道要多久。

    如果您希望每个分组超过 1 个,这很繁琐但可能,您只需再次进行基本相同的操作,

    ...
    _non_winners = shuffle(df[~_won])
    
    dupeys = _non_winners.duplicated('Event ID')
    new_df = pandas.concat([
        winners,
        _non_winners[~dupeys]])
    
    _non_winners = _non_winners[dupeys]
    new_df = pandas.concat([
        new_df,
        _non_winners[~_non_winners.duplicated('race_event_id')]])
    

    【讨论】:

      猜你喜欢
      • 2013-06-27
      • 1970-01-01
      • 2011-09-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多