shapleyvalue.com

Attribute value according to a parsimonious game theory solution
Log | Files | Refs

ProportionalApprovalVoting.html (8236B)


      1 <!DOCTYPE html>
      2 
      3 <!-- 
      4 	Sources:
      5 		+ https://gist.github.com/cmatskas/8725a6ee4f5f1a8e1ceahttps://gist.github.com/cmatskas/8725a6ee4f5f1a8e1cea
      6 		+ cdnjs
      7 -->
      8 
      9 <head>
     10     <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0-alpha1/jquery.min.js"></script>
     11     <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-csv/0.71/jquery.csv-0.71.min.js"></script>
     12     <script type="text/javascript">  
     13     $(document).ready(function() {
     14 
     15     // The event listener for the file upload
     16     document.getElementById('txtFileUpload').addEventListener('change', upload, false);
     17 
     18     document.getElementById('txtFileUpload').addEventListener('click', reset, false);
     19     
     20     function reset(){
     21         document.getElementById("txtFileUpload").value = null;
     22         // This way, the event change fires even if you upload the same file twice
     23     }
     24     
     25     // Method that checks that the browser supports the HTML5 File API
     26     function browserSupportFileUpload() {
     27         var isCompatible = false;
     28         if (window.File && window.FileReader && window.FileList && window.Blob) {
     29         isCompatible = true;
     30         }
     31         return isCompatible;
     32     }
     33 
     34     // Method that reads and processes the selected file
     35     function upload(evt) {
     36         uploadedSameFileTwice = false;
     37         if (!browserSupportFileUpload()) {
     38             alert('The File APIs are not fully supported in this browser!');
     39             } else {
     40                 // alert("Checkpoint Charlie");
     41                 // var data = null;
     42                 data = null;
     43                 var file = evt.target.files[0];
     44                 var reader = new FileReader();
     45                 reader.readAsText(file);
     46                 reader.onload = function(event) {
     47                     var csvData = event.target.result;
     48                     data = $.csv.toArrays(csvData);
     49                     if (data && data.length > 0) {
     50 	                   // alert('Imported -' + data.length + '- rows successfully!');
     51                         wrapperProportionalApprovalVoting(data);
     52                     } else {
     53                         alert('No data to import!');
     54                     }
     55                 };
     56                 reader.onerror = function() {
     57                     alert('Unable to read ' + file.fileName);
     58                 };
     59             }
     60         }
     61 	
     62     function wrapperProportionalApprovalVoting(data){
     63 	let dataColumn1 = data.map(x => x[1]);
     64 		// this gets us the first column (columns start at 0).
     65 		// data[][1] breaks the thing without throwing an error in the browser.
     66 
     67 	let dataColumn1Split = dataColumn1.map( element => element.split(", "));
     68 		// One row of the first column might be "Candidate1, Candidate2". 
     69 		// This transforms it to ["Candidate1", "Candidate2"]
     70 
     71 
     72 	let uniqueCandidates = findUnique(dataColumn1Split);
     73 		// Finds all the candidates
     74 	
     75 	// In this voting method, all voters start with a weight of 1, which changes as candidates are elected
     76 	// So that voters who have had one of their candidates elected have less influence for the next candidates.
     77 	
     78 	let weights = Array(dataColumn1Split.length).fill(1);
     79 
     80 	// Find the most popular one, given the weights. Update the weights
     81 
     82 	//alert("\n"+dataColumn1Split[0]);
     83 	
     84 	let n = document.getElementById("numWinners").value;
     85 	let winners = [];
     86 	
     87 	for(i=0; i<n; i++){
     88 		let newWinner = findTheNextMostPopularOneGivenTheWeights(dataColumn1Split, weights, uniqueCandidates, winners);
     89 		winners.push(newWinner);
     90 		weights = updateWeightsGivenTheNewWinner(dataColumn1Split, weights, newWinner);
     91 	}
     92 	//alert(winners);
     93 
     94 	// Display the winners.
     95 	displayWinners(winners);
     96     }
     97 
     98     function displayWinners(winners){
     99 
    100 	// Header
    101 
    102 
    103 	// Ordered list with the winners
    104 	///alert(document.getElementsByTagName("OL")[0]);
    105 	
    106 	if(document.getElementsByTagName("OL")[0]==undefined){
    107 		headerH3 = document.createElement("h3");
    108         headerH3.innerHTML = "Winners under Proportional Approval Voting:";
    109 
    110         document.body.appendChild(headerH3);
    111 
    112         orderedList = document.createElement("OL"); // Creates an ordered list
    113         for(let i =0; i<winners.length; i++){
    114             HTMLWinner = document.createElement("li");
    115             HTMLWinner.appendChild(document.createTextNode(winners[i]));
    116             orderedList.appendChild(HTMLWinner);
    117         }	
    118         
    119         document.body.appendChild(orderedList);
    120         
    121 	}else{
    122 	
    123         oldOL = document.getElementsByTagName("OL")[0];
    124         oldOL.remove();
    125 
    126         orderedList = document.createElement("OL"); // Creates an ordered list
    127         for(let i =0; i<winners.length; i++){
    128             HTMLWinner = document.createElement("li");
    129             HTMLWinner.appendChild(document.createTextNode(winners[i]));
    130             orderedList.appendChild(HTMLWinner);
    131         }	
    132         
    133         document.body.appendChild(orderedList);
    134 
    135 
    136 	}
    137 	
    138     }
    139     
    140     function findTheNextMostPopularOneGivenTheWeights(arrayOfArrays, weights, uniqueCandidates, winners){
    141 	let popularity = Array(uniqueCandidates.length).fill(0);
    142 	for(let i = 0; i<uniqueCandidates.length; i++){
    143 		for(let j=1; j<arrayOfArrays.length; j++){
    144             // j = 1 because we don't want to include the title
    145             //alert("array = "+arrayOfArrays[j]);
    146 			if(arrayOfArrays[j].includes(uniqueCandidates[i])){
    147 				popularity[i]+= 1/weights[j];	
    148 			}
    149 		}
    150 	}
    151 	
    152 	for(let i = 0; i<popularity.length; i++){
    153         //alert("popularity["+uniqueCandidates[i]+"] ="  +popularity[i]);
    154 	}
    155 
    156 	let maxPopularity = 0;
    157 	let winner = undefined;
    158 	//alert(popularity + "\n"+uniqueCandidates);
    159 	for(let i=0; i<uniqueCandidates.length; i++){
    160 		if(popularity[i]>=maxPopularity && !winners.includes(uniqueCandidates[i])){
    161 			// Note, this breaks a tie pretty arbitrarily
    162 			// Tie breaking mechanism: so obscure as to be random.
    163 			winner = uniqueCandidates[i];
    164 			//alert("new better:" +uniqueCandidates[i]);
    165 			maxPopularity = popularity[i];
    166 		}
    167 	}
    168     //alert(winner);
    169 	return winner;	
    170     } 
    171     
    172     function updateWeightsGivenTheNewWinner(arrayOfArrays, weights, newWinner){
    173 	for(let i=0; i<arrayOfArrays.length; i++){
    174 		
    175 		if(arrayOfArrays[i].includes(newWinner)){
    176 			weights[i] = weights[i]+1;
    177 		}
    178 		
    179 	}
    180 	return weights;
    181     }
    182 
    183     function findUnique(arrayOfArrays){
    184 	let uniqueElements = [];
    185 
    186 	for(let i = 1; i<arrayOfArrays.length; i++){ // We start with the second row (i=1, instead of i=0, because we take the first row to be a header)
    187 		for(let j=0; j<arrayOfArrays[i].length; j++){
    188 			if(!uniqueElements.includes(arrayOfArrays[i][j])){	
    189 				uniqueElements.push(arrayOfArrays[i][j]);
    190 			}
    191 		}
    192 	}
    193 	return uniqueElements;
    194 	
    195     }
    196     });
    197 
    198 </script>
    199     </head>
    200     <body>
    201 
    202 	<h1>Proportional Approval Voting MVP</h1>
    203 	<h2>What is this? How does this work?</h2>
    204 	<p>This is the simplest version of a program which computes the result of an election, under the <a href="https://www.electionscience.org/learn/electoral-system-glossary/#proportional_approval_voting" target="_blank">Proportional Approval Voting</a> method.</p>
    205 	<p>It takes a csv (comma separated value) file, with the same format as <a href="https://docs.google.com/spreadsheets/d/11pBOP6UJ8SSaHIY-s4dYwgBr4PHodh6cIXf-D4yl7HU/edit?usp=sharing" target="_blank">this one</a>, which might be produced by a Google Forms like <a href="https://docs.google.com/forms/d/1_-B5p8ePHnE1jXTGVT_kfRrMRqJuxmm8DPKn-MR1Pok/edit" target="_blank">this one.</a></p>
    206   <p>It computes the result using client-side JavaScript, which means that all operations are run in your browser, as opposed to in a server which is not under your control. In effect, all this webpage does is provide you with a bunch of functions. In fact, you could just load this page, disconnect from the internet, upload your files, and you could still use the webpage to get the results you need.</p> 
    207      <div id="dvImportSegments" class="fileupload ">
    208     <fieldset>
    209         <legend>Upload your CSV File to compute the result</legend>
    210 	<label>Number of winners: </label><input type="number" id="numWinners" value="2">
    211 		<!-- This is not really aesthetic; change. -->
    212         <br>
    213 	<input type="file" name="File Upload" id="txtFileUpload" accept=".csv" />
    214     </fieldset>
    215     </div>
    216      <p>Proudly created by <a href="https://nunosempere.github.io">Nu&ntildeo Sempere.</a></p>
    217     </body>
    218 </html>
    219