I developed [Mule Deer Expert Elicitation App](https://script.google.com/macros/s/AKfycbykbBJA0vlY11n57RxHDjJ7xiBMPjOI5p_SYg7Egn7Tsj_oxi9Fap2uG5PQzyGROHzmqA/exec) while working with Colorado Parks & Wildlife and the USGS to develop a [[Habitat Quantification Tool]] (HQT) for mule deer in western Colorado. We needed to develop a new [[state and transition model]] to forecast and quantify the benefits of habitat restoration projects. The transition probabilities of state and transition models are often parameterized through expert elicitation in the absence of data. A major challenge with eliciting such expertise is that the transition probabilities are not always the intuitive way in which experts understand changes on a landscape. To help experts provide better estimates, I developed a [[data-driven web app]] using [[Plotly]] and [[base/Google Apps Script/Google Apps Script|Google Apps Script]] to instantly translate inputted transition probabilities and forage values into visualizations of expected forage value (a proxy for habitat quality) over time. The app logged the results to a Google Sheet. Under the hood, the app uses [[Markov Chain Monte Carlo]] to simulate stochastic transitions over time. It was a fun challenge to write performant algorithms in JavaScript for this project. Following a brief training, experts were able to successfully use the app independently to provide parameter estimates and a consensus was found amongst a majority of users, allowing us to populate a state and transition model for use in the HQT. ## Markov chain Monte Carlo implementation The code to run the simulations is duplicated below. Full code can be found [here](https://script.google.com/home/projects/1qFQaPko1VtWEkaZGmrf3t4pc5DBwnvOzZU1nik08N-KkmnZ01_NuuX8p/edit). ```JavaScript // TRANSITIONS and FORAGEVALUES provided as user input as dict // var TRANSITIONS = { // "A": [0.0, 0.0, .05, .05, .05], // "B": [0.0, .05, 0.0, 0.0, 0.0], // "C": [.05, 0.0, 0.0, .05, 0.0], // "D": [.05, 0.0, .05, 0.0, .05], // "E": [.05, .05, 0.0, .05, 0.0], // }; // var FORAGEVALUES = { // "A": [50, 50, 50], // "B": [50, 50, 50], // "C": [50, 50, 50], // "D": [50, 50, 50], // "E": [50, 50, 50] // } var STATES = ["A", "B", "C", "D", "E"]; var STATENAMES = { "A": "Shrubland State", "B": "Mesic Shrubland State", "C": "Grassland State", "D": "Perennial Grass Loss State", "E": "Invaded State", } var FORAGEELS = [ ['summerForageState1', 'summerForageState2', 'summerForageState3', 'summerForageState4','summerForageState5'], ['transitionForageState1', 'transitionForageState2', 'transitionForageState3', 'transitionForageState4', 'transitionForageState5'], ['winterForageState1', 'winterForageState2', 'winterForageState3', 'winterForageState4','winterForageState5'] ]; var TRANSELS = [ [false, false, 'transition1to3', 'transition1to4', 'transition1to5'], [false, false, false, false, 'transition2to5'], ['transition3to1',false, false, 'transition3to4', false], ['transition4to1', false, 'transition4to3', false, 'transition4to5'], ['transition5to1', 'transition5to2', false, 'transition5to4', false] ]; function randomChoice(arr) { return arr[Math.floor(arr.length * Math.random())]; } function rollDice(){ // get random number between 0 and 1 return Math.random(); } function testTransition(transitionProbability){ // return true if dice roll is less than equal to transition probability var diceRoll = rollDice(); if (diceRoll <= transitionProbability){ return true } } function advanceState(state, transitions){ // return next state in the simulation (example of starting at state B) var statesArray = Object.keys(transitions); // ["A", "B", "C", "D", "E"] var transitionArray = transitions[state]; // [0, 1, 0, 0, 0] // test each transition var potentialTransitions = []; transitionArray.forEach((transitionProbability, index) => { if(testTransition(transitionProbability)){ var nextState = statesArray[index]; potentialTransitions.push(nextState); } }); // select from potential transitions if (potentialTransitions.length === 0){ return state; } else { nextState = randomChoice(potentialTransitions); return nextState }; } function simulateStates(duration, initialState, transitions){ var year = 1; var stateArray = [initialState]; // run simulation while (year < duration){ currentState = stateArray.slice(-1)[0]; nextState = advanceState(currentState, transitions); stateArray.push(nextState); year += 1; } return stateArray; } function scoreState(state, seasonIndex, forageValues){ return forageValues[state][seasonIndex]; } function scoreStateArray(stateArray, seasonIndex, forageValues){ var valueArray = []; stateArray.forEach(state => { valueArray.push(scoreState(state, seasonIndex, forageValues)); }); return valueArray } function integrateValueArray(valueArray){ return valueArray.reduce((a, b) => a + b) / valueArray.length; } function runSimulation(duration, initialState, seasonIndex, forageValues, transitions){ var stateArray = simulateStates(duration, initialState, transitions); var valueArray = scoreStateArray(stateArray, seasonIndex, forageValues); var totalValue = integrateValueArray(valueArray); return { "stateArray": stateArray, "valueArray": valueArray, "totalValue": totalValue }; } function monteCarlo(repetitions, duration, initialState, seasonIndex, forageValues, transitions){ var stateArrays = []; var valueArrays = []; for(let i = 0; i < repetitions; i++){ resultsObject = runSimulation(duration, initialState, seasonIndex, forageValues, transitions); stateArrays.push(resultsObject['stateArray']); valueArrays.push(resultsObject['valueArray']); }; // reduce valueArray result = valueArrays[0].map((col, i) => valueArrays.map(row => row[i]).reduce((acc, c) => acc + c, 0) / valueArrays.length); return result } function getElementValue(el){ if(el){ return +document.getElementById(el).value // + returns as number } else { return 0 } } function getForage(){ // convert array of elements to array of values var forageValues = FORAGEELS.map(row => { return row.map(getElementValue); }); // transpose array of values (from seasons to states) var transposedArray = forageValues[0].map((_, colIndex) => forageValues.map(row => row[colIndex])); // create object var forageResults = {}; transposedArray.forEach((valueArray, index) => { state = STATES[index]; forageResults[state] = valueArray; }); return forageResults } function getTransitions(){ // get element values (integers) var transitionValues = TRANSELS.map(row => { return row.map(getElementValue) }); // update transition probability for same state transitionValues.forEach((valueArray, index) => { valueArray[index] = 100 - valueArray.reduce((acc, c) => acc + c, 0); }); var transitionResults = {}; transitionValues.forEach((valueArray, index) => { state = STATES[index]; valueArray = valueArray.map(value => value / 100); transitionResults[state] = valueArray; }); return transitionResults } ```