Я пытаюсь вычислить следующий столбец «new_field» тройным циклом по столбцам «name», «val_id» и «fac_id» со следующими условиями.
1. В каждом цикле 'val_id', если 'product' == 'CL', то минимум из 'val_against' и 'our_val_amt', например. min( val_against (134), our_val_amt (424)) поэтому 'NEW FIELD' = 134. Также, если сумма new_field превышает "our_val_amt", вычтите ее из "our_val_amt". например для val_id "xx4", (200 + 300 + 50) = 550, что превышает our_val_amt = 510, поэтому NEW FILED = 510 - 500 (т.е. 200 + 300 после того, как эта сумма превысит our_val_amt) = 10.
2. Если продукт != 'CL' и находится в той же группе 'val_id'. Остаток нужно вычесть из 'our_val_amt' и вставить в 'new_field'. например, 'our_val_amt' (424) - из шага 1 (134) = 290. Это вставлено над 'NEW FIELD'.
Если [продукт] не имеет «CL», ему просто нужно распределить [our_val_amt] между каждым [val_id]. Например, val_id = 'xx7' our_val_amt = 700, это распространяется в первой строке (650), а затем остается 700 - 650 = 50, вставляется в следующую строку со следующим значением 0, как в примере.
3.Повторите шаги для val_id xx2. Расчет NEW FIELD для CL = 104 и XL = 472 - 104 = 368.
В настоящее время вывод работает правильно для «имя» - compx (строка 0–9) и начинает неправильно рассчитываться дальше. Я также не уверен, как работает этот код, поскольку я новичок в Pandas, и буду признателен, если кто-то может объяснить определенную функцию, как думает программа.
df = pd.DataFrame(data=[["compx","xx1","yy1",424,418,"XL"],["compx","xx1","yy2",424,134,"CL"],["compx","xx2","yy3",472,60,"DL"],["compx","xx2","yy4",472,104,"CL"], ["compx", "xx3", "yy5", 490, 50, "XL"], ["compx", "xx3", "yy6", 490, 500, "CL"], ["compx", "xx3", "yy7", 490, 200, "DL"], ["compx", "xx4", "yy8", 510, 200, "CL"], ["compx", "xx4", "yy9", 510, 300, "CL"], ["compx", "xx4", "yy10", 510, 50, "CL"], ["compy", "xx5", "yy11", 510, 200, "CL"], ["compy", "xx5", "yy12", 510, 300, "CL"], ["compy", "xx5", "yy12", 510, 50, "CL"], ["compy", "xx5", "yy13", 510, 30, "DL"], ["compz", "xx6", "yy14", 350, 200, "CL"], ["compz", "xx6", "yy15", 350, 100, "CL"], ["compz", "xx6", "yy16", 350, 50, "XL"], ["compz", "xx6", "yy17", 350, 50, "DL"], ["compz", "xx7", "yy18", 700, 650, "DL"], ["compz", "xx7", "yy19", 700, 200, "DL"], ["compz", "xx7", "yy20", 700, 400, "XL"] ], columns=["name","val_id","fac_id","our_val_amt","val_against","product"])
df
# Compute tuple of "our_val_amt", "val_against" and "product" for easy processing as one column. It is hard to process multiple columns with "transform()".
df["the_tuple"] = df[["our_val_amt", "val_against", "product"]].apply(tuple, axis=1)
def compute_new_field_for_cl(g):
# df_g is a tuple ("our_val_amt", "val_against", "product") indexed as (0, 1, 2).
df_g = g.apply(pd.Series)
df_g["new_field"] = df_g.apply(lambda row: min(row[0], row[1]) if row[2] == "CL" else 0, axis=1)
df_g["cumsum"] = df_g["new_field"].cumsum()
df_g["new_field"] = df_g.apply(lambda row: 0 if row["cumsum"] > row[0] else row["new_field"], axis=1)
df_g["max_cumsum"] = df_g["new_field"].cumsum()
df_g["new_field"] = df_g.apply(lambda row: row[0] - row["max_cumsum"] if row["cumsum"] > row[0] else row["new_field"], axis=1)
return df_g["new_field"]
# Apply above function and compute new field values for "CL".
df["new_field"] = df.groupby("val_id")[["the_tuple"]].transform(compute_new_field_for_cl)
# Re-compute tuple of "our_val_amt", "new_field" and "product".
df["the_tuple"] = df[["our_val_amt", "new_field", "product"]].apply(tuple, axis=1)
def compute_new_field_for_not_cl(g):
# df_g is a tuple ("our_val_amt", "new_field", "product") indexed as (0, 1, 2).
df_g = g.apply(pd.Series)
result_sr = df_g.where(df_g[2] != "CL")[0] - df_g[df_g[2] == "CL"][1].sum()
result_sr = result_sr.fillna(0) + df_g[1]
return result_sr
# Apply above function and compute new field values for "CL".
df["new_field"] = df.groupby("val_id")[["the_tuple"]].transform(compute_new_field_for_not_cl)
df = df.drop("the_tuple", axis=1)
df
Попытка достижения набора данных и вывода new_field.
name |val_id |fac_id | our_val_amt | val_against | product | new_field
compx | xx1 | yy1 | 424 | 418 | XL | 290
compx | xx1 | yy2 | 424 | 134 | CL | 134
compx | xx2 | yy3 | 472 | 60 | DL | 368
compx | xx2 | yy4 | 472 | 104 | CL | 104
compx | xx3 | yy5 | 490 | 50 | XL | 0
compx | xx3 | yy6 | 490 | 500 | CL | 490
compx | xx3 | yy7 | 490 | 200 | DL | 0
compx | xx4 | yy8 | 510 | 200 | CL | 200
compx | xx4 | yy9 | 510 | 300 | CL | 300
compx | xx4 | yy10 | 510 | 50 | CL | 10
compy | xx5 | yy11 | 510 | 200 | CL | 200
compy | xx5 | yy12 | 510 | 300 | CL | 300
compy | xx5 | yy12 | 510 | 50 | CL | 10
compy | xx5 | yy13 | 510 | 30 | DL | 0
compz | xx6 | yy14 | 350 | 200 | CL | 200
compz | xx6 | yy15 | 350 | 100 | CL | 100
compz | xx6 | yy16 | 350 | 50 | XL | 50
compz | xx6 | yy17 | 350 | 50 | DL | 0
compz | xx7 | yy18 | 700 | 650 | DL | 650
compz | xx7 | yy19 | 700 | 200 | DL | 50
compz | xx7 | yy20 | 700 | 400 | XL | 0
Набор данных и вывод new_field, который я сейчас получаю
name |val_id |fac_id | our_val_amt | val_against | product | new_field
compx | xx1 | yy1 | 424 | 418 | XL | 290
compx | xx1 | yy2 | 424 | 134 | CL | 134
compx | xx2 | yy3 | 472 | 60 | DL | 368
compx | xx2 | yy4 | 472 | 104 | CL | 104
compx | xx3 | yy5 | 490 | 50 | XL | 0
compx | xx3 | yy6 | 490 | 500 | CL | 490
compx | xx3 | yy7 | 490 | 200 | DL | 0
compx | xx4 | yy8 | 510 | 200 | CL | 200
compx | xx4 | yy9 | 510 | 300 | CL | 300
compx | xx4 | yy10 | 510 | 50 | CL | 10
compy | xx5 | yy11 | 510 | 200 | CL | 200
compy | xx5 | yy12 | 510 | 300 | CL | 300
compy | xx5 | yy12 | 510 | 50 | CL | 10
compy | xx5 | yy13 | 510 | 30 | DL | 10
compz | xx6 | yy14 | 350 | 200 | CL | 200
compz | xx6 | yy15 | 350 | 100 | CL | 100
compz | xx6 | yy16 | 350 | 50 | XL | 50
compz | xx6 | yy17 | 350 | 50 | DL | 50
compz | xx7 | yy18 | 700 | 650 | DL | 700
compz | xx7 | yy19 | 700 | 200 | DL | 700
compz | xx7 | yy20 | 700 | 400 | XL | 700
Привет, Ажар, извини за путаницу. Я ожидаю, что это произойдет, если продукт «CL» находится в пределах [val_id]. В примере для val_id = 'xx7' нет [product] = 'CL'. Если [продукт] не имеет «CL», ему просто нужно распределить [our_val_amt] между каждым [val_id]. Например, val_id = 'xx7' our_val_amt = 700, это распространяется в первой строке (650), а затем остается 700 - 650 = 50, вставляется в следующую строку со следующим значением 0, как в примере.
На самом деле жаль, что вы смотрите на то, что выводит код. Пожалуйста, посмотрите на «Набор данных и вывод new_field, пытающийся достичь».






