当群组可能不在时,对多个DataFrame中的群组进行迭代

时间:2018-01-11 15:56:02

标签: python pandas dataframe

考虑以下问题。我有一个列出项目的DataFrame。每个项目都具有多值属性A和B,每个项目的值为零或更多。属性存储在自己的DataFrame实例中。对于每个项目,我想获得A和B的所有值的集合,并对这两个集合执行一些操作,包括任何集合为空的情况。

我的初始解决方案是按项目对属性进行分组,然后迭代项目,对于每个项目,使用get_group获取A和B的值,并对这些组执行操作。这适用于以下情况:

# A sample toy operation. One should assume that my_operation
# takes array-like arguments that can be Python lists, 
# pandas.Series etc.  
def my_operation(array_a, array_b):
    return len(array_a) - len(array_b)

def example1():
    items = pd.DataFrame({'Item': ['i1', 'i2', 'i3']})
    items_a = pd.DataFrame({'Item': ['i1', 'i1', 'i2', 'i3'], 'A': [1, 2, 3, 0]})
    items_b = pd.DataFrame({'Item': ['i1', 'i2', 'i2', 'i3'], 'B': [-1, 4, 5, 6]})

    grouped_a = items_a.groupby('Item')
    grouped_b = items_b.groupby('Item')
    for row in items.itertuples():
        a = grouped_a.get_group(row.Item)['A']
        b = grouped_b.get_group(row.Item)['B']
        print(row.Item, my_operation(a, b))

example1()        
打印预期的

i1 1
i2 -1
i3 0

但是,当某些项目的值为零时,这不起作用。例如,以下生成KeyError:

def my_operation(array_a, array_b):
    return len(array_a) - len(array_b)

def example2():
    items = pd.DataFrame({'Item': ['i1', 'i2', 'i3', 'i4']})
    items_a = pd.DataFrame({'Item': ['i1', 'i1', 'i2'], 'A': [1, 2, 3]})
    items_b = pd.DataFrame({'Item': ['i2', 'i2', 'i3'], 'B': [4, 5, 6]})

    grouped_a = items_a.groupby('Item')
    grouped_b = items_b.groupby('Item')
    for row in items.itertuples():
        a = grouped_a.get_group(row.Item)['A']
        b = grouped_b.get_group(row.Item)['B']
        print(row.Item, my_operation(a, b))

example2()

我可以通过在调用组存在的get_group之前进行检查来解决这个问题,如果没有,可以使用空的DataFrame,但它似乎相当沉重,可能表明我错过了一些更简单的解决方案。它可以是什么?

编辑:在实际问题中,集合上的操作是一个复杂的python代码,它不接受NaN值。两个集合都是空的情况相当普遍,必须妥善处理。

编辑2:上述玩具示例的预期答案应为

i1 2
i2 -1
i3 -1
i4 0

编辑3:我重写了示例代码以强调set操作是一个通用的Python函数,它接受表示值集的类似数组的对象。

2 个答案:

答案 0 :(得分:1)

您需要使用某个函数执行groupby以将集合作为apply()的参数

def my_grouping(df, label, on):
    return df.groupby(on)[label].apply(list)

为每个组生成列表。

def my_operation(array_a, array_b):
    try:
        len_a = len(array_a)
    except TypeError:
        len_a = 0

    try:
        len_b = len(array_b)
    except TypeError:
        len_b = 0
    return len_a - len_b

您的my_operation需要处理空项目

def aggregate(df1, df2, items, label1='A', label2='B', on='Item'):
    count1 = my_grouping(df1, label1, on)
    count2 = my_grouping(df2, label2, on)
#     print(count1, count2)
    merged = pd.DataFrame({label1: count1, label2: count2})
#     print(merged)
    result_data = ((idx, my_operation(a, b)) for idx, a, b in merged.itertuples())
    result_df = pd.DataFrame(result_data).rename(columns={0: 'Item', 1:'value'}).set_index('Item')
    return result_df.reindex(items).fillna(0).astype(int)

aggregate函数调用my_grouping,连接2个数组,在每个元素上调用my_operation,然后返回带有结果的DataFrame。最后一行添加了缺失的项目,并用0的

填充它们

可以这样调用:

def example2():
    all_items = ['i1', 'i2', 'i3', 'i4']
    items_a = pd.DataFrame({'Item': ['i1', 'i1', 'i2'], 'A': [1, 2, 3]})
    items_b = pd.DataFrame({'Item': ['i2', 'i2', 'i3'], 'B': [4, 5, 6]})
    return (aggregate(items_a, items_b, all_items))
print(example2())
Item    value
i1  2
i2  -1
i3  -1
i4  0

如果您的实际操作只是长度的差异,那么这个小函数执行完全相同的工作,如果您愿意,可以在一行中完成

def aggregate2(df1, df2, items, label1='A', label2='B', on='Item'):  
    count1 = df1.groupby(on)[label1].count()
    count2 = df2.groupby(on)[label2].count()
    diff = count1 - count2
    return result_df.reindex(items).fillna(0).astype(int)

答案 1 :(得分:0)

之前我没有使用过pandas,但是如果你想仔细检查items数据框中是否存在该项,那么在分组之前是否不能进行检查?

def example2():
    items = pd.DataFrame({'Item': ['i1', 'i2', 'i3', 'i4']})
    items_a = pd.DataFrame({'Item': ['i1', 'i1', 'i2'], 'A': [1, 2, 3]})
    items_b = pd.DataFrame({'Item': ['i2', 'i2', 'i3'], 'B': [4, 5, 6]})

    grouped_a = items_a.groupby('Item')
    grouped_b = items_b.groupby('Item')
    for row in items.itertuples():
        a = []  # so that len(a) returns 0 if get_group() doesn't trigger
        b = []  # so that len(b) returns 0 if get_group() doesn't trigger
        if row.Item in items_a['Item'].values:
            a = grouped_a.get_group(row.Item)
        if row.Item in items_b['Item'].values:
            b = grouped_b.get_group(row.Item)
        print(row.Item, len(a) - len(b))

这可能不是最优雅的解决方案,或者甚至可能是不受欢迎的练习(因为我基本上将a / b重新定义为不同的类型)。但这就是我得到的结果:

i1 2
i2 -1
i3 -1
i4 0

我不确定这是否是您的预期结果,但您没有在OP中提供。

编辑:一种替代方法,它采用相同的方式,但更简洁的是先定义一个函数:

def example2():
    items = pd.DataFrame({'Item': ['i1', 'i2', 'i3', 'i4']})
    items_a = pd.DataFrame({'Item': ['i1', 'i1', 'i2'], 'A': [1, 2, 3]})
    items_b = pd.DataFrame({'Item': ['i2', 'i2', 'i3'], 'B': [4, 5, 6]})

    item_key = 'Item'
    get_group = lambda item, df, key: df.groupby(key).get_group(item) if item in df[key].values else []
    for row in items.itertuples():
        a = get_group(row.Item, items_a, item_key)
        b = get_group(row.Item, items_b, item_key)
        print(row.Item, len(a) - len(b))

它占用较少的线条,但现在它的可读性较低。