From e84df0ca8f9348be1d2decf29004a6394abf4988 Mon Sep 17 00:00:00 2001 From: Jonathan Berrisch Date: Sun, 15 Jun 2025 11:55:12 +0200 Subject: [PATCH] Update crps learning introduction slide + finger alignment --- _extensions/quarto-ext/pointer/pointer.css | 2 +- assets/library.bib | 10 + index.qmd | 350 ++++++++++++++------- 3 files changed, 246 insertions(+), 116 deletions(-) diff --git a/_extensions/quarto-ext/pointer/pointer.css b/_extensions/quarto-ext/pointer/pointer.css index 935107b..bb80ce3 100644 --- a/_extensions/quarto-ext/pointer/pointer.css +++ b/_extensions/quarto-ext/pointer/pointer.css @@ -10,7 +10,7 @@ top: 0; left: 0; opacity: 0; - transform: translate(-50%, -50%); + transform: translate(-30%, 0%); transition: opacity 0.4s ease-in-out; z-index: 99; } diff --git a/assets/library.bib b/assets/library.bib index 9e7dde3..4a2ef03 100644 --- a/assets/library.bib +++ b/assets/library.bib @@ -184,4 +184,14 @@ publisher = {Cornell University}, doi = {10.48550/arXiv.2201.06808}, url = {https://arxiv.org/abs/2201.06808} +} +@article{taylor2023angular, + title = {Angular Combining of Forecasts of Probability Distributions}, + author = {Taylor, James W and Meng, Xiaochun}, + year = {2023}, + month = {5}, + journal = {arXiv preprint arXiv:2305.16735}, + publisher = {Cornell University}, + doi = {10.48550/arXiv.2305.16735}, + url = {https://arxiv.org/abs/2305.16735} } \ No newline at end of file diff --git a/index.qmd b/index.qmd index fc62b63..9a7b6f5 100644 --- a/index.qmd +++ b/index.qmd @@ -776,6 +776,8 @@ Berrisch, J., & Ziel, F. [-@BERRISCH2023105221]. *Journal of Econometrics*, 237( - Horizontal aggregation, vincentization - Combining across probabilities - Vertical aggregation +- Combining at an angle + - @taylor2023angular ::: @@ -789,7 +791,7 @@ Berrisch, J., & Ziel, F. [-@BERRISCH2023105221]. *Journal of Econometrics*, 237( ## Time -```{r, echo = FALSE, fig.height=6, cache = TRUE} +```{r, echo = FALSE, fig.height=8, cache = TRUE} par(mfrow = c(3, 3), mar = c(2, 2, 2, 2)) set.seed(1) # Data @@ -813,113 +815,234 @@ plot(X[, 1], xaxt = "n", yaxt = "n", bty = "n", - col = "#2050f0" + col = "#80C684FF" ) plot(w[, 1], lwd = 4, type = "l", ylim = c(0, 1), xlab = "", - ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "#2050f0" + ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "#80C684FF" ) -text(6, 0.5, TeX("$w_1(t)$"), cex = 2, col = "#2050f0") -arrows(13, 0.25, 15, 0.0, , lwd = 4, bty = "n") +text(6, 0.5, TeX("$w_1(t)$"), cex = 2, col = "#80C684FF") +arrows(13, 0.25, 15, 0.0, , lwd = 4, bty = "n", col = "#414141FF") plot.new() -plot(X[, 2], - lwd = 4, - type = "l", ylim = c(8, 12), - xlab = "", ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "purple" -) -plot(w[, 2], - lwd = 4, type = "l", - ylim = c(0, 1), - xlab = "", - ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "purple" -) -text(6, 0.6, TeX("$w_2(t)$"), cex = 2, col = "purple") -arrows(13, 0.5, 15, 0.5, , lwd = 4, bty = "n") -plot(rowSums(X * w), lwd = 4, type = "l", xlab = "", ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "#298829") +plot.new() +plot.new() + +text(6, 0.6, TeX("$w_2(t)$"), cex = 2, col = "#FFD44EFF") +arrows(13, 0.5, 15, 0.5, , lwd = 4, bty = "n", col = "#414141FF") +plot(rowSums(X * w), lwd = 4, type = "l", xlab = "", ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "#D81A5FFF") plot(X[, 3], lwd = 4, type = "l", ylim = c(8, 12), - xlab = "", ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "#e423b4" + xlab = "", ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "#FFD44EFF" ) plot(w[, 3], lwd = 4, type = "l", ylim = c(0, 1), xlab = "", - ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "#e423b4" + ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "#FFD44EFF" ) -text(6, 0.25, TeX("$w_3(t)$"), cex = 2, col = "#e423b4") -arrows(13, 0.75, 15, 1, , lwd = 4, bty = "n") +text(6, 0.25, TeX("$w_3(t)$"), cex = 2, col = "#FFD44EFF") +arrows(13, 0.75, 15, 1, , lwd = 4, bty = "n", col = "#414141FF") + ``` ## Distribution -```{r, echo = FALSE, fig.height=6, cache = TRUE} -par(mfrow = c(3, 3), mar = c(2, 2, 2, 2)) -set.seed(1) -# Data -X <- matrix(ncol = 3, nrow = 31) +```{ojs} +d3 = require("d3@7") +``` -X[, 1] <- dchisq(0:30, df = 10) -X[, 2] <- dnorm(0:30, mean = 15, sd = 5) -X[, 3] <- dexp(0:30, 0.2) -# Weights -w <- matrix(ncol = 3, nrow = 31) -w[, 1] <- sin(0.05 * 0:30) -w[, 2] <- cos(0.05 * 0:30) -w[, 3] <- seq(from = -2, 0.25, length.out = 31)^2 -w <- (w / rowSums(w)) -# Vis -plot(X[, 1], - lwd = 4, - type = "l", - xlab = "", - ylab = "", - xaxt = "n", - yaxt = "n", - bty = "n", - col = "#2050f0" -) -plot(X[, 2], - lwd = 4, - type = "l", - xlab = "", ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "purple" -) -plot(X[, 3], - lwd = 4, - type = "l", - xlab = "", ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "#e423b4" -) -plot(w[, 1], - lwd = 4, type = "l", - ylim = c(0, 1), - xlab = "", - ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "#2050f0" -) -text(12, 0.5, TeX("$w_1(x)$"), cex = 2, col = "#2050f0") -arrows(26, 0.25, 31, 0.0, , lwd = 4, bty = "n") -plot(w[, 2], - lwd = 4, type = "l", - ylim = c(0, 1), - xlab = "", - ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "purple" -) -text(15, 0.5, TeX("$w_2(x)$"), cex = 2, col = "purple") -arrows(15, 0.25, 15, 0, , lwd = 4, bty = "n") -plot(w[, 3], - lwd = 4, type = "l", - ylim = c(0, 1), - xlab = "", - ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "#e423b4" -) -text(20, 0.5, TeX("$w_3(x)$"), cex = 2, col = "#e423b4") -arrows(5, 0.25, 0, 0, , lwd = 4, bty = "n") -plot.new() -plot(rowSums(X * w), - lwd = 4, type = "l", xlab = "", ylab = "", xaxt = "n", - yaxt = "n", bty = "n", col = "#298829" -) +```{ojs} +cdf_data = FileAttachment("assets/crps_learning/weights_plot/cdf_data.csv").csv({ typed: true }) +``` + +```{ojs} +function updateChartInner(g, x, y, linesGroup, color, line, data) { + // Update axes with transitions + x.domain(d3.extent(data, d => d.x)); + g.select(".x-axis").transition().duration(1500).call(d3.axisBottom(x).ticks(10)); + y.domain([0, d3.max(data, d => d.y)]); + g.select(".y-axis").transition().duration(1500).call(d3.axisLeft(y).ticks(5)); + + // Group data by basis function + const dataByFunction = Array.from(d3.group(data, d => d.b)); + const keyFn = d => d[0]; + + // Update basis function lines + const u = linesGroup.selectAll("path").data(dataByFunction, keyFn); + u.join( + enter => enter.append("path").attr("fill","none").attr("stroke-width",3) + .attr("stroke", (_, i) => color(i)).attr("d", d => line(d[1].map(pt => ({x: pt.x, y: 0})))) + .style("opacity",0), + update => update, + exit => exit.transition().duration(1000).style("opacity",0).remove() + ) + .transition().duration(1000) + .attr("d", d => line(d[1])) + .attr("stroke", (_, i) => color(i)) + .style("opacity",1); +} + +chart = { + // State variable for selected mu parameter + let selectedMu = 1; + + const filteredData = () => cdf_data.filter(d => + Math.abs(selectedMu - d.mu) < 0.001 + ); + + const container = d3.create("div") + .style("max-width", "none") + .style("width", "100%"); + + const controlsContainer = container.append("div") + .style("display", "flex") + .style("gap", "20px") + .style("align-items", "center"); + + // Single slider control for mu + const sliderContainer = controlsContainer.append('div') + .style('display','flex') + .style('align-items','center') + .style('gap','10px') + .style('flex','1'); + + sliderContainer.append('label') + .text('Naive:') + .style('font-size','20px'); + + const muSlider = sliderContainer.append('input') + .attr('type','range') + .attr('min', 0) + .attr('max', 1) + .attr('step', 0.1) + .property('value', selectedMu) + .on('input', function(event) { + selectedMu = +this.value; + muDisplay.text(selectedMu.toFixed(1)); + updateChart(filteredData()); + }) + .style('width', '100%') + //.style('-webkit-appearance', 'none') + .style('appearance', 'none') + .style('height', '8px') + .style('background', '#BDBDBDFF'); + + const muDisplay = sliderContainer.append('span') + .text(selectedMu.toFixed(1)) + .style('font-size','20px'); + + // Add Reset button + controlsContainer.append('button') + .text('Reset') + .style('font-size', '20px') + .style('align-self', 'center') + .style('margin-left', 'auto') + .on('click', () => { + selectedMu = 0.5; + muSlider.property('value', selectedMu); + muDisplay.text(selectedMu.toFixed(1)); + updateChart(filteredData()); + }); + + // Build SVG + const width = 600; + const height = 450; + const margin = {top: 40, right: 20, bottom: 40, left: 40}; + const innerWidth = width - margin.left - margin.right; + const innerHeight = height - margin.top - margin.bottom; + + // Set controls container width to match SVG plot width + controlsContainer.style("max-width", "none").style("width", "100%"); + // Distribute each control evenly and make sliders full-width + controlsContainer.selectAll("div").style("flex", "1").style("min-width", "0px"); + controlsContainer.selectAll("input").style("width", "100%").style("box-sizing", "border-box"); + + // Create scales + const x = d3.scaleLinear() + .range([0, innerWidth]); + + const y = d3.scaleLinear() + .range([innerHeight, 0]); + + // Create a color scale for the basis functions + const color = d3.scaleOrdinal(["#80C684FF", "#FFD44EFF", "#D81A5FFF"]); + + // Create SVG + const svg = d3.create("svg") + .attr("width", "100%") + .attr("height", "auto") + .attr("viewBox", [0, 0, width, height]) + .attr("preserveAspectRatio", "xMidYMid meet") + .attr("style", "max-width: 100%; height: auto;"); + + // Create the chart group + const g = svg.append("g") + .attr("transform", `translate(${margin.left},${margin.top})`); + + // Add axes + const xAxis = g.append("g") + .attr("transform", `translate(0,${innerHeight})`) + .attr("class", "x-axis") + .call(d3.axisBottom(x).ticks(10)) + .style("font-size", "20px"); + + const yAxis = g.append("g") + .attr("class", "y-axis") + .call(d3.axisLeft(y).ticks(5)) + .style("font-size", "20px"); + + // Add a horizontal line at y = 0 + g.append("line") + .attr("x1", 0) + .attr("x2", innerWidth) + .attr("y1", y(0)) + .attr("y2", y(0)) + .attr("stroke", "#000") + .attr("stroke-opacity", 0.2); + + // Add gridlines + g.append("g") + .attr("class", "grid-lines") + .selectAll("line") + .data(y.ticks(5)) + .join("line") + .attr("x1", 0) + .attr("x2", innerWidth) + .attr("y1", d => y(d)) + .attr("y2", d => y(d)) + .attr("stroke", "#ccc") + .attr("stroke-opacity", 0.5); + + // Create a line generator + const line = d3.line() + .x(d => x(d.x)) + .y(d => y(d.y)) + .curve(d3.curveBasis); + + // Group to contain the basis function lines + const linesGroup = g.append("g") + .attr("class", "basis-functions"); + + // Store the current basis functions for transition + let currentBasisFunctions = new Map(); + + // Function to update the chart with new data + function updateChart(data) { + updateChartInner(g, x, y, linesGroup, color, line, data); + } + + // Store the update function + svg.node().update = updateChart; + + // Initial render + updateChart(filteredData()); + + container.node().appendChild(svg.node()); + return container.node(); +} ``` ::: @@ -2586,42 +2709,39 @@ weights_q %>% ## Knot Placement Illustration -```{ojs} -d3 = require("d3@7") -``` - ```{ojs} bsplineData = FileAttachment("assets/mcrps_learning/basis_functions.csv").csv({ typed: true }) ``` ```{ojs} -function updateChartInner(g, x, y, linesGroup, color, line, data) { - // Update axes with transitions - x.domain([0, d3.max(data, d => d.x)]); - g.select(".x-axis").transition().duration(1500).call(d3.axisBottom(x).ticks(10)); - y.domain([0, d3.max(data, d => d.y)]); - g.select(".y-axis").transition().duration(1500).call(d3.axisLeft(y).ticks(5)); +// Defined above +// function updateChartInner(g, x, y, linesGroup, color, line, data) { +// // Update axes with transitions +// x.domain([0, d3.max(data, d => d.x)]); +// g.select(".x-axis").transition().duration(1500).call(d3.axisBottom(x).ticks(10)); +// y.domain([0, d3.max(data, d => d.y)]); +// g.select(".y-axis").transition().duration(1500).call(d3.axisLeft(y).ticks(5)); - // Group data by basis function - const dataByFunction = Array.from(d3.group(data, d => d.b)); - const keyFn = d => d[0]; +// // Group data by basis function +// const dataByFunction = Array.from(d3.group(data, d => d.b)); +// const keyFn = d => d[0]; - // Update basis function lines - const u = linesGroup.selectAll("path").data(dataByFunction, keyFn); - u.join( - enter => enter.append("path").attr("fill","none").attr("stroke-width",3) - .attr("stroke", (_, i) => color(i)).attr("d", d => line(d[1].map(pt => ({x: pt.x, y: 0})))) - .style("opacity",0), - update => update, - exit => exit.transition().duration(1000).style("opacity",0).remove() - ) - .transition().duration(1000) - .attr("d", d => line(d[1])) - .attr("stroke", (_, i) => color(i)) - .style("opacity",1); -} +// // Update basis function lines +// const u = linesGroup.selectAll("path").data(dataByFunction, keyFn); +// u.join( +// enter => enter.append("path").attr("fill","none").attr("stroke-width",3) +// .attr("stroke", (_, i) => color(i)).attr("d", d => line(d[1].map(pt => ({x: pt.x, y: 0})))) +// .style("opacity",0), +// update => update, +// exit => exit.transition().duration(1000).style("opacity",0).remove() +// ) +// .transition().duration(1000) +// .attr("d", d => line(d[1])) +// .attr("stroke", (_, i) => color(i)) +// .style("opacity",1); +// } -chart = { +chart0 = { // State variables for selected parameters let selectedMu = 0.5; let selectedSig = 1;