如何为DataFrame中每个唯一ID标记sold状态前29天内的行
问题需求
给定如下结构的Pandas DataFrame:
id | date | status ________________________ ... ... ... 1 |2020-01-01 | reserve 1 |2020-01-02 | sold 2 |2020-01-01 | free 3 |2020-01-03 | reserve 3 |2020-01-25 | signed 3 |2020-01-30 | sold ... ... ... 10 |2020-01-02 | signed 10 |2020-02-15 | sold ... ... ....
需要实现:
- 找到所有
status为sold的行 - 将每个
sold行往前29天内的所有行标记为1,其余行标记为0 - 最终得到带
label列的DataFrame,示例如下:
id | date | status | label _________________________________ ... ... ... ... 1 |2020-01-01 | reserve | 1 1 |2020-01-02 | sold | 1 2 |2019-12-02 | free | 0 # no sold status for 2 3 |2020-01-03 | reserve | 1 3 |2020-01-25 | signed | 1 3 |2020-01-30 | sold | 1 ... ... ... ... 10 |2020-01-02 | signed | 0 10 |2020-02-15 | sold | 1 # more than 29 days from 2020-02-15 ... ... .... ...
输入DataFrame构造代码:
d = {'id': [1, 1, 2, 3, 3, 3, 10, 10], 'date': ['2020-01-01', '2020-01-02', '2020-01-01', '2020-01-03', '2020-01-25', '2020-01-30', '2020-01-02', '2020-02-15'], 'status': ['reserve', 'sold', 'free', 'reserve', 'signed', 'sold', 'signed', 'sold'] } df = pd.DataFrame(data=d)
原尝试代码存在错误,无法正确实现需求:
from datetime import timedelta import pandas as pd def make_labels(df): def get_indices(df): return list(df[df['date'] >= df.iloc[-1]['date'] - timedelta(days=29)].index) df.sort_values(['id', 'date'], inplace=True) zero_labels = pd.Series(0, index = df.index, name='sold_labels') one_lables = df.groupby('id')['status'].apply(lambda s: get_indices if s.iloc[-1] == 'sold').sum() zero_labels.loc[one_lables] = 1 return zero_labels df['label'] = make_labels(df)
问题分析
原代码的核心问题:
groupby('id')['status'].apply(...)中,lambda返回的是get_indices函数对象而非调用结果,且仅处理了每组最后一行是sold的情况,忽略了组内可能存在多个sold行的场景- 未将
date列转换为datetime类型,直接进行日期运算会触发错误 - 逻辑上只覆盖了每组最后一个
sold行的29天范围,不符合"所有sold行往前29天内的行都标记为1"的需求
解决方案
正确实现步骤:
- 将
date列转换为datetime类型,确保日期运算有效 - 按
id分组,对每组内的行按日期排序 - 对每组,提取所有
sold行的日期,判断每行日期是否落在任意一个sold日期的前29天到该sold日期的区间内 - 生成对应的
label列
实现代码:
from datetime import timedelta import pandas as pd # 构造输入DataFrame d = {'id': [1, 1, 2, 3, 3, 3, 10, 10], 'date': ['2020-01-01', '2020-01-02', '2020-01-01', '2020-01-03', '2020-01-25', '2020-01-30', '2020-01-02', '2020-02-15'], 'status': ['reserve', 'sold', 'free', 'reserve', 'signed', 'sold', 'signed', 'sold'] } df = pd.DataFrame(data=d) # 转换date列为datetime类型 df['date'] = pd.to_datetime(df['date']) def assign_label(group): # 按日期排序组内数据 group = group.sort_values('date').reset_index(drop=True) # 获取组内所有sold行的日期 sold_dates = group[group['status'] == 'sold']['date'] if sold_dates.empty: # 无sold行,全部标记0 group['label'] = 0 return group # 对每行判断是否在任意一个sold日期的前29天区间内 group['label'] = group['date'].apply( lambda x: any((sold_date - timedelta(days=29) <= x) & (x <= sold_date) for sold_date in sold_dates) ).astype(int) return group # 按id分组应用标签赋值逻辑 df = df.groupby('id').apply(assign_label).reset_index(drop=True) print(df)
输出结果
运行后得到的DataFrame如下:
id date status label 0 1 2020-01-01 reserve 1 1 1 2020-01-02 sold 1 2 2 2020-01-01 free 0 3 3 2020-01-03 reserve 1 4 3 2020-01-25 signed 1 5 3 2020-01-30 sold 1 6 10 2020-01-02 signed 0 7 10 2020-02-15 sold 1
内容的提问来源于stack exchange,提问作者Michael
相关产品推荐
相关产品推荐

