【发布时间】:2022-01-20 10:21:10
【问题描述】:
我有一个带有 for 循环的工作代码,我想在速度方面进行优化(该项目已被拒绝,因为它很慢)。
这是我的情况:我有一个 Excel 文件,我从该文件中将一个包含数千行和几列的表导入到 Pandas DataFrame 中。第一列是单调递增的时间戳序列,频率为 15 分钟。
我必须使用这个表来计算一些额外的列,将它们附加到原始表中,并将生成的 DataFrame 保存到一个新的 Excel 文件中。
使代码变慢的是额外列的核心计算。这是工作代码的sn-p:
import pandas as pd
from datetime import timedelta as td
cons_prod = pd.read_csv("sample.csv", index_col=0, parse_dates=True)
soc_dct = {} # State of charge (kW)
charge_dct = {} # Charge (kW)
discharge_dct = {} # Discharge (kW)
acc_dct = {} # Auto-consumption NEW (kW)
lst_dct = {} # Lost injection due to battery efficiency (kW)
inj_dct = {} # Injection NEW (kW)
gridcons_dct = {} # Grid Consumption NEW (kW)
agg_dct = {} # Additional Auto-consumption through battery (kW)
battery_parameters = {
"power": 50,
"energy": 130,
"efficiency": 0.9,
"minsoc": 0.1,
"soct0": 65.0,
}
bp_energy = battery_parameters["energy"]
bp_power = battery_parameters["power"]
soct0 = 0.5 * bp_energy
for t in cons_prod.index:
L = cons_prod.loc[t, "Injection (kW)"]
m = cons_prod.loc[t, "Grid Consumption (kW)"]
k = cons_prod.loc[t, "Auto-consumption (kW)"]
f = cons_prod.loc[t, "Consumption (kW)"]
if t == cons_prod.index[0]:
# State of charge (kW)
soc_dct.setdefault(t, soct0)
# Charge (kW)
charge_dct.setdefault(
t,
min(
L,
(bp_energy - soc_dct[t]) * 4,
bp_power,
)
* battery_parameters["efficiency"]
if L >= 0
else 0,
)
# Discharge (kW)
discharge_dct.setdefault(
t,
-min(
m,
4 * soc_dct[t] - 4 * (battery_parameters["minsoc"] * bp_energy),
bp_power,
)
if m >= 0
else 0,
)
# Auto-consumption NEW (kW)
acc_dct.setdefault(t, k - discharge_dct[t])
# Lost injection due to battery efficiency (kW)
lst_dct.setdefault(
t,
(charge_dct[t] / battery_parameters["efficiency"]) - charge_dct[t],
)
# Injection NEW (kW)
inj_dct.setdefault(t, L - charge_dct[t] - lst_dct[t])
# Grid Consumption NEW (kW)
gridcons_dct.setdefault(t, f - acc_dct[t])
# Additional Auto-consumption through battery (kW)
agg_dct.setdefault(t, acc_dct[t] - k)
else:
# State of charge (kW)
soc_dct.setdefault(
t,
soc_dct[t - td(minutes=15)]
+ (charge_dct[t - td(minutes=15)] + discharge_dct[t - td(minutes=15)]) / 4,
)
# Charge (kW)
charge_dct.setdefault(
t,
min(
L,
(bp_energy - soc_dct[t]) * 4,
bp_power,
)
* battery_parameters["efficiency"]
if L >= 0
else 0,
)
# Discharge (kW)
discharge_dct.setdefault(
t,
-min(
m,
4 * soc_dct[t] - 4 * (battery_parameters["minsoc"] * bp_energy),
bp_power,
)
if m >= 0
else 0,
)
# Auto-consumption NEW (kW)
acc_dct.setdefault(t, k - discharge_dct[t])
# Lost injection due to battery efficiency (kW)
lst_dct.setdefault(
t, charge_dct[t] / battery_parameters["efficiency"] - charge_dct[t]
)
# Injection NEW (kW)
inj_dct.setdefault(t, L - charge_dct[t] - lst_dct[t])
# Grid Consumption NEW (kW)
gridcons_dct.setdefault(t, f - acc_dct[t])
# Additional Auto-consumption through battery (kW)
agg_dct.setdefault(t, acc_dct[t] - k)
# Creating a DataFrame with all the values
output_df = pd.DataFrame(
data=[
soc_dct,
charge_dct,
discharge_dct,
acc_dct,
lst_dct,
inj_dct,
gridcons_dct,
agg_dct,
]
).T
output_df.columns = [
"State of charge (kW)",
"Charge (kW)",
"Discharge (kW)",
"Auto-consumption NEW (kW)",
"Lost injection due to battery efficiency (kW)",
"Injection NEW (kW)",
"Grid Consumption NEW (kW)",
"Additional Auto-consumption through battery (kW)",
]
charge_dct = {} # Charge (kW)
discharge_dct = {} # Discharge (kW)
acc_dct = {} # Auto-consumption NEW (kW)
lst_dct = {} # Lost injection due to battery efficiency (kW)
inj_dct = {} # Injection NEW (kW)
gridcons_dct = {} # Grid Consumption NEW (kW)
agg_dct = {} # Additional Auto-consumption through battery (kW)
for t in cons_prod.index:
L = cons_prod.loc[t, "Injection (kW)"]
m = cons_prod.loc[t, "Grid Consumption (kW)"]
k = cons_prod.loc[t, "Auto-consumption (kW)"]
f = cons_prod.loc[t, "Consumption (kW)"]
if t == cons_prod.index[0]:
# State of charge (kW)
soc_dct.setdefault(t, soct0)
# Charge (kW)
charge_dct.setdefault(
t,
min(
L,
(bp_energy - soc_dct[t]) * 4,
bp_power,
)
* battery_parameters["efficiency"]
if L >= 0
else 0,
)
# Discharge (kW)
discharge_dct.setdefault(
t,
-min(
m,
4 * soc_dct[t] - 4 * (battery_parameters["minsoc"] * bp_energy),
bp_power,
)
if m >= 0
else 0,
)
# Auto-consumption NEW (kW)
acc_dct.setdefault(t, k - discharge_dct[t])
# Lost injection due to battery efficiency (kW)
lst_dct.setdefault(
t,
(charge_dct[t] / battery_parameters["efficiency"]) - charge_dct[t],
)
# Injection NEW (kW)
inj_dct.setdefault(t, L - charge_dct[t] - lst_dct[t])
# Grid Consumption NEW (kW)
gridcons_dct.setdefault(t, f - acc_dct[t])
# Additional Auto-consumption through battery (kW)
agg_dct.setdefault(t, acc_dct[t] - k)
else:
# State of charge (kW)
soc_dct.setdefault(
t,
soc_dct[t - td(minutes=15)]
+ (charge_dct[t - td(minutes=15)] + discharge_dct[t - td(minutes=15)]) / 4,
)
# Charge (kW)
charge_dct.setdefault(
t,
min(
L,
(bp_energy - soc_dct[t]) * 4,
bp_power,
)
* battery_parameters["efficiency"]
if L >= 0
else 0,
)
# Discharge (kW)
discharge_dct.setdefault(
t,
-min(
m,
4 * soc_dct[t] - 4 * (battery_parameters["minsoc"] * bp_energy),
bp_power,
)
if m >= 0
else 0,
)
# Auto-consumption NEW (kW)
acc_dct.setdefault(t, k - discharge_dct[t])
# Lost injection due to battery efficiency (kW)
lst_dct.setdefault(
t, charge_dct[t] / battery_parameters["efficiency"] - charge_dct[t]
)
# Injection NEW (kW)
inj_dct.setdefault(t, L - charge_dct[t] - lst_dct[t])
# Grid Consumption NEW (kW)
gridcons_dct.setdefault(t, f - acc_dct[t])
# Additional Auto-consumption through battery (kW)
agg_dct.setdefault(t, acc_dct[t] - k)
# Creating a DataFrame with all the values
output_df = pd.DataFrame(
data=[
soc_dct,
charge_dct,
discharge_dct,
acc_dct,
lst_dct,
inj_dct,
gridcons_dct,
agg_dct,
]
).T
output_df.columns = [
"State of charge (kW)",
"Charge (kW)",
"Discharge (kW)",
"Auto-consumption NEW (kW)",
"Lost injection due to battery efficiency (kW)",
"Injection NEW (kW)",
"Grid Consumption NEW (kW)",
"Additional Auto-consumption through battery (kW)",
]
cons_prod是导入DataFrame的表。
如您所见,我们有两种情况:当t == cons_prod.index[0](即时间戳的第一项)时,计算使用相同t处的值。但是,从第二个时间戳开始,一些计算引用了之前的值(这里指的是使用索引t - td(minutes=15) 之前的 15 分钟)。
这些是我努力摆脱 for 循环的原因。
对可能的问题的一些解释
- 问:你为什么使用字典? 答:因为我发现它们比其他数据类型填充得更快,以后我可以使用它们来创建 DataFrame。
- 问:时间戳是否一致,例如它们有缺失值吗? 答:没有缺失值,因为我之前编写了一个函数来确保完全填充时间戳。
- 问:计算当前不引用 DataFrame 中的前一行,而是引用字典中的行!为什么会有误导性的标题? 答:这是迄今为止我能想到的最好的解决方案,但我想知道我对 Pandas 的不完全了解是否隐藏了一个更简单、更快的解决方案。
希望框架清晰。
提前谢谢你!
编辑:根据要求,添加cons_prod 的100-lines sample 并修改之前的代码以满足MRE 的要求。
编辑 2:我尝试从字典转移到 Pandas 查找,尝试尽可能优化。这是我想出的代码:
from time import time as tt
cp = cons_prod.copy(deep=True)
# Initialise the columns filling them with zeroes
cp["State of charge (kW)"] = 0
cp["Charge (kW)"] = 0
cp["Discharge (kW)"] = 0
# Storing the position of the columns in variables
cp_soc = cp.columns.get_loc("State of charge (kW)")
cp_charge = cp.columns.get_loc("Charge (kW)")
cp_discharge = cp.columns.get_loc("Discharge (kW)")
cp_inj = cp.columns.get_loc("Injection (kW)")
cp_gridcons = cp.columns.get_loc("Grid Consumption (kW)")
# Storing the values of the battery dictionary lookups in variables
bp_energy = dct_bp["energy"]
bp_power = dct_bp["power"]
bp_efficiency = dct_bp["efficiency"]
bp_soct0 = dct_bp["soct0"]
bp_minsoc = dct_bp["minsoc"]
start1 = tt() # Measuring time
for row in cp.itertuples(name=None): # Using itertuples to gain some speed
L = cp.loc[row[0], "Injection (kW)"]
m = cp.loc[row[0], "Grid Consumption (kW)"]
k = cp.loc[row[0], "Auto-consumption (kW)"]
f = cp.loc[row[0], "Consumption (kW)"]
if row[0] == cp.index[0]:
cp.iloc[0, cp_soc] = bp_soct0
cp.iloc[0, cp_charge] = float(
min(L, (bp_energy - bp_soct0) * 4, bp_power) * bp_efficiency
if L >= 0
else 0,
)
cp.iloc[0, cp_discharge] = float(
-min(
m,
4 * bp_soct0 - 4 * (bp_minsoc * bp_energy),
bp_power,
)
if m >= 0
else 0
)
else:
t = pd.Index(cp.index).get_loc(row[0])
cp.iloc[t, cp_soc] = float(
cp.iloc[t - 1, cp_soc]
+ (cp.iloc[t - 1, cp_charge] + cp.iloc[t - 1, cp_discharge]) / 4
)
cp.iloc[t, cp_charge] = float(
min(L, (bp_energy - cp.iloc[t, cp_soc]) * 4, bp_power) * bp_efficiency
if L >= 0
else 0,
)
cp.iloc[t, cp_discharge] = float(
-min(
m,
4 * cp.iloc[t, cp_soc] - 4 * (dct_bp["minsoc"] * bp_energy),
bp_power,
)
if m >= 0
else 0
)
end1 = tt() - start1
print(f"Pandas lookup took {end1:.2f} seconds")
使用这段代码,我平均每完成一项任务需要 42 秒,而我过去使用字典的时间不到 20 秒。
【问题讨论】:
-
Ciao Filippo,您介意添加minimal reproducible example 吗?特别是在
cons_prod中有一个数据样本会很棒 -
您好@rpanai,感谢您的回答!我对代码进行了更改,只是为了复制粘贴和使用。至于
sample.csv,使用我附上的Pastebin中的数据。 -
考虑在数据框中创建空列(例如:
soc_dct),然后逐行递增地填充它们。在这种情况下,使用 dicts 实际上可能会更慢。 (1) 我不知道说字典填得更快是什么意思。 dict 的使用需要散列,然后(可能)线性搜索,所有这些都可能比在 pandas 中按索引访问数组要慢,(2)将 dict 转换为 df 需要时间,(3)t - td(minutes=15)总是指前一行df 因为数据以 15 分钟为增量,这意味着通过t - td(minutes=15)的完整时间戳查找字典是浪费的。 -
感谢@Ankur 的评论。我想指出我不访问字典,但我只是在每个循环中用一个新的键值对填充它们。然后使用 dicts 在原始数据框中添加列,由于它们的 O(1) 时间复杂度,我选择了它们而不是列表(或类似的)。尽管如此,我会尝试你创建空列并填充它们的方法,我会告诉你的。
-
@Ankur,添加了我直接使用 Pandas 查找的尝试。它比使用字典要慢(除非我仍然可以优化它?)。
标签: python pandas dataframe for-loop optimization