合并pandas数据框消耗了太多的内存。

4
我正在为我所学课程的最终项目参加这个Kaggle比赛,并且为此我试图复制这篇笔记本,但他使用的获取滞后特征的函数对我来说占用了太多内存。以下是他的代码:
def lag_feature(df, lags, col):
    tmp = df[['date_block_num','shop_id','item_id',col]]
    for i in lags:
        shifted = tmp.copy()
        shifted.columns = ['date_block_num','shop_id','item_id', col+'_lag_'+str(i)]
        shifted['date_block_num'] += i
        df = pd.merge(df, shifted, on=['date_block_num','shop_id','item_id'], how='left')
    return df

在我的电脑上失败后,我做了一些小修改来尝试减少内存使用,开始使用Google Colab,因为它比我的笔记本电脑有更多的内存,所以这是我的代码:

def lag_feature(df, lags, col):
  df = dd.from_pandas(df, chunksize=1000)
  tmp = df[['date_block_num','shop_id','item_id',col]]
  for i in lags:
    shifted = tmp[tmp.date_block_num + i <= 34].copy()
    shifted.columns = ['date_block_num','shop_id','item_id', col+'_lag_'+str(i)]
    shifted['date_block_num'] += i
    df = dd.merge(df, shifted, on=['date_block_num','shop_id','item_id'], how='left')
  return df.compute()

但仍然使用了过多的内存,到了代码使用谷歌提供的10 Gb内存的极限。

sales_train = lag_feature(sales_train, [1, 2, 3, 12, 20], 'item_cnt_month')

有什么方法可以减少内存使用吗?只是为了展示,这是我的数据框:

Int64Index: 2829445 entries, 0 to 3134798
Data columns (total 8 columns):
date                object
date_block_num      int8
item_cnt_day        float16
item_id             int16
item_price          float16
shop_id             int8
item_cnt_month      float16
item_category_id    int8
dtypes: float16(3), int16(1), int8(3), object(1)
memory usage: 152.9+ MB

补充一些信息,列'date_block_num'保存一个数字,用于标识该特征发生的月份,我想做的是从上个月获取一些数据并将其添加到该行。因此,如果我有一个滞后期为1,意味着我想为数据框中的每个产品获取一个月前的数据,并将其添加到名称为'feature_lag_1'的另一列中。例如,对于这个数据框:

         date  date_block_num  item_cnt_day  item_id  item_price  shop_id  \
0  14.09.2013               8           1.0     2848        99.0       24   
1  14.09.2013               8           1.0     2848        99.0       24   
2  14.09.2013               8           1.0     2848        99.0       24   
3  01.09.2013               8           1.0     2848        99.0       24   
4  01.09.2013               8           1.0     2848        99.0       24   

   item_cnt_month  item_category_id
0             2.0                30
1             2.0                30 
2             2.0                30 
3             2.0                30 
4             2.0                30

并且这个函数调用:

sales_train = lag_feature(sales_train, [1], 'item_cnt_month')

我想要这个输出:
         date  date_block_num  item_cnt_day  item_id  item_price  shop_id  \
0  14.09.2013               8           1.0     2848        99.0       24   
1  14.09.2013               8           1.0     2848        99.0       24   
2  14.09.2013               8           1.0     2848        99.0       24   
3  01.09.2013               8           1.0     2848        99.0       24   
4  01.09.2013               8           1.0     2848        99.0       24   

   item_cnt_month  item_category_id  item_cnt_month_lag_1  
0             2.0                30                   3.0  
1             2.0                30                   3.0  
2             2.0                30                   3.0  
3             2.0                30                   3.0  
4             2.0                30                   3.0  

1
你能提供一个包含数据和期望输出的 [mcve] 吗?我不明白为什么你需要在这里进行合并。你可能只需要使用 .shift 或者简单地使用索引将新的 Series 作为列添加,并自动对齐索引即可。 - ALollz
现在我只是在等待看看MRockilin的答案是否有效,一旦处理完成,我就可以发布更多细节,但如果它有效,我可以尝试更好地描述它。只是我的笔记本还在处理中。 - João Areias
我做了一些改动,希望能有所帮助。一旦我可以的话,我会添加示例。 - João Areias
@ALollz,如果有帮助的话,我已经添加了更多信息。 - João Areias
@Trenton_M 是的,我是。 - João Areias
显示剩余2条评论
3个回答

