Public
Edited
Nov 22, 2022
9 stars
Bringing the TypeScript Language Server to Observable3 ways using 'Import' expedites data science workflowsTereza Iofciu, PhD, Observable Ambassador, Head of Data Science at neuefischeHow our community builds Observable4 ways to get started with JavaScript for data analysis3 tips for data scientists to move from model to production - fast
Visualizing monthly retail reports to better uncover insights
Andrew Wooldridge, Observable Ambassador, Senior Software EngineerMeet the newest members of the Observable team4 ways JavaScript makes data analysis faster and easierAmbassador Spotlight: Aaron Kyle Dennis, Social Development Specialist at Asian Development BankMeet the newest members of the Observable teamAmbassador Spotlight: Andrea Mignone, Software Engineer and Data ArchitectAmbassador Spotlight: Maxene Graze, Data Visualization Engineer at MURALAmbassador Spotlight: David Kirkby, Professor of Cosmology at UC IrvineMeet the newest members of the Observable teamJune 2022 - What's new on Observable?Create more expressive & articulate data visualizations with Plot 0.5Duplicate avatars retrospectiveMeet the Newest Members of the Observable TeamObservable for Excel Users: IntroductionWatch the NBA Finals Players Data Jam on ObservableMay 2022 - What's new on Observable?Meet the newest members of the Observable teamUpcoming Data Analyst Events with ObservableAnalyzing Star Wars movies + the first anniversary of Observable Plot!April 2022 - What's new on Observable?Carl Wilson joins Observable as Head of RecruitingAmanda Waite joins Observable as Engineering LeadBlack History Month ActivityPlot CheatsheetsVisualizing The New York Times’s Mini Crossword2021 Retrospective2021 Observable Community RecognitionIntroducing AuthorsObservable Ambassadors Take Up The #30DayMapChallengeFuture of Data Work: Empowering Data Practitioners in One PlaceSpreadsheet-based data collaboration gets supercharged with ObservableFuture of Data Work: Collaboration and No LimitsAverting Disaster with Collaborative Data VisualizationObservable shares its first Research Report: State of Dataviz 2021Observable: Discovering a New World#ObservableFTW contest & live events week! July 26-30Introducing Observable for Enterprise2021 Pride Month and LGBTQIA+ EqualityIntroducing Observable Templates!Database outage on May 18, 2021When What You Notice Isn’t What’s WrongIntroducing Observable Plot“Yes And”: Collaboration for software developersAnnouncing the Women’s History Month DataViz Contest WinnersAnnouncing the Black History DataViz Contest WinnersWe Don't Know Everything: A better system for feedback10 Years of Open-Source VisualizationHow D3 Moved UsPowering the D3 CommunityD3 Turns 10Building Community: Announcing Our AmbassadorsObservable Company ValuesEasier Embedding and Sharing with Observable
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
data
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
adjusted = true // by default we will look at the adjusted for inflation numbers in everything
Insert cell
d3.utcMonth.floor(d3.utcMonth.offset(new Date(), -1))
Insert cell
allYears = mrtssales92Present.sheetNames.flatMap(sheet => parseYear(sheet, adjusted))
.sort((a,b) => d3.ascending(a.date, b.date))
.concat(advanced.filter(d => d.adjusted == adjusted))
Insert cell
industryDateMap = d3.rollup(allYears, v => new d3.InternMap(v.map(d => [d.date, d])), d => d.naics)
Insert cell
parsedYear = parseYear("2021", adjusted)
Insert cell
naicsMap = d3.rollup(parsedYear, v => v[0].business, d => d.naics)
Insert cell
percent = d3.format("0.2%")
Insert cell
Insert cell
// Theoretically we could pull this live from the Census website, but we run the risk of parsing errors.
// For now we can update it monthly, we can easily see the last updated date in the File Attachments menu in the top right
mrtssales92Present = FileAttachment("mrtssales92-present@1.xlsx").xlsx()
Insert cell
function parseYear(year, adjusted = true) {
// we convert the headers on row 5 into dates (if they are valid) and create a lookup
// we then use this to create tidy records of sales estimates for each business in each month
let headerYears = Object.entries(mrtssales92Present.sheet(year, { range: "A5:N5" })[0])
.slice(2)
.map(d => [d[0], tp(d[1].replace("May","May.").replace("(p)","").trim())])
.filter(d => d[1])

/*
The spreadsheet supplies two sets of numbers for each year, those adjusted for inflation and those not adjusted. We can parse them separately, the structure of each year is the same
*/
let data = mrtssales92Present.sheet(year, { range: adjusted ? "A73:N110" : "A7:N71", headers: false })
.flatMap(d => {
return headerYears.map(y => {
let naics = String(d["A"])
let business = d["B"]
if(!naics) naics = historicalTotalNamesToNaics.get(business)
return {
naics,
business,
date: y[1],
sales: d[y[0]],
adjusted
}
})
})
return data.sort((a,b) => d3.ascending(a.date, b.date))
}
Insert cell
tp = d3.utcParse("%b. %Y")
Insert cell
Insert cell
Insert cell
marts_current = FileAttachment("marts_current@2.xlsx").xlsx()
Insert cell
Insert cell
table1 = marts_current.sheet("Table 1.", { range: "A11:N67"})
Insert cell
Inputs.table(table1, { layout: "fixed"})
Insert cell
advancedDate = {
let rawMonth = marts_current.sheet("Table 1.", { range: "E8:E8"})[0].E.slice(0,3)
let rawYear = marts_current.sheet("Table 1.", { range: "F7:F7"})[0].F
return tp(`${rawMonth}. ${rawYear}`)
}
Insert cell
advanced = {
// create an array with entries
// Retail & food services
let entries = table1.filter(d => !isNaN(+d.E))
.flatMap((d,i) => {
if(i < 5) {
// handle the total cases
let business = d.B.replace(/…/g,"").replace(/\./g,"").trim()
if(business.indexOf("gasoline") == 0) business = "Total (excl. motor vehicle & parts & gasoline stations)"
business = "Retail & food services, " + business
let naics = advancedTotalNamesToNaics.get(business)
business = historicalTotalNaicsToNames.get(naics)
let pnot = {
naics,
business,
sales: d.E,
date: advancedDate,
adjusted: false
}
let padjusted = {
naics,
business,
sales: d.J,
date: advancedDate,
adjusted: true
}
return [pnot, padjusted]
}
let business = d.B.replace(/…/g,"").replace(/\./g,"").trim()
// because the spreadsheet has some business names split across 2 lines we'll manually get their naics code
let naics = String(d.A).replace(/\s/,"")
if(business == "supplies dealers") naics = "444"
if(business == "stores") naics = "448"
if(business == "instrument, & book stores") naics = "451"
business = naicsMap.get(naics)
let pnot = {
naics,
business,
sales: d.E,
date: advancedDate,
adjusted: false
}
let padjusted = {
naics,
business,
sales: d.J,
date: advancedDate,
adjusted: true
}
return [pnot, padjusted]
})
return entries
}
Insert cell
naicsMap.get("4411,4412")
Insert cell
Inputs.table(advanced)
Insert cell
Insert cell
table2 = marts_current.sheet("Table 2.", { range: "A14:C54"})
Insert cell
advancedDifference = {
// create an array with entries
// Retail & food services
let entries = table2.filter(d => d.C !== undefined && d.C != " ")
.map((d,i) => {
if(i < 5) {
// handle the total cases
let business = d.B.replace(/…/g,"").replace(/\./g,"").trim()
if(business.indexOf("gasoline") == 0) business = "Total (excl. motor vehicle & parts & gasoline stations)"
business = "Retail & food services, " + business
let naics = advancedTotalNamesToNaics.get(business)
business = historicalTotalNaicsToNames.get(naics)
let p = {
naics,
business,
difference: d.C,
date: advancedDate
}
return p
}
let bus = d.B.replace(/…/g,"").replace(/\./g,"").trim()
// because the spreadsheet has some business names split across 2 lines we'll manually get their naics code
let naics = String(d.A).replace(/\s/,"")
if(bus == "supplies dealers") naics = "444"
if(bus == "stores") naics = "448"
if(bus == "instrument, & book stores") naics = "451"
bus = naicsMap.get(naics)
let p = {
naics,
business: bus,
difference: d.C,
date: advancedDate,
}
return p
})
return entries
}
Insert cell
Inputs.table(advancedDifference)
Insert cell
historicalTotalNames = [
["Retail and food services sales, total", "1*"],
["Retail sales and food services excl motor vehicle and parts", "2*"],
["Retail sales and food services excl gasoline stations", "3*"],
["Retail sales and food services excl motor vehicle and parts and gasoline stations", "4*"],
["Retail sales, total", "5*"],
["Retail sales, total (excl. motor vehicle and parts dealers)", "6*"],
["GAFO(1)", "7*"],
]
Insert cell
historicalTotalNamesToNaics = new Map(historicalTotalNames)
Insert cell
historicalTotalNaicsToNames = new Map(historicalTotalNames.map(d => d.map(e => e).reverse()))
Insert cell
advancedTotalNames = [
["Retail & food services, total", "1*"],
["Retail & food services, Total (excl motor vehicle & parts)", "2*"],
["Retail & food services, Total (excl gasoline stations)", "3*"],
["Retail & food services, Total (excl. motor vehicle & parts & gasoline stations)","4*"],
["Retail & food services, Retail", "5*"]
]
Insert cell
advancedTotalNamesToNaics = new Map(advancedTotalNames)
Insert cell
import { button as downloadButton } from "@jeremiak/download-data-button"
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