有没有办法将序列作为 Pandas dataframe.shift() 的句点传递?

Is there a way to pass a series as period for Pandas dataframe.shift()?

提问人:Aco 提问时间:6/9/2023 最后编辑:Aco 更新时间:6/9/2023 访问量:51

问:

我有一个表格,我试图通过将去年最后一个月的值合并到计算中来填充 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

Python Pandas DataFrame numpy 数据操作

评论

0赞 mozway 6/9/2023
为了清楚起见,您能提供确切的预期输出吗?(在代码开头使用以提高可重复性)np.random.seed(0)
0赞 mozway 6/9/2023
感谢您的更新,您能否再增加一年,看看 2024 年应该如何表现?

答:

0赞 Phoenix 6/9/2023 #1

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

评论

0赞 Aco 6/9/2023
嗨,凤凰城。谢谢你的回答。我想我没有明确表示我希望 2023 年全年成为 2022 年最后一个价值的产品 *2。我会纠正我的问题。
0赞 Phoenix 6/9/2023
@Aco 像这样?
0赞 mozway 6/9/2023 #2

确切的逻辑尚不清楚,但如果您想获得每年的最后一个值,将其乘以 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
0赞 Willam 6/9/2023 #3

您的方法的问题在于该操作无法按预期工作。 将按指定的周期数移动序列。在您的例子中,您尝试传递一个序列 (df['month']) 作为句点,这不会为您提供预期的结果。相反,您需要找到每年“metric_new”的最后一个值,并为 NaN 正向填充该值。shiftshift(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”值的两倍。这应该会给你带来想要的结果。