A couple of suggestions for a little more generality; the input event could be used on the form itself, as it propagates the events down to the inputs. From there one can use the HTMLFormElement API to grab the sibling inputs and output.
Also when using the input event, it allows for the <output> elements to update on every change, and to show for example input errors in real-time.
For semantic markup, I would suggest using <fieldset> and <legend>, although they still suffer from getting special treatment from some browser vendors which can make styling difficult. I would also recommend using <input type="number">'s min, max and step attributes.
document.addEventListener('DOMContentLoaded', () => { const outputs = Array.from(document.querySelectorAll('output')) const averageusing = xs(thing, fun) => {fun(thing) const average let= filledary ==> xs using(ary.map(x => Number(x.value)).filter(Boolean), return xs => (filledxs.reduce((x, y) => x + y, 0) / filledxs.length).toFixed(1)) }const lastAsAverage = coll => using([...coll], xs => xs.pop().value = average(xs)) document.forms[0].addEventListener('input', ({target: {parentElement: {elements: inputs}}}) => { let elems = Array.from(elements) elems.pop().value = average(elems) outputs.slice(-1)[0].value =[inputs, average(outputsoutputs].sliceforEach(0, -1)lastAsAverage) }) }) input:invalid { background-color: #faa } <form> <fieldset> <legend>Physics:</legend> <input type="number"> <input type="number"> <input type="number"> <output></output> </fieldset> <fieldset> <legend>History:</legend> <input type="number"> <input type="number"> <input type="number"> <output></output> </fieldset> <output></output> <input type="reset"> </form>