假设数据框具有以下格式:
关于数据的一些重要说明,数据集非常大,有数百万行,因此解决方案需要扩展。有数千家独特的商店和数千种独特的产品,每家商店都有跨多个日期的数据,比简单示例数据集中显示的两个多
更新此原始问题,因为存在一些清晰度问题: Pandas fill row values using previous period
d = {'store': ['s1', 's1', 's1', 's2', 's2', 's2'], 'product': ['a', 'a', 'b', 'c', 'b', 'b'], 'amount': [1, 2, 3, 5, 2, 3],'value': [1, 2, 3, 5, 2, 3], 'date': ['2020-6-6', '2020-6-7', '2020-6-7',
'2020-6-6', '2020-6-6','2020-6-7']}
df = pd.DataFrame(data=d)
print(df)
store product amount value date
0 s1 a 1 1 2020-6-6
1 s1 a 2 2 2020-6-7
2 s1 b 3 3 2020-6-7
3 s2 c 5 5 2020-6-6
4 s2 b 2 2 2020-6-6
5 s2 b 3 3 2020-6-7
对于商店 S2,产品 c 在 2020 年 6 月 7 日不再存在,我希望能够计算每种产品的百分比变化或数量差异。
例如:df['diff'] = df.groupby(['store','product'])['amount'].diff()
但是为了使其起作用并显示例如 c 的差异是 -3 和 -100%,c 需要在下一个日期出现,并且数量设置为 0
这是我要找的结果:
print(df)
store product amount value date
0 s1 a 1 1 2020-6-6
1 s1 a 2 2 2020-6-7
2 s1 b 3 3 2020-6-7
3 s2 c 5 5 2020-6-6
4 s2 b 2 2 2020-6-6
5 s2 b 3 3 2020-6-7
6 s2 c 0 0 2020-6-7
答案 0 :(得分:1)
我对您的需求以及您将收到的数据做了一些假设。第一个是您只关心填写对象有存货的第一个日期和感兴趣的最后一天之间的日期(对于我的程序来说,所有商店都被认为是相同的)。第二个是商店之间的库存不一致,但在某个时间点,所有独特的库存都会在某个时间点进行。此外,我认为丢失的库存有可能在结束日期之前的某个时间点重新进货。如果这些假设中的任何一个是错误的,它们都可以很容易地在代码中修复。为了方便复制粘贴,整个代码在下面,下面是解释。
d = {'store': ['s1', 's1', 's1', 's2', 's2', 's2'], 'product': ['a', 'a', 'b', 'c', 'b', 'b'], 'amount': [1, 2, 3, 5, 2, 3],'value': [1, 2, 3, 5, 2, 3], 'date': [6, 7, 7, 6, 6, 7]}
df = pd.DataFrame(data=d)
store_set = set(df['store'])
end_date = end_date = max(df['date'])
all_missing = []
for store in store_set:
store_rows=df.loc[df['store'] == store]
inventory = set(store_rows['product'])
for product in inventory:
product_rows=df.loc[df['product'] == product]
product_dates = set(product_rows['date'])
start_date = min(product_dates)
need_dates = set(range(start_date,end_date+1))
missing_dates = need_dates.difference(product_dates)
for missing in missing_dates:
missing_row = [store,product,0,0,missing]
all_missing.append(missing_row)
missing_frame = pd.DataFrame(all_missing, columns=df.columns)
df=df.append(missing_frame)
注意:为了其余代码的简单起见,我将日期更改为整数,但您可以非常轻松地添加代码来读取和写入字符串。
d = {'store': ['s1', 's1', 's1', 's2', 's2', 's2'], 'product': ['a', 'a', 'b', 'c', 'b', 'b'], 'amount': [1, 2, 3, 5, 2, 3],'value': [1, 2, 3, 5, 2, 3], 'date': [6, 7, 7, 6, 6, 7]}
df = pd.DataFrame(data=d)
store_set = set(df['store'])
end_date = end_date = max(df['date'])
all_missing = []
这将初始化集合并创建一组商店以及需要将库存填充到的最后日期。此外,它会创建一个空列表,其中将包含将附加到数据框的所有缺失行。这样做是因为 Pandas append 比普通 append 慢,所以我们只想做一次,但不需要。
for store in store_set:
store_rows=df.loc[df['store'] == store]
inventory = set(store_rows['product'])
for product in inventory:
product_rows=df.loc[df['product'] == product]
product_dates = set(product_rows['date'])
这些循环确定每家商店中的独特商品以及它们在哪些时间段内有货。
start_date = min(product_dates)
need_dates = set(range(start_date,end_date+1))
missing_dates = need_dates.difference(product_dates)
这将创建应该在该商店中存在该产品的一组日期(即使它为零)。为简单起见,我假设日期是连续的,但如果不是这种情况,这可以很容易地解决。
for missing in missing_dates:
missing_row = [store,product,0,0,missing]
all_missing.append(missing_row)
这会在每次缺少时将具有 0 个数量和价值的当前产品添加到当前商店
missing_frame = pd.DataFrame(all_missing, columns=df.columns)
df=df.append(missing_frame)
最后我们离开了循环并将所有缺失的数据附加到原始帧中。请注意,这显然不是按顺序排列的,但现在可以使用功能中烘焙的 Pandas 将其排序为所需的配置。
答案 1 :(得分:0)
我不知道这段代码在大规模上是否有效,但它以最少的操作完成了这项工作。
TL;TR
cols = ["store", "product", "date"]
df1 = df[df.groupby("store")["date"].apply(lambda store: store < store.max())]
df1 = df1.assign(amount=0, value=0, date=df["date"]+pd.DateOffset(days=1))
df1 = df[cols].merge(df1, on=cols, how="outer", indicator=True)
df1 = df1.loc[lambda x: x["_merge"] == "right_only"].drop(columns="_merge")
out = pd.concat([df, df1])
>>> out
store product amount value date
0 s1 a 1.0 1.0 2020-06-06
1 s1 a 2.0 2.0 2020-06-07
2 s1 b 3.0 3.0 2020-06-07
3 s2 c 5.0 5.0 2020-06-06
4 s2 b 2.0 2.0 2020-06-06
5 s2 b 3.0 3.0 2020-06-07
6 s2 c 0.0 0.0 2020-06-07
详情:
>>> df1 = df[df.groupby("store")["date"].apply(lambda store: store < store.max())]
store product amount value date
0 s1 a 1 1 2020-06-06
3 s2 c 5 5 2020-06-06
4 s2 b 2 2 2020-06-06
>>> df1 = df1.assign(amount=0, value=0, date=df["date"]+pd.DateOffset(days=1))
store product amount value date
0 s1 a 0 0 2020-06-07 # date already exist in df <- drop
3 s2 c 0 0 2020-06-07 # missing date in df <- keep
4 s2 b 0 0 2020-06-07 # date already exist in df <- drop
df1
中查找 df
中不可用的行(基于 cols
)>>> df1 = df[cols].merge(df1, on=cols, how="outer", indicator=True)
store product date amount value _merge
0 s1 a 2020-06-06 NaN NaN left_only
1 s1 a 2020-06-07 0.0 0.0 both
2 s1 b 2020-06-07 NaN NaN left_only
3 s2 c 2020-06-06 NaN NaN left_only
4 s2 b 2020-06-06 NaN NaN left_only
5 s2 b 2020-06-07 0.0 0.0 both
6 s2 c 2020-06-07 0.0 0.0 right_only # keep it, drop others
df
(right_only
指示符)中不存在的日期:>>> df1 = df1.loc[lambda x: x["_merge"] == "right_only"].drop(columns="_merge")
store product date amount value
6 s2 c 2020-06-07 0.0 0.0
df
和 df1
:>>> out = pd.concat([df, df1])
store product amount value date
0 s1 a 1.0 1.0 2020-06-06
1 s1 a 2.0 2.0 2020-06-07
2 s1 b 3.0 3.0 2020-06-07
3 s2 c 5.0 5.0 2020-06-06
4 s2 b 2.0 2.0 2020-06-06
5 s2 b 3.0 3.0 2020-06-07
6 s2 c 0.0 0.0 2020-06-07
单行版:
>>> pd.concat([df, df[cols].merge(df[df.groupby("store")["date"] \
.apply(lambda store: store < store.max())] \
.assign(amount=0, value=0, date=df["date"]+pd.DateOffset(days=1)),
on=cols, how="outer", indicator=True) \
.loc[lambda x: x["_merge"] == "right_only"] \
.drop(columns="_merge")])
store product amount value date
0 s1 a 1.0 1.0 2020-06-06
1 s1 a 2.0 2.0 2020-06-07
2 s1 b 3.0 3.0 2020-06-07
3 s2 c 5.0 5.0 2020-06-06
4 s2 b 2.0 2.0 2020-06-06
5 s2 b 3.0 3.0 2020-06-07
6 s2 c 0.0 0.0 2020-06-07