У меня есть следующий DataFrame, и я хочу вычислить «долю».
import pandas as pd
d = {"col1":["A", "A", "A", "B", "B", "B"], "col2":["start_amount", "mid_amount", "end_amount", "start_amount", "mid_amount", "end_amount"], "amount":[0, 2, 8, 1, 2, 3]}
df_test = pd.DataFrame(d)
df_test["share"] = 0
for i in range(len(df_test)):
df_test.loc[i, "share"] = df_test.loc[i, "amount"] / df_test.loc[(df_test["col1"] == df_test.loc[i, "col1"]) & (df_test["col2"] == "end_amount"), "amount"].values
Это работает, но далеко не эффективно. Есть ли лучший способ сделать мой расчет?
Это эквивалентно выбору строк с «end_amount», а затем выполнению карты для каждого «col1», чтобы затем разделить «сумму»:
s = df_test.loc[df_test['col2'].eq('end_amount')].set_index('col1')['amount']
df_test['share'] = df_test['amount']/df_test['col1'].map(s)
Выход:
col1 col2 amount share
0 A start_amount 0 0.000000
1 A mid_amount 2 0.250000
2 A end_amount 8 1.000000
3 B start_amount 1 0.333333
4 B mid_amount 2 0.666667
5 B end_amount 3 1.000000
Вы можете использовать groupby
и transform
, чтобы получить конечную сумму для каждого значения в 'col1'
:
df_test["share"] = df_test["amount"] / df_test.groupby("col1")["amount"].transform("last")
col1 col2 amount share
0 A start_amount 0 0.000000
1 A mid_amount 2 0.250000
2 A end_amount 8 1.000000
3 B start_amount 1 0.333333
4 B mid_amount 2 0.666667
5 B end_amount 3 1.000000
Если вам нужна доля, вам нужно вычислить общую сумму каждой группы, а не максимальную. Но это зависит от ваших потребностей.
Предполагая, что доля связана с общей суммой каждой группы, поместите общую сумму группы в новый столбец и вычислите деление.
Если вам нужен максимум для каждой группы, измените .transform("sum")
на .transform("max")
.
d = {"col1": ["A", "A", "A", "B", "B", "B"], "amount": [0, 2, 8, 1, 2, 3]}
df_test = pd.DataFrame(d)
df_test['total_amount'] = df_test.groupby('col1')['amount'].transform('sum')
df_test['share'] = df_test['amount'] / df_test['total_amount']
print(df_test)
Я бы, вероятно, использовал groupby
, чтобы найти максимальную сумму для каждой группы, объединил бы максимальную сумму с исходными данными по строке группы, а затем разделил бы, чтобы получить долю.
Предполагая, что максимальное значение каждой группы — это окончательная_сумма:
df_test = pd.DataFrame(d)
df_test = df_test.merge(
df_test.groupby('col1')['amount'].max().rename('max_amount'), #Take the max for each group, and rename the resulting series
left_on=['col1'],right_index=True,how='left' #Merge Logic
)
df_test['share'] = df_test['amount']/df_test['max_amount']
В качестве альтернативы можно разрезать end_amount, объединить его обратно с исходными данными с переименованным столбцом значений, а затем разделить, чтобы получить долю:
df_test = pd.DataFrame(d)
df_test = df_test.merge(
df_test[df_test['col2']=='end_amount'][['col1','amount']].rename({'amount':'max_amount'},axis=1), #Take the end_amount for each 'col1', and rename the resulting column
left_on=['col1'],right_on=['col1'],how='left' #Merge Logic
)
df_test['share'] = df_test['amount']/df_test['max_amount']
Результат обеих техник:
col1 col2 amount max_amount share
0 A start_amount 0 8 0.000000
1 A mid_amount 2 8 0.250000
2 A end_amount 8 8 1.000000
3 B start_amount 1 3 0.333333
4 B mid_amount 2 3 0.666667
5 B end_amount 3 3 1.000000
Обновлено: я думаю, что ответ Скулдена — лучший, хотя в своем примере он использует сумму. При максимальном предположении:
df_test = pd.DataFrame(d)
df_test['max_amount']=df_test.groupby('col1')['amount'].transform('max')
df_test['share'] = df_test['amount']/df_test['max_amount']
import pandas as pd
import numpy as np
# Create the DataFrame
d = {
"col1": ["A", "A", "A", "B", "B", "B"],
"col2": ["start_amount", "mid_amount", "end_amount", "start_amount", "mid_amount", "end_amount"],
"amount": [0, 2, 8, 1, 2, 3]
}
df = pd.DataFrame(d)
# Calculate the end_amount for each group
end_amounts = df[df['col2'] == 'end_amount'].set_index('col1')['amount']
'''
col1
A 8
B 3
Name: amount, dtype: int64
'''
# Map end_amounts to the original DataFrame
df['end_amounts'] = df['col1'].map(end_amounts)
# Calculate shares
df['share'] = df['amount'] / df['end_amounts']
print(df)
'''
col1 col2 amount end_amounts share
0 A start_amount 0 8 0.000000
1 A mid_amount 2 8 0.250000
2 A end_amount 8 8 1.000000
3 B start_amount 1 3 0.333333
4 B mid_amount 2 3 0.666667
5 B end_amount 3 3 1.000000
'''
Метод 2: Numpy-решение
import pandas as pd
import numpy as np
d = {
"col1": ["A", "A", "A", "B", "B", "B"],
"col2": ["start_amount", "mid_amount", "end_amount", "start_amount", "mid_amount", "end_amount"],
"amount": [0, 2, 8, 1, 2, 3]
}
df = pd.DataFrame(d)
# Convert to NumPy arrays for efficiency
col1 = df['col1'].values
col2 = df['col2'].values
amount = df['amount'].values
# Find unique groups and their end_amounts
unique_groups, end_amounts_idx = np.unique(col1, return_index=True)
end_amounts_dict = {
ug: amount[(col1 == ug) & (col2 == "end_amount")][0]
for ug in unique_groups
}
print(end_amounts_dict)#{'A': 8, 'B': 3}
# Convert the dictionary to a NumPy array for efficient indexing
end_amounts = np.array([end_amounts_dict[element] for element in col1])
print(end_amounts)#[8 8 8 3 3 3]
# Calculate shares using vectorized operations
shares = amount / end_amounts
# Add the computed shares back to the DataFrame
df['share'] = shares
print(df)
'''
col1 col2 amount share
0 A start_amount 0 0.000000
1 A mid_amount 2 0.250000
2 A end_amount 8 1.000000
3 B start_amount 1 0.333333
4 B mid_amount 2 0.666667
5 B end_amount 3 1.000000
'''
Хотя это дает тот же результат, это не эквивалентно коду OP. Это приведет к неправильному выводу, если «end_amount» не всегда является последней строкой.