4
你面临的内存问题可能是由于有多个(子)相同数据帧的副本引起的。在pandas中不需要这样做,正如其他人指出的那样,.shift函数可以实现你所需的功能。
首先创建一个有两个商店(24和25)的pandas数据帧。
df = pd.DataFrame({'shop_id':[24, 24, 24, 24, 24, 25, 25, 25, 25, 25],
                   'item_id': [2000, 2000, 2000, 3000, 3000, 1000, 1000, 1000, 1000, 1000], 
                   'date_block_num': [7, 8, 9, 7, 8, 5, 6, 7, 8, 9], 
                   'item_cnt_month': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]})

+-------+-------+--------------+--------------+
|shop_id|item_id|date_block_num|item_cnt_month|
+-------+-------+--------------+--------------+
|     24|   2000|             7|             1|
|     24|   2000|             8|             2|
|     24|   2000|             9|             3|
|     24|   3000|             7|             4|
|     24|   3000|             8|             5|
|     25|   1000|             5|             6|
|     25|   1000|             6|             7|
|     25|   1000|             7|             8|
|     25|   1000|             8|             9|
|     25|   1000|             9|            10|
+-------+-------+--------------+--------------+

在商店24中,有物品2000和3000。

日期块7中有1件物品2000,日期块8中有2件物品2000,以此类推。

目标是创建一个item_cnt_month滞后列,该列具有n个月前的item_cnt_month值,对于该商店的该物品。

要创建滞后特征,可以使用以下函数。

def lag_features(df, lags, group_cols, shift_col):
    """
    Arguments:
        df (pd.DataFrame)
        lags (list((int)): the number of months to lag by
        group_cols (list(str)): the list of columns that need to be the merged key
        shift_col (str): the column name that is to be shifted by
    """

    for lag in lags:
        new_col = '{0}_lag_{1}'.format(shift_col, lag)
        df[new_col] = df.groupby(group_cols)[shift_col].shift(lag)

    return df

通过调用

lags = [1, 2]
group_cols = ['shop_id', 'item_id']
shift_col = 'item_cnt_month'
order_col = 'date_block_num' 

df = df.sort_values(by=group_cols+[order_col], ascending=True)
df = lag_features(df, lags, group_cols, shift_col)

结果如下:
+-------+-------+--------------+--------------+--------------------+--------------------+
|shop_id|item_id|date_block_num|item_cnt_month|item_cnt_month_lag_1|item_cnt_month_lag_2|
+-------+-------+--------------+--------------+--------------------+--------------------+
|     24|   2000|             7|             1|                 NaN|                 NaN|
|     24|   2000|             8|             2|                 1.0|                 NaN|
|     24|   2000|             9|             3|                 2.0|                 1.0|
|     24|   3000|             7|             4|                 NaN|                 NaN|
|     24|   3000|             8|             5|                 4.0|                 NaN|
|     25|   1000|             5|             6|                 NaN|                 NaN|
|     25|   1000|             6|             7|                 6.0|                 NaN|
|     25|   1000|             7|             8|                 7.0|                 6.0|
|     25|   1000|             8|             9|                 8.0|                 7.0|
|     25|   1000|             9|            10|                 9.0|                 8.0|
+-------+-------+--------------+--------------+--------------------+--------------------+

请注意,由于没有显式的连接操作,您需要使用 .sort_values(all key columns and date column) 将数据框正确排序。

1

调用 df.compute() 将完整结果转换为 Pandas 数据帧,因此如果您的结果不适合内存,则 Dask 无法帮助您。

相反,更常见的做法是不调用 compute,并最终计算适合内存的某种聚合,或者如果需要完整数据框,则使用类似 df.to_parquet() 的方法将其写入磁盘。


这仍然使用了太多的内存,时间太长了,难道没有其他的选择吗?最终的数据框不应该使用那么多的内存,只是多了一列而已。 - João Areias

0
该函数可在Python 3.6+中进行重写,代码如下(需要预先按顺序排列数据框):
df = df.sort_values(['date_block_num']).reset_index(drop=True)

def lag_feature(df, lags, col):
    key_columns = ['shop_id', 'item_id']
    for lag in lags:
        all_but_col = list(df.columns.difference([col]))
        df[f'{col}_lag_{lag}'] = (df.set_index(all_but_col)
                                    .groupby(level=key_columns)
                                    .shift(lag)
                                    .reset_index(drop=True))
    return df

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接