Как эффективно рассчитать долю агрегированного столбца

У меня есть следующий 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

Это работает, но далеко не эффективно. Есть ли лучший способ сделать мой расчет?

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
0
96
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Ответ принят как подходящий

Это эквивалентно выбору строк с «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

Хотя это дает тот же результат, это не эквивалентно коду OP. Это приведет к неправильному выводу, если «end_amount» не всегда является последней строкой.

mozway 01.07.2024 18:41

Если вам нужна доля, вам нужно вычислить общую сумму каждой группы, а не максимальную. Но это зависит от ваших потребностей.

Предполагая, что доля связана с общей суммой каждой группы, поместите общую сумму группы в новый столбец и вычислите деление.

Если вам нужен максимум для каждой группы, измените .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
'''

Другие вопросы по теме