---
title: "Data Science Methods for Forecasting in Energy and Economics"
date: 2025-07-10
format:
    revealjs:
        embed-resources: true
        footer: ""
execute:
  daemon: false
highlight-style: github
---

## B-spline Basis Functions

```{ojs}
d3 = require("d3@7")
```

```{ojs}
bsplineData = FileAttachment("basis_functions.csv").csv({ typed: true })

muValues = Array.from(new Set(bsplineData.map(d => d.mu))).sort((a, b) => a - b)
minMu = Math.min(...muValues)
maxMu = Math.max(...muValues)
```

```{ojs}
chart = {
  // State variables for selected parameters
  let selectedMu = 0.5;
  let selectedSig = 1;
  let selectedNonc = 0;
  let selectedTailw = 1;
  const filteredData = () => bsplineData.filter(d =>
    Math.abs(selectedMu - d.mu) < 0.001 &&
    d.sig === selectedSig &&
    d.nonc === selectedNonc &&
    d.tailw === selectedTailw
  );
  const container = d3.create("div").style("margin-bottom", "20px");
  const controlsContainer = container.append("div")
    .style("display", "flex")
    .style("gap", "20px");
  // μ slider control
  const muControl = controlsContainer.append("div").style("display","flex").style("align-items","center").style("gap","10px");
  muControl.append("label").text("μ:").style("font-size","16px");
  muControl.append("input")
    .attr("type","range").attr("min",minMu).attr("max",maxMu).attr("step",0.2)
    .property("value",selectedMu)
    .on("input", function() { selectedMu = +this.value; muControl.select("span").text(selectedMu); updateChart(filteredData()); });
  muControl.append("span").text(selectedMu).style("font-size","16px");
  // sigma control
  const sigControl = controlsContainer.append("div").style("display","flex").style("align-items","center").style("gap","10px");
  sigControl.append("label").text("Sigma:").style("font-size","16px");
  sigControl.append("input")
    .attr("type","range").attr("min", -2).attr("max", 2).attr("step", 1)
    .property("value", Math.log2(selectedSig))
    .on("input", function() { selectedSig = 2 ** (+this.value); sigControl.select("span").text(selectedSig); updateChart(filteredData()); });
  sigControl.append("span").text(selectedSig).style("font-size","16px");
  // nonc control
  const noncControl = controlsContainer.append("div").style("display","flex").style("align-items","center").style("gap","10px");
  noncControl.append("label").text("Non-centrality:").style("font-size","16px");
  noncControl.append("select").selectAll("option").data([-4,0,2,4]).enter().append("option").attr("value", d => d).property("selected", d => d === selectedNonc).text(d => d);
  noncControl.select("select").on("change", function() { selectedNonc = +this.value; updateChart(filteredData()); });
  // tail weight control
  const tailwControl = controlsContainer.append("div").style("display","flex").style("align-items","center").style("gap","10px");
  tailwControl.append("label").text("Tail weight:").style("font-size","16px");
  tailwControl.append("select").selectAll("option").data([0.25,0.5,1,2,4]).enter().append("option").attr("value", d => d).property("selected", d => d === selectedTailw).text(d => d);
  tailwControl.select("select").on("change", function() { selectedTailw = +this.value; updateChart(filteredData()); });
  // Build SVG
   const width = 800;
   const height = 400;
   const margin = {top: 40, right: 20, bottom: 40, left: 40};
   const innerWidth = width - margin.left - margin.right;
   const innerHeight = height - margin.top - margin.bottom;
   
   // Create scales
   const x = d3.scaleLinear()
     .domain([0, 1])
     .range([0, innerWidth]);
     
   const y = d3.scaleLinear()
     .domain([0, 0.7])
     .range([innerHeight, 0]);
   
   // Create a color scale for the basis functions
   const color = d3.scaleOrdinal(d3.schemeCategory10);
   
   // Create SVG
   const svg = d3.create("svg")
     .attr("width", width)
     .attr("height", height)
     .attr("viewBox", [0, 0, width, height])
     .attr("style", "max-width: 100%; height: auto;");
   
   // Add chart title
  //  svg.append("text")
  //    .attr("class", "chart-title")
  //    .attr("x", width / 2)
  //    .attr("y", 20)
  //    .attr("text-anchor", "middle")
  //    .attr("font-size", "16px")
  //    .attr("font-weight", "bold");
   
   // 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", "16px");
   
   const yAxis = g.append("g")
     .attr("class", "y-axis")
     .call(d3.axisLeft(y).ticks(5))
     .style("font-size", "16px");
   
   // Add axis labels
  //  g.append("text")
  //    .attr("x", innerWidth / 2)
  //    .attr("y", innerHeight + 35)
  //    .attr("text-anchor", "middle")
  //    .text("x");
   
  //  g.append("text")
  //    .attr("transform", "rotate(-90)")
  //    .attr("x", -innerHeight / 2)
  //    .attr("y", -30)
  //    .attr("text-anchor", "middle")
  //    .text("y");
   
   // 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) {
     // 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));
     
     // Update the chart title
    //  svg.select(".chart-title")
    //    .text(`B-spline Basis Functions (${selectedKnots} knots, μ = ${selectedMu})`);
     
     // Create a key function to track basis functions
     const keyFn = d => d[0];
     
     // Update basis function lines with proper enter/update/exit pattern
     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);
   }
   
   // Store the update function
   svg.node().update = updateChart;
   
   // Initial render
   updateChart(filteredData());
   
   container.node().appendChild(svg.node());
   return container.node();
}
```
