Skip to content

Instantly share code, notes, and snippets.

@bdiesel
Created August 8, 2017 00:35
Show Gist options
  • Select an option

  • Save bdiesel/9021673ce0158ff853ac1e29f5779314 to your computer and use it in GitHub Desktop.

Select an option

Save bdiesel/9021673ce0158ff853ac1e29f5779314 to your computer and use it in GitHub Desktop.
let tableData = require('./dataset_test.json');
//let tableData = require('./100k.json');
let removeResiduals = true;
// Primary segmentation (One row for each combination)
// (36x12x5=2,160 possible combinations; approx 220 used)
// segments are AND together
// user can define composite variables that combine separate variables
// (e.g. ‘high ticket buyer and outside 25 mile radius of store’ as one row)
let filterArray = {
//each segment can be multiple key value pairs
segments: [{
keys: ["primary_buyer_group", "primary_recency_ranges"],
//values: ["MICROSITES EXCEPT", "OPENBOX.COM"] // OR these
values: [
["AMAZON", "OPENBOX.COM", "MICROSITES EXCEPT"],
["37-48"]
]
},
{
keys: ["primary_score_range"], // AND these
values: [
["Direct Seg 15-20"]
]
},
{
keys: ["primary_score_range"], // AND these
values: [
["Direct Seg 1-4"]
]
}, {
keys: ["primary_buyer_group"],
values: [
["WISH LIST"]
]
}, {
keys: ["primary_recency_ranges"],
values: [
["13-24"]
]
}
],
// Each split is an additional filter on all rows in the matrix.
// Customers are assigned to first split for which they qualify, starting from the left
splits: [{
keys: ["split_buyer_group_1"],
values: [
["MULTIES"]
]
},
{
keys: ["split_locations"],
values: [
["NON-RADIUS (Non-Store Area)"]
]
},
{
keys: ["split_buyer_group_2"],
values: [
["Browse Only - 0-6 Mo"]
]
},
{
keys: ["split_zip_match"],
values: [
["Version 4 - 50 Mile Radius zip Matches - Rekey"]
]
},
{
keys: ["split_zip_match"],
values: [
["Version 4 - 50 Mile Radius zip Matches - Rekey"]
]
},
{
keys: ["split_style"],
values: [
["LP - Modern"]
]
}
]
};
class SelectionMatrix {
constructor(data, filters, removeResiduals) {
this.tableData = data;
this.filterArray = filters;
this.removeResiduals = removeResiduals;
}
generateMatrix() {
//stored for output
let segArray = [];
// Creates a copy of tableData array and itterates over the table
// by the segmenet removing the appropiate elements.
this.filterArray.segments.reduce(function(cur, seg) {
//get the segement ad an array
let segDataArray = this.getSegments(cur, seg);
//get the split as object {total: int, splits: []}
let mySplits = this.getSplits(segDataArray, this.filterArray.splits);
//elements to remove after each segment/row pass
let deleteList = [];
if (this.removeResiduals) {
deleteList = segDataArray;
} else {
//flatten splits 2d array to 1d array
deleteList = mySplits.splits.reduce((a, b) => a.concat(b), []);
}
//Add the segment to the output
segArray.push({
allSegments: segDataArray,
splits: mySplits.splits,
residuals: mySplits.residuals
});
//this is a 2d passed back to the reduce element.
return cur.filter(el => !deleteList.includes(el));
}.bind(this), this.tableData);
//we return all the elements and do the counting later.
return segArray;
};
// This is used to render the oput of the table.
drawCounts() {
let matrix = this.generateMatrix();
let rows = [];
let num_cols = matrix[0].splits.length;
let splitsColTotals = new Array(num_cols).fill(0);
let colResids = new Array(num_cols).fill(0);
let allSelectionTotal = 0
matrix.forEach(row => {
let splitsLen = [];
let splitTotal = 0;
row.splits.forEach((col, idx) => {
splitsLen.push(col.length);
splitTotal += col.length;
allSelectionTotal += col.length;
splitsColTotals[idx] += col.length;
});
rows.push({
total: row.allSegments.length,
splits: splitsLen,
splitsTotal: splitTotal,
residuals: row.residuals.length
});
});
return {
rows: rows,
splits_col_total: splitsColTotals,
all_selections_total: allSelectionTotal,
column_residuals: colResids
};
};
// This is used to generate the rows of the table.
getSegments(matrix, segmentGroup) {
return segmentGroup.keys.reduce(function(cur, keyVal, idx) {
let rowBucket = []; //rolls up the row row buckets incase
cur = segmentGroup.values[idx].reduce(function(res, val) {
res = cur.filter(el => el[keyVal] == val);
rowBucket.push.apply(rowBucket, res);
return rowBucket;
}, []);
return cur;
}, matrix);
};
getSplits(segData, splitsFilter) {
let segSplits = [];
splitsFilter.forEach(splitFilter => {
let split = splitFilter.keys.reduce(function(cur, keyVal, idx) {
let colBucket = []; //rolls up the col buckets incase
cur = splitFilter.values[idx].reduce(function(res, val) {
res = cur.filter(el => el[keyVal] == val);
//remove element from row so it can't be counted twice.
segData = segData.filter(el => !res.includes(el));
colBucket.push.apply(colBucket, res);
return colBucket;
}, []);
return cur;
}, segData);
segSplits.push(split);
});
//the remaining are residuals.
return { splits: segSplits, residuals: segData };
}
} // End SelectionMatrix class
//let runningTime = "Not Calculated";
let runningTime = process.hrtime();
let dataMatrix = new SelectionMatrix(tableData, filterArray, removeResiduals)
let counts = dataMatrix.drawCounts();
runningTime = process.hrtime(runningTime);
console.log("Output Matrix:", counts, "Running Time:", runningTime);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment