ОТРЕДАКТИРОВАНО ДЛЯ ЯСНОСТИ
Я работаю над моделью региональной электроэнергетической системы. в настоящее время у меня есть два кадра данных: один содержит информацию о муниципалитете и энергетическом узле, другой - это спрос на энергию с течением времени в муниципалитете, как показано ниже (а не фактический df, который я использую)
municip = {
"muni_id": [1401, 1402, 1407, 1415, 1419, 1480, 1480, 1427, 1484],
"muni_name": ["Har", "Par", "Ock", "Ste", "Tjo", "Gbg", "Gbg", "Sot", "Lys"],
"new_muni_id": [1401, 1402, 1480, 1415, 1415, 1480, 1480, 1484, 1484],
"new_muni_name": ["Har", "Har", "Gbg", "Ste", "Ste", "Gbg", "Gbg", "Lys", "Lys"],
"new_node_id": ["HAR1", "PAR1", "GBG2", "STE1", "STE1", "GBG1", "GBG2", "LYS1", "LYS1"]
}
df_1 = pd.DataFrame(municip)
demand = {
"period": [1, 2, 3, 4, 5],
1401: [2, 4, 4, 1, 2],
1402: [1, 1, 3, 3, 5],
1407: [2, 4, 4, 1, 2],
1415: [1, 1, 3, 3, 5],
1419: [1, 1, 3, 3, 5],
1480: [1, 1, 3, 3, 5],
1427: [2, 4, 4, 1, 2],
1484: [1, 2, 3, 4, 5]
}
df_2 = pd.DataFrame(demand)
есть «старый» и «новый» muni_id и имя, потому что в некоторых муниципалитетах может не быть узла, поэтому я агрегировал спрос до ближайшего узла. df_2 — это спрос во всем муниципалитете, причем число в столбцах указывает muni_id.
мое намерение состоит в том, чтобы равномерно распределить муниципальный спрос на энергетический узел (в качестве начала для простоты)
Я попробовал следующее:
создать новый фрейм данных со столбцами периода и списком узлов
назначьте спрос на муниципалитет, который имеет одинаковый старый и новый идентификатор/имя, разделенный на количество узлов в этом же муниципалитете, например, выше Gbg имеет 2 узла (GBG1 и GBG2), поэтому спрос на GBG1 и GBG2 будет спросом 1480 (Гбг) разделить на два.
для муниципалитета, который имеет разные старый и новый идентификатор/название, требование старого идентификатора муниципалитета будет добавлено к требованию назначенного узла на основе предыдущего шага. например, в df_1 muni_id 1407 (Ock) назначен узлу GBG2, поэтому потребность GBG2 в первом периоде будет равна 2,5, где 0,5 — это предыдущий шаг (потребность Gbg / 2), 2 — текущий шаг (потребность Ок)
import pandas as pd
def profiling(df_1, df_2):
# create empty df
nodes = df_1["new_node_id"].unique()
node_df = pd.DataFrame(columns=["period"] + list(nodes))
node_df["period"] = df_2["period"]
# loop based on node id
for node in nodes:
node_info = df_1[df_1["new_node_id"] == node]
muni_id = node_info["muni_id"].values[0]
new_muni_id = node_info["new_muni_id"].values[0]
# assign demand to all nodes that has same old/new id
if muni_id == new_muni_id :
cumul_node = len(df_1[df_1["muni_id"] == muni_id])
node_df[node] = df_2[muni_id] / cumul_node
# assign demand to node for muni that has different old/new id
else:
cumul_node = len(df_1[df_1["new_muni_id"] == new_muni_id])
add_demand = df_2[muni_id] / cumul_node
if node in node_df:
node_df[node] += add_demand
else:
node_df[node] = add_demand
return node_df
Однако это приводит к тому, что некоторые узлы возвращают значения NaN, особенно муниципалитеты, которые имеют разные старые/новые идентификаторы, как показано в таблице ниже.
по моей логике, почасовая потребность GBG2 должна составлять половину от 1480 (Gbg muni) + 1407 (Ock muni), поскольку Ock назначается GBG2 как новый узел, т.е. 2,5 в первый период. То же самое касается узла LYS1, где общая потребность составит 1427 (Sot) и 1484 (Lys).
Поэтому я ожидаю получить что-то вроде этого
Может ли кто-нибудь улучшить мою функцию, или, возможно, в моей логике добавления значений есть ошибка?
Извините за длинный вопрос и заранее спасибо за помощь!
спасибо за предложение! отредактирую вопрос
Я запутался, пытаясь решить эту проблему с помощью фрагмента кода, который вы предоставили для циклов, поэтому вот мой подход:
def profiling(df_1, df_2):
df_1['num_nodes'] = df_1.groupby('muni_id')['new_node_id'].transform('count')
df_2_melted = df_2.melt(id_vars=['period'], var_name='muni_id', value_name='demand')
merged_df = df_2_melted.merge(df_1, on='muni_id', how='left')
merged_df['demand'] = merged_df['demand'] / merged_df['num_nodes']
final_df = merged_df.pivot_table(index='period', columns='new_node_id', values='demand', aggfunc='sum').reset_index()
final_df = final_df.rename_axis(None, axis=1)
return final_df
Если мы разберем код:
df_1['num_nodes'] = df_1.groupby('muni_id')['new_node_id'].transform('count')
Это подсчитывает количество узлов в каждой группе узлов muni_id
и возвращает Series
, присвоенный столбцу num_nodes
.
df_2_melted = df_2.melt(id_vars=['period'], var_name='muni_id', value_name='demand')
Использование melt
создает новый DataFrame
, в который добавляется столбец с именем muni_id
для переключения с таблицы n_period x n_muni
на один столбец значений для спроса за период для данного узла. Это означает, что каждая строка соответствует определенной паре period
и muni_id
с соответствующим спросом.
merged_df = df_2_melted.merge(df_1, on='muni_id', how='left')
Это объединяет df_2_melted
с df_1
с помощью столбца muni_id
. Это добавит всю информацию из df_1
в df_2_melted
для каждого period
, muni_id
и demand
.
merged_df['demand'] = merged_df['demand'] / merged_df['num_nodes']
Это делит спрос на количество узлов, найденных на шаге 1.
final_df = merged_df.pivot_table(index='period', columns='new_node_id', values='demand', aggfunc='sum').reset_index()
При этом создается сводная таблица, в которой period
в качестве строк и node_id
в качестве столбцов. Таблица заполнена значениями из столбца demand
, и чтобы обеспечить правильную обработку старых и новых идентификаторов муниципалитетов, используется aggfunc='sum'
. .reset_index()
позволяет превратить индекс в столбец period
.
final_df = final_df.rename_axis(None, axis=1)
Удаляет имя new_node_id
индекса, поскольку оно больше не является этим.
Используя приведенный вами пример данных, я получаю:
municip = {
"muni_id": [1401, 1402, 1407, 1415, 1419, 1480, 1480, 1427, 1484],
"muni_name": ["Har", "Par", "Ock", "Ste", "Tjo", "Gbg", "Gbg", "Sot", "Lys"],
"new_muni_id": [1401, 1402, 1480, 1415, 1415, 1480, 1480, 1484, 1484],
"new_muni_name": ["Har", "Har", "Gbg", "Ste", "Ste", "Gbg", "Gbg", "Lys", "Lys"],
"new_node_id": ["HAR1", "PAR1", "GBG2", "STE1", "STE1", "GBG1", "GBG2", "LYS1", "LYS1"]
}
df_1 = pd.DataFrame(municip)
demand = {
"period": [1, 2, 3, 4, 5],
1401: [2, 4, 4, 1, 2],
1402: [1, 1, 3, 3, 5],
1407: [2, 4, 4, 1, 2],
1415: [1, 1, 3, 3, 5],
1419: [1, 1, 3, 3, 5],
1480: [1, 1, 3, 3, 5],
1427: [2, 4, 4, 1, 2],
1484: [1, 2, 3, 4, 5]
}
df_2 = pd.DataFrame(demand)
profiling(df_1, df_2)
>>>
period GBG1 GBG2 HAR1 LYS1 PAR1 STE1
0 1 0.5 2.5 2.0 3.0 1.0 2.0
1 2 0.5 4.5 4.0 6.0 1.0 2.0
2 3 1.5 5.5 4.0 7.0 3.0 6.0
3 4 1.5 2.5 1.0 5.0 3.0 6.0
4 5 2.5 4.5 2.0 7.0 5.0 10.0
Это здорово и намного короче того, что у меня есть! Спасибо! В будущих случаях мне следовало бы больше внимания уделять поворотным таблицам!
вы можете добавить примеры данных (но короче) в виде кода
DataFrame(...)
и таблицу с ожидаемым результатом, чтобы мы могли запустить ваш код и протестировать идеи.