@dra2020/district-analytics 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +651 -122
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -89432,7 +89432,7 @@ function reduceCSplits(CxD, districtTotals) {
|
|
|
89432
89432
|
for (let i = 1; i < nD; i++) {
|
|
89433
89433
|
let split_total = CxDreducedC[i][j];
|
|
89434
89434
|
if (split_total > 0) {
|
|
89435
|
-
if (areRoughlyEqual(split_total, districtTotals[i - 1])) {
|
|
89435
|
+
if (U.areRoughlyEqual(split_total, districtTotals[i - 1], S.EQUAL_TOLERANCE)) {
|
|
89436
89436
|
CxDreducedC[0][j] += split_total;
|
|
89437
89437
|
CxDreducedC[i][j] = 0;
|
|
89438
89438
|
}
|
|
@@ -89455,7 +89455,7 @@ function reduceDSplits(CxD, countyTotals) {
|
|
|
89455
89455
|
for (let j = 1; j < nC; j++) {
|
|
89456
89456
|
let split_total = CxDreducedD[i][j];
|
|
89457
89457
|
if (split_total > 0) {
|
|
89458
|
-
if (areRoughlyEqual(split_total, countyTotals[j - 1])) {
|
|
89458
|
+
if (U.areRoughlyEqual(split_total, countyTotals[j - 1], S.EQUAL_TOLERANCE)) {
|
|
89459
89459
|
CxDreducedD[i][0] += split_total;
|
|
89460
89460
|
CxDreducedD[i][j] = 0;
|
|
89461
89461
|
}
|
|
@@ -89519,13 +89519,6 @@ function calcDistrictFractions(CxD, districtTotals) {
|
|
|
89519
89519
|
return g;
|
|
89520
89520
|
}
|
|
89521
89521
|
exports.calcDistrictFractions = calcDistrictFractions;
|
|
89522
|
-
// Deal with decimal census "counts" due to disagg/re-agg
|
|
89523
|
-
function areRoughlyEqual(x, y) {
|
|
89524
|
-
let delta = Math.abs(x - y);
|
|
89525
|
-
let result = (delta < S.EQUAL_TOLERANCE) ? true : false;
|
|
89526
|
-
return result;
|
|
89527
|
-
}
|
|
89528
|
-
exports.areRoughlyEqual = areRoughlyEqual;
|
|
89529
89522
|
function splitScore(splits) {
|
|
89530
89523
|
let e;
|
|
89531
89524
|
if (splits.length > 0) {
|
|
@@ -89800,7 +89793,7 @@ exports.scorePolsbyPopper = scorePolsbyPopper;
|
|
|
89800
89793
|
/*! exports provided: partisan, minority, traditionalPrinciples, default */
|
|
89801
89794
|
/***/ (function(module) {
|
|
89802
89795
|
|
|
89803
|
-
module.exports = JSON.parse("{\"partisan\":{\"bias\":{\"range\":[0,0.2],\"weight\":[50,80]},\"impact\":{\"weight\":[50,0],\"threshold\":4},\"competitiveness\":{\"overall\":{\"range\":[0,0.67],\"weight\":25},\"marginal\":{\"range\":[0,0.85],\"weight\":75},\"range\":[0.45,0.55],\"distribution\":[0.25,0.75],\"weight\":[0,20]},\"weight\":[100,80]},\"minority\":{\"range\":[0.37,0.5],\"distribution\":[0.25,0.75],\"shift\":0.12,\"bonus\":20},\"traditionalPrinciples\":{\"compactness\":{\"reock\":{\"range\":[0.25,0.5],\"weight\":50},\"polsby\":{\"range\":[0.1,0.5],\"weight\":50},\"weight\":[0,50]},\"splitting\":{\"county\":{\"range\":[1,1.71],\"allowableSplitsMultiplier\":1.5,\"weight\":50},\"district\":{\"range\":[1,1.5],\"weight\":50},\"weight\":[0,50]},\"popdev\":{\"range\":[[0.0075,0.002],[0.1,-1]],\"weight\":[0,0]},\"weight\":[0,20]}}");
|
|
89796
|
+
module.exports = JSON.parse("{\"partisan\":{\"bias\":{\"range\":[0,0.2],\"weight\":[50,80]},\"impact\":{\"weight\":[50,0],\"threshold\":4},\"competitiveness\":{\"overall\":{\"range\":[0,0.67],\"weight\":25},\"marginal\":{\"range\":[0,0.85],\"weight\":75},\"range\":[0.45,0.55],\"distribution\":[0.25,0.75],\"weight\":[0,20]},\"bonus\":2,\"weight\":[100,80]},\"minority\":{\"range\":[0.37,0.5],\"distribution\":[0.25,0.75],\"shift\":0.12,\"bonus\":20},\"traditionalPrinciples\":{\"compactness\":{\"reock\":{\"range\":[0.25,0.5],\"weight\":50},\"polsby\":{\"range\":[0.1,0.5],\"weight\":50},\"weight\":[0,50]},\"splitting\":{\"county\":{\"range\":[1,1.71],\"allowableSplitsMultiplier\":1.5,\"weight\":50},\"district\":{\"range\":[1,1.5],\"weight\":50},\"weight\":[0,50]},\"popdev\":{\"range\":[[0.0075,0.002],[0.1,-1]],\"weight\":[0,0]},\"weight\":[0,20]}}");
|
|
89804
89797
|
|
|
89805
89798
|
/***/ }),
|
|
89806
89799
|
|
|
@@ -89842,6 +89835,11 @@ function impactWeight(context, overridesJSON) {
|
|
|
89842
89835
|
return iW;
|
|
89843
89836
|
}
|
|
89844
89837
|
exports.impactWeight = impactWeight;
|
|
89838
|
+
function winnerBonus(overridesJSON) {
|
|
89839
|
+
const bonus = config_json_1.default.partisan.bonus;
|
|
89840
|
+
return bonus;
|
|
89841
|
+
}
|
|
89842
|
+
exports.winnerBonus = winnerBonus;
|
|
89845
89843
|
// The maximum # of unearned seats that scores positively
|
|
89846
89844
|
function unearnedThreshold(overridesJSON) {
|
|
89847
89845
|
const threshold = config_json_1.default.partisan.impact.threshold;
|
|
@@ -90156,12 +90154,13 @@ function evalMinorityOpportunity(p, bLog = false) {
|
|
|
90156
90154
|
}
|
|
90157
90155
|
}
|
|
90158
90156
|
// Sum the # of level 1 (37–50%) and level 2 (> 50%) opportunity districts - ignore total minority
|
|
90159
|
-
let l1 = 0;
|
|
90160
|
-
let l2 = 0;
|
|
90161
|
-
bucketsByDemo.slice(1).forEach(function (buckets)
|
|
90162
|
-
|
|
90163
|
-
|
|
90164
|
-
|
|
90157
|
+
// let l1: number = 0;
|
|
90158
|
+
// let l2: number = 0;
|
|
90159
|
+
// bucketsByDemo.slice(1).forEach(function (buckets: number[]): void
|
|
90160
|
+
// {
|
|
90161
|
+
// l1 += buckets[T.OpportunityBucket.Level1];
|
|
90162
|
+
// l2 += buckets[T.OpportunityBucket.Level2]
|
|
90163
|
+
// });
|
|
90165
90164
|
// Sum the # of opportunity districts - ignore total minority
|
|
90166
90165
|
const oD = U.sumArray(opptyByDemo.slice(1));
|
|
90167
90166
|
// Sum the # of proportion districts - ignore total minority
|
|
@@ -90173,8 +90172,8 @@ function evalMinorityOpportunity(p, bLog = false) {
|
|
|
90173
90172
|
averageDVf: averageDVf,
|
|
90174
90173
|
bucketsByDemographic: bucketsByDemo
|
|
90175
90174
|
},
|
|
90176
|
-
nOpportunity1: l1,
|
|
90177
|
-
nOpportunity2: l2,
|
|
90175
|
+
// nOpportunity1: l1,
|
|
90176
|
+
// nOpportunity2: l2,
|
|
90178
90177
|
nProportional: pD,
|
|
90179
90178
|
opportunityDistricts: oD,
|
|
90180
90179
|
score: score
|
|
@@ -90193,17 +90192,22 @@ function calcDistrictsByDemo(MfArray, N) {
|
|
|
90193
90192
|
return districtsByDemo;
|
|
90194
90193
|
}
|
|
90195
90194
|
exports.calcDistrictsByDemo = calcDistrictsByDemo;
|
|
90195
|
+
// NOTE - Shift minority proportions up, so 37% minority scores like 49% share,
|
|
90196
|
+
// but use the uncompressed seat probability distribution. This makes a 37%
|
|
90197
|
+
// district have a 40% chance of winning, a 42% district have an 84% chance,
|
|
90198
|
+
// and a 50% district have a 99% chance. Below 37% has no chance.
|
|
90199
|
+
//
|
|
90196
90200
|
function estMinorityOpportunity(Mf) {
|
|
90201
|
+
// NOTE - Switch to compress the probability distribution
|
|
90202
|
+
const bCompress = false;
|
|
90203
|
+
const dist = bCompress ? C.minorityOpportunityDistribution() : [0.0, 1.0];
|
|
90204
|
+
const range = C.minorityOpportunityRange();
|
|
90197
90205
|
const _normalizer = new normalize_1.Normalizer(Mf);
|
|
90198
|
-
// NOTE - Shift proportions, so 37% minority scores like 49% partisan
|
|
90199
90206
|
const shift = C.minorityShift();
|
|
90200
90207
|
_normalizer.wipNum += shift;
|
|
90201
|
-
|
|
90202
|
-
|
|
90203
|
-
const
|
|
90204
|
-
_normalizer.clip(distBeg, distEnd);
|
|
90205
|
-
_normalizer.unitize(distBeg, distEnd);
|
|
90206
|
-
const oppty = partisan_1.estSeatProbability(_normalizer.wipNum, range);
|
|
90208
|
+
_normalizer.clip(dist[C.BEG], dist[C.END]);
|
|
90209
|
+
_normalizer.unitize(dist[C.BEG], dist[C.END]);
|
|
90210
|
+
const oppty = (Mf < range[C.BEG]) ? 0.0 : partisan_1.estSeatProbability(_normalizer.wipNum, dist);
|
|
90207
90211
|
return oppty;
|
|
90208
90212
|
}
|
|
90209
90213
|
exports.estMinorityOpportunity = estMinorityOpportunity;
|
|
@@ -90350,20 +90354,36 @@ const normalize_1 = __webpack_require__(/*! ./normalize */ "./src/normalize.ts")
|
|
|
90350
90354
|
// NOTE - I'm passing T.VfArray's into everything. District indices = array indices.
|
|
90351
90355
|
// NOTE - I do not (cannot) assume that the values are sorted.
|
|
90352
90356
|
// SCORE BIAS & COMPETITIVENESS
|
|
90353
|
-
/* SCORECARD FIELDS
|
|
90357
|
+
/* SCORECARD FIELDS:
|
|
90354
90358
|
|
|
90355
|
-
* ^S# [
|
|
90356
|
-
* ^S% [
|
|
90359
|
+
* ^S# [propS] = the Democratic seats closest to proportional
|
|
90360
|
+
* ^S% [propSf] = the corresponding Democratic seat share
|
|
90357
90361
|
* S! [fptpS] = the estimated number of Democratic seats using first past the post
|
|
90358
|
-
* S# [
|
|
90359
|
-
* S% [
|
|
90362
|
+
* S# [estS] = the estimated Democratic seats, using seat probabilities
|
|
90363
|
+
* S% [estSf] = the estimated Democratic seat share fraction, calculated as S# / N
|
|
90364
|
+
|
|
90365
|
+
* Bs50 [s50V] = Seat bias as a fraction of N
|
|
90366
|
+
* Bv50 [v50S] = Votes bias as a fraction
|
|
90367
|
+
* decl [decl] = Declination
|
|
90368
|
+
* gSym [gSym] = Global symmetry
|
|
90369
|
+
|
|
90370
|
+
* EG [EG] = Efficiency gap as a fraction
|
|
90371
|
+
* sVg [BsGf] = Seats bias @ <V> (geometric)
|
|
90372
|
+
* prop [prop] = Disproportionality
|
|
90373
|
+
* MM [mMs] = Mean – median difference using statewide Vf
|
|
90374
|
+
* TO [tOf] = Turnout bias
|
|
90375
|
+
* MM' [mMd] = Mean – median difference using average district v
|
|
90376
|
+
* LO [LO] = Lopsided outcomes
|
|
90377
|
+
|
|
90360
90378
|
* B% [bias]= the bias calculated as S% – ^S%
|
|
90361
90379
|
* B$ [bias.score] = the bias score normalized [0–100]
|
|
90362
90380
|
|
|
90363
90381
|
* UE# [unearnedS] = the number of unearned seats (R = positive; D = negative)
|
|
90364
90382
|
* I$ [impact.score] -- the unearned seats normalized [0–100] as impact
|
|
90365
90383
|
|
|
90366
|
-
* R
|
|
90384
|
+
* R [bigR] = Overall responsiveness
|
|
90385
|
+
* r [littleR] = The point responsiveness at V%
|
|
90386
|
+
* MIR [MIR] = Minimal inverse responsiveness
|
|
90367
90387
|
* rD [rD] = the estimated # of responsive districts (using probabilities)
|
|
90368
90388
|
* rD% [rDf] = the estimated # of responsive districts, as a fraction of N
|
|
90369
90389
|
|
|
@@ -90379,23 +90399,42 @@ const normalize_1 = __webpack_require__(/*! ./normalize */ "./src/normalize.ts")
|
|
|
90379
90399
|
* >P$ [score2] = the combined partisan score used to compare plans *within* a state, along with traditional districting principles
|
|
90380
90400
|
|
|
90381
90401
|
*/
|
|
90382
|
-
function scorePartisan(Vf, VfArray,
|
|
90402
|
+
function scorePartisan(Vf, VfArray, options) {
|
|
90403
|
+
const bAlternateMetrics = options.alternates;
|
|
90404
|
+
const bConstrained = options.constrained;
|
|
90405
|
+
const shift = options.shift;
|
|
90383
90406
|
const N = VfArray.length;
|
|
90384
|
-
const
|
|
90385
|
-
const
|
|
90386
|
-
const fptpS =
|
|
90407
|
+
const propS = propSeats(N, Vf);
|
|
90408
|
+
const propSf = propSeatShare(propS, N);
|
|
90409
|
+
const fptpS = bAlternateMetrics ? estFPTPSeats(VfArray) : undefined;
|
|
90387
90410
|
const range = bConstrained ? C.competitiveDistribution() : undefined;
|
|
90388
|
-
const
|
|
90389
|
-
const
|
|
90390
|
-
const bias =
|
|
90391
|
-
const biasScore = scorebias(bias, Vf,
|
|
90392
|
-
const unearnedS =
|
|
90393
|
-
const impactScore = scoreImpact(unearnedS);
|
|
90394
|
-
|
|
90395
|
-
|
|
90396
|
-
const
|
|
90397
|
-
const
|
|
90398
|
-
const
|
|
90411
|
+
const estS = estSeats(VfArray, range);
|
|
90412
|
+
const estSf = estSeatShare(estS, N);
|
|
90413
|
+
const bias = estBias(estSf, propSf);
|
|
90414
|
+
const biasScore = scorebias(bias, Vf, estSf);
|
|
90415
|
+
const unearnedS = estUnearnedSeats(propS, estS);
|
|
90416
|
+
const impactScore = scoreImpact(unearnedS, Vf, N);
|
|
90417
|
+
// Calculate additional alternate metrics for reference
|
|
90418
|
+
// NOTE - Use the uncompressed seat probability function
|
|
90419
|
+
const inferredSVpoints = bAlternateMetrics ? inferSVpoints(Vf, VfArray, shift) : undefined;
|
|
90420
|
+
const TOf = bAlternateMetrics ? calcTurnoutBias(Vf, VfArray) : undefined;
|
|
90421
|
+
const Bs50 = bAlternateMetrics ? estPartisanBias(inferredSVpoints, N) : undefined;
|
|
90422
|
+
const Bs50f = (!(Bs50 === undefined)) ? U.trim(Bs50 / N) : undefined;
|
|
90423
|
+
const Bv50f = bAlternateMetrics ? estVotesBias(inferredSVpoints, N) : undefined;
|
|
90424
|
+
const decl = bAlternateMetrics ? calcDeclination(VfArray) : undefined;
|
|
90425
|
+
const gSym = bAlternateMetrics ? calcGlobalSymmetry(inferredSVpoints, Bs50f) : undefined;
|
|
90426
|
+
const EG = bAlternateMetrics ? calcEfficiencyGap(Vf, estSf) : undefined;
|
|
90427
|
+
const BsGf = bAlternateMetrics ? estGeometricSeatsBias(Vf, inferredSVpoints) : undefined;
|
|
90428
|
+
const prop = bAlternateMetrics ? calcDisproportionality(Vf, estSf) : undefined;
|
|
90429
|
+
const mMs = bAlternateMetrics ? estMeanMedianDifference(VfArray, Vf) : undefined;
|
|
90430
|
+
const mMd = bAlternateMetrics ? estMeanMedianDifference(VfArray) : undefined;
|
|
90431
|
+
const LO = bAlternateMetrics ? calcLopsidedOutcomes(VfArray) : undefined;
|
|
90432
|
+
// Calculate alternate responsiveness metrics for reference
|
|
90433
|
+
const bigR = bAlternateMetrics ? calcBigR(Vf, estSf) : undefined;
|
|
90434
|
+
const littleR = bAlternateMetrics ? estResponsiveness(Vf, inferredSVpoints) : undefined;
|
|
90435
|
+
const MIR = bAlternateMetrics ? calcMinimalInverseResponsiveness(Vf, littleR) : undefined;
|
|
90436
|
+
const rD = (!bConstrained || bAlternateMetrics) ? estResponsiveDistricts(VfArray) : undefined;
|
|
90437
|
+
const rDf = bAlternateMetrics ? estResponsiveDistrictsShare(rD, N) : undefined;
|
|
90399
90438
|
const Cn = countCompetitiveDistricts(VfArray);
|
|
90400
90439
|
const cD = bConstrained ? estCompetitiveDistricts(VfArray) : rD;
|
|
90401
90440
|
const cDf = estCompetitiveDistrictsShare(cD, N);
|
|
@@ -90403,12 +90442,11 @@ function scorePartisan(Vf, VfArray, bAll = false, bConstrained = true) {
|
|
|
90403
90442
|
const Md = estMarginalCompetitiveDistricts(Mrange, VfArray);
|
|
90404
90443
|
const Mdf = estMarginalCompetitiveShare(Md, Mrange);
|
|
90405
90444
|
const competitivenessScore = scoreCompetitiveness(Mdf, cDf);
|
|
90406
|
-
|
|
90407
|
-
|
|
90408
|
-
|
|
90409
|
-
|
|
90410
|
-
|
|
90411
|
-
probableSf: probableSf,
|
|
90445
|
+
let biasScoring = {
|
|
90446
|
+
propS: propS,
|
|
90447
|
+
propSf: propSf,
|
|
90448
|
+
estS: estS,
|
|
90449
|
+
estSf: estSf,
|
|
90412
90450
|
bias: bias,
|
|
90413
90451
|
score: biasScore
|
|
90414
90452
|
};
|
|
@@ -90417,9 +90455,6 @@ function scorePartisan(Vf, VfArray, bAll = false, bConstrained = true) {
|
|
|
90417
90455
|
score: impactScore
|
|
90418
90456
|
};
|
|
90419
90457
|
let competitiveScoring = {
|
|
90420
|
-
r: R,
|
|
90421
|
-
rD: rD,
|
|
90422
|
-
rDf: rDf,
|
|
90423
90458
|
c: Cn,
|
|
90424
90459
|
cD: cD,
|
|
90425
90460
|
cDf: cDf,
|
|
@@ -90428,8 +90463,22 @@ function scorePartisan(Vf, VfArray, bAll = false, bConstrained = true) {
|
|
|
90428
90463
|
mDf: Mdf,
|
|
90429
90464
|
score: competitivenessScore
|
|
90430
90465
|
};
|
|
90431
|
-
if (
|
|
90432
|
-
|
|
90466
|
+
if (bAlternateMetrics) {
|
|
90467
|
+
biasScoring.tOf = TOf;
|
|
90468
|
+
biasScoring.fptpS = fptpS;
|
|
90469
|
+
biasScoring.s50V = Bs50f;
|
|
90470
|
+
biasScoring.v50S = Bv50f;
|
|
90471
|
+
biasScoring.decl = decl;
|
|
90472
|
+
biasScoring.gSym = gSym;
|
|
90473
|
+
biasScoring.eG = EG;
|
|
90474
|
+
biasScoring.sVg = BsGf;
|
|
90475
|
+
biasScoring.prop = prop;
|
|
90476
|
+
biasScoring.mMs = mMs;
|
|
90477
|
+
biasScoring.mMd = mMd;
|
|
90478
|
+
biasScoring.lO = LO;
|
|
90479
|
+
competitiveScoring.bigR = bigR;
|
|
90480
|
+
competitiveScoring.littleR = littleR;
|
|
90481
|
+
competitiveScoring.mIR = MIR;
|
|
90433
90482
|
competitiveScoring.rD = rD;
|
|
90434
90483
|
competitiveScoring.rDf = rDf;
|
|
90435
90484
|
}
|
|
@@ -90458,35 +90507,49 @@ function weightPartisan(bS, iS, cS, context) {
|
|
|
90458
90507
|
return score;
|
|
90459
90508
|
}
|
|
90460
90509
|
exports.weightPartisan = weightPartisan;
|
|
90461
|
-
function
|
|
90462
|
-
|
|
90463
|
-
|
|
90464
|
-
exports.printPartisanScorecardHeader = printPartisanScorecardHeader;
|
|
90465
|
-
function printPartisanScorecardRow(xx, name, N, Vf, s) {
|
|
90466
|
-
console.log('%s, %s, %i, %f, %f, %i, %f, %i, %f, %f, %f, %i, %f, %i, %f, %f, %f, %i, %f, %f, %i, %i, %f, %f, %i, %i, %i', xx, name, N, U.trim(1 / N), Vf, s.bias.bestS, s.bias.bestSf, s.bias.fptpS, s.bias.probableS, s.bias.probableSf, s.bias.bias, s.bias.score, s.impact.unearnedS, s.impact.score, s.competitiveness.r, s.competitiveness.rD, s.competitiveness.rDf, s.competitiveness.c, s.competitiveness.cD, s.competitiveness.cDf, s.competitiveness.mRange[C.BEG], s.competitiveness.mRange[C.END], s.competitiveness.mD, s.competitiveness.mDf, s.competitiveness.score, s.score, s.score2);
|
|
90510
|
+
function extraBonus(Vf) {
|
|
90511
|
+
const okExtra = (0.5 - Vf) * (C.winnerBonus() - 1.0);
|
|
90512
|
+
return U.trim(okExtra);
|
|
90467
90513
|
}
|
|
90468
|
-
exports.
|
|
90469
|
-
function scorebias(
|
|
90514
|
+
exports.extraBonus = extraBonus;
|
|
90515
|
+
function scorebias(rawBias, Vf, Sf) {
|
|
90470
90516
|
if (isAntimajoritarian(Vf, Sf)) {
|
|
90471
90517
|
return 0;
|
|
90472
90518
|
}
|
|
90473
90519
|
else {
|
|
90474
|
-
|
|
90520
|
+
// Adjust bias to incorporate an acceptable winner's bonus based on Vf
|
|
90521
|
+
const extra = extraBonus(Vf);
|
|
90522
|
+
const adjusted = adjustBias(Vf, rawBias, extra);
|
|
90523
|
+
// Then normalize
|
|
90524
|
+
const _normalizer = new normalize_1.Normalizer(adjusted);
|
|
90475
90525
|
const worst = C.biasRange()[C.BEG];
|
|
90476
90526
|
const best = C.biasRange()[C.END];
|
|
90527
|
+
_normalizer.positive();
|
|
90477
90528
|
_normalizer.clip(worst, best);
|
|
90478
90529
|
_normalizer.unitize(worst, best);
|
|
90479
90530
|
_normalizer.invert();
|
|
90480
|
-
// _normalizer.decay();
|
|
90481
90531
|
_normalizer.rescale();
|
|
90482
90532
|
const score = _normalizer.normalizedNum;
|
|
90483
90533
|
return score;
|
|
90484
90534
|
}
|
|
90485
90535
|
}
|
|
90486
90536
|
exports.scorebias = scorebias;
|
|
90537
|
+
// Adjust bias to account for a winner's bonus
|
|
90538
|
+
function adjustBias(Vf, bias, extra) {
|
|
90539
|
+
if (Vf > 0.5)
|
|
90540
|
+
return Math.min(bias - extra, 0);
|
|
90541
|
+
else
|
|
90542
|
+
return Math.max(bias - extra, 0);
|
|
90543
|
+
}
|
|
90544
|
+
exports.adjustBias = adjustBias;
|
|
90487
90545
|
// Normalize unearned seats
|
|
90488
|
-
function scoreImpact(rawUE) {
|
|
90489
|
-
|
|
90546
|
+
function scoreImpact(rawUE, Vf, N) {
|
|
90547
|
+
// Adjust impact to incorporate an acceptable winner's bonus based on Vf
|
|
90548
|
+
const extra = extraBonus(Vf);
|
|
90549
|
+
const adjustedBias = adjustBias(Vf, rawUE / N, extra);
|
|
90550
|
+
const adjustedImpact = adjustedBias * N;
|
|
90551
|
+
// Then normalize
|
|
90552
|
+
const _normalizer = new normalize_1.Normalizer(adjustedImpact);
|
|
90490
90553
|
const worst = C.unearnedThreshold();
|
|
90491
90554
|
const best = 0.0;
|
|
90492
90555
|
_normalizer.positive();
|
|
@@ -90538,11 +90601,11 @@ const { erf } = __webpack_require__(/*! mathjs */ "./node_modules/mathjs/main/es
|
|
|
90538
90601
|
// Estimate the probability of a seat win for district, given a Vf
|
|
90539
90602
|
function estSeatProbability(Vf, range) {
|
|
90540
90603
|
if (range) {
|
|
90604
|
+
// If a range is provided, it defines end points of a compressed probability
|
|
90605
|
+
// distribution. These *aren't* the points where races start or stop being
|
|
90606
|
+
// contested, just the end points of a distribution that yields the desired
|
|
90607
|
+
// probabilities in the typical competitive range [45-55%].
|
|
90541
90608
|
const _normalizer = new normalize_1.Normalizer(Vf);
|
|
90542
|
-
// The end points of a compressed probability distribution
|
|
90543
|
-
// NOTE - These aren't the points where races start or stop being contested,
|
|
90544
|
-
// just the end points of a distribution that yields the desired behavior
|
|
90545
|
-
// in the typical competitive range [45-55%].
|
|
90546
90609
|
const distBeg = range[C.BEG];
|
|
90547
90610
|
const distEnd = range[C.END];
|
|
90548
90611
|
_normalizer.clip(distBeg, distEnd);
|
|
@@ -90550,6 +90613,7 @@ function estSeatProbability(Vf, range) {
|
|
|
90550
90613
|
return seatProbabilityFn(_normalizer.wipNum);
|
|
90551
90614
|
}
|
|
90552
90615
|
else {
|
|
90616
|
+
// Otherwise, use the full probability distribution.
|
|
90553
90617
|
return seatProbabilityFn(Vf);
|
|
90554
90618
|
}
|
|
90555
90619
|
}
|
|
@@ -90564,19 +90628,27 @@ function estDistrictResponsiveness(Vf) {
|
|
|
90564
90628
|
return U.trim(1.0 - 4.0 * Math.pow((estSeatProbability(Vf) - 0.5), 2));
|
|
90565
90629
|
}
|
|
90566
90630
|
exports.estDistrictResponsiveness = estDistrictResponsiveness;
|
|
90567
|
-
function inferSVpoints(Vf, VfArray, range) {
|
|
90631
|
+
function inferSVpoints(Vf, VfArray, shift, range) {
|
|
90568
90632
|
const nDistricts = VfArray.length;
|
|
90569
90633
|
let SVpoints = [];
|
|
90570
90634
|
for (let shiftedVf of shiftRange()) {
|
|
90571
|
-
const shiftedVPI = shiftDistricts(Vf, VfArray, shiftedVf);
|
|
90572
|
-
const shiftedSf =
|
|
90635
|
+
const shiftedVPI = shiftDistricts(Vf, VfArray, shiftedVf, shift);
|
|
90636
|
+
const shiftedSf = estSeats(shiftedVPI, range) / nDistricts;
|
|
90573
90637
|
SVpoints.push({ v: shiftedVf, s: shiftedSf });
|
|
90638
|
+
// TODO - Why can't I trim these? Why does that only break the Hypotheticals?!?
|
|
90639
|
+
// SVpoints.push({v: U.trim(Number(shiftedVf)), s: shiftedSf});
|
|
90574
90640
|
}
|
|
90575
90641
|
return SVpoints;
|
|
90576
90642
|
}
|
|
90577
90643
|
exports.inferSVpoints = inferSVpoints;
|
|
90578
|
-
|
|
90579
|
-
|
|
90644
|
+
function shiftDistricts(Vf, VfArray, shiftedVf, shift) {
|
|
90645
|
+
if (shift == 0 /* Proportional */)
|
|
90646
|
+
return shiftProportionally(Vf, VfArray, shiftedVf);
|
|
90647
|
+
else
|
|
90648
|
+
return shiftUniformly(Vf, VfArray, shiftedVf);
|
|
90649
|
+
}
|
|
90650
|
+
// Shift districts proportionally
|
|
90651
|
+
function shiftProportionally(Vf, VfArray, shiftedVf) {
|
|
90580
90652
|
let shiftedVfArray;
|
|
90581
90653
|
if (shiftedVf < Vf) {
|
|
90582
90654
|
// Shift down: D's to R's
|
|
@@ -90594,40 +90666,48 @@ function shiftDistricts(Vf, VfArray, shiftedVf) {
|
|
|
90594
90666
|
}
|
|
90595
90667
|
return shiftedVfArray;
|
|
90596
90668
|
}
|
|
90597
|
-
//
|
|
90669
|
+
// Shift districts uniformly
|
|
90670
|
+
function shiftUniformly(Vf, VfArray, shiftedVf) {
|
|
90671
|
+
const shift = shiftedVf - Vf;
|
|
90672
|
+
const shiftedVfArray = VfArray.map((v => v + shift));
|
|
90673
|
+
return shiftedVfArray;
|
|
90674
|
+
}
|
|
90675
|
+
// Generate a range of v's in 1/2% increments
|
|
90598
90676
|
function shiftRange() {
|
|
90599
|
-
const range = [];
|
|
90600
|
-
const
|
|
90601
|
-
const upper = 75 / 100;
|
|
90677
|
+
const range = [0.25, 0.75];
|
|
90678
|
+
const axisRange = [];
|
|
90602
90679
|
const step = (1 / 100) / 2;
|
|
90603
|
-
for (let v =
|
|
90604
|
-
|
|
90680
|
+
for (let v = range[0]; v <= range[1] + S.EPSILON; v += step) {
|
|
90681
|
+
axisRange.push(v);
|
|
90605
90682
|
}
|
|
90606
|
-
return
|
|
90683
|
+
return axisRange;
|
|
90607
90684
|
}
|
|
90608
|
-
// ESTIMATE BIAS
|
|
90685
|
+
// ESTIMATE BIAS
|
|
90686
|
+
//
|
|
90687
|
+
// NOTE: By convention, '+' = R bias; '-' = D bias.
|
|
90688
|
+
//
|
|
90609
90689
|
// ^S# - The # of Democratic seats closest to proportional @ statewide Vf
|
|
90610
90690
|
// The "expected number of seats" from http://bit.ly/2Fcuf4q
|
|
90611
|
-
function
|
|
90691
|
+
function propSeats(N, Vf) {
|
|
90612
90692
|
return Math.round((N * Vf) - S.EPSILON);
|
|
90613
90693
|
}
|
|
90614
|
-
exports.
|
|
90694
|
+
exports.propSeats = propSeats;
|
|
90615
90695
|
// ^S% - The corresponding Democratic seat share
|
|
90616
|
-
function
|
|
90696
|
+
function propSeatShare(bestS, N) {
|
|
90617
90697
|
return U.trim(bestS / N);
|
|
90618
90698
|
}
|
|
90619
|
-
exports.
|
|
90620
|
-
// S# - The estimated # of Democratic seats
|
|
90621
|
-
function
|
|
90699
|
+
exports.propSeatShare = propSeatShare;
|
|
90700
|
+
// S# - The estimated # of Democratic seats, using seat probabilities
|
|
90701
|
+
function estSeats(VfArray, range) {
|
|
90622
90702
|
// Python: sum([est_seat_probability(vpi) for vpi in vpi_by_district])
|
|
90623
90703
|
return U.trim(U.sumArray(VfArray.map(v => estSeatProbability(v, range))));
|
|
90624
90704
|
}
|
|
90625
|
-
exports.
|
|
90626
|
-
// S% - The estimated Democratic seat share fraction
|
|
90627
|
-
function
|
|
90628
|
-
return U.trim(
|
|
90705
|
+
exports.estSeats = estSeats;
|
|
90706
|
+
// S% - The estimated Democratic seat share fraction
|
|
90707
|
+
function estSeatShare(estS, N) {
|
|
90708
|
+
return U.trim(estS / N);
|
|
90629
90709
|
}
|
|
90630
|
-
exports.
|
|
90710
|
+
exports.estSeatShare = estSeatShare;
|
|
90631
90711
|
// F# - The estimated number of Democratic seats using first past the post
|
|
90632
90712
|
function estFPTPSeats(VfArray) {
|
|
90633
90713
|
// Python: sum([1.0 for vpi in vpi_by_district if (vpi > 0.5)])
|
|
@@ -90641,34 +90721,33 @@ function estFPTPSeats(VfArray) {
|
|
|
90641
90721
|
}));
|
|
90642
90722
|
}
|
|
90643
90723
|
exports.estFPTPSeats = estFPTPSeats;
|
|
90644
|
-
// B% -
|
|
90645
|
-
function
|
|
90646
|
-
return U.trim(
|
|
90724
|
+
// B% - The bias calculated as ^S% — S%
|
|
90725
|
+
function estBias(estSf, propSf) {
|
|
90726
|
+
return U.trim(propSf - estSf);
|
|
90647
90727
|
}
|
|
90648
|
-
exports.
|
|
90728
|
+
exports.estBias = estBias;
|
|
90649
90729
|
// UE# - The estimated # of unearned seats
|
|
90650
90730
|
// UE_# from http://bit.ly/2Fcuf4q
|
|
90651
|
-
function
|
|
90652
|
-
|
|
90653
|
-
return U.trim(best - probable);
|
|
90731
|
+
function estUnearnedSeats(proportional, probable) {
|
|
90732
|
+
return U.trim(proportional - probable);
|
|
90654
90733
|
}
|
|
90655
|
-
exports.
|
|
90734
|
+
exports.estUnearnedSeats = estUnearnedSeats;
|
|
90656
90735
|
// ESTIMATE RESPONSIVENESS ("COMPETITIVE")
|
|
90657
90736
|
// R# - Estimate responsiveness at the statewide vote share
|
|
90658
90737
|
function estResponsiveness(Vf, inferredSVpoints) {
|
|
90659
|
-
|
|
90660
|
-
//
|
|
90661
|
-
|
|
90662
|
-
|
|
90663
|
-
|
|
90664
|
-
|
|
90665
|
-
|
|
90666
|
-
|
|
90667
|
-
return
|
|
90738
|
+
let R = undefined;
|
|
90739
|
+
// NOTE - Seat values are already fractions [0.0–1.0] here.
|
|
90740
|
+
const lowerPt = findBracketingLowerVf(Vf, inferredSVpoints);
|
|
90741
|
+
const upperPt = findBracketingUpperVf(Vf, inferredSVpoints);
|
|
90742
|
+
if (!(U.areRoughlyEqual((upperPt.v - lowerPt.v), 0, S.EPSILON))) {
|
|
90743
|
+
R = ((upperPt.s - lowerPt.s) / (upperPt.v - lowerPt.v));
|
|
90744
|
+
R = U.trim(R);
|
|
90745
|
+
}
|
|
90746
|
+
return R;
|
|
90668
90747
|
}
|
|
90669
90748
|
exports.estResponsiveness = estResponsiveness;
|
|
90670
90749
|
// Find the S(V) point that brackets a Vf value on the lower end
|
|
90671
|
-
function
|
|
90750
|
+
function findBracketingLowerVf(Vf, inferredSVpoints) {
|
|
90672
90751
|
let lowerPt = inferredSVpoints[0];
|
|
90673
90752
|
let smallerPoints = [];
|
|
90674
90753
|
for (let pt of inferredSVpoints) {
|
|
@@ -90684,7 +90763,7 @@ function findLowerBracket(Vf, inferredSVpoints) {
|
|
|
90684
90763
|
return lowerPt;
|
|
90685
90764
|
}
|
|
90686
90765
|
// Find the S(V) point that brackets a Vf value on the upper end
|
|
90687
|
-
function
|
|
90766
|
+
function findBracketingUpperVf(Vf, inferredSVpoints) {
|
|
90688
90767
|
let upperPt = inferredSVpoints[-1];
|
|
90689
90768
|
for (let pt of inferredSVpoints) {
|
|
90690
90769
|
if (pt.v >= Vf) {
|
|
@@ -90695,6 +90774,35 @@ function findUpperBracket(Vf, inferredSVpoints) {
|
|
|
90695
90774
|
}
|
|
90696
90775
|
return upperPt;
|
|
90697
90776
|
}
|
|
90777
|
+
// The corresponding functions via the Sf y-axis (vs. Vf x-axis)
|
|
90778
|
+
// Find the S(V) point that brackets a Sf value on the lower end
|
|
90779
|
+
function findBracketingLowerSf(Sf, inferredSVpoints) {
|
|
90780
|
+
let lowerPt = inferredSVpoints[0];
|
|
90781
|
+
let smallerPoints = [];
|
|
90782
|
+
for (let pt of inferredSVpoints) {
|
|
90783
|
+
if (pt.s <= Sf) {
|
|
90784
|
+
smallerPoints.push(pt);
|
|
90785
|
+
}
|
|
90786
|
+
else {
|
|
90787
|
+
break;
|
|
90788
|
+
}
|
|
90789
|
+
}
|
|
90790
|
+
// The last smaller point
|
|
90791
|
+
lowerPt = smallerPoints.slice(-1)[0];
|
|
90792
|
+
return lowerPt;
|
|
90793
|
+
}
|
|
90794
|
+
// Find the S(V) point that brackets a Sf value on the upper end
|
|
90795
|
+
function findBracketingUpperSf(Sf, inferredSVpoints) {
|
|
90796
|
+
let upperPt = inferredSVpoints[-1];
|
|
90797
|
+
for (let pt of inferredSVpoints) {
|
|
90798
|
+
if (pt.s >= Sf) {
|
|
90799
|
+
// The first bigger point
|
|
90800
|
+
upperPt = { v: pt.v, s: pt.s };
|
|
90801
|
+
break;
|
|
90802
|
+
}
|
|
90803
|
+
}
|
|
90804
|
+
return upperPt;
|
|
90805
|
+
}
|
|
90698
90806
|
// rD - Estimate the number of responsive districts, given a set of Vf's
|
|
90699
90807
|
function estResponsiveDistricts(VfArray) {
|
|
90700
90808
|
// Python: sum([est_district_responsiveness(vpi) for vpi in vpi_by_district])
|
|
@@ -90764,7 +90872,7 @@ function estMarginalCompetitiveShare(Md, Mrange) {
|
|
|
90764
90872
|
}
|
|
90765
90873
|
exports.estMarginalCompetitiveShare = estMarginalCompetitiveShare;
|
|
90766
90874
|
function findMarginalDistricts(Vf, VfArray, N) {
|
|
90767
|
-
const bestS =
|
|
90875
|
+
const bestS = propSeats(N, Vf);
|
|
90768
90876
|
const fptpS = estFPTPSeats(VfArray);
|
|
90769
90877
|
// Find the marginal districts IDs (indexed 1–N)
|
|
90770
90878
|
let minId;
|
|
@@ -90785,6 +90893,328 @@ function findMarginalDistricts(Vf, VfArray, N) {
|
|
|
90785
90893
|
return [minId, maxId];
|
|
90786
90894
|
}
|
|
90787
90895
|
exports.findMarginalDistricts = findMarginalDistricts;
|
|
90896
|
+
// ADVANCED/ALTERNATE METRICS FOR REFERENCE
|
|
90897
|
+
function calcTurnoutBias(statewideVf, VfArray) {
|
|
90898
|
+
const districtAvg = U.avgArray(VfArray);
|
|
90899
|
+
const turnoutBias = statewideVf - districtAvg;
|
|
90900
|
+
return U.trim(turnoutBias);
|
|
90901
|
+
}
|
|
90902
|
+
exports.calcTurnoutBias = calcTurnoutBias;
|
|
90903
|
+
// PARTISAN BIAS - I'm using John Nagle's simple seat bias below, which is what
|
|
90904
|
+
// PlanScore is doing:
|
|
90905
|
+
//
|
|
90906
|
+
// "Partisan bias is the difference between each party’s seat share and 50 %
|
|
90907
|
+
// in a hypothetical, perfectly tied election.For example, if a party would
|
|
90908
|
+
// win 55 % of a plan’s districts if it received 50 % of the statewide vote,
|
|
90909
|
+
// then the plan would have a bias of 5 % in this party’s favor.To calculate
|
|
90910
|
+
// partisan bias, the observed vote share in each district is shifted by the
|
|
90911
|
+
// amount necessary to simulate a tied statewide election.Each party’s seat
|
|
90912
|
+
// share in this hypothetical election is then determined. The difference
|
|
90913
|
+
// between each party’s seat share and 50 % is partisan bias."
|
|
90914
|
+
//
|
|
90915
|
+
// This is *not* King's & others' geometric partisan bias metric per se.
|
|
90916
|
+
// That is below.
|
|
90917
|
+
function estPartisanBias(inferredSVpoints, nDistricts) {
|
|
90918
|
+
return estSeatBias(inferredSVpoints, nDistricts);
|
|
90919
|
+
}
|
|
90920
|
+
exports.estPartisanBias = estPartisanBias;
|
|
90921
|
+
// SEATS BIAS -- John Nagle's simple seat bias @ 50% (alpha), a fractional # of seats.
|
|
90922
|
+
function estSeatBias(inferredSVpoints, nDistricts) {
|
|
90923
|
+
const half = 0.5;
|
|
90924
|
+
const tolerance = 0.001;
|
|
90925
|
+
let dSeats = 0;
|
|
90926
|
+
for (let pt of inferredSVpoints) {
|
|
90927
|
+
if (U.areRoughlyEqual(pt.v, half, tolerance)) {
|
|
90928
|
+
dSeats = pt.s * nDistricts;
|
|
90929
|
+
break;
|
|
90930
|
+
}
|
|
90931
|
+
}
|
|
90932
|
+
const rSeats = nDistricts - dSeats;
|
|
90933
|
+
const Bs = (rSeats - dSeats) / 2.0;
|
|
90934
|
+
// NOTE - That is the same as (N/2) - S(0.5).
|
|
90935
|
+
// const BsAlt = (nDistricts / 2.0) - dSeats;
|
|
90936
|
+
return U.trim(Bs);
|
|
90937
|
+
}
|
|
90938
|
+
exports.estSeatBias = estSeatBias;
|
|
90939
|
+
// VOTES BIAS -- John Nagle's simple vote bias @ 50% (alpha2), a percentage.
|
|
90940
|
+
function estVotesBias(inferredSVpoints, nDistricts) {
|
|
90941
|
+
let extraVf = 0.0;
|
|
90942
|
+
// Interpolate the extra Vf required @ Sf = 0.5
|
|
90943
|
+
const lowerPt = findBracketingLowerSf(0.5, inferredSVpoints);
|
|
90944
|
+
const upperPt = findBracketingUpperSf(0.5, inferredSVpoints);
|
|
90945
|
+
if ((upperPt.s - lowerPt.s) != 0) {
|
|
90946
|
+
const ratio = (upperPt.v - lowerPt.v) / (upperPt.s - lowerPt.s);
|
|
90947
|
+
const deltaS = 0.5 - lowerPt.s;
|
|
90948
|
+
extraVf = lowerPt.v + (ratio * deltaS) - 0.5;
|
|
90949
|
+
}
|
|
90950
|
+
extraVf = U.trim(extraVf);
|
|
90951
|
+
return extraVf;
|
|
90952
|
+
}
|
|
90953
|
+
exports.estVotesBias = estVotesBias;
|
|
90954
|
+
// GEOMETRIC SEATS BIAS (@ V = statewide vote share)
|
|
90955
|
+
function estGeometricSeatsBias(Vf, inferredSVpoints) {
|
|
90956
|
+
const bgsSVpoints = inferGeometricSeatsBiasPoints(inferredSVpoints);
|
|
90957
|
+
// Interpolate the seat fraction @ Vf
|
|
90958
|
+
const lowerPt = findBracketingLowerVf(Vf, bgsSVpoints);
|
|
90959
|
+
const upperPt = findBracketingUpperVf(Vf, bgsSVpoints);
|
|
90960
|
+
const ratio = (upperPt.s - lowerPt.s) / (upperPt.v - lowerPt.v);
|
|
90961
|
+
const deltaV = Vf - lowerPt.v;
|
|
90962
|
+
const deltaS = ratio * deltaV;
|
|
90963
|
+
const BsGf = lowerPt.s + deltaS;
|
|
90964
|
+
return U.trim(BsGf);
|
|
90965
|
+
}
|
|
90966
|
+
exports.estGeometricSeatsBias = estGeometricSeatsBias;
|
|
90967
|
+
function inferGeometricSeatsBiasPoints(inferredSVpoints) {
|
|
90968
|
+
const nPoints = inferredSVpoints.length;
|
|
90969
|
+
const inverseSVpoints = invertSVPoints(inferredSVpoints);
|
|
90970
|
+
let bgsSVpoints = [];
|
|
90971
|
+
for (let i = 0; i < nPoints; i++) {
|
|
90972
|
+
const Vf = inferredSVpoints[i].v;
|
|
90973
|
+
const sD = inferredSVpoints[i].s;
|
|
90974
|
+
const sR = inverseSVpoints[i].s;
|
|
90975
|
+
const BsGf = 0.5 * (sR - sD);
|
|
90976
|
+
bgsSVpoints.push({ v: Vf, s: BsGf });
|
|
90977
|
+
}
|
|
90978
|
+
return bgsSVpoints;
|
|
90979
|
+
}
|
|
90980
|
+
exports.inferGeometricSeatsBiasPoints = inferGeometricSeatsBiasPoints;
|
|
90981
|
+
function invertSVPoints(inferredSVpoints) {
|
|
90982
|
+
let invertedSVpoints = [];
|
|
90983
|
+
for (let pt of inferredSVpoints) {
|
|
90984
|
+
const Vd = pt.v;
|
|
90985
|
+
const Sd = pt.s;
|
|
90986
|
+
const Vr = U.trim(1.0 - Vd);
|
|
90987
|
+
const Sr = 1.0 - Sd;
|
|
90988
|
+
invertedSVpoints.push({ v: Vr, s: Sr });
|
|
90989
|
+
}
|
|
90990
|
+
invertedSVpoints.sort(function (a, b) {
|
|
90991
|
+
return a.v - b.v;
|
|
90992
|
+
});
|
|
90993
|
+
return invertedSVpoints;
|
|
90994
|
+
}
|
|
90995
|
+
exports.invertSVPoints = invertSVPoints;
|
|
90996
|
+
// EFFICIENCY GAP -- note the formulation used. Also, to accommodate turnout bias,
|
|
90997
|
+
// we would need to have D & R votes, not just shares.
|
|
90998
|
+
function calcEfficiencyGap(Vf, Sf, shareType = 0 /* Democratic */) {
|
|
90999
|
+
let efficiencyGap;
|
|
91000
|
+
if (shareType == 1 /* Republican */) {
|
|
91001
|
+
// NOTE - This is the common formulation:
|
|
91002
|
+
//
|
|
91003
|
+
// EG = (Sf – 0.5) – (2 × (Vf – 0.5))
|
|
91004
|
+
//
|
|
91005
|
+
// in which it is implied that '-' = R bias; '+' = D bias.
|
|
91006
|
+
efficiencyGap = U.trim((Sf - 0.5) - (2.0 * (Vf - 0.5)));
|
|
91007
|
+
}
|
|
91008
|
+
else {
|
|
91009
|
+
// NOTE - This is the alternate formulation in which '+' = R bias; '-' = D bias,
|
|
91010
|
+
// which is consistent with all our other metrics.
|
|
91011
|
+
efficiencyGap = U.trim((2.0 * (Vf - 0.5)) - (Sf - 0.5));
|
|
91012
|
+
}
|
|
91013
|
+
return U.trim(efficiencyGap);
|
|
91014
|
+
}
|
|
91015
|
+
exports.calcEfficiencyGap = calcEfficiencyGap;
|
|
91016
|
+
// MEAN–MEDIAN DIFFERENCE
|
|
91017
|
+
//
|
|
91018
|
+
// From PlanScore.org: "The mean-median difference is a party’s median vote share
|
|
91019
|
+
// minus its mean vote share, across all of a plan’s districts. For example, if
|
|
91020
|
+
// a party has a median vote share of 45 % and a mean vote share of 50 %, then
|
|
91021
|
+
// the plan has a mean - median difference of 5 % against this party. When the
|
|
91022
|
+
// mean and the median diverge significantly, the district distribution is skewed
|
|
91023
|
+
// in favor of one party and against its opponent. Conversely, when the mean and
|
|
91024
|
+
// the median are close, the district distribution is more symmetric."
|
|
91025
|
+
//
|
|
91026
|
+
// From Princeton Gerrymandering Project: "The mean-median difference is calculated
|
|
91027
|
+
// by subtracting the average vote share of either party across all districts from
|
|
91028
|
+
// the median vote share of the same party across all districts. A negative mean -
|
|
91029
|
+
// median difference indicates that the examined party has an advantage; a positive
|
|
91030
|
+
// difference indicates that the examined party is disadvantaged."
|
|
91031
|
+
//
|
|
91032
|
+
// So:
|
|
91033
|
+
// * With D VPI, '+' = R bias; '-' = D bias <<< We're using this convention.
|
|
91034
|
+
// * With R VPI, '-' = R bias; '+' = D bias.
|
|
91035
|
+
function estMeanMedianDifference(VfArray, Vf) {
|
|
91036
|
+
const meanVf = Vf ? Vf : U.avgArray(VfArray);
|
|
91037
|
+
const medianVf = U.medianArray(VfArray);
|
|
91038
|
+
// NOTE - Switched order to get the signs correct
|
|
91039
|
+
const difference = meanVf - medianVf;
|
|
91040
|
+
// const difference: number = medianVf - meanVf;
|
|
91041
|
+
return U.trim(difference);
|
|
91042
|
+
}
|
|
91043
|
+
exports.estMeanMedianDifference = estMeanMedianDifference;
|
|
91044
|
+
// HELPERS FOR DECLINATION & LOPSIDED OUTCOMES
|
|
91045
|
+
// Key r(v) points, defined in Fig. 19:
|
|
91046
|
+
// * VfArray are Democratic seat shares (by convention).
|
|
91047
|
+
// * But the x-axis of r(v) graphs us Republican seat share.
|
|
91048
|
+
// * So, you have to invert the D/R axis for Vb; and
|
|
91049
|
+
// * Invert the D/R probabilities for Va.
|
|
91050
|
+
function keyRVpoints(VfArray) {
|
|
91051
|
+
const nDistricts = VfArray.length;
|
|
91052
|
+
const estS = estSeats(VfArray);
|
|
91053
|
+
const Sb = estSeatShare(estS, nDistricts);
|
|
91054
|
+
// TODO - Understand why the corresponding V to Sb is always @ 0.5.
|
|
91055
|
+
// John Nagle: "This is the dividing vote for party A vs party B wins defined
|
|
91056
|
+
// by Warrington.My modification just puts fractions of districts to the each side."
|
|
91057
|
+
const Rb = Sb / 2;
|
|
91058
|
+
const Ra = (1 + Sb) / 2;
|
|
91059
|
+
const Vb = 1.0 - (U.sumArray(VfArray.map(v => estSeatProbability(v) * v))) / estS;
|
|
91060
|
+
const Va = (U.sumArray(VfArray.map(v => estSeatProbability(1 - v) * (1 - v)))) / (nDistricts - estS);
|
|
91061
|
+
const keyPoints = {
|
|
91062
|
+
Sb: Sb,
|
|
91063
|
+
Ra: Ra,
|
|
91064
|
+
Rb: Rb,
|
|
91065
|
+
Va: Va,
|
|
91066
|
+
Vb: Vb
|
|
91067
|
+
};
|
|
91068
|
+
return keyPoints;
|
|
91069
|
+
}
|
|
91070
|
+
exports.keyRVpoints = keyRVpoints;
|
|
91071
|
+
function isASweep(Sf, nDistricts) {
|
|
91072
|
+
const oneDistrict = 1 / nDistricts;
|
|
91073
|
+
const bSweep = ((Sf > (1 - oneDistrict)) || (Sf < oneDistrict)) ? true : false;
|
|
91074
|
+
return bSweep;
|
|
91075
|
+
}
|
|
91076
|
+
exports.isASweep = isASweep;
|
|
91077
|
+
function radiansToDegrees(radians) {
|
|
91078
|
+
const degrees = radians * (180 / Math.PI);
|
|
91079
|
+
return degrees;
|
|
91080
|
+
}
|
|
91081
|
+
exports.radiansToDegrees = radiansToDegrees;
|
|
91082
|
+
// DECLINATION
|
|
91083
|
+
//
|
|
91084
|
+
// Declination is calculated using the key r(v) points, defined in Fig. 19.
|
|
91085
|
+
// Note that district vote shares are D shares, so party A = Rep & B = Dem.
|
|
91086
|
+
function calcDeclination(VfArray) {
|
|
91087
|
+
const { Sb, Ra, Rb, Va, Vb } = keyRVpoints(VfArray);
|
|
91088
|
+
const bSweep = isASweep(Sb, VfArray.length);
|
|
91089
|
+
const bTooFewDistricts = (VfArray.length < 5) ? true : false;
|
|
91090
|
+
const bVaAt50 = (U.areRoughlyEqual((Va - 0.5), 0.0, S.EPSILON)) ? true : false;
|
|
91091
|
+
const bVbAt50 = (U.areRoughlyEqual((0.5 - Vb), 0.0, S.EPSILON)) ? true : false;
|
|
91092
|
+
let decl;
|
|
91093
|
+
if (bSweep || bTooFewDistricts || bVaAt50 || bVbAt50) {
|
|
91094
|
+
decl = undefined;
|
|
91095
|
+
}
|
|
91096
|
+
else {
|
|
91097
|
+
const lTan = (Sb - Rb) / (0.5 - Vb);
|
|
91098
|
+
const rTan = (Ra - Sb) / (Va - 0.5);
|
|
91099
|
+
const lAngle = radiansToDegrees(Math.atan(lTan));
|
|
91100
|
+
const rAngle = radiansToDegrees(Math.atan(rTan));
|
|
91101
|
+
decl = rAngle - lAngle;
|
|
91102
|
+
decl = U.trim(decl);
|
|
91103
|
+
}
|
|
91104
|
+
return decl;
|
|
91105
|
+
}
|
|
91106
|
+
exports.calcDeclination = calcDeclination;
|
|
91107
|
+
// LOPSIDED OUTCOMES
|
|
91108
|
+
//
|
|
91109
|
+
// This is a measure of packing bias is:
|
|
91110
|
+
//
|
|
91111
|
+
// LO = (1⁄2 - vB) - (vA – 1⁄2) Eq. 5.4.1 on P. 26
|
|
91112
|
+
//
|
|
91113
|
+
// "The ideal for this measure is that the excess vote share for districts
|
|
91114
|
+
// won by party A averaged over those districts equals the excess vote share
|
|
91115
|
+
// for districts won by party B averaged over those districts.
|
|
91116
|
+
// A positive value of LO indicates greater packing of party B voters and,
|
|
91117
|
+
// therefore, indicates a bias in favor of party A."
|
|
91118
|
+
function calcLopsidedOutcomes(VfArray) {
|
|
91119
|
+
const { Sb, Ra, Rb, Va, Vb } = keyRVpoints(VfArray);
|
|
91120
|
+
const bSweep = isASweep(Sb, VfArray.length);
|
|
91121
|
+
let LO;
|
|
91122
|
+
if (bSweep) {
|
|
91123
|
+
LO = undefined;
|
|
91124
|
+
}
|
|
91125
|
+
else {
|
|
91126
|
+
LO = (0.5 - Vb) - (Va - 0.5);
|
|
91127
|
+
LO = U.trim(LO);
|
|
91128
|
+
}
|
|
91129
|
+
return LO;
|
|
91130
|
+
}
|
|
91131
|
+
exports.calcLopsidedOutcomes = calcLopsidedOutcomes;
|
|
91132
|
+
// TODO - Add unit tests <<< Depends on S(V)
|
|
91133
|
+
// GLOBAL SYMMETRY - Fig. 17 in Section 5.1
|
|
91134
|
+
function calcGlobalSymmetry(inferredSVpoints, S50V) {
|
|
91135
|
+
const invertedSVpoints = invertSVPoints(inferredSVpoints);
|
|
91136
|
+
let gSym = 0.0;
|
|
91137
|
+
for (let i in inferredSVpoints) {
|
|
91138
|
+
gSym += Math.abs(inferredSVpoints[i].s - invertedSVpoints[i].s) / 2;
|
|
91139
|
+
}
|
|
91140
|
+
const sign = (S50V < 0) ? -1 : 1;
|
|
91141
|
+
gSym *= sign;
|
|
91142
|
+
return U.trim(gSym);
|
|
91143
|
+
}
|
|
91144
|
+
exports.calcGlobalSymmetry = calcGlobalSymmetry;
|
|
91145
|
+
// RAW DISPROPORTIONALITY
|
|
91146
|
+
//
|
|
91147
|
+
// gamma = Sf – Vf : Eq.C.1.1 on P. 42
|
|
91148
|
+
function calcDisproportionality(Vf, Sf) {
|
|
91149
|
+
const gamma = Vf - Sf;
|
|
91150
|
+
// const gamma = Sf - Vf;
|
|
91151
|
+
return U.trim(gamma);
|
|
91152
|
+
}
|
|
91153
|
+
exports.calcDisproportionality = calcDisproportionality;
|
|
91154
|
+
// TODO - Add unit tests <<< Depends on S(V)
|
|
91155
|
+
// BIG 'R': Defined in Footnote 22 on P. 10
|
|
91156
|
+
function calcBigR(Vf, Sf) {
|
|
91157
|
+
let bigR = undefined;
|
|
91158
|
+
if (!(U.areRoughlyEqual(Vf, 0.5, S.EPSILON))) {
|
|
91159
|
+
bigR = (Sf - 0.5) / (Vf - 0.5);
|
|
91160
|
+
bigR = U.trim(bigR);
|
|
91161
|
+
}
|
|
91162
|
+
return bigR;
|
|
91163
|
+
}
|
|
91164
|
+
exports.calcBigR = calcBigR;
|
|
91165
|
+
// TODO - Add unit tests <<< Depends on S(V)
|
|
91166
|
+
// MINIMAL INVERSE RESPONSIVENESS
|
|
91167
|
+
//
|
|
91168
|
+
// zeta = (1 / r) - (1 / r_sub_max) : Eq. 5.2.1
|
|
91169
|
+
//
|
|
91170
|
+
// where r_sub_max = 10 or 20 for balanced and unbalanced states, respectively.
|
|
91171
|
+
function calcMinimalInverseResponsiveness(Vf, r) {
|
|
91172
|
+
let MIR = undefined;
|
|
91173
|
+
if (!(U.areRoughlyEqual(r, 0, S.EPSILON))) {
|
|
91174
|
+
const bBalanced = isBalanced(Vf);
|
|
91175
|
+
const ideal = bBalanced ? 0.1 : 0.2;
|
|
91176
|
+
MIR = (1 / r) - ideal;
|
|
91177
|
+
MIR = U.trim(MIR);
|
|
91178
|
+
}
|
|
91179
|
+
return MIR;
|
|
91180
|
+
}
|
|
91181
|
+
exports.calcMinimalInverseResponsiveness = calcMinimalInverseResponsiveness;
|
|
91182
|
+
function isBalanced(Vf) {
|
|
91183
|
+
const [lower, upper] = C.competitiveRange();
|
|
91184
|
+
const bBalanced = ((Vf > upper) || (Vf < lower)) ? false : true;
|
|
91185
|
+
return bBalanced;
|
|
91186
|
+
}
|
|
91187
|
+
// HELPERS
|
|
91188
|
+
function printPartisanScorecardHeader() {
|
|
91189
|
+
console.log('XX, Name, N, V%, ^S#, S#, B%, B$, UE#, I$, C#, Cd, Md, C$, <P$');
|
|
91190
|
+
}
|
|
91191
|
+
exports.printPartisanScorecardHeader = printPartisanScorecardHeader;
|
|
91192
|
+
function printPartisanScorecardRow(xx, name, N, Vf, s) {
|
|
91193
|
+
console.log('%s, %s, %i, %f, %i, %f, %f, %i, %f, %i, %i, %f, %f, %i, %i', xx, // 1
|
|
91194
|
+
name, // 2
|
|
91195
|
+
N, // 3
|
|
91196
|
+
Vf, // 4
|
|
91197
|
+
s.bias.propS, // 5
|
|
91198
|
+
s.bias.estS, // 6
|
|
91199
|
+
s.bias.bias, // 7
|
|
91200
|
+
s.bias.score, s.impact.unearnedS, // 9
|
|
91201
|
+
s.impact.score, s.competitiveness.c, // 11
|
|
91202
|
+
s.competitiveness.cD, s.competitiveness.mD, s.competitiveness.score, s.score // 15
|
|
91203
|
+
);
|
|
91204
|
+
}
|
|
91205
|
+
exports.printPartisanScorecardRow = printPartisanScorecardRow;
|
|
91206
|
+
// Generate partisan details (Table 1)
|
|
91207
|
+
function printPartisanDetailsHeader() {
|
|
91208
|
+
console.log('XX, <V>, S(<V>), S50V, V50S, Decl, B_G, EG, Beta, l-gamma, mM, TO, mM\', LO, R, r, Zeta');
|
|
91209
|
+
}
|
|
91210
|
+
exports.printPartisanDetailsHeader = printPartisanDetailsHeader;
|
|
91211
|
+
function printPartisanDetailsRow(xx, name, N, Vf, s) {
|
|
91212
|
+
console.log('%s, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f', xx, Vf, s.bias.estSf, s.bias.s50V, s.bias.v50S, s.bias.decl, s.bias.gSym, s.bias.eG, s.bias.sVg, // Beta
|
|
91213
|
+
s.bias.prop, // Lower-gamma
|
|
91214
|
+
s.bias.mMs, s.bias.tOf, s.bias.mMd, s.bias.lO, s.competitiveness.bigR, s.competitiveness.littleR, s.competitiveness.mIR // Zeta
|
|
91215
|
+
);
|
|
91216
|
+
}
|
|
91217
|
+
exports.printPartisanDetailsRow = printPartisanDetailsRow;
|
|
90788
91218
|
|
|
90789
91219
|
|
|
90790
91220
|
/***/ }),
|
|
@@ -90809,7 +91239,6 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
90809
91239
|
return result;
|
|
90810
91240
|
};
|
|
90811
91241
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
90812
|
-
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
90813
91242
|
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
90814
91243
|
const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
|
|
90815
91244
|
const compact_1 = __webpack_require__(/*! ./compact */ "./src/compact.ts");
|
|
@@ -90852,7 +91281,12 @@ function scorePlan(p, overridesJSON) {
|
|
|
90852
91281
|
populationDeviation: pdS
|
|
90853
91282
|
};
|
|
90854
91283
|
// PARTISAN ("fair") subcategories - bias, impact, & competitiveness (plus lots of supporting measures)
|
|
90855
|
-
const
|
|
91284
|
+
const options = {
|
|
91285
|
+
alternates: true,
|
|
91286
|
+
constrained: true,
|
|
91287
|
+
shift: 0 /* Proportional */
|
|
91288
|
+
};
|
|
91289
|
+
const pS = partisan_1.scorePartisan(p.partisanProfile.statewideVf, p.partisanProfile.vfArray, options);
|
|
90856
91290
|
// Combine the partisan/partisan & traditional principles/best scores into an overall
|
|
90857
91291
|
// score for comparing plans w/in a state
|
|
90858
91292
|
let score = weightOverall(pS.score2, tpS.score, 1 /* WithinAState */);
|
|
@@ -90905,11 +91339,26 @@ function mixinMinorityBonus(score, minorityBonus) {
|
|
|
90905
91339
|
}
|
|
90906
91340
|
exports.mixinMinorityBonus = mixinMinorityBonus;
|
|
90907
91341
|
function printScorecardHeader() {
|
|
90908
|
-
console.log('XX, Name, N, V%, ^S#, S#, B%, B$, UE#, I$, C#, Cd, Md, C$,
|
|
91342
|
+
console.log('XX, Name, N, V%, ^S#, S#, B%, B$, UE#, I$, C#, Cd, Md, C$, >P$, Rc, Rc$, Pc, Pc$, G$, Cs, Cs$, Ds, Ds$, S$, Eq, Eq$, T$, Od, M$, $$$');
|
|
90909
91343
|
}
|
|
90910
91344
|
exports.printScorecardHeader = printScorecardHeader;
|
|
90911
91345
|
function printScorecardRow(xx, name, N, Vf, s) {
|
|
90912
|
-
console.log('%s, %s, %i, %f, %i, %f, %f, %i, %f, %i, %i, %f, %f, %i, %i, %
|
|
91346
|
+
console.log('%s, %s, %i, %f, %i, %f, %f, %i, %f, %i, %i, %f, %f, %i, %i, %f, %i, %f, %i, %i, %f, %i, %f, %i, %i, %f, %i, %i, %f, %i, %i', xx, // 1
|
|
91347
|
+
name, // 2
|
|
91348
|
+
N, // 3
|
|
91349
|
+
Vf, // 4
|
|
91350
|
+
s.partisan.bias.propS, // 5
|
|
91351
|
+
s.partisan.bias.estS, // 6
|
|
91352
|
+
s.partisan.bias.bias, // 7
|
|
91353
|
+
s.partisan.bias.score, s.partisan.impact.unearnedS, // 9
|
|
91354
|
+
s.partisan.impact.score, s.partisan.competitiveness.c, // 11
|
|
91355
|
+
s.partisan.competitiveness.cD, s.partisan.competitiveness.mD, s.partisan.competitiveness.score, s.partisan.score2, // 15
|
|
91356
|
+
s.traditionalPrinciples.compactness.reock.raw, s.traditionalPrinciples.compactness.reock.normalized, s.traditionalPrinciples.compactness.polsby.raw, s.traditionalPrinciples.compactness.polsby.normalized, s.traditionalPrinciples.compactness.score, s.traditionalPrinciples.splitting.county.raw, s.traditionalPrinciples.splitting.county.normalized, s.traditionalPrinciples.splitting.district.raw, s.traditionalPrinciples.splitting.district.normalized, s.traditionalPrinciples.splitting.score, s.traditionalPrinciples.populationDeviation.raw, s.traditionalPrinciples.populationDeviation.normalized, s.traditionalPrinciples.score,
|
|
91357
|
+
// s.minority.nOpportunity1,
|
|
91358
|
+
// s.minority.nOpportunity2,
|
|
91359
|
+
s.minority.opportunityDistricts,
|
|
91360
|
+
// s.minority.nProportional,
|
|
91361
|
+
s.minority.score, s.score);
|
|
90913
91362
|
}
|
|
90914
91363
|
exports.printScorecardRow = printScorecardRow;
|
|
90915
91364
|
|
|
@@ -90966,6 +91415,66 @@ const sample_profile_json_1 = __importDefault(__webpack_require__(/*! ../testdat
|
|
|
90966
91415
|
exports.sampleProfile = sample_profile_json_1.default;
|
|
90967
91416
|
const sample_scorecard_json_1 = __importDefault(__webpack_require__(/*! ../testdata/samples/sample-scorecard.json */ "./testdata/samples/sample-scorecard.json"));
|
|
90968
91417
|
exports.sampleScorecard = sample_scorecard_json_1.default;
|
|
91418
|
+
exports.Metrics = {
|
|
91419
|
+
// PARTISAN
|
|
91420
|
+
// Bias metrics
|
|
91421
|
+
Vf: { label: "Statewide D vote share", abbr: "V" },
|
|
91422
|
+
estS: { label: "Probable D seats", abbr: "S_V" },
|
|
91423
|
+
estSf: { label: "Probable D seat share", units: 1 /* Percentage */, abbr: "S%" },
|
|
91424
|
+
propS: { label: "D seats closest to proportional", abbr: "^S" },
|
|
91425
|
+
propSf: { label: "D seat share closest to proportional", units: 1 /* Percentage */, abbr: "^S%" },
|
|
91426
|
+
fptpS: { label: "Probable FPTP D seats", abbr: "S!" },
|
|
91427
|
+
bias: { label: "Effective bias", units: 1 /* Percentage */, abbr: "B%", description: "Calculated as S% – ^S%" },
|
|
91428
|
+
biasScore: { label: "Bias score", abbr: "B$", description: "Bias score normalized [0–100]" },
|
|
91429
|
+
// Additional bias metrics
|
|
91430
|
+
s50v: { label: "Seats bias", units: 1 /* Percentage */, abbr: "BS_50", symbol: "\u{03B1}" },
|
|
91431
|
+
v50s: { label: "Votes bias", units: 1 /* Percentage */, abbr: "BV_50", symbol: "\u{03B1}2" },
|
|
91432
|
+
decl: { label: "Declination", units: 2 /* Degrees */, abbr: "decl", symbol: "\u{03B4}" },
|
|
91433
|
+
gSym: { label: "Global symmetry", abbr: "GS", symbol: "\u{0393}" },
|
|
91434
|
+
EG: { label: "Efficiency gap", units: 1 /* Percentage */, abbr: "EG", symbol: "\u{03B3}2" },
|
|
91435
|
+
BsGf: { label: "Partisan bias", units: 1 /* Percentage */, abbr: "BS_V", symbol: "\u{03D0}" },
|
|
91436
|
+
prop: { label: "Disproprtionality", units: 1 /* Percentage */, abbr: "prop", symbol: "\u{03B3}" },
|
|
91437
|
+
MM: { label: "Mean–median", abbr: "MM" },
|
|
91438
|
+
LO: { label: "Lopsided outcomes", abbr: "LO" },
|
|
91439
|
+
// Impact metrics
|
|
91440
|
+
unearnedS: { label: "Unearned seats", abbr: "UE" },
|
|
91441
|
+
impactScore: { label: "Impact score", abbr: "I$", description: "Impact score normalized to [0–100]" },
|
|
91442
|
+
// Competitiveness/responsiveness metrics
|
|
91443
|
+
c: { label: "Simple competitive districts", abbr: "C", description: "Count of districts in the range [45–55%]" },
|
|
91444
|
+
cD: { label: "Probable competitive districts", abbr: "Cd", description: "Probable competitive districts, using probabilities" },
|
|
91445
|
+
cDf: { label: "Probable competitive districts share", units: 1 /* Percentage */, abbr: "Cdf" },
|
|
91446
|
+
mD: { label: "Competitive marginal districts", abbr: "Md", description: "Probable competitive marginal districts, using probabilities" },
|
|
91447
|
+
mDf: { label: "Competitive marginal districts share", units: 1 /* Percentage */, abbr: "Mdf" },
|
|
91448
|
+
competitivenessScore: { label: "Competitiveness score", abbr: "C$", description: "Competitiveness score normalized to [0–100]" },
|
|
91449
|
+
// Additional competitiveness/responsiveness metrics
|
|
91450
|
+
rD: { label: "Responsive districts", abbr: "Rd" },
|
|
91451
|
+
rDf: { label: "Responsive districts %", abbr: "Rd%" },
|
|
91452
|
+
bigR: { label: "Overall responsiveness", abbr: "R" /*, symbol: "\u{}" */ },
|
|
91453
|
+
littleR: { label: "Point responsiveness", abbr: "r", symbol: "\u{03C1}" },
|
|
91454
|
+
MIR: { label: "Minimal inverse responsiveness", abbr: "MIR", symbol: "\u{03B6}" },
|
|
91455
|
+
partisanScore: { label: "Partisan score", abbr: "P$" },
|
|
91456
|
+
// * <P$ [score] = the combined partisan score used to compare plans *across* states
|
|
91457
|
+
// * >P$ [score2] = the combined partisan score used to compare plans *within* a state, along with traditional districting principles
|
|
91458
|
+
// MINORITY
|
|
91459
|
+
oD: { label: "Opportunity districts", abbr: "Od" },
|
|
91460
|
+
minorityBonus: { label: "Minority bonus", abbr: "M$" },
|
|
91461
|
+
// TRADITIONAL DISTRICTING PRINCIPLES
|
|
91462
|
+
reockRaw: { label: "Reock", abbr: "Rc" },
|
|
91463
|
+
reockNormalized: { label: "Reock", abbr: "Rc$", description: "Reock normalized to [0–100]" },
|
|
91464
|
+
polsbyRaw: { label: "Polsby-Popper", abbr: "Pc" },
|
|
91465
|
+
polsbyNormalized: { label: "Polsby-Popper", abbr: "Pc$", description: "Polsby-Popper normalized to [0–100]" },
|
|
91466
|
+
compactnessScore: { label: "Compactness score", abbr: "G$" },
|
|
91467
|
+
countySplittingRaw: { label: "County splitting", abbr: "Cs" },
|
|
91468
|
+
countySplittingNormalized: { label: "County splitting", abbr: "Cs$", description: "County splitting normalized to [0–100]" },
|
|
91469
|
+
districtSplittingRaw: { label: "District splitting", abbr: "Ds" },
|
|
91470
|
+
districtSplittingNormalized: { label: "District splitting", abbr: "Ds$", description: "District splitting normalized to [0–100]" },
|
|
91471
|
+
splittingScore: { label: "Splitting score", abbr: "S$" },
|
|
91472
|
+
populationDeviationRaw: { label: "Population deviation", abbr: "Eq" },
|
|
91473
|
+
populationDeviationNormalized: { label: "County splitting", abbr: "Eq$", description: "Population deviation normalized to [0–100]" },
|
|
91474
|
+
traditionalPrinciplesScore: { label: "Traditional districting principles score", abbr: "T$", description: "Traditional principles score normalized to [0–100]" },
|
|
91475
|
+
// OVERALL SCORE
|
|
91476
|
+
score: { label: "Overall score", abbr: "$$$" }
|
|
91477
|
+
};
|
|
90969
91478
|
|
|
90970
91479
|
|
|
90971
91480
|
/***/ }),
|
|
@@ -91008,11 +91517,31 @@ function maxArray(arr) {
|
|
|
91008
91517
|
return Math.max(...arr);
|
|
91009
91518
|
}
|
|
91010
91519
|
exports.maxArray = maxArray;
|
|
91520
|
+
// Modified from https://jsfiddle.net/Lucky500/3sy5au0c/
|
|
91521
|
+
function medianArray(arr) {
|
|
91522
|
+
if (arr.length === 0)
|
|
91523
|
+
return 0;
|
|
91524
|
+
arr.sort(function (a, b) {
|
|
91525
|
+
return a - b;
|
|
91526
|
+
});
|
|
91527
|
+
var half = Math.floor(arr.length / 2);
|
|
91528
|
+
if (arr.length % 2)
|
|
91529
|
+
return arr[half];
|
|
91530
|
+
return (arr[half - 1] + arr[half]) / 2.0;
|
|
91531
|
+
}
|
|
91532
|
+
exports.medianArray = medianArray;
|
|
91011
91533
|
function initArray(n, value) {
|
|
91012
91534
|
return Array.from(Array(n), () => value);
|
|
91013
91535
|
}
|
|
91014
91536
|
exports.initArray = initArray;
|
|
91015
91537
|
// MISCELLANEOUS
|
|
91538
|
+
// Deal with decimal census "counts" due to disagg/re-agg
|
|
91539
|
+
function areRoughlyEqual(x, y, tolerance) {
|
|
91540
|
+
let delta = Math.abs(x - y);
|
|
91541
|
+
let result = (delta < tolerance) ? true : false;
|
|
91542
|
+
return result;
|
|
91543
|
+
}
|
|
91544
|
+
exports.areRoughlyEqual = areRoughlyEqual;
|
|
91016
91545
|
// Round a fractional number [0-1] to the desired level of PRECISION.
|
|
91017
91546
|
function trim(fullFraction, digits = undefined) {
|
|
91018
91547
|
if (digits == 0) {
|
|
@@ -91064,7 +91593,7 @@ module.exports = JSON.parse("{\"state\":\"NC\",\"planName\":\"NC 116th Congressi
|
|
|
91064
91593
|
/*! exports provided: score, partisan, minority, traditionalPrinciples, default */
|
|
91065
91594
|
/***/ (function(module) {
|
|
91066
91595
|
|
|
91067
|
-
module.exports = JSON.parse("{\"score\":5,\"partisan\":{\"bias\":{\"
|
|
91596
|
+
module.exports = JSON.parse("{\"score\":5,\"partisan\":{\"bias\":{\"propS\":7,\"propSf\":0.5385,\"estS\":4.1925,\"estSf\":0.3225,\"bias\":0.216,\"tOf\":-0.0023,\"fptpS\":3,\"s50V\":0.2172,\"v50S\":0.045,\"decl\":36.5164,\"gSym\":6.6602,\"eG\":0.2846,\"sVg\":0.2098,\"prop\":0.2695,\"mMd\":0.0593,\"mMs\":0.057,\"lO\":0.1106,\"score\":0},\"impact\":{\"unearnedS\":2.8075,\"score\":0},\"competitiveness\":{\"bigR\":-16.926,\"littleR\":4.3523,\"mIR\":0.1298,\"rD\":4.0207,\"rDf\":0.3093,\"c\":6,\"cD\":0.7266,\"cDf\":0.0559,\"mRange\":[5,11],\"mD\":0.7134,\"mDf\":0.1019,\"score\":10},\"score\":3,\"score2\":2},\"minority\":{\"report\":{\"bucketsByDemographic\":[[10,14],[3,0],[1,9],[0,0],[0,0],[0,0]],\"averageDVf\":0.6737588682747838},\"nProportional\":18,\"opportunityDistricts\":12.9424,\"score\":14},\"traditionalPrinciples\":{\"score\":18,\"compactness\":{\"score\":35,\"reock\":{\"raw\":0.3373,\"normalized\":35,\"notes\":{}},\"polsby\":{\"raw\":0.2418,\"normalized\":35,\"notes\":{}}},\"splitting\":{\"score\":0,\"county\":{\"raw\":1.1474,\"normalized\":0,\"notes\":{}},\"district\":{\"raw\":1.4839,\"normalized\":3,\"notes\":{}}},\"populationDeviation\":{\"raw\":0.016,\"normalized\":0,\"notes\":{\"maxDeviation\":11693}}}}");
|
|
91068
91597
|
|
|
91069
91598
|
/***/ })
|
|
91070
91599
|
|