基于成对数值,删除pandas数据帧中的行

4
我有如下数据框:
df = pd.DataFrame({'User':['a','a','a','b','b','b'],
                 'Type':['101','102','101','101','101','102'],
                 'Qty':[10, -10, 10, 30, 5, -5]})

我想要删除 df['Type'] = 101 和 102 的成对值,其中 df['Qty'] 相互抵消。最终结果如下:
df = pd.DataFrame({'User':['a','b'],
                     'Type':['101', '101'],
                     'Qty':[10, 30})

我尝试将负数转换为绝对值并删除重复项,代码如下:

df['Qty'] = df['Qty'].abs()
df.drop_duplicates(subset=['Qty'], keep='first')

但它错误地给我了这样的数据框:

df = pd.DataFrame({'User':['a','b', 'b'],
                     'Type':['101', '101', '101'],
                     'Qty':[10, 30, 5})

1
你需要按用户删除配对吗?还有只有101102两种类型吗?并且需要测试101102类型吗? - jezrael
如果您想删除所有重复项,可以使用keep=False而不是'first''last' - Tanzin Farhat
@jezrael 是的,我需要按用户删除成对数据。实际上,102总是会抵消101,因为102是101的反向项目。只会有101和102。我不明白你最后一个问题的意思。 - rain123
@TanzinFarhat 我尝试过了,但是我得到的是df = pd.DataFrame({'User':['b'], 'Type':[ '101'], 'Qty':[30}),而不是我真正想要的。 - rain123
3个回答

3

Idea是创建每个组的索引值的组合,并测试每个子组是否包含两种Type并且和为0,以便设置这些匹配对:

#solution need unique index values
df = df.reset_index(drop=True)

from  itertools import combinations
    
out = set()
def f(x):
    for i in combinations(x.index, 2):
        a = x.loc[list(i)]
        if (set(a['Type']) == set(['101','102'])) and (a['Qty'].sum() == 0):
           out.add(i)

df.groupby('User').apply(f)

print (out)
{(0, 1), (4, 5), (1, 2)}

然后删除所有成对的副本,例如这里的(1,2)

s = pd.Series(list(out)).explode()
idx = s.index[s.duplicated()]
final = s.drop(idx)
print (final)
0    0
0    1
1    4
1    5
dtype: object

最后从原始数据中删除行:

df = df.drop(final)
print (df)
  User Type  Qty
2    a  101   10
3    b  101   30

2

如果只有两个“Type”(在这种情况下是101102,那么您可以编写以下自定义函数:

  • 使用包含'Qty'的绝对值的键构建一个字典。
  • 字典的值包含与'Qty'相对应的'Type'值的列表。
from collections import defaultdict
def f(x):
    new = defaultdict(list)
    for k,v in x[['Type', 'Qty']].itertuples(index=None,name=None):
        if not new[abs(v)]:
            new[abs(v)].append(k)
        elif new[abs(v)][-1] !=k:
            new[abs(v)].pop()
        else:
            new[abs(v)].append(k)
    return pd.Series(new,name='Qty').rename_axis(index='Type')

逻辑很简单:
  • 每当遇到一个新的键时,将其对应的'Type'添加到列表中。
  • 如果已经存在该键,则检查上一个值即先前添加的'Type'是否等于当前'Type'值。例如,如果 new = {10:['101']},并且当前键为'102',则删除'101'。因此,new = {10:[]}
  • 如果它的键已经存在,且上一个'Type'和当前'Type'匹配,则简单地将当前'Type'附加到列表中。例如,如果 new = {10:['101']},并且当前'Type''101',则将其附加到new中。因此,new = {10:['101', '101']}
df.groupby('User').apply(f).explode().dropna().reset_index()

  User  Type  Qty
0    a    10  101
1    b    30  101

2

迭代所有记录并将匹配保存在列表中,确保没有一个索引被重复配对似乎在这里起作用。


import pandas as pd

df = pd.DataFrame({'User':['a','a','a','b','b','b'],
                 'Type':['101','102','101','101','101','102'],
                 'Qty':[10, -10, 10, 30, 5, -5]})



# create a list to collect all indices that we are going to remove
records_to_remove = []
# a dictionary to map which group mirrors the other
pair = {'101': '102', '102':'101'}

# let's go over each row one by one,
for i in df.index:
    current_record = df.iloc[i]
    # if we haven't stored this index already for removal
    if i not in records_to_remove:
        pair_type = pair[current_record['Type']]
        pair_quantity = -1*current_record['Qty']
        # search for all possible matches to this row
        match_records = df[(df['Type']==pair_type) & (df['Qty']==pair_quantity)]
        if match_records.empty:
            # if no matches fond move on to the next row
            continue
        else:
            # if a match is found, take the first of such records
            first_match_index = match_records.index[0]
            if first_match_index not in records_to_remove:
                # store the indices in the list to remove only if they're not already present
                records_to_remove.append(i)
                records_to_remove.append(first_match_index)
                
df = df.drop(records_to_remove)

输出:

   User Type  Qty
2     a  101   10
3     b  101   30

看看这个对你是否有效!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接