Python 一段时间内的Groupby

Python 一段时间内的Groupby,python,pandas,pandas-groupby,Python,Pandas,Pandas Groupby,我有一个表,其中包含ID、日期、目标(可能是多类的,但现在是二进制的,其中1是失败的)和基于日期列的yearmonth列。以下是此表的前8行: 一行 身份证件 日期 目标 年月 0 A. 2015-03-16 0 2015-03 1. A. 2015-05-29 1. 2015-05 2. A. 2015-08-02 1. 2015-08 3. A. 2015-09-05 1. 2015-09 4. A. 2015-09-22 0 2015-09 5. A. 2015-10-15 1. 2015

我有一个表,其中包含ID、日期、目标(可能是多类的,但现在是二进制的,其中1是失败的)和基于日期列的yearmonth列。以下是此表的前8行:

一行 身份证件 日期 目标 年月 0 A. 2015-03-16 0 2015-03 1. A. 2015-05-29 1. 2015-05 2. A. 2015-08-02 1. 2015-08 3. A. 2015-09-05 1. 2015-09 4. A. 2015-09-22 0 2015-09 5. A. 2015-10-15 1. 2015-10 6. A. 2015-11-09 1. 2015-11 7. B 2015-04-17 0 2015-04
您可以通过在
'id'
上将数据帧与其自身合并来实现这一点

首先,我们将创建一个月的第一个
“fom”
列,因为您的日期逻辑希望基于前几个月而不是具体的日期进行回顾。然后,我们将数据帧与自身合并,带上索引,以便我们最终可以分配回结果

使用月偏移量,我们可以将其过滤为仅在该行观察的3个月内保留观察值,然后我们
groupby
原始索引,并取
'target'
的平均值以获得失败百分比,我们可以将其分配回(索引对齐)

如果输出中有
NaN
,那是因为该行在前3个月内没有观察到,因此无法计算

#df['date'] = pd.to_datetime(df['date'], dayfirst = True)
df['fom'] = df['date'].astype('datetime64[M]')   # Credit @anky

df1 = df.reset_index()
df1 = (df1.drop(columns='target').merge(df1, on='id', suffixes=['', '_past']))

df1 = df1[df1.fom_past.between(df1.fom-pd.offsets.DateOffset(months=3), 
                               df1.fom-pd.offsets.DateOffset(months=1))]

df['Pct_fail'] = df1.groupby('index').target.mean()*100


如果您的内存有问题,我们可以采用非常慢的循环方法,为每一行划分子集,然后计算该子集的平均值

def get_prev_avg(row, df):
    df = df[df['id'].eq(row['id'])
            & df['fom'].between(row['fom']-pd.offsets.DateOffset(months=3), 
                                row['fom']-pd.offsets.DateOffset(months=1))]

    if not df.empty:
        return df['target'].mean()*100
    else:
        return np.NaN

#df['date'] = pd.to_datetime(df['date'], dayfirst = True)
df['fom'] = df['date'].astype('datetime64[M]')

df['Pct_fail'] = df.apply(lambda row: get_prev_avg(row, df), axis=1)

我修改了@ALollz代码,以便它更好地应用于我的原始数据集,其中我有一个多类目标,我希望获得类1和2的PctFails,加上事务的nr,我需要在不同的时间段按不同的列进行分组。此外,决定使用日期之前的最后x个月比使用日历月更简单、更好。所以我的解决方案是:

df = pd.DataFrame({'Id':['A','A','A','A','A','A','A','B'],'Type':['T1','T3','T1','T2','T2','T1','T1','T3'],'date' :['2015-03-16','2015-05-29','2015-08-10','2015-09-05','2015-09-22','2015-11-08','2015-11-09','2015-04-17'],'target':[2,1,2,1,0,1,2,0]} )
df['date'] = pd.to_datetime(df['date'], dayfirst = True)

