@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.
@@ -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
- let bGoodPoints = true;
717
- // bGoodPoints = (Number.isNaN(pctDem)) ? false : true;
718
- // bGoodPoints = (Number.isNaN(pctWhite)) ? false : true;
719
- // bGoodPoints = (Number.isNaN(pctMinority)) ? false : true;
720
- // bGoodPoints = (Number.isNaN(pctBlack)) ? false : true;
721
- // bGoodPoints = (Number.isNaN(pctHispanic)) ? false : true;
722
- // bGoodPoints = (Number.isNaN(pctPacific)) ? false : true;
723
- // bGoodPoints = (Number.isNaN(pctAsian)) ? false : true;
724
- // bGoodPoints = (Number.isNaN(pctNative)) ? false : true;
725
- if (bGoodPoints) {
726
- ids.push(geoID);
727
- whitePts.push({ x: pctWhite, y: pctDem });
728
- minorityPts.push({ x: pctMinority, y: pctDem });
729
- blackPts.push({ x: pctBlack, y: pctDem });
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 U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
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
- // 11-03-2020 - Added for racially polarized voting analysis
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
- // Either take take the minorities to analyze in as selections -or- infer them
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
- if (points === undefined)
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
- const roundUp = 0.0;
1513
- const fractional = proportion * nDistricts;
1514
- const integral = Math.round(fractional + roundUp);
1515
- return integral;
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
- let result = {};
1520
- const n = points.length;
1521
- let sum_x = 0;
1522
- let sum_y = 0;
1523
- let sum_xy = 0;
1524
- let sum_xx = 0;
1525
- let sum_yy = 0;
1526
- for (let i = 0; i < n; i++) {
1527
- const pt = points[i];
1528
- sum_x += pt.x;
1529
- sum_y += pt.y;
1530
- sum_xy += (pt.x * pt.y);
1531
- sum_xx += (pt.x * pt.x);
1532
- sum_yy += (pt.y * pt.y);
1533
- }
1534
- // y = mx + b
1535
- result['m'] = (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x);
1536
- result['b'] = (sum_y - result.m * sum_x) / n;
1537
- 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);
1538
- return result;
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
- function characterizeDemographicVoting(points) {
1541
- const { m, b, r2 } = linearRegression(points);
1542
- const maxVAPPct = 1.0; // 100%
1543
- const maxDemPct = (m * maxVAPPct) + b;
1544
- // NOTE - You can't trim the implied max D %, because it's a regression line!
1545
- // const maxDemPct: number = Math.max(0.0, Math.min(1.0, (m * maxVAPPct) + b));
1546
- const result = {
1547
- m: U.trim(m),
1548
- b: U.trim(b),
1549
- r2: U.trim(r2),
1550
- f1: U.trim(maxDemPct),
1551
- points: points
1552
- };
1553
- return result;
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" ***!