In this version we set aside almost all of the predictive modeling we used on Election Night and in the first week of declaration vote counting.
Now there is very little uncertainty left in vote counts and we’ve seen the Y/N split in almost divisions for all types of declaration votes. Further, we also have a good empirical handle on how much vote is yet to be counted (in the “awaiting processing” counts of declaration votes provided by the AEC) and how much of that will be added to the count, how much will be formal, and the Y/N split.
In short, at this late stage of the count we largely have arithmetic to perform, not predictive modeling.
Code
functionaddLoessRegression(Plot) { Plot.loess=function ({ x, y, bandwidth, order,...options }) {console.log("greetings from Plot.loess");const z = options.z|| options.stroke;// maybeZreturn Plot.transform(options,function (data, facets) {//var points = loess_best(data,x,y);const Z = Plot.valueof(data, z);const X = Plot.valueof(data,x);const Y = Plot.valueof(data,y);const loess_data = [];for(let i =0; i < X.length; i++) { loess_data.push([X[i],Y[i]]); }console.log("Plot.loess has constructed data:")console.log(loess_data);const regFacets = [];const points_out = [];var loess_data_subset = [];//console.log(loess_data_subset);for (const facet of facets) {const regFacet = [];for (const I of Z ? d3.group(facet, (i) => Z[i]).values() : [facet]) {//console.log("I:");console.log(I.length); loess_data_subset =Array(I.length);//console.log("loess_data_subset")//console.log(loess_data_subset);for(let j =0; j < I.length; j++){ //console.log(j + " " + I[j]);//console.log(loess_data[I[j]]); loess_data_subset[j] = loess_data[I[j]]; };//console.log(loess_data_subset);const reg =loess_best(loess_data_subset);for( const d of reg){const j = points_out.push(d) -1;if (z) d[z] = Z[I[0]]; regFacet.push(j); } } regFacets.push(regFacet); }//console.log("Plot.loess is returning points:")//console.log(points_out);return { data: points_out,facets: regFacets }; }); };return Plot;}functionloess_best(data){//console.log("loess_best has been passed data:")//console.log(data);const localData = data.filter(d =>!isNaN(d[0]) &!isNaN(d[1]))//console.log(localData);const grid =loess_grid(localData).sort(function(x,y){return d3.ascending(x.gcv,y.gcv);})//console.log("loess_best has grid:")//console.log(grid);const bw = grid.length>0? grid[0].bw:0.5;console.log("bandwidth selected: "+ bw);// fit loess with this bandwidthconst f = d3.regressionLoess().x(d=>d[0]).y(d=>d[1]).bandwidth(bw) (localData);//console.log(f);return f;}functionloess_resid(data,f){const y_vec = data.map(d=>d[1])const yhat = f.map(d=>d[1])const n = data.lengthvar e =newArray(n)for(let i =0; i < n; i++){ e[i] = y_vec[i] - yhat[i]; }return e;}functionloess_trace(data,bandwidth){//console.log("loess_trace has bandwidth " + bandwidth);const loess_data = data.slice().sort(function(x,y){return d3.ascending(x[0],y[0]);})const x_vec = loess_data.map(d => d[0]);//console.log("loess_trace x");//console.log(x_vec);const M =150;const n = x_vec.length> M ? M : x_vec.length;var index =newArray(n);if(n > M){for(let i =0; i < n; i++){ index[i] =Math.floor(i * x_vec.length/ n); } } else {for(let i =0; i < n; i++){ index[i] = i; } }var trace =0;var y_tmp =newArray(n).fill(0);var tmpData = [];var f;for(let i=0; i<n; i++){ tmpData.push([x_vec[index[i]],0]); }// step through indicesfor(let i=0; i<n; i++){ tmpData[i][1] =1.0; f = d3.regressionLoess().x(d=>d[0]).y(d=>d[1]).bandwidth(bandwidth) (tmpData); trace += f.map(d => d[1])[i]; tmpData[i][1] =0.0; }return trace;}functionloess_gcv(data,bw){//console.log("loess_gcv has bandwidth " + bw);const loess_data = data.slice().sort(function(x,y){return d3.ascending(x[0],y[0]);})const f = d3.regressionLoess().x(d=>d[0]).y(d=>d[1]).bandwidth(bw)(loess_data);const e =loess_resid(loess_data,f);//console.log("loess_gcv has residuals");//console.log(e);const v =loess_trace(loess_data,bw);const n = e.lengthvar num =0;for(let i=0; i<n; i++){ num +=Math.pow(e[i],2); }const gcv = (num/n)/Math.pow(1- v/n,2);return gcv;}functionloess_grid(data){//console.log("loess_grid has been passed data:");//console.log(data);const bw = d3.range(.05,1,.05);//console.log("loess_grid: bandwidths to assess");//console.log(bw);var out =newArray(bw.length);for(let i=0; i<bw.length; i++){//console.log("loess_grid: assessing with bandwidth " + bw[i]); out[i] = {bw: bw[i],//sse: loess_sse(data,bw[i],xDim,yDim),//trace: loess_trace(data,x,y,bw[i]),gcv:loess_gcv(data,bw[i]) } }//console.log("loess_grid will return")//console.log(out);return out.filter(d =>!isNaN(d.gcv));}functionaddRegression(Plot) { Plot.regression=function ({ x, y, type, bandwidth, order,...options }) { type =String(type).toLowerCase();const regressor = type ==="quad"? reg.regressionQuad(): type ==="poly"? reg.regressionPoly(): type ==="pow"? reg.regressionPow(): type ==="exp"? reg.regressionExp(): type ==="log"? reg.regressionLog(): type ==="loess"? reg.regressionLoess(): reg.regressionLinear();if (bandwidth && regressor.bandwidth) regressor.bandwidth(bandwidth);if (order && regressor.order) regressor.order(order);const z = options.z|| options.stroke;// maybeZreturn Plot.transform(options,function (data, facets) {const X = Plot.valueof(data, x);const Y = Plot.valueof(data, y);const Z = Plot.valueof(data, z); regressor.x((i) => X[i]).y((i) => Y[i]);const regFacets = [];const points = [];for (const facet of facets) {const regFacet = [];for (const I of Z ? d3.group(facet, (i) => Z[i]).values() : [facet]) {const reg =regressor(I);for (const d of reg) {const j = points.push(d) -1;if (z) d[z] = Z[I[0]]; regFacet.push(j); } } regFacets.push(regFacet); }return { data: points,facets: regFacets }; }); };return Plot;}functionmarkTransition({key,selector,attributes,duration}) {let currentStateMap =null;functionrenderWrapper(target) { returnfunctionrender(...args) {let rootElement = target.render(...args);/* Actual render function */let lastStateMap = currentStateMap;let data = target.data; currentStateMap =newMap(); d3.select(rootElement).selectAll(selector).each(function(d){ /* Store new values in currentStateMap */let attr = {};for(let attrName of attributes) { attr[attrName] = d3.select(this).attr(attrName) } currentStateMap.set(key(data[d]),attr); }).each(function(d){ /* Assign values from last frame */let attrs = lastStateMap?.get(key(data[d]));if(attrs) {for(let attrName in attrs) { d3.select(this).attr(attrName,attrs[attrName]); } } }).transition() /* (Re)assign new values */.duration(duration).call(s => { attributes.forEach(attrName => { s.attr(attrName,d=>currentStateMap.get(key(data[d]))[attrName]); }) });return rootElement; } }returnfunction(mark) {returnnewProxy(mark,{get:(target, name)=>name==="render"?renderWrapper(target):target[name]}); }}
Timestamp of latest results: 15:56:49 05 November 2023 AEDT
1 How much vote is in?
PP = Polling Place including declaration vote types (postal votes, absentees etc) as among a division’s “polling places”
Reporting includes partial/incomplete reports; n.b., counting of declaration vote types takes place over weeks after Election Night.
2 How much vote its yet to be counted?
Counted provides a measure of the progress of the count, along with the Processed and Awaiting columns.
Postal ballots tend to be processed first in the post-election period, as many postal ballots are returned prior to Election Day or shortly thereafter. Attention then turns to other forms of declaration voting that historically have formed a much smaller portion of the total votes. Provisional ballots have had very low acceptance rates in recent elections.
2.1 National summary of count progress
The following divisions appear to have finished counting votes (are no longer reporting declaration vote progress):
2.2 Declaration votes yet to processed or counted, by division
Code
dec_progress =transpose(dec_progress_raw)dec_progress_division =transpose(dec_progress_division_raw)dec_progress_tmp = [ { Division:"",type:null,issued:null,received:null,counted:null,not_returned:null,rejected:null,processed:null,awaiting:null,timeStamp:null } ];viewof dec_progress_search = Inputs.search( dec_progress_division.map(d => d.Division))viewof dec_progress_division_detail = Inputs.table( dec_progress_division.filter(d => dec_progress_search.includes(d.Division)), {columns: ["Division","state","issued","received","counted","processed","awaiting" ],format: {"issued": x => d3.format(",")(x),"received": x => d3.format(",")(x),"counted": x => d3.format(",")(x),"processed": x => d3.format(",")(x),"awaiting": x => d3.format(",")(x), },multiple:false })
Code
dec_progress_division_detail ==null? htl.html`<p class = "tab_info">Select a division for declaration vote count progress details by vote type</p>`: htl.html`<p class = "tab_info">Declaration vote count progress details for ${dec_progress_division_detail.Division}:</p>`
Code
Inputs.table( dec_progress_division_detail ==null? dec_progress_tmp : dec_progress.filter(d => d.Division== dec_progress_division_detail.Division& d.type!="Total"), {columns: ["pp_name","yes_per","formal_rate","accept_rate","yes_per_processed","awaiting" ],header: {"type":"Type","yes_per":"Y% ATD","formal_rate":"Formal % ATD","accept_rate":"Accept % ATD","yes_per_processed":"Y% Processed","awaiting":"Awaiting" },format: {"yes_per": x => d3.format(".1%")(x),"formal_rate": x => d3.format(".1%")(x),"accept_rate": x => d3.format(".1%")(x),"yes_per_processed": x => d3.format(".1%")(x),"awaiting": x => d3.format(",")(x) } })
2.3 Declaration votes accept/formal/Yes details, by vote type and division
Code
viewof vote_type = Inputs.select( dec_progress.map(d => d.pp_name).filter(d => d !="Total"), {unique:true,multiple:false } )viewof ddd = Inputs.table( dec_progress.filter(d => d.pp_name== vote_type), {columns: ["Division","state","counted","processed","awaiting","yes_per","accept_rate","yes_per_processed" ],header: {"processed":"Processed","counted":"Counted","awaiting":"Awaiting","yes_per":"Y/Formal","accept_rate":"Accept/Processed","yes_per_processed":"Y/Processed" },format: {"yes_per": x => d3.format(".1%")(x),"accept_rate": x => d3.format(".1%")(x),"yes_per_processed": x => d3.format(".1%")(x) } })
3 Yes %, actual and forecast of final results
For a small number of seat/vote-type instances we’re yet to see any votes enter the count. We fit a predictive model to these cases, predicting Yes % as a function of characteristics of the division.
3.1 Forecasted outcomes, divisions and vote types lacking counts
All quantities other than “Awaiting” are modelled:
3.2 State and national
ATD is actual to date
Columns after “awaiting” are (partially) based on modeling, with uncertainty intervals shown as “lo” and “hi” columns.
Uncertainty will be small late in the count as the number of missing division/vote-types results decreases.
3.3 Divisions
Code
division_tabData =transpose(seatLevel_raw)tmpTab = division_tabData.filter(d => d.Division== ced_report_Division)viewof ced_report_Division = Inputs.table( division_tabData, {header: {yes:"Y (ATD)","formal.x":"Formal ATD",yes_per_actual:"Y% ATD",awaiting:"Awaiting","formal.y":"Formal",yes_per:"Y%",yes_per_lo:"Y% (lo)",yes_per_up:"Y% (up)" },format: {"yes_per_actual": x => d3.format(".1%")(x),"yes_per": x => d3.format(".1%")(x),"yes_per_lo": x => d3.format(".1%")(x),"yes_per_up": x => d3.format(".1%")(x),"Y": x => d3.format(",")(Math.round(x)),"formal.y": x => d3.format(",")(Math.round(x)) },multiple:false })
Code
ced_report_Division ==null? htl.html`<p class = "tab_info">Select a division for details by vote type</p>`: htl.html`<p class = "tab_info">Details for ${ced_report_Division.Division} by vote type:</p>`
Code
ced_report =transpose(ced_report_raw)ced_report_tmp = [ { pp_name:"",Y:null,//N: null,formal:null,Y_operative:null,N_operative:null,formal_operative:null,yes_per:null } ];Inputs.table( ced_report_Division ==null? ced_report_tmp : ced_report.filter(d => d.Division== ced_report_Division.Division), {maxWidth:800,columns: ["pp_name","awaiting","yes_modelled_per","formal_modelled_per","formal","Y","yes_lo","yes_up","yes_per" ],header: {pp_name:"type",awaiting:"Awaiting",yes_modelled_per:"Y/Processed",formal_modelled_per:"Formal/Processed",yes_lo:"Y lo",yes_up:"Y up",formal:"Formal",yes_per:"Y/Formal" },format: {"pp_name": x => ["All Ord Votes","All Dec Votes","Total"].includes(x) ? htl.html`<span style="font-weight: bold;">${x}</span>`: x,yes_modelled_per: x => d3.format(".1%")(x),formal_modelled_per: x => d3.format(".1%")(x),"Y": x => d3.format(",")(Math.round(x)),"yes_lo": x => d3.format(",")(Math.round(x)),"yes_up": x => d3.format(",")(Math.round(x)),"formal": x => d3.format(",")(Math.round(x)),"yes_per": x => d3.format(".1%")(x) } })
4 Differences by vote type, by division
Yes % among categories of declaration votes, vs Yes % among ordinary votes, by division. Color indicates if Yes % is actual or forecast (forecasts replaced by actuals as the count moves on through different classes of declaration votes).
7 Results by Indigeneity of polling place catchments
Indigeneity of a polling place is estimated by:
noting the SA1s of the voters using that polling place in the 2022 election (AEC data),
across those SA1s, noting the proportion of adult citizens who identify as Aboriginal or Torres Strait Islander on the 2021 Census (ABS),
forming a composite estimate of the Indigeneity of each polling place as a weighted sum of the indigeneity of the contributing SA1s.
7.1 Top 25 polling places by Indigeneity
The following table shows the top 25 polling places with results by estimated level of Indigeneity:
7.2 Divisions with six highest levels of Indigenous population
The six electoral divisions with the most Aboriginal and Torres Strait Islander people (as a proportion of all adult citizens in the electoral division):
Within these divisions we see the following relationships between
Indigeneity of polling place catchment (defined above)