def get_prev_avg(row, df, columnname, lastxmonths):
    df = df[df[columnname].eq(row[columnname])
                & df['date'].between(row['date']-pd.offsets.DateOffset(months=lastxmonths), 
                                    row['date']-pd.offsets.DateOffset(days=1))]
    if not df.empty:
        NrTransactions= len(df['target'])
        PctMinorFails= (df['target'].where(df['target'] == 1).count())/len(df['target'])*100
        PctMajorFails= (df['target'].where(df['target'] == 2).count())/len(df['target'])*100
        return pd.Series([NrTransactions, PctMinorFails, PctMajorFails])
    else:
        return pd.Series([np.NaN, np.NaN, np.NaN])
    
for lastxmonths in [3, 4]:
    for columnname in ['Id','Type']:
        df[['NrTransactionsBy' + str(columnname) + 'Last' + str(lastxmonths) +'Months',
            'PctMinorFailsBy' + str(columnname) + 'Last' + str(lastxmonths) +'Months',
            'PctMajorFailsBy' + str(columnname) + 'Last' + str(lastxmonths) +'Months'
           ]]= df.apply(lambda row: get_prev_avg(row, df, columnname, lastxmonths), axis=1)

我的原始数据集每次迭代都需要几个小时,虽然不是很好,但不确定如何进一步优化它。

Perfect,这正是我想要的,谢谢@Alolz。关闭,使其成为基于不同回望期的函数!谢谢again@Sam是的,这应该不会太糟糕,你基本上可以传入3个参数,一个用于df,然后另外两个用于lookback窗口(即在本例中为3和1,或者如果你总是希望回溯在前一个月结束,那么就只传入一个用于3的参数)。然后返回
df1.groupby('index').target.mean()*100
,这样当我返回到我的原始df(顺便说一句,它有大约120万行)时,您可以将其分配回,不幸的是,由于合并本身,我得到了一个内存错误:“无法为数组分配852.GiB(114287601070,)和数据类型int64@Sam哇,那么你的ID中肯定有很多重复。因为合并在
ID
中,你可以尝试通过每个ID分别进行上述操作,这样你就可以处理更小的部分。但是如果这仍然是一个问题(比如说10k行合并10k行),可能需要重新考虑行上的循环(非常慢)。我认为主要的问题是行可以属于多个组,因此
groupby
并不理想,滚动也不会喜欢非单调的日期列,因此非常棘手。谢谢,你是这样一个数据向导。你的get\u prev\u avg函数最终花费了将近3小时(160分钟)对于我的数据集来说,这并不理想,但也不是一个交易破坏者。然而,我将在一个新的答案中发布一些缺失的内容。
df = pd.DataFrame({'Id':['A','A','A','A','A','A','A','B'],'Type':['T1','T3','T1','T2','T2','T1','T1','T3'],'date' :['2015-03-16','2015-05-29','2015-08-10','2015-09-05','2015-09-22','2015-11-08','2015-11-09','2015-04-17'],'target':[2,1,2,1,0,1,2,0]} )
df['date'] = pd.to_datetime(df['date'], dayfirst = True)

def get_prev_avg(row, df, columnname, lastxmonths):
    df = df[df[columnname].eq(row[columnname])
                & df['date'].between(row['date']-pd.offsets.DateOffset(months=lastxmonths), 
                                    row['date']-pd.offsets.DateOffset(days=1))]
    if not df.empty:
        NrTransactions= len(df['target'])
        PctMinorFails= (df['target'].where(df['target'] == 1).count())/len(df['target'])*100
        PctMajorFails= (df['target'].where(df['target'] == 2).count())/len(df['target'])*100
        return pd.Series([NrTransactions, PctMinorFails, PctMajorFails])
    else:
        return pd.Series([np.NaN, np.NaN, np.NaN])
    
for lastxmonths in [3, 4]:
    for columnname in ['Id','Type']:
        df[['NrTransactionsBy' + str(columnname) + 'Last' + str(lastxmonths) +'Months',
            'PctMinorFailsBy' + str(columnname) + 'Last' + str(lastxmonths) +'Months',
            'PctMajorFailsBy' + str(columnname) + 'Last' + str(lastxmonths) +'Months'
           ]]= df.apply(lambda row: get_prev_avg(row, df, columnname, lastxmonths), axis=1)