Код дал сбой в крайнем случае, когда есть несколько продуктов, не относящихся к CL, и our_val_amt нужно было распределить так, чтобы последние несколько продуктов могли получить значение 0. Я спросил об этом варианте использования в последнем/предыдущем вопросе; но оно осталось без ответа. У вас могут быть такие угловые случаи, и вам необходимо выполнить комплексный тест.
Ниже приведена обновленная логика. Комментарии добавляются перед каждой строкой обработки, чтобы объяснить, что она делает.
df = pd.DataFrame(data=[["compx","xx1","yy2",424,134,"CL",134],["compx","xx1","yy1",424,418,"XL",290],["compx","xx2","yy4",472,104,"CL",104],["compx","xx2","yy3",472,60,"DL",368],["compx","xx3","yy6",490,500,"CL",490],["compx","xx3","yy5",490,50,"XL",0],["compx","xx3","yy7",490,200,"DL",0],["compx","xx4","yy8",510,200,"CL",200],["compx","xx4","yy9",510,300,"CL",300],["compx","xx4","yy10",510,50,"CL",10],["compy","xx5","yy11",510,200,"CL",200],["compy","xx5","yy12",510,300,"CL",300],["compy","xx5","yy12",510,50,"CL",10],["compy","xx5","yy13",510,30,"DL",0],["compz","xx6","yy14",350,200,"CL",200],["compz","xx6","yy15",350,100,"CL",100],["compz","xx6","yy16",350,50,"XL",50],["compz","xx6","yy17",350,50,"DL",0],["compz","xx7","yy18",700,650,"DL",650],["compz","xx7","yy19",700,200,"DL",50],["compz","xx7","yy20",700,400,"XL",0]], columns=["name","val_id","fac_id","our_val_amt","val_against","product","new_field_expected"])
# Compute tuple of "our_val_amt", "val_against" and "product" for easy processing as one column. It is hard to process multiple columns with "transform()".
df["the_tuple"] = df[["our_val_amt", "val_against", "product"]].apply(tuple, axis=1)
def compute_new_field_for_cl(g):
# df_g is a tuple ("our_val_amt", "val_against", "product") indexed as (0, 1, 2).
df_g = g.apply(pd.Series)
df_g["new_field"] = df_g.apply(lambda row: min(row[0], row[1]) if row[2] == "CL" else 0, axis=1)
# Cumulative sum for comparison
df_g["cumsum"] = df_g["new_field"].cumsum()
# Previous row's sum for comparison
df_g["cumsum_prev"] = df_g["cumsum"].shift(periods=1)
# if our_val_amt >= sum then use min(our_val_amt, val_against)
# else if our_val_amt < sum then take partial of first record such that our_val_amt == sum else take `0` for the rest records
df_g["new_field"] = df_g.apply(lambda row: 0 if row["cumsum_prev"] > row[0] else row[0] - row["cumsum_prev"] if row["cumsum"] > row[0] else row["new_field"], axis=1)
return df_g["new_field"]
# Apply above function and compute new field values for "CL".
df["new_field"] = df.groupby("val_id")[["the_tuple"]].transform(compute_new_field_for_cl)
# Re-compute tuple of "our_val_amt", "val_against", "new_field" and "product".
df["the_tuple"] = df[["our_val_amt", "val_against", "new_field", "product"]].apply(tuple, axis=1)
def compute_new_field_for_not_cl(g):
# df_g is a tuple ("our_val_amt", "val_against", "new_field", "product") indexed as (0, 1, 2, 3).
df_g = g.apply(pd.Series)
# print(df_g)
cl_sum = df_g[df_g[3] == "CL"][2].sum()
if cl_sum > 0:
df_g["new_field"] = df_g.where(df_g[3] != "CL")[0] - df_g[df_g[3] == "CL"][2].sum()
df_g["new_field"] = df_g["new_field"].fillna(df_g[2])
# Cumulative sum for comparison
df_g["cumsum"] = df_g["new_field"].cumsum()
# if our_val_amt < sum then take diff (our_val_amt - sum) else take `0` for the rest records
df_g["new_field"] = df_g.apply(lambda row: 0 if row["cumsum"] > row[0] else row["new_field"], axis=1)
else:
df_g["new_field"] = df_g.apply(lambda row: min(row[0], row[1]) if row[3] != "CL" else row[2], axis=1)
# Cumulative sum for comparison
df_g["cumsum"] = df_g["new_field"].cumsum()
# Previous row's sum for comparison
df_g["cumsum_prev"] = df_g["cumsum"].shift(periods=1)
# if our_val_amt >= sum then use min(our_val_amt, val_against)
# else if our_val_amt < sum then take partial of first record such that our_val_amt == sum else take `0` for the rest records
df_g["new_field"] = df_g.apply(lambda row: 0 if row["cumsum_prev"] > row[0] else row[0] - row["cumsum_prev"] if row["cumsum"] > row[0] else row["new_field"], axis=1)
return df_g["new_field"]
# Apply above function and compute new field values for "CL".
df["new_field"] = df.groupby("val_id")[["the_tuple"]].transform(compute_new_field_for_not_cl)
df = df.drop("the_tuple", axis=1)
print(df)
Выход:
name val_id fac_id our_val_amt val_against product new_field_expected new_field
0 compx xx1 yy2 424 134 CL 134 134.00
1 compx xx1 yy1 424 418 XL 290 290.00
2 compx xx2 yy4 472 104 CL 104 104.00
3 compx xx2 yy3 472 60 DL 368 368.00
4 compx xx3 yy6 490 500 CL 490 490.00
5 compx xx3 yy5 490 50 XL 0 0.00
6 compx xx3 yy7 490 200 DL 0 0.00
7 compx xx4 yy8 510 200 CL 200 200.00
8 compx xx4 yy9 510 300 CL 300 300.00
9 compx xx4 yy10 510 50 CL 10 10.00
10 compy xx5 yy11 510 200 CL 200 200.00
11 compy xx5 yy12 510 300 CL 300 300.00
12 compy xx5 yy12 510 50 CL 10 10.00
13 compy xx5 yy13 510 30 DL 0 0.00
14 compz xx6 yy14 350 200 CL 200 200.00
15 compz xx6 yy15 350 100 CL 100 100.00
16 compz xx6 yy16 350 50 XL 50 50.00
17 compz xx6 yy17 350 50 DL 0 0.00
18 compz xx7 yy18 700 650 DL 650 650.00
19 compz xx7 yy19 700 200 DL 50 50.00
20 compz xx7 yy20 700 400 XL 0 0.00
Ваше объяснение противоречит ожидаемым значениям (650, 50, 0) для val_id = "xx7". В описании вы ожидаете, что значения
new_fieldбудут вычтены изour_val_amt, если product ! = "CL"; но в ожидаемом результате вы ничего не вычли из 700; но вместо этого скопировалval_against. Это не ясно. Как вы вычисляете значения для xx7?