@dra2020/district-analytics 10.2.6 → 10.3.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/district-analytics.js +162 -107
- package/dist/district-analytics.js.map +1 -1
- package/dist/src/_api.d.ts +2 -1
- package/dist/src/_data.d.ts +2 -1
- package/dist/src/minority.d.ts +2 -1
- package/dist/src/types.d.ts +0 -40
- package/package.json +2 -1
|
@@ -122,7 +122,6 @@ const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
|
|
|
122
122
|
const score_1 = __webpack_require__(/*! ./score */ "./src/score.ts");
|
|
123
123
|
const results_1 = __webpack_require__(/*! ./results */ "./src/results.ts");
|
|
124
124
|
const results_2 = __webpack_require__(/*! ./results */ "./src/results.ts");
|
|
125
|
-
// 11-03-2020 - Added for racially polarized voting analysis
|
|
126
125
|
const minority_1 = __webpack_require__(/*! ./minority */ "./src/minority.ts");
|
|
127
126
|
const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "@dra2020/poly"));
|
|
128
127
|
const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
@@ -678,6 +677,7 @@ class Districts {
|
|
|
678
677
|
let pacificPts = [];
|
|
679
678
|
let asianPts = [];
|
|
680
679
|
let nativePts = [];
|
|
680
|
+
let dataDump = []; // For testing
|
|
681
681
|
// Gather [demographic %, D %] points for the selected district ID
|
|
682
682
|
let i = districtID;
|
|
683
683
|
// HACK - Because "this" gets ghosted inside the forEach loop below
|
|
@@ -713,37 +713,20 @@ class Districts {
|
|
|
713
713
|
const pctPacific = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "PacC" /* PacificPop */) / totalVAP;
|
|
714
714
|
const pctAsian = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "AsnC" /* AsianPop */) / totalVAP;
|
|
715
715
|
const pctNative = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "NatC" /* NativePop */) / totalVAP;
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
//
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
hispanicPts.push({ x: pctHispanic, y: pctDem });
|
|
731
|
-
pacificPts.push({ x: pctPacific, y: pctDem });
|
|
732
|
-
asianPts.push({ x: pctAsian, y: pctDem });
|
|
733
|
-
nativePts.push({ x: pctNative, y: pctDem });
|
|
734
|
-
}
|
|
735
|
-
else {
|
|
736
|
-
if (bLog) {
|
|
737
|
-
console.log("RPV: NaN value for feature ", geoID);
|
|
738
|
-
console.log("* pctDem: ", pctDem);
|
|
739
|
-
console.log("* pctWhite: ", pctWhite);
|
|
740
|
-
console.log("* pctMinority: ", pctMinority);
|
|
741
|
-
console.log("* pctBlack: ", pctBlack);
|
|
742
|
-
console.log("* pctHispanic: ", pctHispanic);
|
|
743
|
-
console.log("* pctPacific: ", pctPacific);
|
|
744
|
-
console.log("* pctAsian: ", pctAsian);
|
|
745
|
-
console.log("* pctNative: ", pctNative);
|
|
746
|
-
}
|
|
716
|
+
ids.push(geoID);
|
|
717
|
+
whitePts.push({ x: pctWhite, y: pctDem });
|
|
718
|
+
minorityPts.push({ x: pctMinority, y: pctDem });
|
|
719
|
+
blackPts.push({ x: pctBlack, y: pctDem });
|
|
720
|
+
hispanicPts.push({ x: pctHispanic, y: pctDem });
|
|
721
|
+
pacificPts.push({ x: pctPacific, y: pctDem });
|
|
722
|
+
asianPts.push({ x: pctAsian, y: pctDem });
|
|
723
|
+
nativePts.push({ x: pctNative, y: pctDem });
|
|
724
|
+
// Dump test data based on config switch
|
|
725
|
+
const s = outerThis._session;
|
|
726
|
+
const bDump = (('dump' in s.config) && s.config.dump) ? true : false;
|
|
727
|
+
if (bDump) {
|
|
728
|
+
const row = [pctWhite, pctMinority, pctBlack, pctHispanic, pctPacific, pctAsian, pctNative, pctDem];
|
|
729
|
+
dataDump.push(row);
|
|
747
730
|
}
|
|
748
731
|
}
|
|
749
732
|
else {
|
|
@@ -759,6 +742,7 @@ class Districts {
|
|
|
759
742
|
}
|
|
760
743
|
});
|
|
761
744
|
// ... but only keep points for the demographics that are going to be analyzed
|
|
745
|
+
dataDump = [];
|
|
762
746
|
return {
|
|
763
747
|
ids: ids,
|
|
764
748
|
white: whitePts,
|
|
@@ -1445,7 +1429,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
1445
1429
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
1446
1430
|
};
|
|
1447
1431
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1448
|
-
const
|
|
1432
|
+
const RPV = __importStar(__webpack_require__(/*! @dra2020/racial-voting */ "@dra2020/racial-voting"));
|
|
1449
1433
|
const majority_minority_json_1 = __importDefault(__webpack_require__(/*! ../static/majority-minority.json */ "./static/majority-minority.json"));
|
|
1450
1434
|
const vra5_preclearance_json_1 = __importDefault(__webpack_require__(/*! ../static/vra5-preclearance.json */ "./static/vra5-preclearance.json"));
|
|
1451
1435
|
// TODO - 2020: Update/revise this, when the update comes out in September:
|
|
@@ -1467,92 +1451,152 @@ function getVRASection5(s) {
|
|
|
1467
1451
|
return stateVRAPre;
|
|
1468
1452
|
}
|
|
1469
1453
|
exports.getVRASection5 = getVRASection5;
|
|
1470
|
-
//
|
|
1471
|
-
// Analyze the degree of racially polarized voting for a district
|
|
1454
|
+
// RPV - pulled into a separate component 11-17-2020
|
|
1472
1455
|
function doAnalyzeRacialPolarization(s, districtID, groups, bLog = false) {
|
|
1473
|
-
//
|
|
1474
|
-
// from the % VAP/CVAP for the district (not the state).
|
|
1475
|
-
if (groups == undefined) {
|
|
1476
|
-
const RPV_THRESHOLD = 0.35; // Using ~ the Bethune–Hill threshold
|
|
1477
|
-
groups = {
|
|
1478
|
-
white: true,
|
|
1479
|
-
minority: false,
|
|
1480
|
-
black: (s.districts.table.blackPct[districtID] > RPV_THRESHOLD) ? true : false,
|
|
1481
|
-
hispanic: (s.districts.table.hispanicPct[districtID] > RPV_THRESHOLD) ? true : false,
|
|
1482
|
-
pacific: (s.districts.table.pacificPct[districtID] > RPV_THRESHOLD) ? true : false,
|
|
1483
|
-
asian: (s.districts.table.asianPct[districtID] > RPV_THRESHOLD) ? true : false,
|
|
1484
|
-
native: (s.districts.table.nativePct[districtID] > RPV_THRESHOLD) ? true : false
|
|
1485
|
-
};
|
|
1486
|
-
}
|
|
1456
|
+
// At least one minority group must be specified
|
|
1487
1457
|
if (!(groups.black || groups.hispanic || groups.pacific || groups.asian || groups.native))
|
|
1488
1458
|
return undefined;
|
|
1489
1459
|
const points = s.districts.extractVotesByDemographic(districtID, groups, bLog);
|
|
1490
|
-
|
|
1491
|
-
return undefined;
|
|
1492
|
-
// Make sure there are enough points to do a regression
|
|
1493
|
-
if (points.white.length <= 10)
|
|
1494
|
-
return undefined;
|
|
1495
|
-
// At this point, at least one single-race/ethnicity minority demographic needs
|
|
1496
|
-
// to be analyzed along with white.
|
|
1497
|
-
const result = {
|
|
1498
|
-
ids: points.ids,
|
|
1499
|
-
white: characterizeDemographicVoting(points.white),
|
|
1500
|
-
minority: groups.minority ? characterizeDemographicVoting(points.minority) : undefined,
|
|
1501
|
-
black: groups.black ? characterizeDemographicVoting(points.black) : undefined,
|
|
1502
|
-
hispanic: groups.hispanic ? characterizeDemographicVoting(points.hispanic) : undefined,
|
|
1503
|
-
pacific: groups.pacific ? characterizeDemographicVoting(points.pacific) : undefined,
|
|
1504
|
-
asian: groups.asian ? characterizeDemographicVoting(points.asian) : undefined,
|
|
1505
|
-
native: groups.native ? characterizeDemographicVoting(points.native) : undefined
|
|
1506
|
-
};
|
|
1507
|
-
return result;
|
|
1460
|
+
return RPV.analyzeRacialVoting(points, districtID, groups);
|
|
1508
1461
|
}
|
|
1509
1462
|
exports.doAnalyzeRacialPolarization = doAnalyzeRacialPolarization;
|
|
1463
|
+
/* TODO - RPV: DELETE
|
|
1464
|
+
// 11-03-2020 - Added for racially polarized voting analysis
|
|
1465
|
+
// Analyze the degree of racially polarized voting for a district
|
|
1466
|
+
export function doAnalyzeRacialPolarization(s: AnalyticsSession, districtID: number, groups?: T.MinorityFilter, bLog: boolean = false): T.RPVAnalysis | undefined
|
|
1467
|
+
{
|
|
1468
|
+
// Either take take the minorities to analyze in as selections -or- infer them
|
|
1469
|
+
// from the % VAP/CVAP for the district (not the state).
|
|
1470
|
+
if (groups == undefined)
|
|
1471
|
+
{
|
|
1472
|
+
const RPV_THRESHOLD = 0.35; // Using ~ the Bethune–Hill threshold
|
|
1473
|
+
groups = {
|
|
1474
|
+
white: true,
|
|
1475
|
+
minority: false,
|
|
1476
|
+
black: (s.districts.table.blackPct[districtID] > RPV_THRESHOLD) ? true : false,
|
|
1477
|
+
hispanic: (s.districts.table.hispanicPct[districtID] > RPV_THRESHOLD) ? true : false,
|
|
1478
|
+
pacific: (s.districts.table.pacificPct[districtID] > RPV_THRESHOLD) ? true : false,
|
|
1479
|
+
asian: (s.districts.table.asianPct[districtID] > RPV_THRESHOLD) ? true : false,
|
|
1480
|
+
native: (s.districts.table.nativePct[districtID] > RPV_THRESHOLD) ? true : false
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
if (!(groups.black || groups.hispanic || groups.pacific || groups.asian || groups.native)) return undefined;
|
|
1485
|
+
|
|
1486
|
+
const points = s.districts.extractVotesByDemographic(districtID, groups, bLog);
|
|
1487
|
+
|
|
1488
|
+
if (points === undefined) return undefined;
|
|
1489
|
+
|
|
1490
|
+
// Make sure there are enough points to do a regression
|
|
1491
|
+
if (points.white.length <= 10) return undefined;
|
|
1492
|
+
|
|
1493
|
+
// At this point, at least one single-race/ethnicity minority demographic needs
|
|
1494
|
+
// to be analyzed along with white.
|
|
1495
|
+
|
|
1496
|
+
const result: T.RPVAnalysis = {
|
|
1497
|
+
ids: points.ids,
|
|
1498
|
+
white: characterizeDemographicVoting(points.white),
|
|
1499
|
+
minority: groups.minority ? characterizeDemographicVoting(points.minority) : undefined,
|
|
1500
|
+
black: groups.black ? characterizeDemographicVoting(points.black) : undefined,
|
|
1501
|
+
hispanic: groups.hispanic ? characterizeDemographicVoting(points.hispanic) : undefined,
|
|
1502
|
+
pacific: groups.pacific ? characterizeDemographicVoting(points.pacific) : undefined,
|
|
1503
|
+
asian: groups.asian ? characterizeDemographicVoting(points.asian) : undefined,
|
|
1504
|
+
native: groups.native ? characterizeDemographicVoting(points.native) : undefined
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
return result;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
1510
|
// Cloned from dra-score
|
|
1511
|
-
function calcProportionalDistricts(proportion, nDistricts)
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1511
|
+
function calcProportionalDistricts(proportion: number, nDistricts: number): number
|
|
1512
|
+
{
|
|
1513
|
+
const roundUp = 0.0;
|
|
1514
|
+
const fractional = proportion * nDistricts;
|
|
1515
|
+
const integral = Math.round(fractional + roundUp);
|
|
1516
|
+
|
|
1517
|
+
return integral;
|
|
1516
1518
|
}
|
|
1519
|
+
|
|
1517
1520
|
// https://trentrichardson.com/compute-linear-regressions-in-javascript.html
|
|
1518
|
-
function linearRegression(points)
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1521
|
+
function linearRegression(points: T.Point[]): any
|
|
1522
|
+
{
|
|
1523
|
+
let result: any = {};
|
|
1524
|
+
|
|
1525
|
+
const n: number = points.length;
|
|
1526
|
+
let sum_x = 0;
|
|
1527
|
+
let sum_y = 0;
|
|
1528
|
+
let sum_xy = 0;
|
|
1529
|
+
let sum_xx = 0;
|
|
1530
|
+
let sum_yy = 0;
|
|
1531
|
+
|
|
1532
|
+
for (let i = 0; i < n; i++)
|
|
1533
|
+
{
|
|
1534
|
+
const pt = points[i];
|
|
1535
|
+
|
|
1536
|
+
sum_x += pt.x;
|
|
1537
|
+
sum_y += pt.y;
|
|
1538
|
+
sum_xy += (pt.x * pt.y);
|
|
1539
|
+
sum_xx += (pt.x * pt.x);
|
|
1540
|
+
sum_yy += (pt.y * pt.y);
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
// y = mx + b
|
|
1544
|
+
result['m'] = (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x);
|
|
1545
|
+
result['b'] = (sum_y - result.m * sum_x) / n;
|
|
1546
|
+
result['r2'] = Math.pow((n * sum_xy - sum_x * sum_y) / Math.sqrt((n * sum_xx - sum_x * sum_x) * (n * sum_yy - sum_y * sum_y)), 2);
|
|
1547
|
+
|
|
1548
|
+
return result;
|
|
1539
1549
|
}
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1550
|
+
|
|
1551
|
+
function characterizeDemographicVoting(points: T.Point[]): T.RPVLine
|
|
1552
|
+
{
|
|
1553
|
+
const { m, b, r2 }: any = linearRegression(points);
|
|
1554
|
+
const maxVAPPct = 1.0; // 100%
|
|
1555
|
+
const maxDemPct: number = (m * maxVAPPct) + b;
|
|
1556
|
+
// NOTE - You can't trim the implied max D %, because it's a regression line!
|
|
1557
|
+
// const maxDemPct: number = Math.max(0.0, Math.min(1.0, (m * maxVAPPct) + b));
|
|
1558
|
+
|
|
1559
|
+
const result = {
|
|
1560
|
+
m: U.trim(m),
|
|
1561
|
+
b: U.trim(b),
|
|
1562
|
+
r2: U.trim(r2),
|
|
1563
|
+
sterr: U.trim(standardError(points, m, b)),
|
|
1564
|
+
f1: U.trim(maxDemPct),
|
|
1565
|
+
points: points
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
return result;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// TODO - RPV: Review with Stephen Ansolabehere
|
|
1572
|
+
// https://www.wikihow.com/Calculate-the-Standard-Error-of-Estimate
|
|
1573
|
+
// https://www.stat.cmu.edu/~hseltman/309/Book/chapter9.pdf
|
|
1574
|
+
// y = mx + b
|
|
1575
|
+
function standardError(points: T.Point[], m: number, b: number): number
|
|
1576
|
+
{
|
|
1577
|
+
const n: number = points.length;
|
|
1578
|
+
let sse: number = 0;
|
|
1579
|
+
|
|
1580
|
+
for (let i = 0; i < n; i++)
|
|
1581
|
+
{
|
|
1582
|
+
const pt = points[i];
|
|
1583
|
+
|
|
1584
|
+
const yPrime = yOnLine(pt.x, m, b);
|
|
1585
|
+
const deltaY = pt.y - yPrime;
|
|
1586
|
+
|
|
1587
|
+
sse += Math.pow(deltaY, 2);
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
const sigma = Math.sqrt(sse / n);
|
|
1591
|
+
|
|
1592
|
+
return sigma;
|
|
1554
1593
|
}
|
|
1555
1594
|
|
|
1595
|
+
// For interpolating points on a line
|
|
1596
|
+
const yOnLine = (x: number, m: number, b: number): number => { return (m * x) + b; }
|
|
1597
|
+
const xOnLine = (y: number, m: number, b: number): number => { return (y - b) / m; }
|
|
1598
|
+
*/
|
|
1599
|
+
|
|
1556
1600
|
|
|
1557
1601
|
/***/ }),
|
|
1558
1602
|
|
|
@@ -2667,6 +2711,17 @@ module.exports = require("@dra2020/poly");
|
|
|
2667
2711
|
|
|
2668
2712
|
/***/ }),
|
|
2669
2713
|
|
|
2714
|
+
/***/ "@dra2020/racial-voting":
|
|
2715
|
+
/*!*****************************************!*\
|
|
2716
|
+
!*** external "@dra2020/racial-voting" ***!
|
|
2717
|
+
\*****************************************/
|
|
2718
|
+
/*! no static exports found */
|
|
2719
|
+
/***/ (function(module, exports) {
|
|
2720
|
+
|
|
2721
|
+
module.exports = require("@dra2020/racial-voting");
|
|
2722
|
+
|
|
2723
|
+
/***/ }),
|
|
2724
|
+
|
|
2670
2725
|
/***/ "@dra2020/util":
|
|
2671
2726
|
/*!********************************!*\
|
|
2672
2727
|
!*** external "@dra2020/util" ***!
|