В этом нет ничего плохого:
val w = Window.partitionBy($"id").orderBy($"time".asc).rangeBetween(-240*3600, 0)
data.select($"*"
mean($"errorGeneral").over(w),
min($"errorGeneral").over(w)
)
Внутренне Spark придется перетасовать и заказывать только один раз.
Если это медленно, то это потому, что оконные функции находятся наверхусамые дорогие операции в Spark, особенно с такими широкими кадрами.