Spaces:
Running
Running
Download core.js from Hugdrigo/education-data-lab: direct link, hf CLI and curl.
- Browser
- Download file 7.06 kB
-
https://huggingface.co/spaces/Hugdrigo/education-data-lab/resolve/main/core.js
- Command line
-
hf download hf://spaces/Hugdrigo/education-data-lab/core.js
-
curl -L -o core.js https://huggingface.co/spaces/Hugdrigo/education-data-lab/resolve/main/core.js
7.06 kB
| /* Education Data Lab: deterministic analytics, no external dependencies. */ | |
| export const COUNTRIES = Object.freeze({ | |
| ARG:'Argentina',BOL:'Bolivia',BRA:'Brazil',CHL:'Chile',COL:'Colombia',CRI:'Costa Rica',DOM:'Dominican Republic',ECU:'Ecuador',MEX:'Mexico',PAN:'Panama',PER:'Peru',PRY:'Paraguay',URY:'Uruguay',GTM:'Guatemala',HND:'Honduras',SLV:'El Salvador' | |
| }); | |
| export const INDICATORS = Object.freeze({ | |
| 'SE.PRM.CMPT.ZS':{short:'Primary completion',label:'Primary completion rate',unit:'%',group:'Education',tip:'Gross intake ratio to last grade; may exceed 100. Not cohort graduation.'}, | |
| 'SE.SEC.ENRR':{short:'Secondary enrollment',label:'Secondary school enrollment, gross',unit:'%',group:'Education',tip:'Gross enrollment is not net enrollment; can exceed 100.'}, | |
| 'SE.TER.ENRR':{short:'Tertiary enrollment',label:'Tertiary school enrollment, gross',unit:'%',group:'Education',tip:'Gross enrollment, not degree completion; can exceed 100.'}, | |
| 'SE.XPD.TOTL.GD.ZS':{short:'Education spending',label:'Government expenditure on education, total',unit:'% GDP',group:'Education',tip:'Government spending as a share of GDP; not per-student investment.'}, | |
| 'SE.ADT.LITR.ZS':{short:'Adult literacy',label:'Literacy rate, adult total (15+)',unit:'%',group:'Education',tip:'Survey/reporting coverage can be sparse.'}, | |
| 'IT.NET.USER.ZS':{short:'Internet use',label:'Individuals using the Internet',unit:'%',group:'Context',tip:'Context series; not a direct education outcome.'}, | |
| 'NY.GDP.PCAP.CD':{short:'GDP per capita',label:'GDP per capita, current US$',unit:'US$',group:'Context',tip:'Nominal current USD is affected by prices and FX.'}, | |
| 'SP.POP.TOTL':{short:'Population',label:'Population, total',unit:'people',group:'Context',tip:'Context series; not a schooling population.'} | |
| }); | |
| export const IDS = Object.keys(INDICATORS); | |
| export const COUNTRY_IDS = Object.keys(COUNTRIES); | |
| export function validYear(y){return Number.isInteger(Number(y))&&Number(y)>=1990&&Number(y)<=2026;} | |
| export function finite(v){return typeof v==='number'&&Number.isFinite(v);} | |
| export function normalizeRows(rows){ | |
| if(!Array.isArray(rows))throw new Error('Expected array of observations'); | |
| const out=[],seen=new Set(); | |
| for(const row of rows){ | |
| if(!row||!COUNTRIES[row.iso3]||!INDICATORS[row.indicator]||!validYear(row.year))continue; | |
| if(row.value===null||row.value===undefined||row.value==='')continue; | |
| const val=typeof row.value==='number'?row.value:Number(row.value); | |
| if(!finite(val))continue; | |
| const key=[row.iso3,Number(row.year),row.indicator].join('|'); | |
| if(seen.has(key))continue;seen.add(key); | |
| out.push({iso3:row.iso3,year:Number(row.year),indicator:row.indicator,value:val,footnote:String(row.footnote||'').slice(0,240),status:String(row.status||'').slice(0,24)}); | |
| } | |
| return out.sort((a,b)=>a.year-b.year||a.iso3.localeCompare(b.iso3)||a.indicator.localeCompare(b.indicator)); | |
| } | |
| export function subset(rows,cc,indicator,start,end){return rows.filter(r=>cc.includes(r.iso3)&&r.indicator===indicator&&r.year>=start&&r.year<=end);} | |
| export function latest(rows,country,ind,start,end){const a=rows.filter(r=>r.iso3===country&&r.indicator===ind&&r.year>=start&&r.year<=end); return a.length?a.reduce((best,x)=>x.year>best.year?x:best):null;} | |
| export function paired(rows,x,y,cc,start,end){ | |
| const selected=rows.filter(r=>cc.includes(r.iso3)&&[x,y].includes(r.indicator)&&r.year>=start&&r.year<=end);const g=new Map(); | |
| for(const r of selected){const k=r.iso3+'|'+r.year;if(!g.has(k))g.set(k,{iso3:r.iso3,year:r.year});g.get(k)[r.indicator]=r.value;} | |
| return [...g.values()].filter(o=>finite(o[x])&&finite(o[y])).sort((a,b)=>a.year-b.year||a.iso3.localeCompare(b.iso3)); | |
| } | |
| export function pearson(points,x,y){if(points.length<3)return null;const a=points.map(p=>p[x]),b=points.map(p=>p[y]);const n=a.length,ma=a.reduce((s,v)=>s+v,0)/n,mb=b.reduce((s,v)=>s+v,0)/n;let num=0,da=0,db=0;for(let i=0;i<n;i++){const u=a[i]-ma,v=b[i]-mb;num+=u*v;da+=u*u;db+=v*v;}return da>0&&db>0?num/Math.sqrt(da*db):null;} | |
| export function growth(oldValue,newValue){return finite(oldValue)&&finite(newValue)&&oldValue!==0?100*(newValue/oldValue-1):null;} | |
| export function change(first,last){return first&&last&&first.year!==last.year?{amount:last.value-first.value,from:first.year,to:last.year}:null;} | |
| export function coverage(rows,countries,inds,start,end){const years=end-start+1,eligible=countries.length*inds.length*years; const count=rows.filter(r=>countries.includes(r.iso3)&&inds.includes(r.indicator)&&r.year>=start&&r.year<=end).length;return {count,eligible,ratio:eligible?count/eligible:0};} | |
| export function status(rows,countries,inds,start,end){const c=coverage(rows,countries,inds,start,end);let lags=[];for(const cc of countries)for(const i of inds){const x=latest(rows,cc,i,start,end);if(x)lags.push(end-x.year);}return { ...c,meanLag:lags.length?lags.reduce((a,b)=>a+b,0)/lags.length:null,availablePairs:lags.length,totalPairs:countries.length*inds.length};} | |
| export function formatValue(n,ind){if(!finite(n))return 'N/A';const unit=INDICATORS[ind]?.unit||'%';if(unit==='US$')return '$'+Intl.NumberFormat('en-US',{notation:'compact',maximumFractionDigits:2}).format(n);if(unit==='people')return Intl.NumberFormat('en-US',{notation:'compact',maximumFractionDigits:1}).format(n);return n.toFixed(Math.abs(n)<10?2:1)+(unit==='% GDP'?'% GDP':'%');} | |
| export function escapeHTML(s){return String(s??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));} | |
| export function csvCell(v){let s=String(v??'');if(typeof v==='string'&&/^[=+\-@\t\r]/.test(s))s="'"+s;return /[",\n\r]/.test(s)?'"'+s.replace(/"/g,'""')+'"':s;} | |
| export function toCSV(rows){const cols=['iso3','country','year','indicator','indicator_label','value','unit','footnote','observation_status'];return [cols.join(','),...rows.map(r=>[r.iso3,COUNTRIES[r.iso3],r.year,r.indicator,INDICATORS[r.indicator]?.label,r.value,INDICATORS[r.indicator]?.unit,r.footnote||'',r.status||''].map(csvCell).join(','))].join('\r\n');} | |
| export function validateSession(x){if(!x||x.format!=='edlab-session-v1'||!Array.isArray(x.rows)||x.rows.length>25000)throw new Error('Invalid Education Data Lab session');const rows=normalizeRows(x.rows);if(!rows.length)throw new Error('No supported observations');return {rows,mode:'imported',importedAt:new Date().toISOString(),sourceNote:'Imported user session; provenance not independently verified.'};} | |
| export function timeline(rows,countries,ind,start,end){const a=subset(rows,countries,ind,start,end);return countries.map(cc=>({iso3:cc,points:a.filter(r=>r.iso3===cc).sort((a,b)=>a.year-b.year)}));} | |
| export function compareCountries(rows,countries,ind,start,end){return countries.map(c=>({country:c,point:latest(rows,c,ind,start,end)})).filter(x=>x.point).sort((a,b)=>b.point.value-a.point.value);} | |
| export function crossCountryGap(rows,a,b,ind,start,end,year){const p=rows.find(r=>r.iso3===a&&r.indicator===ind&&r.year===year),q=rows.find(r=>r.iso3===b&&r.indicator===ind&&r.year===year);return p&&q?{year,delta:p.value-q.value,a:p.value,b:q.value}:null;} | |