Published
Edited
May 25, 2021
Insert cell
# O*Net Occupation-Skills Matrix
## Visualization of Occupations and Skills by Groupings

2021-05-22 started this.

Insert cell
// cell: Main Display of skillsForOccup with occup index on x-axis and for y-axis show abilities
chart = {
/*
Input is skillsForOccup which has obj for each occup with occupId, seq info, and arays im and lv with
number for each skill.
5/24 addition of sequence info to permit sort in d3.selection by maj grp, random or onto group
** this did not work, not sure why not, BUT: myData is an entry PER POINT, not PER Occup
** we really need to sort occupations first in skillsForOccup and then create myData
*/
const myData = []
let numOccup = 0
let numPts = 0
let cntByImLv = Array(9).fill(0) // count of occup-skill by im-lv
console.log("DISPLAY MODE " + obscfg.occupSortSeq + " NUM skill-occup " + Object.entries(skillsForOccup).length)
// Go through skillsForOccup in the sort sequence set in obscfg.occupSortSeq
console.log("**** skills for occup len " + Object.entries(skillsForOccup).length)
let occupIndex = 0 // not reliable!
for (const [key, val] of Object.entries(skillsForOccup)) {
occupIndex += 1
let myx = occupIndex // start at 1
if (occupIndex < 2000) { // 997 total so over that is all
// console.log("myx " + myx + " ", valueObj.im)
// get all abilities and levels for this occup
numOccup += 1
for (let j = 0; j < 35; j++) {
if ( val.im[j] === -1 && val.lv[j] === -1) {
// do nothing, skip
} else {
numPts += 1
// what if either is not assigned (-1)? skip it even if only one
if (val.im[j] >= 0 && val.lv[j] >= 0) {
const imKeyVal = val.im[j]
const lvKeyVal = val.lv[j]
// const myColorIdx = imKeyVal * 3 + lvKeyVal // 0, 0 = 0; 1,2 = 5 rightmost of second row, looks ok
let myColorIdx
if (lvKeyVal === 0) myColorIdx = 2
if (lvKeyVal === 1) myColorIdx = 5
if (lvKeyVal === 2) myColorIdx = 8
cntByImLv[myColorIdx] += 1
const myDataRow = {
occupId: val.occupId,
seqMaj: val.seqMaj,
seqRnd: val.seqRnd,
seqOnt: val.seqOnt,
x: myx * 4, // not myx * 4 -- how did that work? << PROBLEM: Needs to be AFTER sort!!
y: 10 + j * 10,
col: imlvArColors[myColorIdx]
}
// myData.push({ x: myx * 4, y: 10 + (10 * j), col: imlvArColors[myColorIdx] }) // abil indexes going up and y also (vert loc going down)
myData.push(myDataRow)
}
}
}
}
}
console.log("DISPLAY MATRIX DONE: " + numOccup + " pts: " + numPts)
console.log("cnt by im lv : ", cntByImLv)
d3.select("svg").remove()
// let mySvg = d3.select("#svgMain").append("svg")
/* let mySvg = d3.select(DOM.svg(600, 400)) // cannot do: "body").append("svg") try also d3.create("svg")...
// .attr("width", "600")
// .attr("height", "400")
// .attr("x", "0") // see if this will align left margin; it did NOT
.append("g") */
const height = 400
/* let mySvg = d3.select(DOM.svg(width, height))
.append("g") */
// try create:
let mySvg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
mySvg.append("g")
let priorOccupId = ""
let xAxisCol = 0
mySvg.selectAll("dot")
.data(myData.sort((a, b) => {
/* why no-go: if (obscfg.occupSortSeq === "ont") return d3.ascending(a.SeqOnt, b.SeqOnt) */
if (obscfg.occupSortSeq === "maj") return a.seqMaj - b.seqMaj
if (obscfg.occupSortSeq === "rnd") return a.seqRnd - b.seqRnd
if (obscfg.occupSortSeq === "ont") return a.seqOnt- b.seqOnt
})
)
.enter().append("circle")
// 5/25: .attr("cx", d => d.x)
.attr("cx", (d, i) => {
if (d.occupId != priorOccupId) {
xAxisCol += 1
priorOccupId = d.occupId
// console.log("id = " + d.occupId)
} else {
// no action
}
return xAxisCol * 3
})
.attr("cy", d => d.y)
.attr("r", 2)
.attr("fill", (d) => {
return (d.col)
})
return mySvg.node()
}
Insert cell
// cell: convert data_OccupSkil table to an object of objects with occup id as key and for each skill:
// { skill idx: 1 to 35, col: color code index based on level-importance eg hilow }
/* 5/23/21: Need better analysis (and debugging):
// well: Make more dynamic - store the actual data values for IM and LV to allow easily changing thresholds!
these element names are im and lv
well, why not this: Have ability array of 35 entries one per ability, showing IM and LV values*100 as integers
put thresholds in obscfg! avgIM, hiIM, avgLV, hiLV
5/25 working on test file of only 7 records - need to filter data_OccupSkill for only those occup in data_occup(TST)
*/
skillsForOccup = {
let skoccup = {} // dict with occup id as key
let numRows = 0
let numSelected = 0
let numOccupIds = 0
console.log("START ** ***")
data_OccupSkill.forEach((row, i) => {
const dataValue = parseFloat(row['Data Value'])
numRows += 1
const occupId = row["SOC_Code"]
if (obsSOC2Idx.hasOwnProperty(occupId) && i < 100000) { // earlier elim if value only 2: if (dataValue > 2.0 && i < 600) {
numSelected += 1

// convert element id to skill list index
const skIdx = skillsIndex[skId]
let myRow // will be either new object or retrieved from skoccup
// AHAH: this is an array not a dict: if (!skoccup.hasOwnProperty(occupId)) {
// Fix with table lookup - or with separate dict of what values have been filled?
// or make a dict like it ought to be!!!
// 5/22/21 converting to dict with occcup id as key
if (!skoccup.hasOwnProperty(occupId)) {
// create the entry object and insert it
// 5/24 retrieve seq info from data_Occup[j] were j is the index from dictOccupIdx for SOC_Code
const occupIdx = obsSOC2Idx[occupId]
// const myOccup = data_Occup[occupIdx]
const myOccup = data_OccupTST[occupIdx]
myRow = {
occupId,
seqMaj: myOccup.SeqMajGrp,
seqRnd: myOccup.SeqRandom,
seqOnt: myOccup.SeqOntoGrp,
im: Array(35).fill(-1),
lv: Array(35).fill(-1)
}
skoccup[occupId] = myRow
numOccupIds += 1
} else {
myRow = skoccup[occupId]
}
// add current row ability index and value of IM or LV; converted to 0, 1 or 2 based on threshold
// whether addedd or existing, myRow should point into the array

let keyVal = 0 // default to low; 1 = abv, 2 = hi

if (row["Scale ID"] === 'IM') {
if (dataValue >= obscfg.imAbv) keyVal = 1
if (dataValue > obscfg.imHi) keyVal = 2
myRow.im[skIdx] = keyVal
// console.log("abl " + skIdx + " imval " + dataValue + " " + keyVal)
} else {
if (dataValue >= obscfg.lvAbv) keyVal = 1
if (dataValue > obscfg.lvHi) keyVal = 2
myRow.lv[skIdx] = keyVal
// console.log("abl " + skIdx + " lvval " + value + " " + keyVal)
}
// console.log("MyRow ", myRow)
}
})
console.log("In skills: Num sk-oc " + numRows + " sel: " + numSelected + " occup " + numOccupIds)
// analyze skill-occups: Number with 0 for all abils; Counts by keyvalue
let imCnts = [0, 0, 0]
let lvCnts = [0, 0, 0]
let numAllZeroIm = 0
let numAllZeroLv = 0
let numskoc = 0 // diag remove
for (const [key, val] of Object.entries(skoccup)) {
numskoc += 1
// if (numskoc < 5) console.log("im vals ", val.im)
let imAllZero = true
let lvAllZero = true
for (let j = 0; j < 35; j++) {
if (val.im[j] > -1) {
if (val.im[j] < 0 || val.im[j] > 2) console.log("Error at im " + key + " " + j + " " + val.im[j])
if (val.im[j] > 0) imAllZero = false
imCnts[val.im[j]] += 1
}
if (val.lv[j] > -1) {
if (val.lv[j] < 0 || val.lv[j] > 2) console.log("Error at lv " + key + " " + j)
if (val.lv[j] > 0) lvAllZero = false
lvCnts[val.lv[j]] += 1
}
}
if (imAllZero) numAllZeroIm += 1
if (lvAllZero) numAllZeroLv += 1
}
console.log(" cnts im ", imCnts)
console.log(" cnts lv ", lvCnts)
console.log(" Num all zero IM ", numAllZeroIm)
console.log(" Num all zero LV ", numAllZeroLv)
return skoccup
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// cell: create a dictionary of SOC Id's pointing to the occupation index
obsSOC2Idx = {
let result = {}
data_OccupTST.forEach((occup, i) => { result[occup["SOC_Code"]] = i }) // simply have presence for SOC_Code in dict
return result
}
Insert cell
Insert cell
// cell: Read in file - Occupations list of 997 SOC Occupations
data_OccupTST = d3.csvParse(await FileAttachment("SOC_OccupCSVTST3.csv").text())
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more