Pandas Dataframe 循环遍历列效率低下

Posted

技术标签:

【中文标题】Pandas Dataframe 循环遍历列效率低下【英文标题】:Pandas Dataframe inefficient for loop through columns 【发布时间】:2019-11-14 02:10:04 【问题描述】:

我有每个单元格和日期的降水数据(1800 行和 15k 列)。

                          486335  486336  486337
2019-07-03 13:35:54.445       0       2      22
2019-07-04 13:35:54.445       0       1       1
2019-07-05 13:35:54.445      16       8      22
2019-07-06 13:35:54.445       0       0       0
2019-07-07 13:35:54.445       0      11       0

我想找出达到特定降雨量 (>15mm) 的日期,并计算该事件发生后降雨量较少 (

我编写了一个 for 循环来完成这项工作,但需要几天时间才能完成 ;(。我是 python 的初学者,所以也许有一些其他方法的提示。

from datetime import datetime, timedelta, date
import datetime
import pandas as pd

#Existing Data
index_dates =  pd.date_range(pd.datetime.today(), periods=10).tolist()
df = pd.DataFrame('486335':[0,0,16,0,0,0,2,1,8,2],'486336':[2,1,8,0,11,16,0,1,6,8],'486337':[22,1,22,0,0,0,5,3,6,1],index=index_dates)
columns = df.columns 
counter_columns = 0

iteration = -1 #Iterations Steps
counter = 10 #10 precipitation values per column
duration = 0 #days with no or less than pp_max_1 rain 
count = False

index_list = df.index #Index for updating df / Integear
period_range = 0  #Amount of days after Event without much rain Integear
period_amount = 0 #Amount of PP in dry days except event Integear
event_amount = 0.0  #Amount of heavy rainfall on the event date Float
pp = 0 #actual precipitation
pp_sum = 0.0 #mm
pp_min = 15.0 #mm min pp for start to count dry days until duration_min_after
pp_max_1 = 0.11 #max pp for 1 day while counting dry days
dry_days = 0 #dry days after event

for x in df:
    for y in df[x]:
        iteration = iteration + 1
        if iteration == counter:
            iteration = 0
            counter_columns = counter_columns + 1
            print("column :",counter_columns, "finished")
        if y >= pp_min and count == False:
            duration = duration + 1
            count = True
            start_period = index_list[iteration]
            event_amount = y
            index = iteration
            pp_sum = pp_sum + y
        elif y >= pp_min and count == True or y >= pp_max_1 and count == True:
            end_period = index_list[iteration]
            dry_periods = dry_periods.append("start_period":start_period ,"end_period":end_period,"period_range":duration,"period_amount":pp_sum ,"event_amount":event_amount, "cell":columns[counter_columns],ignore_index=True).sort_values('period_range',ascending=False)
            duration = 0
            count = False
            pp_sum = 0
        elif pp <= pp_max_1 and count == True:
            duration = duration + 1
            pp_sum = pp_sum + y
        else:
            continue
print(dry_periods)

输出如下所示

start_period              end_period period_range  \
0  2019-07-05 13:15:05.545 2019-07-09 13:15:05.545            4   
1  2019-07-05 13:15:05.545 2019-07-09 13:15:05.545            4   
2  2019-07-05 13:15:36.569 2019-07-09 13:15:36.569            4   
3  2019-07-05 13:15:36.569 2019-07-09 13:15:36.569            4   
4  2019-07-05 13:16:16.372 2019-07-09 13:16:16.372            4   
5  2019-07-05 13:16:16.372 2019-07-09 13:16:16.372            4   


    period_amount event_amount    cell  
0            16.0           16  486335  
1            22.0           22  486337  
2            16.0           16  486335  
3            22.0           22  486337  
4            16.0           16  486335  
5            22.0           22  486337  

【问题讨论】:

@Hanggy 问:列里面是什么? (由于声誉无法评论) 总而言之,它看起来并没有那么糟糕。我只能想象,索引访问可能会消耗时间。如果您将“for y in df[x]:”替换为“for period, y in df[x].items():”并在您当前执行的所有位置设置“start_period=period”,您能否尝试一下性能如何变化索引访问(end_period 同样)?我希望表现更好。这样,您还可以摆脱“迭代”变量以及与之相关的技术代码。 还有一件事,我认为您也可以摆脱“如果迭代 == 计数器:”。我宁愿将这里执行的代码添加到外循环中(在内循环后面)。可能这并没有节省很多运行时间,但它使代码更容易理解和更容易维护,因为在进入循环之前你不需要知道代码执行了多少次迭代(我认为你可以摆脱“计数器”变量也是这样)。 python 中的循环是最节省内存的解决方案,总是尝试构建数据帧的矢量化解决方案,在您的数据帧中,rain >15 和rain 完全矢量化是不可能的,但是 serge ballesta 给你一个很好的方法 【参考方案1】:

您可以避免对行进行迭代,因为它不适用于大型数据帧。

这是一种不同的方法,不确定它是否对您的完整数据框更有效:

periods=[]
for cell in df.columns:
    sub = pd.DataFrame('amount': df[cell].values, index=df.index)
    sub['flag'] = pd.cut(sub['amount'], [0.11, 15, np.inf],
                         labels=[0, 1]).astype(np.float)
    sub.loc[sub.flag>0, 'flag']=sub.loc[sub.flag>0, 'flag'].cumsum()
    sub.flag.ffill(inplace=True)
    x = sub[sub.flag>0].reset_index().groupby('flag').agg(
        'index':['min', 'max'], 'amount': 'sum')
    x.columns = ['start', 'end', 'amount']
    x['period_range'] = (x.end - x.start).dt.days + 1
    x['cell'] = cell
    x.reindex(columns=['start', 'end', 'period_range', 'cell'])
    periods.append(x)

resul = pd.concat(periods).reset_index(drop=True)

【讨论】:

不错!你真的需要上面的填充物吗?如果您跳过 loc[sub.flag>0 并且还对零求和,它不会给出相同的结果吗? 周期长度是从第一个周期开始到最后一个周期结束的长度,对吧? @jottbe:问题是 0.11 到 15 之间的任何值都会中断当前的干燥期而不开始新的组。 周期长度是事件开始到该事件最后一天之间的天数 + 1。 不错的解决方案。到目前为止,我还没有遇到过 pd.cut 。我相信它会让我的生活更简单。但是,当多个事件发生在同一列中时,您如何处理呢?还是已经这样做了?【参考方案2】:

因为我没有你的整个数据集,所以我不能说是什么消耗了时间,但我猜这是因为索引访问,当你获取周期和你在循环中执行的排序操作时。也许您想尝试以下代码。 它应该在逻辑上等同于您的代码,除了一些更改:

duration = 0 #days with no or less than pp_max_1 rain 
count = False

index_list = df.index #Index for updating df / Integear
period_range = 0  #Amount of days after Event without much rain Integear
period_amount = 0 #Amount of PP in dry days except event Integear
event_amount = 0.0  #Amount of heavy rainfall on the event date Float
pp = 0 #actual precipitation
pp_sum = 0.0 #mm
pp_min = 15.0 #mm min pp for start to count dry days until duration_min_after
pp_max_1 = 0.11 #max pp for 1 day while counting dry days
dry_days = 0 #dry days after event
dry_periods= list()

for counter_columns, column in enumerate(df.columns, 1):
    for period, y in df[column].items():
        if not count and y >= pp_min:
            duration += 1
            count = True
            start_period = period
            event_amount = y
            pp_sum += y
        elif count and (y >= pp_min or y >= pp_max_1):
            end_period = period
            dry_periods.append(
                    "start_period":  start_period ,
                    "end_period":    end_period,
                    "period_range":  duration,
                    "period_amount": pp_sum ,
                    "event_amount":  event_amount, 
                    "cell":          column)
            duration = 0
            count =    False
            pp_sum =   0
        elif count and pp <= pp_max_1:
            duration += 1
            pp_sum   += y
    print("column :",counter_columns, "finished")

dry_periods.sort(key=lambda record: record['period_range'])
print(dry_periods)

变化如下:

删除了 index_list[iteration] 访问,我认为这可能会花费一些时间 删除了整个迭代计数器逻辑,因为与之相关的逻辑可以放在内部循环之外,这样内部循环就会变得更小,尽管它可能并没有真正提高性能 比较 count == True 不是必须的,你可以在 if 子句中写 count 代替 将增量和求和逻辑从 var = var + num 更改为 var += num(这可能是个人喜好问题,如果您愿意,也可以跳过它,它不会对性能产生如此大的影响) 然后我将你的 dry_periods 的排序逻辑放在循环之外,因为在我看来你的循环逻辑不依赖于要排序的集合 --> 也许这甚至是对性能的最大影响

顺便说一句。因为我不知道dry_periods到底是怎么定义的,所以我只是把它当作一个列表。也请看看条件

elif count and (y >= pp_min or y >= pp_max_1):

以上。这对我来说看起来很可疑,但这只是你程序中重写的条件。如果没问题,可能你可以去掉其中一个比较,因为我猜是pp_min

【讨论】:

以上是关于Pandas Dataframe 循环遍历列效率低下的主要内容,如果未能解决你的问题,请参考以下文章

Python Pandas 遍历DataFrame的正确姿势 速度提升一万倍

Python Pandas 遍历DataFrame的正确姿势 速度提升一万倍

pandas.DataFrame.loc好慢,怎么遍历访问DataFrame比较快

pandas.DataFrame.loc好慢,怎么遍历访问DataFrame比较快

pandas.DataFrame.loc好慢,怎么遍历访问DataFrame比较快

Python/Pandas 遍历列