Elegant way to do the dataframe operations twice
You're indeed doing a lot of stuff twice. The first step is recognizing that you have a problem :D

The following collection of functions could be used to decrease the repetition:

def load_df(item_name):
   return df[df['item_name'] == item_name]

def get_hr_op(predicate):
   """ Predicate is a lambda """
   hr = final_df[predicate(final_df['total_hr'])]
   min_hr = hr.groupby(['subject_id','date'])['value'].min().reset_index()
   op = hr.merge(min_hr, on = ['subject_id','date','value'],how = 'inner')
   return op.set_index(['subject_id','date'])

def make_con(add_qty):
   def con(x):
      return (
         x.shift(2).ge(x.shift(1))
      ) &amp; (
         x.ge(x.shift(2).add(3))
      ) &amp; (
         x.shift(-1).ge(
            x.shift(2).add(add_qty)
         )
      )
   return con

def apply_field(name, title, add_qty):
   con = make_con(add_qty)
   op[name] = op.groupby('subject_id')['value'].transform(con).map({True:title,False:''})



You get the idea. Basically, functions are your friend. There may be some fancier way of cleaning this up using Pandas-specific stuff, but this is the Python way.