在pandas中,有多个条件时,可以使用条件移位来从当前行值中减去“上一行值”。

4

我有以下数据框:

Disease     HeartRate   State    MonthStart   MonthEnd    
Covid       89          Texas    2020-02-28   2020-03-31      
Covid       91          Texas    2020-03-31   2020-04-30     
Covid       87          Texas    2020-07-31   2020-08-30      
Cancer      90          Texas    2020-02-28   2020-03-31 
Cancer      88          Florida  2020-03-31   2020-04-30      
Covid       89          Florida  2020-02-28   2020-03-31      
Covid       87          Florida  2020-03-31   2020-04-30      
Flu         90          Florida  2020-02-28   2020-03-31        

我需要在“心脏”列中从“当前行”减去“上一行”的值,并创建一个新的列。

但是,有一些条件:

  1. 仅当“疾病”和“状态”列具有相同的值时,才会减去行值。
  2. 仅当行在连续的月份中时,才会减去行值。如果时间线上有断点,则不会减去值。
  3. 如果没有要减去的上一行值,则只放置“心率”值。

期望输出结果:

Disease     HeartRate   State    MonthStart   MonthEnd     HeartRateDiff
Covid       89          Texas    2020-02-28   2020-03-31    89      
Covid       91          Texas    2020-03-31   2020-04-30    2     
Covid       87          Texas    2020-07-31   2020-08-30    87      
Cancer      90          Texas    2020-02-28   2020-03-31    90 
Cancer      88          Florida  2020-03-31   2020-04-30    88          
Covid       89          Florida  2020-02-28   2020-03-31    89      
Covid       87          Florida  2020-03-31   2020-04-30    -2      
Flu         90          Florida  2020-02-28   2020-03-31    90      

我知道如何使用以下代码从当前行中减去前一行:

df[‘DiffHeartRate’] = df.groupby(['Disease', 'State'])['HeartRate'].transform(pd.Series.diff)

然而,我目前遇到两个问题:

  1. 如果没有上一行可供相减,如何保持相同的值。
  2. 检查时间轴的连续性(是否为下一个月)。

有没有更好的方法?任何帮助都将不胜感激。谢谢!


这是一个奇怪的操作。而且,你的日期看起来顺序不对。 - anon01
你是否考虑过年份跨度的情况?在你的示例数据中添加两行:Covid 92 Texas 2020-12-31 2020-01-31Covid 93 Texas 2021-01-31 2021-02-28。这两行数据是相邻月份的,应该显示HearRateDiff1。然后可以查看哪些解决方案给出了正确的结果。 - SeaBean
实际上,使用2个月份数字(例如1表示一月,12表示十二月)检查连续月份的解决方案会在年份变化时(月份从12变为1)失败并给出错误的结果。 - SeaBean
这是一个很好的观察 @SeaBean。谢谢你的通知,非常感谢。不过,你能否看一下这个问题:https://stackoverflow.com/questions/67453646/create-new-dataframe-using-multiple-conditions-across-different-timeline-and-loc - Roy
4个回答

2
我已经使用了 groupbynp.wheredf.fillna() 的组合来完成你的任务。
可能还有更高效的方法,但我希望这可以帮到你。 输入 df
Disease HeartRate   State   MonthStart  MonthEnd
0   Covid   89  Texas   2020-02-28  2020-03-31
1   Covid   91  Texas   2020-03-31  2020-04-30
2   Covid   87  Texas   2020-07-31  2020-08-30
3   Cancer  90  Texas   2020-02-28  2020-03-31
4   Cancer  88  Florida 2020-03-31  2020-04-30
5   Covid   89  Florida 2020-02-28  2020-03-31
6   Covid   87  Florida 2020-03-31  2020-04-30
7   Flu 90  Florida 2020-02-28  2020-03-31

获取 HeartRateDiff,就像你以前做过的那样。
df['DiffHeartRate'] = df.groupby(['Disease', 'State'])['HeartRate'].transform(pd.Series.diff)

对于连续的月份,我会将上个月的值作为一个列添加进去。
然后使用np.where简单地检查这些月份是否是连续的。
df['MonthStart'] = pd.to_datetime(df['MonthStart'])
df['PrevMonth'] = df['MonthStart'].shift().dt.month
df['DiffHeartRateFinal'] = np.where(df['PrevMonth']==df['MonthStart'].dt.month-1, df['DiffHeartRate'], df['HeartRate'])

最后,用心率填充所有的NAN值。
df['DiffHeartRateFinal'] = df['DiffHeartRateFinal'].fillna(df['HeartRate'])

输出

Disease HeartRate   State   MonthStart  MonthEnd    DiffHeartRateFinal
Covid   89  Texas   2020-02-28  2020-03-31  89.0
Covid   91  Texas   2020-03-31  2020-04-30  2.0
Covid   87  Texas   2020-07-31  2020-08-30  87.0
Cancer  90  Texas   2020-02-28  2020-03-31  90.0
Cancer  88  Florida 2020-03-31  2020-04-30  88.0
Covid   89  Florida 2020-02-28  2020-03-31  89.0
Covid   87  Florida 2020-03-31  2020-04-30  -2.0
Flu     90  Florida 2020-02-28  2020-03-31  90.0

2

尝试:

import numpy as np

df.MonthStart = pd.to_datetime(df.MonthStart)
df.MonthEnd = pd.to_datetime(df.MonthEnd)


