@dra2020/district-analytics 2.0.3 → 2.0.6
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 +52 -21
- package/dist/cli.js.map +1 -1
- package/dist/district-analytics.js +52 -21
- 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();
|
|
@@ -8504,6 +8517,8 @@ class Districts {
|
|
|
8504
8517
|
this.statistics[DistrictField.AsianPct][summaryRow] = stateAsianPop / stateVAPPop;
|
|
8505
8518
|
this.statistics[DistrictField.NativePct][summaryRow] = stateNativePop / stateVAPPop;
|
|
8506
8519
|
}
|
|
8520
|
+
console.log(`${nMissingDataset} features with missing datasets.`);
|
|
8521
|
+
console.log(`${nMissingProperty} features with missing properties.`);
|
|
8507
8522
|
}
|
|
8508
8523
|
// NOTE - I did not roll these into district statistics, because creating the
|
|
8509
8524
|
// district shapes themselves is the big district-by-district activity, these
|
|
@@ -8558,36 +8573,46 @@ exports.Features = Features;
|
|
|
8558
8573
|
// f is a direct GeoJSON feature
|
|
8559
8574
|
// p is a geoID
|
|
8560
8575
|
function _getFeatures(f, datasetKey, p) {
|
|
8561
|
-
// Echo parameters for debugging
|
|
8562
|
-
// console.log("f =", f, "k = ", datasetKey, "p =", p);
|
|
8563
8576
|
// Shim to load sample data2.json from disk for command-line scaffolding
|
|
8564
8577
|
if (f.properties && f.properties['datasets']) {
|
|
8578
|
+
if (!f.properties['datasets'][datasetKey]) {
|
|
8579
|
+
// Feature is missing the dataset
|
|
8580
|
+
nMissingDataset += 1;
|
|
8581
|
+
console.log(`${nMissingDataset}: Data ${datasetKey} missing for feature ${f} Returning zero.`);
|
|
8582
|
+
return 0;
|
|
8583
|
+
}
|
|
8565
8584
|
return f.properties['datasets'][datasetKey][p];
|
|
8566
8585
|
}
|
|
8567
8586
|
// NOTE - The fGetW() code from dra-client below here ...
|
|
8568
8587
|
// Direct property?
|
|
8569
|
-
if (f.properties && f.properties[p] !== undefined)
|
|
8588
|
+
if (f.properties && f.properties[p] !== undefined) {
|
|
8570
8589
|
return f.properties[p];
|
|
8590
|
+
}
|
|
8571
8591
|
// Joined property?
|
|
8572
8592
|
let a = _fGetJoined(f);
|
|
8573
8593
|
if (a) {
|
|
8574
8594
|
for (let i = 0; i < a.length; i++) {
|
|
8575
8595
|
let o = a[i];
|
|
8576
8596
|
if (!datasetKey) {
|
|
8577
|
-
if (o[p] !== undefined)
|
|
8597
|
+
if (o[p] !== undefined) {
|
|
8578
8598
|
return o[p];
|
|
8599
|
+
}
|
|
8579
8600
|
}
|
|
8580
8601
|
else {
|
|
8581
8602
|
if (o['datasets'] && o['datasets'][datasetKey]) {
|
|
8582
8603
|
let v = (o['datasets'][datasetKey][p]);
|
|
8583
|
-
if ((!(v == null)) && (!(v == undefined)))
|
|
8604
|
+
if ((!(v == null)) && (!(v == undefined))) {
|
|
8584
8605
|
return o['datasets'][datasetKey][p];
|
|
8606
|
+
}
|
|
8585
8607
|
}
|
|
8586
8608
|
}
|
|
8587
8609
|
}
|
|
8588
8610
|
}
|
|
8589
|
-
|
|
8590
|
-
|
|
8611
|
+
// Feature is missing the property
|
|
8612
|
+
nMissingProperty += 1;
|
|
8613
|
+
console.log(`${nMissingProperty}: ${p} value undefined for ${f.properties['GEOID10']}. Returning zero.`);
|
|
8614
|
+
return 0;
|
|
8615
|
+
// return undefined;
|
|
8591
8616
|
}
|
|
8592
8617
|
function _fGetJoined(f) {
|
|
8593
8618
|
return (f.properties && f.properties.joined) ? f.properties.joined : undefined;
|
|
@@ -8632,6 +8657,7 @@ class Plan {
|
|
|
8632
8657
|
}
|
|
8633
8658
|
// NOTE - DON'T remove water-only features from the plan, as they may be required
|
|
8634
8659
|
// for contiguity. Just skip them in aggregating district statistics.
|
|
8660
|
+
//
|
|
8635
8661
|
// removeWaterOnlyFeatures(plan: T.PlanByGeoID): T.PlanByGeoID {
|
|
8636
8662
|
// let newPlan = {} as T.PlanByGeoID;
|
|
8637
8663
|
// for (let geoID in plan) {
|
|
@@ -9135,7 +9161,7 @@ exports.doFindCountiesSplitUnexpectedly = doFindCountiesSplitUnexpectedly;
|
|
|
9135
9161
|
function doFindSplitVTDs(s, bLog = false) {
|
|
9136
9162
|
let test = s.getTest(10 /* VTDSplits */);
|
|
9137
9163
|
let splitVTDs = [];
|
|
9138
|
-
// TODO - SPLITTING: Flesh this out, using
|
|
9164
|
+
// TODO - SPLITTING: Flesh this out, using virtual VTD's ...
|
|
9139
9165
|
test['score'] = splitVTDs.length;
|
|
9140
9166
|
test['details']['splitVTDs'] = splitVTDs;
|
|
9141
9167
|
return test;
|
|
@@ -9382,17 +9408,22 @@ const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
|
9382
9408
|
const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
9383
9409
|
function doPopulationDeviation(s, bLog = false) {
|
|
9384
9410
|
let test = s.getTest(4 /* PopulationDeviation */);
|
|
9385
|
-
|
|
9386
|
-
//
|
|
9411
|
+
let targetSize = s.state.totalPop / s.state.nDistricts;
|
|
9412
|
+
// Compute the min & max district populations
|
|
9413
|
+
// ... excluding the dummy the 'unassigned' 0 and N+1 summary "districts"
|
|
9387
9414
|
let totPopByDistrict = s.districts.statistics[D.DistrictField.TotalPop];
|
|
9388
9415
|
totPopByDistrict = totPopByDistrict.slice(1, -1);
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
let
|
|
9392
|
-
|
|
9393
|
-
//
|
|
9394
|
-
|
|
9395
|
-
|
|
9416
|
+
// Remove empty districts
|
|
9417
|
+
totPopByDistrict = totPopByDistrict.filter(x => x > 0);
|
|
9418
|
+
let min = 0;
|
|
9419
|
+
let max = 0;
|
|
9420
|
+
// If there's more than 1 non-empty district, calculate a non-zero deviation
|
|
9421
|
+
if (totPopByDistrict.length > 1) {
|
|
9422
|
+
min = U.minArray(totPopByDistrict);
|
|
9423
|
+
max = U.maxArray(totPopByDistrict);
|
|
9424
|
+
}
|
|
9425
|
+
// Calculate the raw population deviation
|
|
9426
|
+
let popDev = (max - min) / targetSize;
|
|
9396
9427
|
// Round the raw value to the desired level of precision
|
|
9397
9428
|
popDev = U.trim(popDev);
|
|
9398
9429
|
// Populate the test entry
|
|
@@ -9402,7 +9433,7 @@ function doPopulationDeviation(s, bLog = false) {
|
|
|
9402
9433
|
let totalPop = s.districts.statistics[D.DistrictField.TotalPop];
|
|
9403
9434
|
let popDevPct = s.districts.statistics[D.DistrictField.PopDevPct];
|
|
9404
9435
|
let summaryRow = s.districts.numberOfRows() - 1;
|
|
9405
|
-
totalPop[summaryRow] =
|
|
9436
|
+
totalPop[summaryRow] = targetSize;
|
|
9406
9437
|
popDevPct[summaryRow] = popDev;
|
|
9407
9438
|
return test;
|
|
9408
9439
|
}
|
|
@@ -9766,9 +9797,9 @@ function doPreprocessCensus(s, bLog = false) {
|
|
|
9766
9797
|
// Sum total population by county
|
|
9767
9798
|
totalByCounty[countyFIPS] += value;
|
|
9768
9799
|
}
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
|
|
9800
|
+
else {
|
|
9801
|
+
console.log("Skipping water-only feature in Census preprocessing:", geoID);
|
|
9802
|
+
}
|
|
9772
9803
|
}
|
|
9773
9804
|
// NOTE - The above could be replaced, if I got totals on county.geojson.
|
|
9774
9805
|
// CREATE A FIPS CODE-ORDINAL MAP
|