Get minimum of each group based on hour criteria using pandas
leave the original data intact

Since df is the original data, adding columns to it can have a strange side effect in another part of the calculation. Best is not to touch the original data, and do all the calculations etc in separate series or copied DataFrames

pd.grouper

You can use pd.Grouper to group per day and hour

You can group the date per subject and hour with:

df.groupby(["subject_id", pd.Grouper(key="time_1", freq="h"), "val"])


Then you don't need all those extra columns "hr", "date" and "day" anymore.
There is no reason for the column "t_d" either. since timedelta's can be counted, summed etc as well.

aggregation function

Apart from that,  you can design a specific function to get the requested minimum from one such group.

If you supply it data grouped per subject and date, dropping rows with the same val and checking whether more than 1hr has passed is a lot easier

def day_grouper(group):
    """
    Returns the minimum in `val` that lasted more than 1 hour
    """
    # drop consecutive same values.
    # The first value is retained because it becomes NaN and `NaN != 0`
    group = group.loc[group["val"].diff() != 0]
    # drop those that take less than 1hr
    more_than_1h = group["time_1"].diff().shift(-1) &gt; pd.Timedelta("1h")
    # Check whether the last value started before 23:00
    more_than_1h.iloc[-1] = group["time_1"].iloc[-1].time() &lt; datetime.time(23)
    return group.loc[more_than_1h, "val"].min()


This can then be called like this:

(
    df.groupby(["subject_id", pd.Grouper(key="time_1", freq="d")])
    .apply(day_grouper)
    .rename("val")
    .reset_index()
)


The 2 last rows are to get the format you want

code formatting

Try to follow Pep-8.

£some of your lines are really long, and convention is a space after a comma.

I use black to format my source code, so I don't have to worry about long lines, spaces, etc any more