【问题标题】:Conditional shift: Subtract 'previous row value' from 'current row value' with multiple conditions in pandas条件移位:在熊猫中使用多个条件从“当前行值”中减去“上一行值”
【发布时间】:2021-07-29 10:05:19
【问题描述】:

我有以下数据框:

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. 如果没有要减去的前行值,则仅输入“HeartRate”值。

期望的输出:

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. 检查时间线的连续性(下个月与否)。

有没有更聪明的方法?任何帮助,将不胜感激。谢谢!

【问题讨论】:

  • 这是一个奇怪的操作。此外,您的日期看起来不正常
  • 你考虑过年假的情况吗?在示例数据中添加 2 行:Covid 92 Texas 2020-12-31 2020-01-31Covid 93 Texas 2021-01-31 2021-02-28 。这 2 行是连续的几个月,应该显示 HearRateDiff of 1。然后,您可以查看哪些解决方案给出了正确的结果。
  • 实际上,使用 2 个月数(例如 1 表示 1 月,12 表示 12 月)检查连续月份的解决方案相差 1 将根本无法处理年假(月份数从 12 变为 1 ) 并给出错误的结果。
  • 这是一个很好的观察@SeaBean。谢谢你的告知。非常感激。但是,请您看一下这个问题:stackoverflow.com/questions/67453646/…

标签: python pandas dataframe time-series


【解决方案1】:

你可以试试这样的:

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() 选项让我有机会尝试不同的值。欣赏!
  • 嗨@Pygirl。你会如何解决这个问题:stackoverflow.com/questions/67453646/…
【解决方案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

【讨论】:

    【解决方案3】:

    您可以通过.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) 首先,我们确保日期列是日期时间格式而不是字符串:

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

    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 来实现相同的结果。

    (3) 时间线的连续性(下个月与否)由以下条件检查:

    df['MonthStart'].groupby([df['Disease'], df['State']]).transform('diff').lt(np.timedelta64(2,'M'))
    

    我们检查与上一个日期(组内)的时间差严格小于 2 个月,以确保它在下个月。我们无法检查 此检查也适用于年假(从 12 月到 1 月),其中仅使用月份数字(从 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 你终于重新考虑使用可以处理年假的解决方案了吗?您很可能会在数据中遇到这种情况。如果是,请再次选择并选择适合您的解决方案。我坚持在这里,因为我不希望后续有类似问题的人,因为您会通过查看已接受的解决方案来选择错误的解决方案。感谢您的合作!
    【解决方案4】:

    我使用了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'])
    

    最后,用 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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-23
      • 2015-07-27
      • 2018-11-15
      • 2018-08-06
      相关资源
      最近更新 更多