1

假设具有以下格式的数据框:

关于数据的一些重要说明,数据集非常大,有数十万行,因此解决方案需要扩展。有数千家独特的商店和数千种独特的产品,每家商店都有多个日期的数据,比简单示例数据集中显示的两个多

更新到这个原始问题,因为存在一些清晰度问题: 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
4

2 回答 2

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 追加比普通追加慢,所以我们只想做一次,但它不是必需的。

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 函数将其排序为所需的配置。

于 2021-05-01T20:44:23.243 回答
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

详情

  1. 删除每个商店的所有最后日期,因为我们在下一步从一天开始增加日期。日期必须与已经存在的日期在同一间隔内。
>>> 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
  1. 从一天开始递增日期
>>> 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
  1. 查找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
  1. 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
  1. 最后,合并两个数据框dfdf1
>>> 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
于 2021-05-02T16:05:26.160 回答