使用 apply() 而不是 for 循环 - Pandas
Using apply() rather than for loop - Pandas
我正在使用具有 5 分钟总降雨量的数据提取不同持续时间的最大降雨强度。该代码生成每个持续时间 (DURS) 的最大降雨强度列表。该代码有效,但在使用具有 1,000,000 多行的数据集时速度很慢。我是 Pandas 的新手,我知道 apply()
方法比使用 For 循环快得多,但我不知道如何使用 apply()
方法重写 For 循环。
数据帧示例:
Value[mm] State of value
Date_Time
2020-01-01 00:00:00 1.0 5
2020-01-01 00:05:00 0.5 5
2020-01-01 00:10:00 4.0 5
2020-01-01 00:15:00 2.0 5
2020-01-01 00:20:00 2.0 5
2020-01-01 00:25:00 0.5 5
代码示例:
import matplotlib.pyplot as plt
from matplotlib.ticker import ScalarFormatter
import math, numpy, array, glob
import pandas as pd
import numpy as np
pluvi_file = "rain.csv"
DURS = [5,6,10,15,20,25,30,45,60,90,120,180,270,360,540,720,1440,2880,4320]
df = pd.read_csv(pluvi_file, delimiter=',',parse_dates=[['Date','Time']])
df['Date_Time'] = pd.to_datetime(df['Date_Time'], dayfirst=True)
df.index = df['Date_Time']
del df['Date_Time']
lista = []
for DUR in DURS:
x = str(DUR)+' Min'
df1 = df.groupby(pd.Grouper(freq=x)).sum()
a = df1['Value[mm]'].max()/DUR*60
print(a)
lista.append(a)
输出(mm/hr中每个持续时间的最大降雨强度):
5 66.0
6 60.0
10 54.0
15 40.0
20 40.5
25 30.0
30 34.0
45 26.666666666666664
60 26.5
90 20.666666666666668
120 23.0
180 12.166666666666666
270 8.11111111111111
360 9.416666666666666
540 6.444444444444445
720 4.708333333333333
1440 3.8958333333333335
2880 2.7708333333333335
4320 2.1597222222222223
我如何使用 apply()
方法重写它?
解决方案
这里似乎不适合应用,因为您在组上应用的函数是来自 Essential Basic Functionality 的向量化方法。此外,删除 for 循环看起来并不是一种有前途的性能优化方式,因为你的 DURS
列表中没有太多的持续时间,所以主要问题是分组操作和对组的计算,并且没有太多space 用于优化,至少在我看来是这样。
创建人工数据
import pandas as pd
df = pd.DataFrame({'Date_Time' : ["2020-01-01 00:00:00",
"2020-01-01 00:05:00",
"2020-01-01 00:10:00",
"2020-01-01 00:15:00",
"2020-01-01 00:20:00",
"2020-01-01 00:25:00"],
'Value[mm]' : [1.0,0.5,4.0,2.0,2.0,0.5],
'State of value': [5,5,5,5,5,5]
})
df = df.sample(3900875, replace=True).reset_index(drop=True)
现在,让我们将 Date_Time
设置为索引,并获得我们需要计算值的序列
df['Date_Time'] = pd.to_datetime(df['Date_Time'], dayfirst=True)
df = df.set_index('Date_Time', drop = True)
df = df['Value[mm]']
比较不同方法的性能
分组和循环
%%timeit
lista = []
for DUR in DURS:
x = str(DUR)+' Min'
df1 = df.groupby(pd.Grouper(freq=x)).sum()
a = df1.max()/DUR*60
lista.append(a)
19.6 s ± 439 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
重采样
这里的时间提升可能是随机的,因为它看起来在幕后发生了同样的事情。
%%timeit
def get_max_by_dur(DUR):
return df.resample(str(DUR)+"Min").sum().max()
l_a = [get_max_by_dur(dur)/dur*60 for dur in DURS]
17.2 s ± 559 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
重采样 + Dask
尽管无法正确矢量化 - 您仍然可以使用 Dask 进行一些并行化和优化。
!python3 -m pip install "dask[dataframe]" --upgrade
import dask.dataframe as dd
%%timeit
dd_df = dd.from_pandas(df, npartitions = 1)
def get_max_by_dur(DUR):
return dd_df.resample(str(DUR)+"Min").sum().max()
l_a = [(get_max_by_dur(dur)/dur*60).compute() for dur in DURS]
2.21 s ± 110 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
关于应用和优化的几句话
通常,您使用 apply 沿 DataFrame 的轴应用函数。因此,这是通过 DataFrame 本身的行或列循环的替代,但实际上,apply 只是一个具有一些额外功能的美化循环。因此,当性能很重要时,您通常希望像这样优化代码。
- 向量化或Essential Basic Functionality(如您所做)
- Cython routines or numba
- List comprehension.
- 应用方法
- 迭代
插图
假设您想要获得两列的乘积
1).矢量化或基本方法。
基本方法:
df["product"] = df.prod(axis=1)
162 ms ± 9.28 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
矢量化:
import numpy as np
def multiply(Value,State): # you may use lambda here as well
return Value*State
%timeit df["new_column"] = np.vectorize(multiply) (df["Value[mm]"], df["State of value"])
853 ms ± 42.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
如果您已经编写了一些循环,它会非常有用。您通常可以用 @numba.jit
装饰它并获得显着的性能提升。当您想计算一些难以向量化的迭代值时,它也非常有用。
由于我们选择的函数是乘法,与通常应用相比,您不会有任何好处。
%%cython
cdef double cython_multiply(double Value, double State):
return Value * State
%timeit df["new_column"] = df.apply(lambda row:multiply(row["Value[mm]"], row["State of value"]), axis = 1)
1min 38s ± 4 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
3).列表理解。
它是 pythonic 的,也非常类似于 for 循环。
%timeit df["new_column"] = [x*y for x, y in zip(df["Value[mm]"], df["State of value"])]
1.56 s ± 160 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
4).应用方法
注意,它有多慢。
%timeit df["new_column"] = df.apply(lambda row:row["Value[mm]"]*row["State of value"], axis = 1)
1min 37s ± 4.76 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
5).遍历行
双元组:
%%timeit
list_a = []
for row in df.itertuples():
list_a.append(row[2]*row[3])
df['product'] = list_a
9.81 s ± 831 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
iterrows(你可能不应该使用它):
%%timeit
list_a = []
for row in df.iterrows():
list_a.append(row[1][1]*row[1][2])
df['product'] = list_a
6min 40s ± 1min 8s per loop (mean ± std. dev. of 7 runs, 1 loop each)
我正在使用具有 5 分钟总降雨量的数据提取不同持续时间的最大降雨强度。该代码生成每个持续时间 (DURS) 的最大降雨强度列表。该代码有效,但在使用具有 1,000,000 多行的数据集时速度很慢。我是 Pandas 的新手,我知道 apply()
方法比使用 For 循环快得多,但我不知道如何使用 apply()
方法重写 For 循环。
数据帧示例:
Value[mm] State of value
Date_Time
2020-01-01 00:00:00 1.0 5
2020-01-01 00:05:00 0.5 5
2020-01-01 00:10:00 4.0 5
2020-01-01 00:15:00 2.0 5
2020-01-01 00:20:00 2.0 5
2020-01-01 00:25:00 0.5 5
代码示例:
import matplotlib.pyplot as plt
from matplotlib.ticker import ScalarFormatter
import math, numpy, array, glob
import pandas as pd
import numpy as np
pluvi_file = "rain.csv"
DURS = [5,6,10,15,20,25,30,45,60,90,120,180,270,360,540,720,1440,2880,4320]
df = pd.read_csv(pluvi_file, delimiter=',',parse_dates=[['Date','Time']])
df['Date_Time'] = pd.to_datetime(df['Date_Time'], dayfirst=True)
df.index = df['Date_Time']
del df['Date_Time']
lista = []
for DUR in DURS:
x = str(DUR)+' Min'
df1 = df.groupby(pd.Grouper(freq=x)).sum()
a = df1['Value[mm]'].max()/DUR*60
print(a)
lista.append(a)
输出(mm/hr中每个持续时间的最大降雨强度):
5 66.0
6 60.0
10 54.0
15 40.0
20 40.5
25 30.0
30 34.0
45 26.666666666666664
60 26.5
90 20.666666666666668
120 23.0
180 12.166666666666666
270 8.11111111111111
360 9.416666666666666
540 6.444444444444445
720 4.708333333333333
1440 3.8958333333333335
2880 2.7708333333333335
4320 2.1597222222222223
我如何使用 apply()
方法重写它?
解决方案
这里似乎不适合应用,因为您在组上应用的函数是来自 Essential Basic Functionality 的向量化方法。此外,删除 for 循环看起来并不是一种有前途的性能优化方式,因为你的 DURS
列表中没有太多的持续时间,所以主要问题是分组操作和对组的计算,并且没有太多space 用于优化,至少在我看来是这样。
创建人工数据
import pandas as pd
df = pd.DataFrame({'Date_Time' : ["2020-01-01 00:00:00",
"2020-01-01 00:05:00",
"2020-01-01 00:10:00",
"2020-01-01 00:15:00",
"2020-01-01 00:20:00",
"2020-01-01 00:25:00"],
'Value[mm]' : [1.0,0.5,4.0,2.0,2.0,0.5],
'State of value': [5,5,5,5,5,5]
})
df = df.sample(3900875, replace=True).reset_index(drop=True)
现在,让我们将 Date_Time
设置为索引,并获得我们需要计算值的序列
df['Date_Time'] = pd.to_datetime(df['Date_Time'], dayfirst=True)
df = df.set_index('Date_Time', drop = True)
df = df['Value[mm]']
比较不同方法的性能
分组和循环
%%timeit
lista = []
for DUR in DURS:
x = str(DUR)+' Min'
df1 = df.groupby(pd.Grouper(freq=x)).sum()
a = df1.max()/DUR*60
lista.append(a)
19.6 s ± 439 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
重采样
这里的时间提升可能是随机的,因为它看起来在幕后发生了同样的事情。
%%timeit
def get_max_by_dur(DUR):
return df.resample(str(DUR)+"Min").sum().max()
l_a = [get_max_by_dur(dur)/dur*60 for dur in DURS]
17.2 s ± 559 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
重采样 + Dask
尽管无法正确矢量化 - 您仍然可以使用 Dask 进行一些并行化和优化。
!python3 -m pip install "dask[dataframe]" --upgrade
import dask.dataframe as dd
%%timeit
dd_df = dd.from_pandas(df, npartitions = 1)
def get_max_by_dur(DUR):
return dd_df.resample(str(DUR)+"Min").sum().max()
l_a = [(get_max_by_dur(dur)/dur*60).compute() for dur in DURS]
2.21 s ± 110 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
关于应用和优化的几句话
通常,您使用 apply 沿 DataFrame 的轴应用函数。因此,这是通过 DataFrame 本身的行或列循环的替代,但实际上,apply 只是一个具有一些额外功能的美化循环。因此,当性能很重要时,您通常希望像这样优化代码。
- 向量化或Essential Basic Functionality(如您所做)
- Cython routines or numba
- List comprehension.
- 应用方法
- 迭代
插图
假设您想要获得两列的乘积
1).矢量化或基本方法。
基本方法:
df["product"] = df.prod(axis=1)
162 ms ± 9.28 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
矢量化:
import numpy as np
def multiply(Value,State): # you may use lambda here as well
return Value*State
%timeit df["new_column"] = np.vectorize(multiply) (df["Value[mm]"], df["State of value"])
853 ms ± 42.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
如果您已经编写了一些循环,它会非常有用。您通常可以用 @numba.jit
装饰它并获得显着的性能提升。当您想计算一些难以向量化的迭代值时,它也非常有用。
由于我们选择的函数是乘法,与通常应用相比,您不会有任何好处。
%%cython
cdef double cython_multiply(double Value, double State):
return Value * State
%timeit df["new_column"] = df.apply(lambda row:multiply(row["Value[mm]"], row["State of value"]), axis = 1)
1min 38s ± 4 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
3).列表理解。
它是 pythonic 的,也非常类似于 for 循环。
%timeit df["new_column"] = [x*y for x, y in zip(df["Value[mm]"], df["State of value"])]
1.56 s ± 160 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
4).应用方法
注意,它有多慢。
%timeit df["new_column"] = df.apply(lambda row:row["Value[mm]"]*row["State of value"], axis = 1)
1min 37s ± 4.76 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
5).遍历行
双元组:
%%timeit
list_a = []
for row in df.itertuples():
list_a.append(row[2]*row[3])
df['product'] = list_a
9.81 s ± 831 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
iterrows(你可能不应该使用它):
%%timeit
list_a = []
for row in df.iterrows():
list_a.append(row[1][1]*row[1][2])
df['product'] = list_a
6min 40s ± 1min 8s per loop (mean ± std. dev. of 7 runs, 1 loop each)