python - 如何在 Python 中对这个 for 循环进行矢量化?
问题描述
我的数据有 4 列 AD 有整数。我正在添加一个新列 E,它的第一个值与 D 列中的第一个值相同。如果 E 列中的前一个值为负数,E 中的下一个值应该是 D 列中的对应值,否则它在列中取对应值C。
import pandas as pd
import numpy as np
from pandas import Series, DataFrame
data=pd.read_excel('/Users/xxxx/Documents/PY Notebooks/Data/yyyy.xlsx')
data1=data.copy()
data1['E']=np.nan
data1.at[0,'E']=data1['D'][0]
l=len(data1)
for i in range(l-1):
if data1['E'][i]<0:
data1.at[i+1,'E']=data1['D'][i+1]
else:
data1.at[i+1,'E']=data1['C'][i+1]
解决方案
TL;DR:转到基准代码并使用方法 1。
简答
没有。矢量化是不可能的。
长答案
定理:对于这个特定的任务,给定行的输出不能使用任何小于该行的部分长度的向后滚动窗口的有限长度来确定。
因此,无法以矢量化方式处理此输出逻辑。(有关在 CPU 中执行矢量化的想法,请参阅此答案)。只能从数据帧的开头计算输出。
证明:考虑一个数据框的目标行df
。假设有一个大小为 的向后滚动窗口,因此窗口之前存在n < partial length
一个先前的值。df["E"]
我们用 表示这个先前的值state
。
考虑一个特殊情况:df["C"] == -1
并且df["D"] == 1
在窗口内。
- 情况 1 (
state < 0
):此滚动窗口内的输出将是 [1, -1, 1, -1, .....],使最后一个元素(-1)^(n-1)
- 情况 2 (
state >= 0
):输出将是 [-1, 1, -1, 1, .....],使得最后一个元素(-1)^(n)
因此,目标行的输出df["E"]
可能依赖于窗口外的状态变量。QED。
有用的答案
虽然矢量化是不可能的,但这并不意味着不能实现显着的加速。一种简单但非常有效的方法是使用 anumba-compiled generator
来执行顺序生成。它只需要将您的逻辑重新写入生成器函数并添加两行:
import numba
@numba.njit
def my_generator_func():
....
当然,您可能必须先安装 numba。如果这是不可能的,那么使用没有 numba 优化的普通生成器也可以。
基准
基准测试在 i5-8250U (4C8T) 笔记本电脑上执行,配备 16GB RAM,运行 64 位 debian 10。Python 版本为 3.7.9,pandas 为 1.1.3。n = 10^7
(1000 万)条记录用于基准测试。
结果:
1. numba-njit: 2.48s
2. plain generator (no numba): 5.13s
3. original: 271.15s
> 100x
可以针对原始代码实现效率增益。
代码
from datetime import datetime
import pandas as pd
import numpy as np
n = 10000000 # a large number of rows
df = pd.DataFrame({"C": -np.ones(n), "D": np.ones(n)})
#print(df.head())
# ========== Method 1. generator + numba njit ==========
ti = datetime.now()
import numba
@numba.njit
def gen(plus: np.array, minus: np.array):
l = len(plus)
assert len(minus) == l
# first
state = minus[0]
yield state
# second to last
for i in range(l-1):
state = minus[i+1] if state < 0 else plus[i+1]
yield state
df["E"] = [i for i in gen(df["C"].values, df["D"].values)]
tf = datetime.now()
print(f"1. numba-njit: {(tf-ti).total_seconds():.2f}s") # 1. numba-njit: 0.47s
# ========== Method 2. Generator without numba ==========
df = pd.DataFrame({"C": -np.ones(n), "D": np.ones(n)})
ti = datetime.now()
def gen_plain(plus: np.array, minus: np.array):
l = len(plus)
assert len(minus) == l
# first
state = minus[0]
yield state
# second to last
for i in range(l-1):
state = minus[i+1] if state < 0 else plus[i+1]
yield state
df["E"] = [i for i in gen_plain(df["C"].values, df["D"].values)]
tf = datetime.now()
print(f"2. plain generator (no numba): {(tf-ti).total_seconds():.2f}s") #
# ========== Method 3. Direct iteration ==========
df = pd.DataFrame({"C": -np.ones(n), "D": np.ones(n)})
ti = datetime.now()
# code provided by the OP
df['E']=np.nan
df.at[0,'E'] = df['D'][0]
l=len(df)
for i in range(l - 1):
if df['E'][i] < 0:
df.at[i+1,'E'] = df['D'][i+1]
else:
df.at[i+1,'E'] = df['C'][i+1]
tf = datetime.now()
print(f"3. original: {(tf-ti).total_seconds():.2f}s") # 2. 26.61s
推荐阅读
- ruby-on-rails - ./bin/webpack 和 rails assets 之间的区别:预编译?
- jquery - ajax 调用不起作用,为什么它直接传递动作?
- c# - 下载 Execute in Memory Depended EXE C#
- python - 在 jupyter notebook 和 google colab 中使用 .so
- omnet++ - 使用 ieee80211DimensionalRadioMedium 模式时出错
- swiftui - SwiftUI - 关闭第二个模式表不起作用
- python - 如何使用 SciPy(或 SymPy)和 Matplotlib 绘制 Verhulst 方程的相图?
- firebase - 如何在 Firebase 中从 Firebase 获取实时更新
- ios - 如何将映射值添加到 Firestore Swift 5 中的数组?
- css - 根据条件更改背景颜色步进角材料