当一个列表的所有元素都在另一个列表中时,如何分组和求和

时间:2019-04-16 08:01:08

标签: python python-3.x pandas

我有一个数据帧df1。 “交易”列具有一个整数数组。

id     transactions
1      [1,2,3]
2      [2,3]

数据帧df2。 “ items”列具有一个int数组。

items  cost
[1,2]  2.0
[2]    1.0
[2,4]  4.0

如果需要汇总费用,我需要检查项目的所有元素是否都在每次交易中。

预期结果

id    transaction score
 1      [1,2,3]     3.0
 2      [2,3]       1.0

我做了以下

#cross join
-----------
def cartesian_product_simplified(left, right):
   la, lb = len(left), len(right)
   ia2, ib2 = np.broadcast_arrays(*np.ogrid[:la,:lb])

    return pd.DataFrame(
    np.column_stack([left.values[ia2.ravel()], 
     right.values[ib2.ravel()]]))

out=cartesian_product_simplified(df1,df2) 

#column names assigning        
out.columns=['id', 'transactions', 'cost', 'items']

#converting panda series to list
t=out["transactions"].tolist()
item=out["items"].tolist()


#check list present in another list
-------------------------------------
def check(trans,itm):
out_list=list() 
for row in trans:
   ret =np.all(np.in1d(itm, row))
   out_list.append(ret)
return out_list

if true: group and sum
-----------------------
a=check(t,item)
for i in a:
  if(i):
   print(out.groupby(['id','transactions']))['cost'].sum()      
  else:
   print("no")

引发TypeError:'NoneType'对象不可下标。

我是python的新手,不知道如何将所有这些放在一起。当一个列表的所有项目都在另一列表中时,如何对成本进行分组和求和?

2 个答案:

答案 0 :(得分:3)

最简单的方法就是检查所有交易的所有项目:

# df1 and df2 are initialized

def sum_score(transaction):
    score = 0
    for _, row in df2.iterrows():
        if all(item in transaction for item in row["items"]):
            score += row["cost"]
    return score

df1["score"] = df1["transactions"].map(sum_score)

大规模运行将非常缓慢。如果这是一个问题,我们不需要遍历每个项目,而只能预选可能的项目。如果您有足够的内存,可以这样做。对于每个项目,我们都记住它出现在df2中的所有行号。因此,对于每笔交易,我们都会得到项目,获得所有可能的行并仅检查它们。

import collections

# df1 and df2 are initialized

def get_sum_score_precalculated_func(items_cost_df):

    # create a dict of possible indexes to search for an item
    items_search_dict = collections.default_dict(set)
    for i, (_, row) in enumerate(items_cost_df.iterrow()):
        for item in row["items"]:
            items_search_dict[item].add(i)

    def sum_score(transaction):
        possible_indexes = set()
        for i in transaction:
            possible_indexes += items_search_dict[i]

        score = 0
        for i in possible_indexes:
            row = items_cost_df.iloc[i]
            if all(item in transaction for item in row["items"]):
                score += row["cost"]
        return score

    return sum_score

df1["score"] = df1["transactions"].map(get_sum_score_precalculated_func(df2))

我在这里使用 set,这是唯一值的无序存储(它有助于连接可能的行号并避免重复计数)。 collections.defaultdict,这是通常的dict,但是如果您尝试访问未初始化的值,则会使用给定的数据填充它(在我的情况下为空白set)。有助于避免if x not in my_dict: my_dict[x] = set()。我也使用所谓的“关闭”,这意味着sum_score函数将可以访问items_cost_df函数声明后的级别上可以访问的items_search_dictsum_score已返回,并且get_sum_score_precalculated_func

万一这些项目非常独特并且只能在几行df2中找到,那应该快得多。

如果您有很多独特商品,并且有很多相同的交易,则最好先计算每笔独特交易的得分。然后只需加入结果即可。

transactions_score = []
for transaction in df1["transactions"].unique():
    score = sum_score(transaction)
    transaction_score.append([transaction, score])
transaction_score = pd.DataFrame(
    transaction_score,
    columns=["transactions", "score"])
df1 = df1.merge(transaction_score, on="transactions", how="left")

在这里,我使用第一个代码示例中的sum_score

P.S。对于python错误消息,应该有一个行号,这对理解问题有很大帮助。

答案 1 :(得分:1)

# convert df_1 to dictionary for iteration
df_1_dict = dict(zip(df_1["id"], df_1["transactions"]))
# convert df_2 to list for iteration as there is no unique column
df_2_list = df_2.values.tolist()

# iterate through each combination to find a valid one
new_data = []
for rows in df_2_list:
    items = rows[0]
    costs = rows[1]
    for key, value in df_1_dict.items():
        # find common items in both
        common = set(value).intersection(set(items))
        # execute of common item exist in second dataframe 
        if len(common) == len(items):
            new_row = {"id": key, "transactions": value, "costs": costs}
            new_data.append(new_row)

merged_df = pd.DataFrame(new_data)
merged_df = merged_df[["id", "transactions", "costs"]]

# group the data by id to get total cost for each id
merged_df = (
    merged_df
    .groupby(["id"])
    .agg({"costs": "sum"})
    .reset_index()
)