考虑以下问题。我有一个列出项目的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函数,它接受表示值集的类似数组的对象。
答案 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))
它占用较少的线条,但现在它的可读性较低。