【问题标题】:Why does performance decrease with the size of the data frame?为什么性能会随着数据框的大小而降低?
【发布时间】:2020-01-31 13:26:01
【问题描述】:

我正在处理一个相对较大的数据集(大约 500 万个观察值,由大约 550 家公司组成)。

我需要对每家公司使用 60 个月的滚动窗口运行 OLS 回归。当我运行以下代码时,我注意到性能非常缓慢:

for idx, sub_df in master_df.groupby("firm_id"):
    # OLS code

但是,当我首先将数据帧拆分为大约 5.5k dfs,然后对每个 dfs 进行迭代时,性能得到了显着提升。

grouped_df = master_df.groupby("firm_id")
df_list = [group for group in grouped_df]

for df in df_list:
    my_df = df[1]
    # OLS code

我说的是 1-2 周的时间 (24/7) 在第一个版本中完成,而顶部则需要 8-9 小时。

谁能解释为什么将主 df 拆分为 N 个较小的 df,然后迭代每个较小的 df 比迭代主 df 中相同数量的组执行得更好?

非常感谢!

【问题讨论】:

  • 当你运行第一个代码和第二个代码时,内存做了什么。你监控它了吗? ...此外,分组后发生的事情也可能或多或少重要,缺少# OLS code
  • 嘿。因此,每个任务管理器的内存使用率似乎保持在大约 85%。这适用于两种情况。只是在第一种情况下,它需要 AGES 才能完成,而第二种情况则要快得多。两种情况下的帖子分组代码相同。
  • 我想我需要更多的咖啡...刚刚注意到for idx, sub_df in master_df.groupby("firm_id"): 在 for 循环中运行,在 interpreted language 的循环中使用(对象)函数是 10 次中有 9 次是一个坏主意如果没有优化,是否可能需要在每个循环周期中调用(对象)函数......感觉这里也发生了......
  • 为什么你有 my_df = df[1],1 只是你想要执行 OLS 的列名吗?我想知道如果您创建一个 OLS 函数然后在 groupby 中应用 df.groupby("firm_id").apply(OLS_func),性能会如何。
  • df[1] 只是获取实际的 df,因为当我对 groupby 对象使用列表理解时,列表内部有 N 个元组,元组的元素 0 是组键,元素 1 是实际的df。

标签: python pandas performance memory


【解决方案1】:

我无法重现您的观察结果。这是一些生成数据然后分别对直接和间接方法计时的代码。在这两种情况下所花费的时间非常相似。

您是否可能在运行之间不小心按组键对数据帧进行了排序?按组键排序会导致运行时间明显不同。

否则,我开始认为您的代码中可能存在其他一些差异。如果您能发布完整的代码,那就太好了。

import numpy as np
import pandas as pd
from datetime import datetime

def generate_data():
  ''' returns a Pandas DF with columns 'firm_id' and 'score' '''
  # configuration
  np.random.seed(22)
  num_groups = 50000 # number of distinct groups in the DF
  mean_group_length = 200 # how many records per group?
  cov_group_length = 0.10 # throw in some variability in the num records per group
  
  # simulate group lengths
  stdv_group_length = mean_group_length * cov_group_length
  group_lengths = np.random.normal(
    loc=mean_group_length,
    scale=stdv_group_length,
    size=(num_groups,)).astype(int)
  group_lengths[group_lengths <= 0] = mean_group_length
  
  # final length of DF
  total_length = sum(group_lengths)
  
  # compute entries for group key column
  firm_id_list = []
  for i, l in enumerate(group_lengths):
    firm_id_list.extend([(i + 1)] * l)
    
  # construct the DF; data column is 'score' populated with Numpy's U[0, 1)
  result_df = pd.DataFrame(data={
      'firm_id': firm_id_list,
      'score': np.random.rand(total_length)
    })
    
  # Optionally, shuffle or sort the DF by group keys
  
  # ALTERNATIVE 1: (badly) unsorted df
  result_df = result_df.sample(frac=1, random_state=13).reset_index(drop=True)
  # ALTERNATIVE 2: sort by group key
  # result_df.sort_values(by='firm_id', inplace=True)
  return result_df

def time_method(df, method):
  ''' time 'method' with 'df' as its argument '''
  t_start = datetime.now()
  method(df)
  t_final = datetime.now()
  delta_t = t_final - t_start
  print(f"Method '{method.__name__}' took {delta_t}.")
  return

def process_direct(df):
  ''' direct for-loop over groupby object '''
  for group, df in df.groupby('firm_id'):
    m = df.score.mean()
    s = df.score.std()
  return

def process_indirect(df):
  ''' indirect method: generate groups first as list and then loop over list '''
  grouped_df = df.groupby('firm_id')
  group_list = [pair for pair in grouped_df]
  for pair in group_list:
    m = pair[1].score.mean()
    s = pair[1].score.std()

df = generate_data()
time_method(df, process_direct)
time_method(df, process_indirect)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-09-22
    • 2014-01-08
    • 1970-01-01
    • 1970-01-01
    • 2023-02-23
    • 2019-08-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多