Public
Edited
Jul 15, 2021
Importers
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
## About SOC and O*Net
### Standard Occupational Classification (SOC) and the O*Net Online System
There are 35 skills and 997 occupations, organized by the federal Standard Occupational Classificaiton (SOC) System and the O*Net online service into 5 skill sets and 22 occupations. The ontological objective is to organize the skills and occupations along BFO-based ontological lines to see if a more "reason-able" arrangement would be beneficial.
- Skills are divided into 5 groups in the O*Net System by horizontal lines:
- Basic,
- Social,
- Technical (which includes Complex Problem Solving),
- Systems,
- Resource Management.
- The proposed ontological grouping of skills is also 5 groups based on a gradient of cognitive challenge for each skill, with the most challenging at the top:
- Advanced: most cognitively challenging skills, including Complex Problem Solving and selected skills from the Basic, Technical and Systems groups; 10 total.
- RM: challenging Resource Management; 4 total.
- Sound: less challenging Basic; 7 total.
- System: less challenging Systems; 6 total.
- Tech: less challenging technical skills; 8 total with 4 at one level and 4 at the lowest level, for a total of 35.

Showing all occupations is not possible, so they may be filtered in two ways:
- modulo-4 249 at a time, selecting either residue 0 or 2 (eg 14 modulo = remainder 3 and residue 2).
- by O\*Net Major Group (of which there are 22 non-military-specific groups) within an ontological grouping (3-level hierarchy with two main divisions, four sub-divisions and ten sub-sub-divisions).

Each mark represents a skill-occupation pairing with the following legend:
- **Importance** is shown by **shape**, upper left triangle for high, lower left triangle for "above" and rectangle for low. Above is between an average importance for that skill and one standard deviation above the average.
- **Level of expertise** is shown by **color**, red for high, blue for above average, gray for below average metric for that skill level of expertise.



