提问人:Aco 提问时间:6/9/2023 最后编辑:Aco 更新时间:6/9/2023 访问量:51
有没有办法将序列作为 Pandas dataframe.shift() 的句点传递?
Is there a way to pass a series as period for Pandas dataframe.shift()?
问:
我有一个表格,我试图通过将去年最后一个月的值合并到计算中来填充 NaN 值。我有一个整数月份列,所以理论上按月份移动应该总是给我去年的最后一个月。但是我无法让 shift() 以系列作为其周期。
问题的最简单形式如下:
import pandas as pd
import numpy as np
np.random.seed(0)
#dataframe
start_date = '2022-01-31'
end_date = '2023-12-31'
dates = pd.date_range(start=start_date, end=end_date, freq='M')
data = {
'date': dates,
'year': dates.year,
'month': dates.month,
'metric': np.random.randint(1, 15, len(dates))
}
df = pd.DataFrame(data)
#for this exercise I want the data in 2023 to be NaN
df.loc[df['year'] == 2023, 'metric'] = np.nan
我想创建一个新列“metric_new”,将 2022 年的所有值乘以 1.5,并通过将 2022 年的最后一个metric_new值乘以 2023 年来填充 2 年的 NaN 值。
因此,如果 2022-12-31 的指标是 10,那么同一行的metric_new将是 15,而 2023-01-31 和 2023 年全年的metric_new将是 30。
如果将新数据也添加到表中,我希望这能起作用。因此,2024 年的metric_new应该取 2023 年的最后一个值并乘以 2。
我尝试了以下简单的逻辑。这个想法是获取“metric_new”字段的滞后“月”数。不幸的是,这不起作用。
df['metric_new'] = np.where(df['year'] == 2022,
1.5*df['metric'],
2*df['metric_new'].shift(period = df['month']))
生成的表应如下所示:
date year month metric metric_new
0 2022-01-31 2022 1 13.0 19.5
1 2022-02-28 2022 2 6.0 9.0
2 2022-03-31 2022 3 1.0 1.5
3 2022-04-30 2022 4 4.0 6.0
4 2022-05-31 2022 5 12.0 18.0
5 2022-06-30 2022 6 4.0 6.0
6 2022-07-31 2022 7 8.0 12.0
7 2022-08-31 2022 8 10.0 15.0
8 2022-09-30 2022 9 4.0 6.0
9 2022-10-31 2022 10 6.0 9.0
10 2022-11-30 2022 11 3.0 4.5
11 2022-12-31 2022 12 5.0 7.5
12 2023-01-31 2023 1 NaN 15.0
13 2023-02-28 2023 2 NaN 15.0
14 2023-03-31 2023 3 NaN 15.0
15 2023-04-30 2023 4 NaN 15.0
16 2023-05-31 2023 5 NaN 15.0
17 2023-06-30 2023 6 NaN 15.0
18 2023-07-31 2023 7 NaN 15.0
19 2023-08-31 2023 8 NaN 15.0
20 2023-09-30 2023 9 NaN 15.0
21 2023-10-31 2023 10 NaN 15.0
22 2023-11-30 2023 11 NaN 15.0
23 2023-12-31 2023 12 NaN 15.0
答:
shift()
方法需要 period 参数的整数值,但您传递的是 series ()。您应该这样做:df['month']
df['metric_new'] = np.where(df['year'] == 2022, 1.5 * df['metric'], np.nan)
last_value_2022 = df.loc[df['year'] == 2022, 'metric_new'].iloc[-1]
df.loc[df['year'] == 2023, 'metric_new'] = last_value_2022 * 2
df['metric_new'] = df['metric_new'].fillna(method='ffill')
根据您的评论:
date year month metric metric_new
0 2022-01-31 2022 1 10.0 15.0
1 2022-02-28 2022 2 9.0 13.5
2 2022-03-31 2022 3 4.0 6.0
3 2022-04-30 2022 4 2.0 3.0
4 2022-05-31 2022 5 7.0 10.5
5 2022-06-30 2022 6 6.0 9.0
6 2022-07-31 2022 7 5.0 7.5
7 2022-08-31 2022 8 1.0 1.5
8 2022-09-30 2022 9 8.0 12.0
9 2022-10-31 2022 10 1.0 1.5
10 2022-11-30 2022 11 2.0 3.0
11 2022-12-31 2022 12 6.0 9.0
12 2023-01-31 2023 1 NaN 18.0
13 2023-02-28 2023 2 NaN 18.0
14 2023-03-31 2023 3 NaN 18.0
15 2023-04-30 2023 4 NaN 18.0
16 2023-05-31 2023 5 NaN 18.0
17 2023-06-30 2023 6 NaN 18.0
18 2023-07-31 2023 7 NaN 18.0
19 2023-08-31 2023 8 NaN 18.0
20 2023-09-30 2023 9 NaN 18.0
21 2023-10-31 2023 10 NaN 18.0
22 2023-11-30 2023 11 NaN 18.0
23 2023-12-31 2023 12 NaN 18.0
评论
确切的逻辑尚不清楚,但如果您想获得每年的最后一个值,将其乘以 1.5,然后执行累积乘积乘以 2,您可以使用:
df['metric_new'] = df['metric'].mul(1.5)
s = (df
.sort_values(by=['year', 'month'])
.drop_duplicates(subset=['year'], keep='last')
.set_index('year')['metric_new'].fillna(2).cumprod()
)
df.loc[df['metric'].isna(), 'metric_new'] = df['year'].map(s)
输出:
date year month metric metric_new
0 2022-01-31 2022 1 13.0 19.5
1 2022-02-28 2022 2 6.0 9.0
2 2022-03-31 2022 3 1.0 1.5
3 2022-04-30 2022 4 4.0 6.0
4 2022-05-31 2022 5 12.0 18.0
5 2022-06-30 2022 6 4.0 6.0
6 2022-07-31 2022 7 8.0 12.0
7 2022-08-31 2022 8 10.0 15.0
8 2022-09-30 2022 9 4.0 6.0
9 2022-10-31 2022 10 6.0 9.0
10 2022-11-30 2022 11 3.0 4.5
11 2022-12-31 2022 12 5.0 7.5
12 2023-01-31 2023 1 NaN 15.0
13 2023-02-28 2023 2 NaN 15.0
14 2023-03-31 2023 3 NaN 15.0
15 2023-04-30 2023 4 NaN 15.0
16 2023-05-31 2023 5 NaN 15.0
17 2023-06-30 2023 6 NaN 15.0
18 2023-07-31 2023 7 NaN 15.0
19 2023-08-31 2023 8 NaN 15.0
20 2023-09-30 2023 9 NaN 15.0
21 2023-10-31 2023 10 NaN 15.0
22 2023-11-30 2023 11 NaN 15.0
23 2023-12-31 2023 12 NaN 15.0
您的方法的问题在于该操作无法按预期工作。 将按指定的周期数移动序列。在您的例子中,您尝试传递一个序列 (df['month']) 作为句点,这不会为您提供预期的结果。相反,您需要找到每年“metric_new”的最后一个值,并为 NaN 正向填充该值。shift
shift(periods)
下面是方法的修改版本:
import pandas as pd
import numpy as np
np.random.seed(0)
# Dataframe
start_date = '2022-01-31'
end_date = '2023-12-31'
dates = pd.date_range(start=start_date, end=end_date, freq='M')
data = {
'date': dates,
'year': dates.year,
'month': dates.month,
'metric': np.random.randint(1, 15, len(dates))
}
df = pd.DataFrame(data)
# For this exercise, I want the data in 2023 to be NaN
df.loc[df['year'] == 2023, 'metric'] = np.nan
# Multiply all values of 2022 by 1.5
df['metric_new'] = np.where(df['year'] == 2022, 1.5*df['metric'], df['metric'])
# Forward fill 'metric_new' for each year starting from the last month of the previous year
df['metric_new'] = df.groupby((df['year'].shift() != df['year']).cumsum())['metric_new'].ffill()
# For years > 2022, replace 'metric_new' with twice the last value from the previous year
df.loc[df['year'] > 2022, 'metric_new'] = 2 * df.loc[df['year'] == df['year'].unique()[-2], 'metric_new'].values[-1]
此脚本首先将 2022 年的“metric_new”填充为 1.5 倍的“指标”,并复制其他年份的“指标”值。然后,它从上一年的最后一个值开始,在每年内向前填充“metric_new”。最后,它将 2022 年以后年份的“metric_new”值替换为 2022 年最后一个“metric_new”值的两倍。这应该会给你带来想要的结果。
评论
np.random.seed(0)