def cal_diff(x):
    x['DiffHeartRate'] = np.where(x['MonthEnd'].shift().dt.month.eq(
        x['MonthStart'].dt.month), x['HeartRate'].diff(), x['HeartRate'])
    return x


df = df.groupby(['Disease', 'State']).apply(cal_diff)

输出

  Disease  HeartRate    State MonthStart   MonthEnd DiffHeartRate
0   Covid         89    Texas 2020-02-28 2020-03-31            89
1   Covid         91    Texas 2020-03-31 2020-04-30             2
2   Covid         87    Texas 2020-07-31 2020-08-30            87
3  Cancer         90    Texas 2020-02-28 2020-03-31            90
4  Cancer         88  Florida 2020-03-31 2020-04-30            88
5   Covid         89  Florida 2020-02-28 2020-03-31            89
6   Covid         87  Florida 2020-03-31 2020-04-30            -2
7     Flu         90  Florida 2020-02-28 2020-03-31            90

2
您可以尝试类似以下的方法:

您可以尝试类似以下的方法:

df['DiffHeartRate']=(df.groupby(['Disease', 'State', 
          (df.MonthStart.dt.month.ne(df.MonthStart.dt.month.shift()+1)).cumsum()])['HeartRate']
 .apply(lambda x: x.diff())).fillna(df.HeartRate)

    Disease HeartRate   State   MonthStart  MonthEnd    DiffHeartRate
0   Covid   89          Texas   2020-02-28  2020-03-31  89.0
1   Covid   91          Texas   2020-03-31  2020-04-30  2.0
2   Covid   87          Texas   2020-07-31  2020-08-30  87.0
3   Cancer  90          Texas   2020-02-28  2020-03-31  90.0
4   Cancer  88          Florida 2020-03-31  2020-04-30  88.0
5   Covid   89          Florida 2020-02-28  2020-03-31  89.0
6   Covid   87          Florida 2020-03-31  2020-04-30  -2.0
7   Flu     90          Florida 2020-02-28  2020-03-31  90.0

逻辑和其他答案相同,但表达方式不同。


非常感谢你,@Pygirl。'ne' 的想法很棒,我学到了。另外,fillna() 选项让我有机会尝试不同的值。非常感激! - Roy
嗨@Pygirl。你会如何处理这个问题:https://stackoverflow.com/questions/67453646/create-new-dataframe-using-multiple-conditions-across-different-timeline-and-loc - Roy

2
您可以通过以下方式使用 .mask().groupby().transform() 来实现:
df['HeartRateDiff'] = (df['HeartRate'].mask(
                           df['MonthStart'].groupby([df['Disease'], df['State']]).transform('diff').lt(np.timedelta64(2,'M')),
                           df.groupby(['Disease', 'State'])['HeartRate'].transform('diff')
                           )
                      )

细节:

(1) 首先,我们确保日期列的格式为datetime,而不是字符串:

如果您的日期列已经是datetime格式,则可以跳过此步骤。

df['MonthStart'] = pd.to_datetime(df['MonthStart'])
df['MonthEnd'] = pd.to_datetime(df['MonthEnd'])

(2) 心率变化(组内)的获取方式为:
df.groupby(['Disease', 'State'])['HeartRate'].transform('diff')

我们可以在`.transform()`中简单地使用`'diff'`,而不是使用`pd.Series.diff`来实现相同的结果。
时间线的连续性(下个月或否)由以下条件检查:
df['MonthStart'].groupby([df['Disease'], df['State']]).transform('diff').lt(np.timedelta64(2,'M'))

我们检查前一日期(在同一组内)的时间差严格小于2个月,以确保它在下一个月内。 我们不能检查<= 1个月,因为某些连续两个月份之间的日期差可以达到32天。 请注意,此检查也适用于年度转换(从12月到1月),仅使用月份数字的逻辑检查会导致错误结果(4)最后,我们通过对现有列 HeartRate 使用 .mask() 获得新列: .mask() 在其第一个参数中测试条件,并在条件为真时将行替换为其第二个参数中的值。 当不满足条件时,它保留行的原始值。 因此,实现了我们对值的条件替换目标。 输出:
  Disease  HeartRate    State MonthStart   MonthEnd  HeartRateDiff
0   Covid         89    Texas 2020-02-28 2020-03-31             89
1   Covid         91    Texas 2020-03-31 2020-04-30              2
2   Covid         87    Texas 2020-07-31 2020-08-30             87
3  Cancer         90    Texas 2020-02-28 2020-03-31             90
4  Cancer         88  Florida 2020-03-31 2020-04-30             88
5   Covid         89  Florida 2020-02-28 2020-03-31             89
6   Covid         87  Florida 2020-03-31 2020-04-30             -2
7     Flu         90  Florida 2020-02-28 2020-03-31             90

真的很棒,@SeaBean。您解释问题的方式使我更好地理解了它。我也学会了“lt”的用法。 - Roy
@Roy 很高兴能帮忙!我认为你需要选择一个适用于年度断点的解决方案,因为这种情况在你的数据中很可能会发生。你是否可以重新考虑接受的答案解决方案? - SeaBean
你好@Roy,你最近是否重新考虑使用一个可以应对年份变化的解决方案呢?在数据处理中,这种情况很可能会出现。如果是的话,请再次选择并选择适合你的解决方案。我在这里坚持这一点,因为我不希望后续遇到类似问题的人会通过已接受的解决方案选错方案。感谢你的合作! - SeaBean

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接