@dra2020/district-analytics 2.0.4 → 2.0.7
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/README.md +4 -0
- package/dist/cli.js +45 -16
- package/dist/cli.js.map +1 -1
- package/dist/district-analytics.js +45 -16
- package/dist/district-analytics.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Analytics for scoring congressional and legislative district maps.
|
|
4
4
|
|
|
5
|
+
## Build Status
|
|
6
|
+
|
|
7
|
+
[](https://circleci.com/gh/dra2020/district-analytics)
|
|
8
|
+
|
|
5
9
|
## Sample Code
|
|
6
10
|
|
|
7
11
|
```javascript
|
package/dist/cli.js
CHANGED
|
@@ -8074,6 +8074,13 @@ class AnalyticsSession {
|
|
|
8074
8074
|
this.features = new D.Features(this, SessionRequest['data'], this.config['datasets']);
|
|
8075
8075
|
this.plan = new D.Plan(this, SessionRequest['plan']);
|
|
8076
8076
|
this.districts = new D.Districts(this, SessionRequest['districtShapes']);
|
|
8077
|
+
// TODO - Confirming that the graph includes OUT_OF_STATE neighbors
|
|
8078
|
+
// if (U.keyExists(S.OUT_OF_STATE, this.graph._graph)) {
|
|
8079
|
+
// console.log("Contiguity graph includes out-of-state neighbors.");
|
|
8080
|
+
// }
|
|
8081
|
+
// else {
|
|
8082
|
+
// console.log("Contiguity graph does NOT include out-of-state neighbors.");
|
|
8083
|
+
// }
|
|
8077
8084
|
// NOTE: I've pulled these out of the individual analytics to here. Eventually,
|
|
8078
8085
|
// we could want them to passed into an analytics session as data, along with
|
|
8079
8086
|
// everything else. For now, this keeps branching out of the main code.
|
|
@@ -8183,6 +8190,9 @@ const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"
|
|
|
8183
8190
|
const valid_1 = __webpack_require__(/*! ./valid */ "./src/valid.ts");
|
|
8184
8191
|
const compact_1 = __webpack_require__(/*! ./compact */ "./src/compact.ts");
|
|
8185
8192
|
const political_1 = __webpack_require__(/*! ./political */ "./src/political.ts");
|
|
8193
|
+
// DEBUG COUNTERS
|
|
8194
|
+
let nMissingDataset = 0;
|
|
8195
|
+
let nMissingProperty = 0;
|
|
8186
8196
|
// DISTRICT STATISTICS
|
|
8187
8197
|
// Indexes for statistics fields in Districts
|
|
8188
8198
|
// NOTE - Not a const, so the number can be determined dynamically
|
|
@@ -8267,6 +8277,9 @@ class Districts {
|
|
|
8267
8277
|
// TODO - OPTIMIZE by async'ing this?
|
|
8268
8278
|
// TODO - Is there a way to do this programmatically off data? Does it matter?
|
|
8269
8279
|
recalcStatistics(bLog = false) {
|
|
8280
|
+
// Initialize debug counters
|
|
8281
|
+
nMissingDataset = 0;
|
|
8282
|
+
nMissingProperty = 0;
|
|
8270
8283
|
// Compute these once per recalc cycle
|
|
8271
8284
|
let targetSize = this._session.state.totalPop / this._session.state.nDistricts;
|
|
8272
8285
|
let deviationThreshold = this._session.populationDeviationThreshold();
|
|
@@ -8318,6 +8331,9 @@ class Districts {
|
|
|
8318
8331
|
// 3 - MORE ...
|
|
8319
8332
|
// HACK - Because "this" gets ghosted inside the forEach loop below
|
|
8320
8333
|
let outerThis = this;
|
|
8334
|
+
// Default the pop dev % for the dummy Unassigned district to 0%.
|
|
8335
|
+
// Default the pop dev % for real (1–N) but empty districts to 100%.
|
|
8336
|
+
let popDevPct = (i > 0) ? (targetSize / targetSize) : 0 / targetSize;
|
|
8321
8337
|
// Get the geoIDs assigned to the district
|
|
8322
8338
|
// Guard against empty districts
|
|
8323
8339
|
let geoIDs = this._session.plan.geoIDsForDistrictID(i);
|
|
@@ -8364,11 +8380,11 @@ class Districts {
|
|
|
8364
8380
|
});
|
|
8365
8381
|
// COMPUTE DERIVED VALUES
|
|
8366
8382
|
// Population deviation % and equal population (boolean) by district.
|
|
8367
|
-
// Default the value for the dummy unassigned district to 0%.
|
|
8368
|
-
let popDevPct = 0 / targetSize;
|
|
8369
8383
|
if (i > 0) {
|
|
8370
|
-
|
|
8371
|
-
|
|
8384
|
+
if (totalPop > 0) {
|
|
8385
|
+
popDevPct = (totalPop - targetSize) / targetSize;
|
|
8386
|
+
bEqualPop = (popDevPct <= deviationThreshold);
|
|
8387
|
+
}
|
|
8372
8388
|
}
|
|
8373
8389
|
// Total two-party (not total total!) votes, Democratic and Republican vote
|
|
8374
8390
|
// shares, and Democratic first-past-the-post win (= 1) or loss (= 0).
|
|
@@ -8457,7 +8473,7 @@ class Districts {
|
|
|
8457
8473
|
}
|
|
8458
8474
|
}
|
|
8459
8475
|
else { // If a district is empty, zero these results (vs. null)
|
|
8460
|
-
this.statistics[DistrictField.PopDevPct][i] =
|
|
8476
|
+
this.statistics[DistrictField.PopDevPct][i] = popDevPct;
|
|
8461
8477
|
this.statistics[DistrictField.DemVotes][i] = 0;
|
|
8462
8478
|
this.statistics[DistrictField.RepVotes][i] = 0;
|
|
8463
8479
|
this.statistics[DistrictField.TwoPartyVote][i] = 0;
|
|
@@ -8504,6 +8520,8 @@ class Districts {
|
|
|
8504
8520
|
this.statistics[DistrictField.AsianPct][summaryRow] = stateAsianPop / stateVAPPop;
|
|
8505
8521
|
this.statistics[DistrictField.NativePct][summaryRow] = stateNativePop / stateVAPPop;
|
|
8506
8522
|
}
|
|
8523
|
+
console.log(`${nMissingDataset} features with missing datasets.`);
|
|
8524
|
+
console.log(`${nMissingProperty} features with missing properties.`);
|
|
8507
8525
|
}
|
|
8508
8526
|
// NOTE - I did not roll these into district statistics, because creating the
|
|
8509
8527
|
// district shapes themselves is the big district-by-district activity, these
|
|
@@ -8558,36 +8576,46 @@ exports.Features = Features;
|
|
|
8558
8576
|
// f is a direct GeoJSON feature
|
|
8559
8577
|
// p is a geoID
|
|
8560
8578
|
function _getFeatures(f, datasetKey, p) {
|
|
8561
|
-
// Echo parameters for debugging
|
|
8562
|
-
// console.log("f =", f, "k = ", datasetKey, "p =", p);
|
|
8563
8579
|
// Shim to load sample data2.json from disk for command-line scaffolding
|
|
8564
8580
|
if (f.properties && f.properties['datasets']) {
|
|
8581
|
+
if (!f.properties['datasets'][datasetKey]) {
|
|
8582
|
+
// Feature is missing the dataset
|
|
8583
|
+
nMissingDataset += 1;
|
|
8584
|
+
console.log(`${nMissingDataset}: Data ${datasetKey} missing for feature ${f} Returning zero.`);
|
|
8585
|
+
return 0;
|
|
8586
|
+
}
|
|
8565
8587
|
return f.properties['datasets'][datasetKey][p];
|
|
8566
8588
|
}
|
|
8567
8589
|
// NOTE - The fGetW() code from dra-client below here ...
|
|
8568
8590
|
// Direct property?
|
|
8569
|
-
if (f.properties && f.properties[p] !== undefined)
|
|
8591
|
+
if (f.properties && f.properties[p] !== undefined) {
|
|
8570
8592
|
return f.properties[p];
|
|
8593
|
+
}
|
|
8571
8594
|
// Joined property?
|
|
8572
8595
|
let a = _fGetJoined(f);
|
|
8573
8596
|
if (a) {
|
|
8574
8597
|
for (let i = 0; i < a.length; i++) {
|
|
8575
8598
|
let o = a[i];
|
|
8576
8599
|
if (!datasetKey) {
|
|
8577
|
-
if (o[p] !== undefined)
|
|
8600
|
+
if (o[p] !== undefined) {
|
|
8578
8601
|
return o[p];
|
|
8602
|
+
}
|
|
8579
8603
|
}
|
|
8580
8604
|
else {
|
|
8581
8605
|
if (o['datasets'] && o['datasets'][datasetKey]) {
|
|
8582
8606
|
let v = (o['datasets'][datasetKey][p]);
|
|
8583
|
-
if ((!(v == null)) && (!(v == undefined)))
|
|
8607
|
+
if ((!(v == null)) && (!(v == undefined))) {
|
|
8584
8608
|
return o['datasets'][datasetKey][p];
|
|
8609
|
+
}
|
|
8585
8610
|
}
|
|
8586
8611
|
}
|
|
8587
8612
|
}
|
|
8588
8613
|
}
|
|
8589
|
-
|
|
8590
|
-
|
|
8614
|
+
// Feature is missing the property
|
|
8615
|
+
nMissingProperty += 1;
|
|
8616
|
+
console.log(`${nMissingProperty}: ${p} value undefined for ${f.properties['GEOID10']}. Returning zero.`);
|
|
8617
|
+
return 0;
|
|
8618
|
+
// return undefined;
|
|
8591
8619
|
}
|
|
8592
8620
|
function _fGetJoined(f) {
|
|
8593
8621
|
return (f.properties && f.properties.joined) ? f.properties.joined : undefined;
|
|
@@ -8632,6 +8660,7 @@ class Plan {
|
|
|
8632
8660
|
}
|
|
8633
8661
|
// NOTE - DON'T remove water-only features from the plan, as they may be required
|
|
8634
8662
|
// for contiguity. Just skip them in aggregating district statistics.
|
|
8663
|
+
//
|
|
8635
8664
|
// removeWaterOnlyFeatures(plan: T.PlanByGeoID): T.PlanByGeoID {
|
|
8636
8665
|
// let newPlan = {} as T.PlanByGeoID;
|
|
8637
8666
|
// for (let geoID in plan) {
|
|
@@ -9135,7 +9164,7 @@ exports.doFindCountiesSplitUnexpectedly = doFindCountiesSplitUnexpectedly;
|
|
|
9135
9164
|
function doFindSplitVTDs(s, bLog = false) {
|
|
9136
9165
|
let test = s.getTest(10 /* VTDSplits */);
|
|
9137
9166
|
let splitVTDs = [];
|
|
9138
|
-
// TODO - SPLITTING: Flesh this out, using
|
|
9167
|
+
// TODO - SPLITTING: Flesh this out, using virtual VTD's ...
|
|
9139
9168
|
test['score'] = splitVTDs.length;
|
|
9140
9169
|
test['details']['splitVTDs'] = splitVTDs;
|
|
9141
9170
|
return test;
|
|
@@ -9771,9 +9800,9 @@ function doPreprocessCensus(s, bLog = false) {
|
|
|
9771
9800
|
// Sum total population by county
|
|
9772
9801
|
totalByCounty[countyFIPS] += value;
|
|
9773
9802
|
}
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
|
|
9803
|
+
else {
|
|
9804
|
+
console.log("Skipping water-only feature in Census preprocessing:", geoID);
|
|
9805
|
+
}
|
|
9777
9806
|
}
|
|
9778
9807
|
// NOTE - The above could be replaced, if I got totals on county.geojson.
|
|
9779
9808
|
// CREATE A FIPS CODE-ORDINAL MAP
|