Insert cell
obscfg.widthMax < width ? obscfg.widthMax : width
Insert cell
Insert cell
tmpShift = (200 - sliderPctDisp) - 52
Insert cell
tmpWbyHor = width/obscfg.pxHor
Insert cell
// cell: Drag object of functions to effect the drag operation on slider rece
// and translate display accordingly
// 2021-06-13 See Word doc on doing this Ntbk (2021-06-13) Drag SVG as Group
drag = {
function dragstarted(event, d) {
// upon click to drag, the group moves horizontally to some point before dragging
// what it is doing is positioning the g at the point of the click; can this be overridden and
// only capture x,y when the mouse is dragged?
console.log("drag start: How to avoid any move? " + event.x)
d3.select(this).attr("stroke", "black")
}
function dragged(event, d) {
if (false) console.log("what is in event? ", event.x)
// translate only the x dimension:
console.log("dragged " + event.x + " THIS.... ", d3.select(this))
// d3.select(this).raise().attr("transform", `translate(${event.x})`)
d3.select(this).attr("x", event.x) // did not work right: ("transform", `translate(${event.x})`)

}
function dragended(event, d) {
// get x of mainGroup first determine delta x
const priorX = d3.select("#sliderTst")
console.log("Is there an event here? ", event)
console.log("Here is prior rect obj ", priorX)
console.log("Here is prior rect val " + priorX._groups[0][0].attributes.x.nodeValue)
const deltaX = event.x - priorX.x
console.log("zzzz ", deltaX)
// translate the mainGroup by a delta: slider dx will mean - test for now
// change of x in group = (newSilderX - oldSliderX)
let test = d3.select("#sliderTst")
.attr("transform", `translate(40)`)
.attr("fill", "green")
d3.select(this).attr("stroke", "gray") // reset
}
return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
}
Insert cell
function sliderClick(event, d) {
console.log("slider clicked " + event.x)
d3.select("#slider")
.attr("x", 120)
}
Insert cell
obsMessage = "Any old message here!"
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
obsStatsBySkill = fnCalcStatsBySkill()
Insert cell
Insert cell
obsStatsByOccup = fnCalcStatsByOccup()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// cell: test
fnGetPoints("11-1011.00")
Insert cell
// cell: Function to get all display points for a given occupation id; SKIP SKILL ELEMENT ZERO!
// Reference: obsSkillSeq = O*Net Sequence or else OccO onto, so order skills accordingly
// 2021-06-11 hopefully getting close!
// 2021-06-22 Definitely getting close - implementing onto skill sequence
// 2021-06-25 Revised onto skills considerably and changed CSV headings, a mess!
function fnGetPoints(occupId) {
// myPoints will be the 35 skill indicators + 36th for occup index level,
// each entry will be x, y and path
// skillIndices will be array of skill idx for desired sequence, based on ONet index
// (if sequence is onet it is the dict_Skills index)
let myPoints = [] // new Array[37].fill(0) // symbol info for this occup
const oc = dict_Occup[occupId]
let numLvAbv = 0
let numLvHigh = 0
// stats array is zero based, 0-34 = skills and index 35 is num LvAbv and LvHi
// above does not appear to be correct
// 6/12 it looks like entry zero is not filled so skip it!
// 6/22 retrieve the ith index from the skills index if O*Net but [ontoIdx] if OccO skill sequence
// this is a bit tricky: skill info is in obsArSkillsAndStats; skillSeqIdx = sequenced indexed of sorted abilities
// skillIdx = the skill's original O*Net index 1-35
console.log("what is skills and stats? ") // it is right there!
for (let skillSeqIdx = 1; skillSeqIdx < 36; skillSeqIdx++) { // skillSeqIdx is the display (sorted) sequence
// all needed is shape info. Get im and lv levels, 0, 1, 2 for low, abv, high and set path
// NOTE: if Onto sequence is desired, the processing is done by onto index, so it will not be consecutive order:
const skillIdx = obsSkillSeq === "O*Net Sequence" ? skillSeqIdx : obsArSkillIdxOnto[skillSeqIdx].skillOntoIdx
// console.log("wwwwwahatt? ?? ", obsArSkillIdxOnto[1])
const currSkill = obsArSkillsAndStats[skillIdx]
if (occupId === "11-1011.00") console.log("Onto chf ex: " + skillSeqIdx + " skill Onet idx: " +
obsArSkillIdxOnto[1].skillOntoIdx + " skillIdx: " + skillIdx + " xx ",
obsArSkillsAndStats[skillIdx])
let imVal = 0 // default
if (oc.im[skillIdx] > currSkill.imAvg + currSkill.imStdDev) {
imVal = 2 // imHigh
} else if (oc.im[skillIdx] > currSkill.imAvg) {
imVal = 1 // imAbv
}
let lvVal = 0 // default low
if (oc.lv[skillIdx] > currSkill.lvAvg + currSkill.lvStdDev) {
lvVal = 2 // lvHigh
} else if (oc.lv[skillIdx] > currSkill.lvAvg) {
lvVal = 1 // lvAbv
}
// get numbers for occup index if im > low for the skill
if (oc.im[skillIdx] > currSkill.imAvg) {
if (oc.lv[skillIdx] > currSkill.lvAvg + currSkill.lvStdDev) {
numLvHigh += 1
/* console.log("High " + oc.lv[skillIdx] + " avg " +
currSkill.lvAvg + " stdd " +
currSkill.lvStdDev) */
} else if (oc.lv[skillIdx] > currSkill.lvAvg) {
numLvAbv += 1
}
}
// we have results for this Occup-skill combination - add to myPoints
myPoints.push({ imVal: imVal, lvVal: lvVal })
}
// we have results for this occupation; push the two occupation index metrics
// onto the array as element 36 with numLvAbv, numLvhigh in the two spots
myPoints.push({ imVal: numLvAbv, lvVal: numLvHigh })
// order skill metric values by seqSkill
// console.log("xxxx " + numLvAbv + " ", myPoints)
return myPoints
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
fnCalcEntMajGrpSkills("ONet")

Insert cell
fnCalcEntMajGrpSkills("Random")
Insert cell
/* 7/2/21 5:50pm - worn out! got the "aggregate" sum of entropies for each group - skill

entropy for each skill - group are calculated by fnCalcEntMajGrpSkills(ONet or Random)
and results (sum only not recomputed running variance) is computed in obsResultsAggregate

To do: process by group ? << think this through - if skills are aggregated, is that a good idea? I think so
BUT: Do we get a running variance across skills? I think not! Best just to use a SUM!!
Then once we have a sum per group, get the running variance for the groups

Quite a number of cells need to be redone / deleted:

*/



obsResults1 = {
const resONetIm = fnCalcEntMajGrpSkills("ONet")
const resRandIm = fnCalcEntMajGrpSkills("Random")
// Phase I: grand total entropy, all groups
let sumForSkill5ONetIm = 0
let sumForSkill5RandIm = 0
let sumForSkillsONetIm = 0
let sumForSkillsRandIm = 0
for (let grpnum = 0; grpnum < resONetIm.length; grpnum++) {
for (let skillnum = 1; skillnum < 36; skillnum++) {
if (skillnum === 5) {
sumForSkill5ONetIm += resONetIm[grpnum][skillnum]
sumForSkill5RandIm += resRandIm[grpnum][skillnum]
}
sumForSkillsONetIm += resONetIm[grpnum][skillnum]
sumForSkillsRandIm += resRandIm[grpnum][skillnum]
}
}
// Phase IIa: Entropy for each Group, all skills
// NOW: can the ONet results for each group be combined to yield an overall all-group entropy?
// (this does not apply to the random case; however, since they are grouped similarly, calculate this as well)
// process: get the running variance across groups
// step one is get group ent across all skills:
let sumForGrpsONetIm = []
let sumForGrpsRandIm = []
let priorONetIm = 0
let priorRandIm = 0
for (let grpnum = 0; grpnum < resONetIm.length; grpnum++) {
for (let skillnum = 0; skillnum < 36; skillnum++) {
}
}


/* for (let grpnum = 0; grpnum < resONetIm.length; grpnum++) {
if (grpnum > 0) {
sumForGrpsONetIm += Math.pow(resONetIm[grpnum] - priorONetIm, 2)
console.log("RExx1111xxx " + resONetIm[grpnum] + " val " + priorONetIm)
sumForGrpsRandIm += Math.pow(resRandIm[grpnum] - priorRandIm, 2)
priorONetIm = resONetIm[grpnum]
priorRandIm = resRandIm[grpnum]
console.log("RExxxxx " + sumForGrpsONetIm + " val " + resONetIm[grpnum])
}
} */
// Pase IIb: combine group entropies - get sum of squares then sqrt of avg
// get average using 21 group differences in the 22 groups
sumForGrpsONetIm = Math.sqrt(sumForGrpsONetIm / 21)
sumForGrpsRandIm = Math.sqrt(sumForGrpsRandIm / 21)
// Phase I results:
// return { ONet: Math.round(sumForSkillsONetIm * 100)/100, Rand: Math.round(sumForSkillsRandIm * 100)/100 }
// Phase II results:
return { ONet: Math.round(sumForGrpsONetIm * 100)/100, Rand: Math.round(sumForGrpsRandIm * 100)/100 }
}
Insert cell
obsResultsAggregate = {
const resONet = fnCalcEntMajGrpSkills("ONet")
const resRand = fnCalcEntMajGrpSkills("Random")
let sumForSkill5ONetIm = 0
let sumForSkill5RandIm = 0
let sumForSkillsONetIm = 0
let sumForSkillsRandIm = 0
for (let grpnum = 0; grpnum < resONet.length; grpnum++) {
for (let skillnum = 1; skillnum < 36; skillnum++) {
if (skillnum === 5) {
sumForSkill5ONetIm += resONet[grpnum][skillnum]
sumForSkill5RandIm += resRand[grpnum][skillnum]
}
sumForSkillsONetIm += resONet[grpnum][skillnum]
sumForSkillsRandIm += resRand[grpnum][skillnum]
}
}
return { ONet: Math.round(sumForSkillsONetIm * 100)/100, Rand: Math.round(sumForSkillsRandIm * 100)/100 }
}
Insert cell
// ARCHIVAL: function to get skill entropies for each group*****
// cell: For each of the 22 ONet major-group-index-sets of desired sequence calculate LeafOntoEntropy
// Param: seq is either ONet or Random
// issue 1: dict_Occup is accessed by OccupId not index
// issue 2: the seqRandom is a random number - needs to be sorted to get an index
// 7/2/21 reference is now a dictionary by OccupId with occupid's for both ONet and Random
function fnCalcEntMajSkillGrps(seq) {
console.log("Hixxxx zzz")
let grpEnt = 0 // for now make it the sum of skill variances Array(36).fill(0)
let allGrpEnt = [] // array for all groups
let numGrps = 0
// const occupLookup = Object.entries(dict_Occup) // will have array of [key, value]
let arSumSquaresIm = Array(36).fill(0) // sum of sqares of adjecent occups for each of 35 skills skipping zero
let arPriorIm = Array(36).fill(0) // im value of prior occup for each skill
// Process each group, and within each group, process all occupations
// process each group:
for (const [key, val] of Object.entries(obsEntMajGrpOccupIds)) {
if (numGrps < 25) {
console.log("Doing group " + numGrps)
let numOccup = 0
// process each occup:
val.forEach((ref, j) => {
const occupId = seq === "ONet" ? ref.ONetOccupId : ref.RandomOccupId
const myOc = dict_Occup[occupId]
// ascertain im has nonzero values
let tot = myOc.im.reduce((a, b) => a + b, 0)
if (tot > 0) {
// get occup from dict_Occup
if (numOccup < 4 && j < 3) console.log("doing occup " + occupId + " keyoc ", myOc.im)
if (numOccup > 0) {
// calculate "running variance" - degree of change from adjacent (leaf) value
for (let i = 1; i < 36; i++) { //*** make 36
arSumSquaresIm[i] += Math.pow(myOc.im[i] - arPriorIm[i], 2)
// console.log("im val= " + myOc.im[i] + " ri " + arPriorIm[i] + " Sq " + arSumSquaresIm[1])
}
arPriorIm = [...myOc.im] // copies actual values; can we just use ref to prior occup?
}
}

numOccup += 1
})
// get sqrt of average
for (let i = 1; i < 36; i++) arSumSquaresIm[i] = Math.sqrt(arSumSquaresIm[i] / numOccup)
numGrps += 1
allGrpEnt.push(arSumSquaresIm)
}


}
console.log("result im: ", arSumSquaresIm)
return allGrpEnt
}
Insert cell
// *** REDO / DELETE this just get sum:
// cell: function to process output of fnCalcEntMajGrpSkills (whether ONet or Random) and return
// input: array of groups with each group having 36 skill entropy values
// Returns: array of occup groups with a total entity for all skills
function fnCalcEntMajGrps(entMajGrpSkills) {
// First step is get aggregate entropy across all skills
let entForMajGrps = [] // results
let priorSkillEnt = 0
const numGrps = entMajGrpSkills.length
for (let grpnum = 0; grpnum < numGrps; grpnum++) {
if (grpnum > 0) {
for (let skillnum = 0; skillnum < 36; skillnum++) {
// sum of squares diff from prior group
entForMajGrps[grpnum] += Math.pow(entMajGrpSkills[skillnum] - priorSkillEnt, 2)
}
priorSkillEnt = entMajGrpSkills[skillnum]
}
// get sqrt of avg of 22 groups considering there are 21 differences (resOnet
entForMajGrps[grpnum] = Math.pow(entForMajGrps[grpnum] / (numGrps - 1))
}
return entForMajGrps
}


Insert cell
// *** this is a newer version I think? THE ONE USED is fnCalcEntMajSkillGrps << MIS-NAMED!
// cell: For each of the 22 ONet major-group-index-sets of desired sequence calculate LeafOntoEntropy
// which of these is used?
// Param: seq is either ONet or Random
// returns array of 22 groups each having ent figure for array of 25 skills
// issue 1: dict_Occup is accessed by OccupId not index
// issue 2: the seqRandom is a random number - needs to be sorted to get an index
// 7/2/21 reference is now a dictionary by OccupId with occupid's for both ONet and Random
function fnCalcEntMajGrpSkills(seq) {
console.log("Hixxxx zzz")
let grpEnt = 0 // for now make it the sum of skill variances Array(36).fill(0)
let allGrpEnt = [] // array for all groups
let numGrps = 0
// const occupLookup = Object.entries(dict_Occup) // will have array of [key, value]
let arSumSquaresIm = Array(36).fill(0) // sum of sqares of adjecent occups for each of 35 skills skipping zero
let arPriorIm = Array(36).fill(0) // im value of prior occup for each skill
// Process each group, and within each group, process all occupations
// process each group:
for (const [key, val] of Object.entries(obsEntMajGrpOccupIds)) {
if (numGrps < 25) {
console.log("Doing group " + numGrps)
let numOccup = 0
// process each occup:
val.forEach((ref, j) => {
const occupId = seq === "ONet" ? ref.ONetOccupId : ref.RandomOccupId
const myOc = dict_Occup[occupId]
// ascertain im has nonzero values
let tot = myOc.im.reduce((a, b) => a + b, 0)
if (tot > 0) {
// get occup from dict_Occup
if (numOccup < 4 && j < 3) console.log("doing occup " + occupId + " keyoc ", myOc.im)
if (numOccup > 0) {
// calculate "running variance" - degree of change from adjacent (leaf) value
for (let i = 1; i < 36; i++) { //*** make 36
arSumSquaresIm[i] += Math.pow(myOc.im[i] - arPriorIm[i], 2)
// console.log("im val= " + myOc.im[i] + " ri " + arPriorIm[i] + " Sq " + arSumSquaresIm[1])
}
arPriorIm = [...myOc.im] // copies actual values; can we just use ref to prior occup?
}
}

numOccup += 1
})
// get sqrt of average
for (let i = 1; i < 36; i++) arSumSquaresIm[i] = Math.sqrt(arSumSquaresIm[i] / numOccup)
numGrps += 1
allGrpEnt.push(arSumSquaresIm)
}


}
console.log("result im: ", arSumSquaresIm)
return allGrpEnt
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
d3 = require("d3@6")
Insert cell
## Observable Notebook Self-documentation
### Main Display Cells
- mainPageNested - Main display of matrix;

### Input Controls - Affecting Output Display
- obsSelType = group of occupations to process: Ref-200, Modulo-4 (0 or 2 for first, third), others (later)
- This controls the set of occupations processed by setting xxxxxyyyyzzz
- obsOccupSeq = occupation sequence: Major O*Net Group, OccO Onto Group, or Random
- This controls the sequence of occupations displayed horizontally
- Upon change the occupations are sorted by Onto Group (***FIELD??) and vertical line dividers between occup groups are set in obArOccupGrpIndices
- obsSkillSeq = O*Net sequence or Skill Onto Group
- This controls the vertical ordering of skills, with indices between skill groups in divHorSkillsONet and divHorSkillsOccO

### Input Data, and Related Array or Dictionary
- data_Occup; converted to dict_Occup
- Note: This also contains the 36-entry arrays for 36 im and lv values (entry zero unused)
- filename = O-Net B_Occupations SOC-Related (2021-06-10).csv
- data_SkillOccup; remains as read from .json file, array of objects
- filename = O-Net C_Skill-Occupation with Metrics (2021-06-10).csv
- data_Skills; converted to dict_Skills << Note: dict_Skills appears NOT to be used!
- filename = O-Net D_Skills_TreeOnto (2021-06-10).csv

### Derived Data: Processing Objects derived from Input
- obsArSelOccupIds: the MAIN ordered list of occupation ids in the desired sequence (ONet or Onto)
- obsArSkillsAndStats: Main reference for display of skills: skillId (from Element_ID in SkillOccup), ONetIdx (as-is), OntoIdx: Onto Seq and the average and standard distribution for each skill across all occupations.
- obsArOccupGrpIndices: currently dispayed occupation indices at which groups change for vertical dividers
- obsArSkillIdxOnto: array for each skill in ONTOLOGICAL order showing the ONetIdx (original, as-read) index for accessing skills table

### Output Entities that appear in Main Page
- obsMessage -- attempt to in effect put console log output on display; did this work out?

### Functions
- fnSkillsOccupAnalyze
- fnOccupAnalyze
- drag {} - object that contains several functions: dragstarted, draffed, dragended to handle the drag event
- use or get rid of: dictOccupSkills, obsStatsBySkill, fuCalcStatsBySkill, obsStatsByOccup, finCalcStatsByOccup
- *** OR: usee calc stats to do entropy!!
- fnCalcStatsBy

### Imports
- Tooltip from "@clhenrick/tooltip-component"

### Standard Notebook cells
- d3
- obscfg - configuration info (window size, margins, etc) plus app-specific constants:
- Skill level colors
- divHorSkillsONet - the indices at which skill group horizontal line appears in O*Net sequence change
- divHorSkillsOccO - same, when in OccO Onto skill sequence
Other?

### Coding Standards
- obscfg execution options for margin, height, width if not full
- obs prefix for an app variable – assumed to be an object not array
- obsAr Prefix for an app variable that is an array of values or objects
- data_ Prefix for data read in from JSON or CSV as array of objects
- dict_ Prefix for input data converted to a dictionary with key, value
- fn Prefix for a cell that is a function
- obsFn Prefix for a cell that is an object containing functions
- Example: drag has dragstarted, dragged, and dragend functions
- Avoid variable names that are identical to common library variables (eg. x, y, value, key)

Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more