如果找不到值,则在 Panda 中插入行
Inserting row in Panda if value can't be found
这是我的数据框:
数据框 1:
客户 C 将在 5 月之后退出,因此没有要计算平均值的行。我想:在一个月的时间里找出该 ID 的缺失值,然后添加一行零持有量,以便有一行将平均值放入:
dataframe2:
按 ID 分组并移动一个月以获取错误消息,如果有错误,请添加行。
我已经尝试过,但 gouping 和 shifting 最终导致下个月更进一步。
好的,这里有两种方法。首先,一种简单的方法,但是使用 O[n * m]
内存存储 n
个不同的日期和 m
个不同的 ID,因为它将 DataFrame 扩展为二维数组(日期 x ID)。然后,第二种方法稍微复杂一些,但只使用所需的内存(O[k]
,其中 k
是输出中的行数)。
可重现的设置
(OP注意:以后请提供一个作为问题的一部分)。
np.random.seed(0)
n = 10
t = pd.date_range('2021-01-01', '2021-07-01', freq='MS')
i = np.arange(len(t))
i = np.sort(np.r_[i, np.random.choice(i, n - len(i), True)])
df = pd.DataFrame({
'Date': t[i],
'ID': np.random.choice(list('ABC'), n),
'Holding': np.random.randint(0, 10, n),
})
>>> df
Date ID Holding
0 2021-01-01 B 6
1 2021-01-01 B 7
2 2021-02-01 C 7
3 2021-03-01 A 8
4 2021-04-01 C 1
5 2021-05-01 A 5
6 2021-05-01 A 9
7 2021-06-01 A 8
8 2021-06-01 C 9
9 2021-07-01 B 4
方法一:简单易懂,但占用内存大
# make a tmp 2D DataFrame date x ID, make sure all months are there
z = df.pivot_table(index='Date', columns='ID', values='Holding', aggfunc=sum).resample('MS').sum()
# optional: append an extra month with zeros
z = z.reindex(z.index.union([z.last_valid_index() + pd.DateOffset(months=1)])).fillna(0)
# at this point:
>>> z
ID A B C
Date
2021-01-01 0.0 13.0 0.0
2021-02-01 0.0 0.0 7.0
2021-03-01 8.0 0.0 0.0
2021-04-01 0.0 0.0 1.0
2021-05-01 14.0 0.0 0.0
2021-06-01 8.0 0.0 9.0
2021-07-01 0.0 4.0 0.0
2021-08-01 0.0 0.0 0.0
过去两个月的平均值(包括当前):
>>> z.rolling(2, min_periods=0).mean()
ID A B C
Date
2021-01-01 0.0 13.0 0.0
2021-02-01 0.0 6.5 3.5
2021-03-01 4.0 0.0 3.5
2021-04-01 4.0 0.0 0.5
2021-05-01 7.0 0.0 0.5
2021-06-01 11.0 0.0 4.5
2021-07-01 4.0 2.0 4.5
2021-08-01 0.0 2.0 0.0
将它们全部放回一起,将平均值作为第二列:
out = z.stack().to_frame('Holding').assign(mo2avg=z.rolling(2, min_periods=0).mean().stack())
# optional: remove entries where both columns are 0
out = out.loc[(out != 0).any(1)]
# and now:
>>> out
Holding mo2avg
Date ID
2021-01-01 B 13.0 13.0
2021-02-01 B 0.0 6.5
C 7.0 3.5
2021-03-01 A 8.0 4.0
C 0.0 3.5
2021-04-01 A 0.0 4.0
C 1.0 0.5
2021-05-01 A 14.0 7.0
C 0.0 0.5
2021-06-01 A 8.0 11.0
C 9.0 4.5
2021-07-01 A 0.0 4.0
B 4.0 2.0
C 0.0 4.5
2021-08-01 B 0.0 2.0
方法 2:内存精简(永远不会扩展到完整日期 x ID)
# make a tmp DataFrame with dates grouped by month start, sum Holdings
# and add a month for each ID:
z = pd.concat([
df,
(df.groupby('ID')['Date'].last() + pd.DateOffset(months=1)).reset_index().assign(Holding=0),
]).set_index('Date').groupby('ID').resample('MS').sum()
>>> z
Holding
ID Date
A 2021-03-01 8
2021-04-01 0
2021-05-01 14
2021-06-01 8
2021-07-01 0
B 2021-01-01 13
2021-02-01 0
2021-03-01 0
2021-04-01 0
2021-05-01 0
2021-06-01 0
2021-07-01 4
2021-08-01 0
C 2021-02-01 7
2021-03-01 0
2021-04-01 1
2021-05-01 0
2021-06-01 9
2021-07-01 0
请注意每个 ID
的所有“缺失”月份是如何填充的,但仅在该 ID
的最后有效日期之后最多一个月。这是计算正确滚动方式所必需的。
现在,我们可以按 'ID'
分组,但保留 'Date'
索引,因此 RollingGroupBy
是正确的:
out = z.assign(mo2avg=z.reset_index('ID').groupby('ID')['Holding'].rolling(2, min_periods=0).mean())
# optional: drop rows where both Holding and avg are 0:
out = out.loc[(out != 0).any(1)]
# swap levels and sort index to make it comparable to the result
# of method 1:
out = out.swaplevel().sort_index()
# finally:
>>> out
Holding mo2avg
Date ID
2021-01-01 B 13 13.0
2021-02-01 B 0 6.5
C 7 7.0
2021-03-01 A 8 8.0
C 0 3.5
2021-04-01 A 0 4.0
C 1 0.5
2021-05-01 A 14 7.0
C 0 0.5
2021-06-01 A 8 11.0
C 9 4.5
2021-07-01 A 0 4.0
B 4 2.0
C 0 4.5
2021-08-01 B 0 2.0
这是我的数据框: 数据框 1:
客户 C 将在 5 月之后退出,因此没有要计算平均值的行。我想:在一个月的时间里找出该 ID 的缺失值,然后添加一行零持有量,以便有一行将平均值放入:
dataframe2:
按 ID 分组并移动一个月以获取错误消息,如果有错误,请添加行。 我已经尝试过,但 gouping 和 shifting 最终导致下个月更进一步。
好的,这里有两种方法。首先,一种简单的方法,但是使用 O[n * m]
内存存储 n
个不同的日期和 m
个不同的 ID,因为它将 DataFrame 扩展为二维数组(日期 x ID)。然后,第二种方法稍微复杂一些,但只使用所需的内存(O[k]
,其中 k
是输出中的行数)。
可重现的设置
(OP注意:以后请提供一个作为问题的一部分)。
np.random.seed(0)
n = 10
t = pd.date_range('2021-01-01', '2021-07-01', freq='MS')
i = np.arange(len(t))
i = np.sort(np.r_[i, np.random.choice(i, n - len(i), True)])
df = pd.DataFrame({
'Date': t[i],
'ID': np.random.choice(list('ABC'), n),
'Holding': np.random.randint(0, 10, n),
})
>>> df
Date ID Holding
0 2021-01-01 B 6
1 2021-01-01 B 7
2 2021-02-01 C 7
3 2021-03-01 A 8
4 2021-04-01 C 1
5 2021-05-01 A 5
6 2021-05-01 A 9
7 2021-06-01 A 8
8 2021-06-01 C 9
9 2021-07-01 B 4
方法一:简单易懂,但占用内存大
# make a tmp 2D DataFrame date x ID, make sure all months are there
z = df.pivot_table(index='Date', columns='ID', values='Holding', aggfunc=sum).resample('MS').sum()
# optional: append an extra month with zeros
z = z.reindex(z.index.union([z.last_valid_index() + pd.DateOffset(months=1)])).fillna(0)
# at this point:
>>> z
ID A B C
Date
2021-01-01 0.0 13.0 0.0
2021-02-01 0.0 0.0 7.0
2021-03-01 8.0 0.0 0.0
2021-04-01 0.0 0.0 1.0
2021-05-01 14.0 0.0 0.0
2021-06-01 8.0 0.0 9.0
2021-07-01 0.0 4.0 0.0
2021-08-01 0.0 0.0 0.0
过去两个月的平均值(包括当前):
>>> z.rolling(2, min_periods=0).mean()
ID A B C
Date
2021-01-01 0.0 13.0 0.0
2021-02-01 0.0 6.5 3.5
2021-03-01 4.0 0.0 3.5
2021-04-01 4.0 0.0 0.5
2021-05-01 7.0 0.0 0.5
2021-06-01 11.0 0.0 4.5
2021-07-01 4.0 2.0 4.5
2021-08-01 0.0 2.0 0.0
将它们全部放回一起,将平均值作为第二列:
out = z.stack().to_frame('Holding').assign(mo2avg=z.rolling(2, min_periods=0).mean().stack())
# optional: remove entries where both columns are 0
out = out.loc[(out != 0).any(1)]
# and now:
>>> out
Holding mo2avg
Date ID
2021-01-01 B 13.0 13.0
2021-02-01 B 0.0 6.5
C 7.0 3.5
2021-03-01 A 8.0 4.0
C 0.0 3.5
2021-04-01 A 0.0 4.0
C 1.0 0.5
2021-05-01 A 14.0 7.0
C 0.0 0.5
2021-06-01 A 8.0 11.0
C 9.0 4.5
2021-07-01 A 0.0 4.0
B 4.0 2.0
C 0.0 4.5
2021-08-01 B 0.0 2.0
方法 2:内存精简(永远不会扩展到完整日期 x ID)
# make a tmp DataFrame with dates grouped by month start, sum Holdings
# and add a month for each ID:
z = pd.concat([
df,
(df.groupby('ID')['Date'].last() + pd.DateOffset(months=1)).reset_index().assign(Holding=0),
]).set_index('Date').groupby('ID').resample('MS').sum()
>>> z
Holding
ID Date
A 2021-03-01 8
2021-04-01 0
2021-05-01 14
2021-06-01 8
2021-07-01 0
B 2021-01-01 13
2021-02-01 0
2021-03-01 0
2021-04-01 0
2021-05-01 0
2021-06-01 0
2021-07-01 4
2021-08-01 0
C 2021-02-01 7
2021-03-01 0
2021-04-01 1
2021-05-01 0
2021-06-01 9
2021-07-01 0
请注意每个 ID
的所有“缺失”月份是如何填充的,但仅在该 ID
的最后有效日期之后最多一个月。这是计算正确滚动方式所必需的。
现在,我们可以按 'ID'
分组,但保留 'Date'
索引,因此 RollingGroupBy
是正确的:
out = z.assign(mo2avg=z.reset_index('ID').groupby('ID')['Holding'].rolling(2, min_periods=0).mean())
# optional: drop rows where both Holding and avg are 0:
out = out.loc[(out != 0).any(1)]
# swap levels and sort index to make it comparable to the result
# of method 1:
out = out.swaplevel().sort_index()
# finally:
>>> out
Holding mo2avg
Date ID
2021-01-01 B 13 13.0
2021-02-01 B 0 6.5
C 7 7.0
2021-03-01 A 8 8.0
C 0 3.5
2021-04-01 A 0 4.0
C 1 0.5
2021-05-01 A 14 7.0
C 0 0.5
2021-06-01 A 8 11.0
C 9 4.5
2021-07-01 A 0 4.0
B 4 2.0
C 0 4.5
2021-08-01 B 0 2.0