假设我有一个学生成绩的数据框,并且想随时间跟踪他们的成绩。 DataFrame可能看起来像这样:
data = [ { "Name": "John", "Period": 1, "Grade": 60 }, { "Name": "John", "Period": 2, "Grade": 80 }, { "Name": "John", "Period": 3, "Grade": 90 }, { "Name": "Bill", "Period": 1, "Grade": 80 }, { "Name": "Bill", "Period": 2, "Grade": 70 }, { "Name": "Bill", "Period": 3, "Grade": 80 }, { "Name": "Tom", "Period": 1, "Grade": 50 }, { "Name": "Tom", "Period": 2, "Grade": 75 }, { "Name": "Tom", "Period": 3, "Grade": 50 } ]
df = pd.DataFrame(data)
df.set_index(["Name", "Period"], inplace=True)
Grade
Name Period
John 1 60
2 80
3 90
Bill 1 80
2 70
3 80
Tom 1 50
2 75
3 50
现在,我想添加一个“更改”列,该列显示每次检查的变化百分比。这些有点像堆叠的DataFrame。如果是一个,我会尝试
df["change"] = (df["Grade"] - df["Grade"].shift(1))/df["Grade"].shift(1)
这将正确地在第一行中返回NaN值,因为它没有先前的值。在上面的DataFrame上执行以下操作:
Grade change
Name Period
John 1 60 NaN
2 80 0.333333
3 90 0.125000
Bill 1 80 -0.111111
2 70 -0.125000
3 80 0.142857
Tom 1 50 -0.375000
2 75 0.500000
3 50 -0.333333
我希望每个外部索引值的第一行的“更改”值为NaN,如下所示:
Grade change
Name Period
John 1 60 NaN
2 80 0.333333
3 90 0.125000
Bill 1 80 NaN
2 70 -0.125000
3 80 0.142857
Tom 1 50 NaN
2 75 0.500000
3 50 -0.333333
稍后在汇总“更改”列时也是如此,因为一个学生的最终成绩会影响下一个学生的第一个成绩,所以不会出现任何疯狂的变化。我知道有些捷径可以简单地进行上述转换,然后将每个第一个“更改”值更改为np.nan,但感觉必须有一个更优雅的方法。
答案 0 :(得分:2)
在MultiIndex
的第一级使用GroupBy.pct_change
:
df["change"] = df.groupby(level=0)['Grade'].pct_change()
print (df)
Grade change
Name Period
John 1 60 NaN
2 80 0.333333
3 90 0.125000
Bill 1 80 NaN
2 70 -0.125000
3 80 0.142857
Tom 1 50 NaN
2 75 0.500000
3 50 -0.333333
使用DataFrameGroupBy.shift
的解决方案:
s = df.groupby(level=0)['Grade'].shift()
df["change"] = (df['Grade'] - s) / s
df["change"] = df['Grade'].div(df.groupby(level=0)['Grade'].shift()).sub(1)
并使用GroupBy.apply
:
df["change"] = df.groupby(level=0)['Grade'].apply(lambda x: (x - x.shift())/ x.shift())
更好:
df["change"] = df.groupby(level=0)['Grade'].apply(lambda x: (x / x.shift()) - 1)